rts-demo
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
import { _decorator, Component, Node, Camera, Vec3, input, Input, EventMouse, EventTouch, KeyCode, Quat } from 'cc';
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
/**
|
||||
* RTS相机控制器
|
||||
* 提供RTS游戏常用的相机控制功能
|
||||
*/
|
||||
@ccclass('RTSCameraController')
|
||||
export class RTSCameraController extends Component {
|
||||
|
||||
@property
|
||||
moveSpeed: number = 10;
|
||||
|
||||
@property
|
||||
rotateSpeed: number = 60;
|
||||
|
||||
@property
|
||||
zoomSpeed: number = 5;
|
||||
|
||||
@property
|
||||
minZoom: number = 5;
|
||||
|
||||
@property
|
||||
maxZoom: number = 30;
|
||||
|
||||
@property
|
||||
boundarySize: number = 50;
|
||||
|
||||
private camera: Camera = null!;
|
||||
private isMouseDown: boolean = false;
|
||||
private lastMousePosition: Vec3 = Vec3.ZERO.clone();
|
||||
private currentZoom: number = 15;
|
||||
|
||||
// 键盘输入状态
|
||||
private keyStates: { [key: string]: boolean } = {};
|
||||
|
||||
onLoad() {
|
||||
this.camera = this.getComponent(Camera)!;
|
||||
this.currentZoom = this.node.position.y;
|
||||
|
||||
// 注册输入事件
|
||||
input.on(Input.EventType.MOUSE_DOWN, this.onMouseDown, this);
|
||||
input.on(Input.EventType.MOUSE_UP, this.onMouseUp, this);
|
||||
input.on(Input.EventType.MOUSE_MOVE, this.onMouseMove, this);
|
||||
input.on(Input.EventType.MOUSE_WHEEL, this.onMouseWheel, this);
|
||||
input.on(Input.EventType.KEY_DOWN, this.onKeyDown, this);
|
||||
input.on(Input.EventType.KEY_UP, this.onKeyUp, this);
|
||||
}
|
||||
|
||||
onDestroy() {
|
||||
// 取消注册输入事件
|
||||
input.off(Input.EventType.MOUSE_DOWN, this.onMouseDown, this);
|
||||
input.off(Input.EventType.MOUSE_UP, this.onMouseUp, this);
|
||||
input.off(Input.EventType.MOUSE_MOVE, this.onMouseMove, this);
|
||||
input.off(Input.EventType.MOUSE_WHEEL, this.onMouseWheel, this);
|
||||
input.off(Input.EventType.KEY_DOWN, this.onKeyDown, this);
|
||||
input.off(Input.EventType.KEY_UP, this.onKeyUp, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 鼠标按下事件
|
||||
*/
|
||||
private onMouseDown(event: EventMouse) {
|
||||
if (event.getButton() === EventMouse.BUTTON_MIDDLE) {
|
||||
this.isMouseDown = true;
|
||||
this.lastMousePosition.set(event.getLocationX(), event.getLocationY(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 鼠标抬起事件
|
||||
*/
|
||||
private onMouseUp(event: EventMouse) {
|
||||
if (event.getButton() === EventMouse.BUTTON_MIDDLE) {
|
||||
this.isMouseDown = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 鼠标移动事件
|
||||
*/
|
||||
private onMouseMove(event: EventMouse) {
|
||||
if (this.isMouseDown) {
|
||||
const deltaX = event.getLocationX() - this.lastMousePosition.x;
|
||||
const deltaY = event.getLocationY() - this.lastMousePosition.y;
|
||||
|
||||
// 相机平移
|
||||
const moveVector = new Vec3(-deltaX * 0.01, 0, deltaY * 0.01);
|
||||
this.node.translate(moveVector);
|
||||
|
||||
this.lastMousePosition.set(event.getLocationX(), event.getLocationY(), 0);
|
||||
|
||||
// 限制相机边界
|
||||
this.clampCameraBounds();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 鼠标滚轮事件
|
||||
*/
|
||||
private onMouseWheel(event: EventMouse) {
|
||||
const scrollY = event.getScrollY();
|
||||
this.currentZoom -= scrollY * this.zoomSpeed * 0.1;
|
||||
this.currentZoom = Math.max(this.minZoom, Math.min(this.maxZoom, this.currentZoom));
|
||||
|
||||
const pos = this.node.position;
|
||||
this.node.setPosition(pos.x, this.currentZoom, pos.z);
|
||||
}
|
||||
|
||||
/**
|
||||
* 键盘按下事件
|
||||
*/
|
||||
private onKeyDown(event: any) {
|
||||
this.keyStates[event.keyCode] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 键盘抬起事件
|
||||
*/
|
||||
private onKeyUp(event: any) {
|
||||
this.keyStates[event.keyCode] = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新相机移动
|
||||
*/
|
||||
update(deltaTime: number) {
|
||||
this.handleKeyboardMovement(deltaTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理键盘移动
|
||||
*/
|
||||
private handleKeyboardMovement(deltaTime: number) {
|
||||
const moveDistance = this.moveSpeed * deltaTime;
|
||||
let moveVector = Vec3.ZERO.clone();
|
||||
|
||||
// WASD 移动
|
||||
if (this.keyStates[KeyCode.KEY_W] || this.keyStates[KeyCode.ARROW_UP]) {
|
||||
moveVector.z -= moveDistance;
|
||||
}
|
||||
if (this.keyStates[KeyCode.KEY_S] || this.keyStates[KeyCode.ARROW_DOWN]) {
|
||||
moveVector.z += moveDistance;
|
||||
}
|
||||
if (this.keyStates[KeyCode.KEY_A] || this.keyStates[KeyCode.ARROW_LEFT]) {
|
||||
moveVector.x -= moveDistance;
|
||||
}
|
||||
if (this.keyStates[KeyCode.KEY_D] || this.keyStates[KeyCode.ARROW_RIGHT]) {
|
||||
moveVector.x += moveDistance;
|
||||
}
|
||||
|
||||
// 应用移动
|
||||
if (!moveVector.equals(Vec3.ZERO)) {
|
||||
this.node.translate(moveVector);
|
||||
this.clampCameraBounds();
|
||||
}
|
||||
|
||||
// QE 旋转
|
||||
if (this.keyStates[KeyCode.KEY_Q]) {
|
||||
const rotateAngle = this.rotateSpeed * deltaTime * Math.PI / 180;
|
||||
const currentRotation = this.node.rotation.clone();
|
||||
const yRotation = Quat.fromAxisAngle(new Quat(), Vec3.UP, rotateAngle);
|
||||
Quat.multiply(currentRotation, currentRotation, yRotation);
|
||||
this.node.rotation = currentRotation;
|
||||
}
|
||||
if (this.keyStates[KeyCode.KEY_E]) {
|
||||
const rotateAngle = -this.rotateSpeed * deltaTime * Math.PI / 180;
|
||||
const currentRotation = this.node.rotation.clone();
|
||||
const yRotation = Quat.fromAxisAngle(new Quat(), Vec3.UP, rotateAngle);
|
||||
Quat.multiply(currentRotation, currentRotation, yRotation);
|
||||
this.node.rotation = currentRotation;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 限制相机边界
|
||||
*/
|
||||
private clampCameraBounds() {
|
||||
const pos = this.node.position;
|
||||
const clampedX = Math.max(-this.boundarySize, Math.min(this.boundarySize, pos.x));
|
||||
const clampedZ = Math.max(-this.boundarySize, Math.min(this.boundarySize, pos.z));
|
||||
|
||||
this.node.setPosition(clampedX, pos.y, clampedZ);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置相机位置
|
||||
*/
|
||||
setCameraPosition(position: Vec3) {
|
||||
this.node.setPosition(position);
|
||||
this.clampCameraBounds();
|
||||
}
|
||||
|
||||
/**
|
||||
* 聚焦到目标位置
|
||||
*/
|
||||
focusOnTarget(target: Vec3, duration: number = 1.0) {
|
||||
const targetPos = new Vec3(target.x, this.currentZoom, target.z);
|
||||
// 这里可以添加缓动动画
|
||||
this.setCameraPosition(targetPos);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "dabc6540-6e9f-450a-9d22-b9af94c20d6d",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
import { _decorator, Component, Node, Vec3, Label, Button, EventHandler } from 'cc';
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
/**
|
||||
* UI控制器 - 管理RTS演示的用户界面
|
||||
*/
|
||||
@ccclass('UIController')
|
||||
export class UIController extends Component {
|
||||
|
||||
@property(Label)
|
||||
selectedUnitsLabel: Label = null!;
|
||||
|
||||
@property(Button)
|
||||
moveButton: Button = null!;
|
||||
|
||||
@property(Button)
|
||||
attackButton: Button = null!;
|
||||
|
||||
@property(Button)
|
||||
gatherButton: Button = null!;
|
||||
|
||||
@property(Button)
|
||||
patrolButton: Button = null!;
|
||||
|
||||
@property(Label)
|
||||
infoLabel: Label = null!;
|
||||
|
||||
// 回调函数
|
||||
public onUnitSelected: ((units: Node[]) => void) | null = null;
|
||||
public onCommandIssued: ((command: string, target?: Vec3 | Node) => void) | null = null;
|
||||
|
||||
private selectedUnitsCount: number = 0;
|
||||
|
||||
onLoad() {
|
||||
this.setupUI();
|
||||
this.updateSelectedUnitsDisplay();
|
||||
this.updateInfoDisplay('RTS 行为树演示 - 点击单位选择,然后发布命令');
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置UI事件
|
||||
*/
|
||||
private setupUI() {
|
||||
// 移动命令按钮
|
||||
if (this.moveButton) {
|
||||
const moveHandler = new EventHandler();
|
||||
moveHandler.target = this.node;
|
||||
moveHandler.component = 'UIController';
|
||||
moveHandler.handler = 'onMoveCommand';
|
||||
this.moveButton.clickEvents.push(moveHandler);
|
||||
}
|
||||
|
||||
// 攻击命令按钮
|
||||
if (this.attackButton) {
|
||||
const attackHandler = new EventHandler();
|
||||
attackHandler.target = this.node;
|
||||
attackHandler.component = 'UIController';
|
||||
attackHandler.handler = 'onAttackCommand';
|
||||
this.attackButton.clickEvents.push(attackHandler);
|
||||
}
|
||||
|
||||
// 收集命令按钮
|
||||
if (this.gatherButton) {
|
||||
const gatherHandler = new EventHandler();
|
||||
gatherHandler.target = this.node;
|
||||
gatherHandler.component = 'UIController';
|
||||
gatherHandler.handler = 'onGatherCommand';
|
||||
this.gatherButton.clickEvents.push(gatherHandler);
|
||||
}
|
||||
|
||||
// 巡逻命令按钮
|
||||
if (this.patrolButton) {
|
||||
const patrolHandler = new EventHandler();
|
||||
patrolHandler.target = this.node;
|
||||
patrolHandler.component = 'UIController';
|
||||
patrolHandler.handler = 'onPatrolCommand';
|
||||
this.patrolButton.clickEvents.push(patrolHandler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置选中单位数量
|
||||
*/
|
||||
setSelectedUnitsCount(count: number) {
|
||||
this.selectedUnitsCount = count;
|
||||
this.updateSelectedUnitsDisplay();
|
||||
this.updateButtonStates();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新选中单位显示
|
||||
*/
|
||||
private updateSelectedUnitsDisplay() {
|
||||
if (this.selectedUnitsLabel) {
|
||||
this.selectedUnitsLabel.string = `选中单位: ${this.selectedUnitsCount}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新按钮状态
|
||||
*/
|
||||
private updateButtonStates() {
|
||||
const hasSelection = this.selectedUnitsCount > 0;
|
||||
|
||||
if (this.moveButton) {
|
||||
this.moveButton.interactable = hasSelection;
|
||||
}
|
||||
if (this.attackButton) {
|
||||
this.attackButton.interactable = hasSelection;
|
||||
}
|
||||
if (this.gatherButton) {
|
||||
this.gatherButton.interactable = hasSelection;
|
||||
}
|
||||
if (this.patrolButton) {
|
||||
this.patrolButton.interactable = hasSelection;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新信息显示
|
||||
*/
|
||||
updateInfoDisplay(message: string) {
|
||||
if (this.infoLabel) {
|
||||
this.infoLabel.string = message;
|
||||
}
|
||||
console.log(`[UI] ${message}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动命令
|
||||
*/
|
||||
onMoveCommand() {
|
||||
if (this.selectedUnitsCount === 0) {
|
||||
this.updateInfoDisplay('请先选择单位');
|
||||
return;
|
||||
}
|
||||
|
||||
// 生成随机目标位置
|
||||
const targetPos = new Vec3(
|
||||
(Math.random() - 0.5) * 20,
|
||||
0,
|
||||
(Math.random() - 0.5) * 20
|
||||
);
|
||||
|
||||
this.onCommandIssued?.('move', targetPos);
|
||||
this.updateInfoDisplay(`发布移动命令到位置 (${targetPos.x.toFixed(1)}, ${targetPos.z.toFixed(1)})`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 攻击命令
|
||||
*/
|
||||
onAttackCommand() {
|
||||
if (this.selectedUnitsCount === 0) {
|
||||
this.updateInfoDisplay('请先选择单位');
|
||||
return;
|
||||
}
|
||||
|
||||
// 生成随机攻击位置
|
||||
const targetPos = new Vec3(
|
||||
(Math.random() - 0.5) * 15,
|
||||
0,
|
||||
(Math.random() - 0.5) * 15
|
||||
);
|
||||
|
||||
this.onCommandIssued?.('attack', targetPos);
|
||||
this.updateInfoDisplay(`发布攻击命令到位置 (${targetPos.x.toFixed(1)}, ${targetPos.z.toFixed(1)})`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集命令
|
||||
*/
|
||||
onGatherCommand() {
|
||||
if (this.selectedUnitsCount === 0) {
|
||||
this.updateInfoDisplay('请先选择单位');
|
||||
return;
|
||||
}
|
||||
|
||||
this.onCommandIssued?.('gather');
|
||||
this.updateInfoDisplay('发布收集资源命令');
|
||||
}
|
||||
|
||||
/**
|
||||
* 巡逻命令
|
||||
*/
|
||||
onPatrolCommand() {
|
||||
if (this.selectedUnitsCount === 0) {
|
||||
this.updateInfoDisplay('请先选择单位');
|
||||
return;
|
||||
}
|
||||
|
||||
this.onCommandIssued?.('patrol');
|
||||
this.updateInfoDisplay('发布巡逻命令');
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示单位信息
|
||||
*/
|
||||
showUnitInfo(unitName: string, unitType: string, health: number, maxHealth: number) {
|
||||
const healthPercent = Math.round((health / maxHealth) * 100);
|
||||
this.updateInfoDisplay(`${unitName} (${unitType}) - 生命值: ${health}/${maxHealth} (${healthPercent}%)`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示行为树状态
|
||||
*/
|
||||
showBehaviorTreeStatus(unitName: string, currentBehavior: string) {
|
||||
this.updateInfoDisplay(`${unitName} 当前行为: ${currentBehavior}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示错误信息
|
||||
*/
|
||||
showError(message: string) {
|
||||
this.updateInfoDisplay(`错误: ${message}`);
|
||||
console.error(`[UI Error] ${message}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示成功信息
|
||||
*/
|
||||
showSuccess(message: string) {
|
||||
this.updateInfoDisplay(`成功: ${message}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "00e982f3-dcf3-44b0-9b63-5e2877c1971e",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
Reference in New Issue
Block a user