diff --git a/config/gameConfig.js b/config/gameConfig.js
index 1dc1e20..d05eaef 100644
--- a/config/gameConfig.js
+++ b/config/gameConfig.js
@@ -12,6 +12,9 @@ export default {
},
scale: {
mode: Phaser.Scale.RESIZE,
- autoCenter: Phaser.Scale.NO_CENTER
+ autoCenter: Phaser.Scale.NO_CENTER,
+ expandParent: true,
+ width: window.innerWidth,
+ height: window.innerHeight
}
};
\ No newline at end of file
diff --git a/index.html b/index.html
index acdd3e5..f186ecd 100644
--- a/index.html
+++ b/index.html
@@ -3,7 +3,24 @@
Multi-Control Demo
-
+
diff --git a/objects/Unit.js b/objects/Unit.js
index 17b4697..3cf3d6b 100644
--- a/objects/Unit.js
+++ b/objects/Unit.js
@@ -1,16 +1,124 @@
+// objects/Unit.js
export default class Unit extends Phaser.Physics.Arcade.Sprite {
constructor(scene, x, y, texture) {
super(scene, x, y, texture);
scene.add.existing(this);
scene.physics.add.existing(this);
+
+ // 启用圆形碰撞体(更自然)
+ this.setCircle(14);
+
+ // 启用与世界边界的碰撞(必须配合 physics.world.setBounds)
this.setCollideWorldBounds(true);
+
+ // 移动目标(x, y)
+ this.target = null;
+
+ // 选中状态
+ this.isSelected = false;
+ this.selectedIndicator = null;
+ this.createSelectedIndicator();
}
- moveTo(targetX, targetY) {
- this.scene.physics.moveToObject(this, {x: targetX, y: targetY}, 200);
+ /**
+ * 创建选中状态指示器
+ */
+ createSelectedIndicator() {
+ // 创建一个外圈指示器,默认隐藏
+ this.selectedIndicator = this.scene.add.circle(this.x, this.y, this.width * 0.7, 0x00ff00, 0.0);
+ this.selectedIndicator.setStrokeStyle(2, 0x00ff00, 1);
+ this.selectedIndicator.setVisible(false);
+
+ // 确保指示器与单位一起移动
+ this.selectedIndicator.setOrigin(0.5, 0.5);
+ // 移除固定位置设置,让指示器随单位和相机一起移动
+ this.selectedIndicator.setScrollFactor(1, 1);
}
- moveRelative(dirX, dirY, speed = 100) {
- this.setVelocity(dirX * speed, dirY * speed);
+ /**
+ * 设置单位选中状态
+ * @param {boolean} isSelected
+ */
+ setSelected(isSelected) {
+ this.isSelected = isSelected;
+ if (this.selectedIndicator) {
+ this.selectedIndicator.setVisible(isSelected);
+ }
+ }
+
+ /**
+ * 设置移动目标点
+ * @param {number} x
+ * @param {number} y
+ */
+ setTarget(x, y) {
+ // 获取世界边界
+ const worldBounds = this.scene.physics.world.bounds;
+
+ // 限制目标点在世界边界内
+ const clampedX = Math.max(worldBounds.x, Math.min(worldBounds.right, x));
+ const clampedY = Math.max(worldBounds.y, Math.min(worldBounds.bottom, y));
+
+ this.target = { x: clampedX, y: clampedY };
+ }
+
+ /**
+ * 清除目标并停止移动
+ */
+ clearTarget() {
+ this.target = null;
+ this.setVelocity(0, 0);
+ }
+
+ /**
+ * 相对移动控制(用于键盘/虚拟摇杆)
+ * @param {number} x - 水平方向 (-1 到 1)
+ * @param {number} y - 垂直方向 (-1 到 1)
+ */
+ moveRelative(x, y) {
+ const speed = 150; // 单位:像素/秒
+ // 归一化方向向量
+ const magnitude = Math.sqrt(x * x + y * y);
+ if (magnitude > 0) {
+ // 有按键输入时,清除目标点,切换到直接控制模式
+ this.clearTarget();
+ const normalizedX = x / magnitude;
+ const normalizedY = y / magnitude;
+ this.setVelocity(normalizedX * speed, normalizedY * speed);
+ } else {
+ // 没有按键输入时,保持当前的目标点(如果有的话)
+ this.setVelocity(0, 0);
+ }
+ }
+
+ /**
+ * 每帧更新移动逻辑(需在 GameScene.update 中调用)
+ */
+ update() {
+ // 更新选中指示器位置
+ if (this.selectedIndicator && this.isSelected) {
+ this.selectedIndicator.setPosition(this.x, this.y);
+ }
+ }
+
+ updateMovement() {
+ if (!this.target) {
+ return;
+ }
+
+ const dx = this.target.x - this.x;
+ const dy = this.target.y - this.y;
+ const distance = Math.sqrt(dx * dx + dy * dy);
+
+ // 到达阈值:5 像素内视为“到达”
+ if (distance < 5) {
+ // 精确对齐到目标点(可选)
+ this.setPosition(this.target.x, this.target.y);
+ this.clearTarget();
+ } else {
+ const speed = 150; // 单位:像素/秒
+ const angle = Math.atan2(dy, dx);
+ this.setVelocity(Math.cos(angle) * speed, Math.sin(angle) * speed);
+ }
}
}
\ No newline at end of file
diff --git a/scenes/GameScene.js b/scenes/GameScene.js
index e620e95..34b6c1e 100644
--- a/scenes/GameScene.js
+++ b/scenes/GameScene.js
@@ -1,26 +1,83 @@
+// scenes/GameScene.js
import Unit from '../objects/Unit.js';
import { InputSystem } from '../systems/InputSystem.js';
import { SelectionSystem } from '../systems/SelectionSystem.js';
import { CommandSystem } from '../systems/CommandSystem.js';
import { VirtualJoystick } from '../ui/VirtualJoystick.js';
+import { MiniMap } from '../ui/MiniMap.js';
export default class GameScene extends Phaser.Scene {
create() {
+ // === 0. 添加窗口大小变化监听 ===
+ window.addEventListener('resize', this.resizeGame.bind(this));
+
+ // === 1. 创建玩家纹理(红圈)===
+ if (!this.textures.exists('player')) {
+ const canvas = document.createElement('canvas');
+ canvas.width = 32;
+ canvas.height = 32;
+ const ctx = canvas.getContext('2d');
+ ctx.fillStyle = '#ff0000';
+ ctx.beginPath();
+ ctx.arc(16, 16, 14, 0, Math.PI * 2);
+ ctx.fill();
+ this.textures.addCanvas('player', canvas);
+ }
+
+ // === 2. 扩大物理世界和相机边界(关键!)===
+ const worldSize = 2000;
+ this.physics.world.setBounds(-worldSize, -worldSize, worldSize * 2, worldSize * 2);
+ this.cameras.main.setBounds(-worldSize, -worldSize, worldSize * 2, worldSize * 2);
+
+ // === 3. 创建单位组 ===
this.unitGroup = this.add.group();
+
for (let i = 0; i < 5; i++) {
- const unit = new Unit(this, 100 + i * 80, 300, 'player');
+ const worldSizePersent = worldSize/100
+ const unit = new Unit(this, worldSizePersent + 2*i * worldSizePersent, 2 * worldSizePersent, 'player');
this.unitGroup.add(unit);
}
+ // === 4. 初始化系统 ===
this.inputSystem = new InputSystem(this);
this.selectionSystem = new SelectionSystem(this);
this.commandSystem = new CommandSystem(this);
-
- // 初始化虚拟摇杆
this.virtualJoystick = new VirtualJoystick(this);
+
+ // === 5. 添加角色之间的碰撞检测 ===
+ this.physics.add.collider(this.unitGroup, this.unitGroup);
+
+ // === 6. 创建小地图 ===
+ this.miniMap = new MiniMap(this, {
+ worldSize: worldSize,
+ mapSize: 200,
+ x: 10,
+ y: 10
+ });
}
update() {
+ // 更新输入(键盘、鼠标)
this.inputSystem.update();
+
+ // 更新所有单位的移动逻辑和选中状态(关键!)
+ this.unitGroup.getChildren().forEach(unit => {
+ if (unit.update) {
+ unit.update();
+ }
+ unit.updateMovement();
+ });
+
+ // 更新小地图
+ if (this.miniMap) {
+ this.miniMap.update();
+ }
+ }
+
+ /**
+ * 窗口大小变化时的处理函数
+ */
+ resizeGame() {
+ this.scale.resize(window.innerWidth, window.innerHeight);
}
}
\ No newline at end of file
diff --git a/systems/CommandSystem.js b/systems/CommandSystem.js
index 9ee4e42..f79e1f3 100644
--- a/systems/CommandSystem.js
+++ b/systems/CommandSystem.js
@@ -11,14 +11,20 @@ export class CommandSystem {
}
onMoveRelative(dir) {
- if (activeUnit) {
- activeUnit.moveRelative(dir.x, dir.y);
- }
+ const targets = selectedUnits.length > 0 ? selectedUnits : (activeUnit ? [activeUnit] : []);
+
+ targets.forEach(unit => {
+ unit.moveRelative(dir.x, dir.y);
+ });
}
onMoveToPoint(x, y) {
const targets = selectedUnits.length > 0 ? selectedUnits : (activeUnit ? [activeUnit] : []);
- targets.forEach(unit => unit.moveTo(x, y));
+
+
+ targets.forEach(unit => {
+ unit.setTarget(x, y); // ← 使用新接口
+ });
}
onSelectUnit(unit) {
diff --git a/systems/ControlMode.js b/systems/ControlMode.js
index 9f3b3ae..a11061c 100644
--- a/systems/ControlMode.js
+++ b/systems/ControlMode.js
@@ -9,7 +9,7 @@ export let selectedUnits = [];
export function setActiveUnit(unit) {
activeUnit = unit;
- selectedUnits = unit ? [unit] : [];
+ // 不再重置selectedUnits数组,这样可以同时选中多个单位
}
export function addToSelection(unit) {
diff --git a/systems/InputSystem.js b/systems/InputSystem.js
index 1f1d973..26b93d7 100644
--- a/systems/InputSystem.js
+++ b/systems/InputSystem.js
@@ -15,8 +15,12 @@ export class InputSystem {
right: [kb.addKey(Phaser.Input.Keyboard.KeyCodes.D), kb.addKey(Phaser.Input.Keyboard.KeyCodes.RIGHT)]
};
+ // 禁用浏览器右键菜单
+ scene.input.mouse.disableContextMenu();
+
// 鼠标/触摸事件
scene.input.on('pointerdown', this.onPointerDown, this);
+ scene.input.on('pointermove', this.onPointerMove, this);
scene.input.on('pointerup', this.onPointerUp, this);
}
@@ -32,12 +36,17 @@ export class InputSystem {
if (this.isKeyDown(this.keys.up)) dir.y -= 1;
if (this.isKeyDown(this.keys.down)) dir.y += 1;
- if (dir.x !== 0 || dir.y !== 0) {
- this.scene.events.emit('move-relative', dir);
- }
+ // 无论是否有按键按下,都发送移动事件
+ // 当dir.x和dir.y都为0时,单位会停止移动
+ this.scene.events.emit('move-relative', dir);
}
onPointerDown(pointer) {
+ // 检查鼠标是否在小地图上,如果是则不处理框选
+ if (this.isMouseOnMiniMap(pointer)) {
+ return;
+ }
+
if (pointer.leftButtonDown()) {
const worldX = pointer.worldX;
const worldY = pointer.worldY;
@@ -57,19 +66,59 @@ export class InputSystem {
this.scene.events.emit('select-unit', clickedUnit);
} else {
// 开始框选或移动指令
- this.selectionStart = { x: pointer.x, y: pointer.y };
+ // 存储世界坐标用于框选
+ this.selectionStart = { x: worldX, y: worldY };
this.scene.events.emit('selection-start', this.selectionStart);
}
+ } else if (pointer.rightButtonDown()) {
+ // 右键点击:移动选中的单位
+ const worldX = pointer.worldX;
+ const worldY = pointer.worldY;
+ this.scene.events.emit('move-to-point', worldX, worldY);
+ }
+ }
+
+ onPointerMove(pointer) {
+ // 检查鼠标是否在小地图上,如果是则不处理框选
+ if (this.isMouseOnMiniMap(pointer)) {
+ return;
+ }
+
+ if (this.selectionStart) {
+ // 使用世界坐标计算框选区域
+ const currentWorldX = pointer.worldX;
+ const currentWorldY = pointer.worldY;
+
+ // 计算当前选择框的位置和大小
+ const rect = new Phaser.Geom.Rectangle(
+ Math.min(this.selectionStart.x, currentWorldX),
+ Math.min(this.selectionStart.y, currentWorldY),
+ Math.abs(currentWorldX - this.selectionStart.x),
+ Math.abs(currentWorldY - this.selectionStart.y)
+ );
+
+ // 更新选择框的位置和大小
+ this.scene.events.emit('selection-update', rect);
}
}
onPointerUp(pointer) {
+ // 检查鼠标是否在小地图上,如果是则不处理框选
+ if (this.isMouseOnMiniMap(pointer)) {
+ this.selectionStart = null;
+ return;
+ }
+
if (this.selectionStart) {
+ // 使用世界坐标计算最终框选区域
+ const currentWorldX = pointer.worldX;
+ const currentWorldY = pointer.worldY;
+
const rect = new Phaser.Geom.Rectangle(
- Math.min(this.selectionStart.x, pointer.x),
- Math.min(this.selectionStart.y, pointer.y),
- Math.abs(pointer.x - this.selectionStart.x),
- Math.abs(pointer.y - this.selectionStart.y)
+ Math.min(this.selectionStart.x, currentWorldX),
+ Math.min(this.selectionStart.y, currentWorldY),
+ Math.abs(currentWorldX - this.selectionStart.x),
+ Math.abs(currentWorldY - this.selectionStart.y)
);
// 如果框太小,视为“点击地面移动”
@@ -83,4 +132,30 @@ export class InputSystem {
this.selectionStart = null;
}
}
+
+ /**
+ * 检查鼠标是否在小地图上
+ * @param {Phaser.Input.Pointer} pointer
+ * @returns {boolean}
+ */
+ isMouseOnMiniMap(pointer) {
+ // 检查场景中是否有小地图
+ if (!this.scene.miniMap) {
+ return false;
+ }
+
+ const miniMap = this.scene.miniMap;
+ const mouseX = pointer.x;
+ const mouseY = pointer.y;
+
+ // 计算小地图的边界
+ const mapLeft = miniMap.x;
+ const mapRight = miniMap.x + miniMap.mapSize;
+ const mapTop = miniMap.y;
+ const mapBottom = miniMap.y + miniMap.mapSize;
+
+ // 检查鼠标是否在小地图边界内
+ return mouseX >= mapLeft && mouseX <= mapRight &&
+ mouseY >= mapTop && mouseY <= mapBottom;
+ }
}
\ No newline at end of file
diff --git a/systems/SelectionSystem.js b/systems/SelectionSystem.js
index 4139834..38fe6d7 100644
--- a/systems/SelectionSystem.js
+++ b/systems/SelectionSystem.js
@@ -7,7 +7,9 @@ export class SelectionSystem {
this.selectionBox = null;
scene.events.on('selection-start', this.onStart, this);
+ scene.events.on('selection-update', this.onUpdate, this);
scene.events.on('selection-end', this.onEnd, this);
+ scene.events.on('select-unit', this.onSelectUnit, this);
}
onStart(pos) {
@@ -20,6 +22,37 @@ export class SelectionSystem {
}
}
+ onUpdate(rect) {
+ if (this.selectionBox && this.selectionBox.visible) {
+ // 更新选择框的位置和大小
+ this.selectionBox.setPosition(rect.centerX, rect.centerY);
+ this.selectionBox.setSize(rect.width, rect.height);
+ }
+ }
+
+ onSelectUnit(unit) {
+ // 清除所有单位的选中状态
+ this.scene.unitGroup.getChildren().forEach(u => {
+ if (u.setSelected) {
+ u.setSelected(false);
+ }
+ });
+
+ // 清除当前选择
+ clearSelection();
+
+ // 选择点击的单位
+ setActiveUnit(unit);
+ addToSelection(unit);
+
+ // 设置选中状态
+ if (unit.setSelected) {
+ unit.setSelected(true);
+ }
+
+ this.scene.events.emit('units-selected', [unit]);
+ }
+
onEnd(rect) {
if (this.selectionBox) {
this.selectionBox.setVisible(false);
@@ -30,12 +63,28 @@ export class SelectionSystem {
const unitRect = unit.getBounds();
return Phaser.Geom.Rectangle.Overlaps(rect, unitRect);
});
+
+
+
+ // 先清除所有单位的选中状态
+ units.forEach(unit => {
+ if (unit.setSelected) {
+ unit.setSelected(false);
+ }
+ });
clearSelection();
- selected.forEach(addToSelection);
if (selected.length > 0) {
- setActiveUnit(selected[0]); // 激活第一个
+ // 先设置激活单位
+ setActiveUnit(selected[0]);
+ // 然后添加所有选中的单位(包括激活单位)
+ selected.forEach(unit => {
+ addToSelection(unit);
+ if (unit.setSelected) {
+ unit.setSelected(true);
+ }
+ });
this.scene.events.emit('units-selected', selected);
}
}