feat(control) 多角色控制

This commit is contained in:
2025-12-30 21:45:25 +08:00
parent be5ee91328
commit f86e5f3a79
11 changed files with 431 additions and 0 deletions
+17
View File
@@ -0,0 +1,17 @@
export default {
type: Phaser.AUTO,
width: window.innerWidth,
height: window.innerHeight,
backgroundColor: '#87ceeb',
physics: {
default: 'arcade',
arcade: {
gravity: { y: 0 },
debug: false
}
},
scale: {
mode: Phaser.Scale.RESIZE,
autoCenter: Phaser.Scale.NO_CENTER
}
};
+12
View File
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Multi-Control Demo</title>
<style>body { margin: 0; }</style>
</head>
<body>
<script src="./phaser.min.js"></script>
<script type="module" src="./main.js"></script>
</body>
</html>
+9
View File
@@ -0,0 +1,9 @@
import gameConfig from './config/gameConfig.js';
import GameScene from './scenes/GameScene.js';
const config = {
...gameConfig,
scene: [GameScene]
};
window.game = new Phaser.Game(config);
+16
View File
@@ -0,0 +1,16 @@
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.setCollideWorldBounds(true);
}
moveTo(targetX, targetY) {
this.scene.physics.moveToObject(this, {x: targetX, y: targetY}, 200);
}
moveRelative(dirX, dirY, speed = 100) {
this.setVelocity(dirX * speed, dirY * speed);
}
}
+26
View File
@@ -0,0 +1,26 @@
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';
export default class GameScene extends Phaser.Scene {
create() {
this.unitGroup = this.add.group();
for (let i = 0; i < 5; i++) {
const unit = new Unit(this, 100 + i * 80, 300, 'player');
this.unitGroup.add(unit);
}
this.inputSystem = new InputSystem(this);
this.selectionSystem = new SelectionSystem(this);
this.commandSystem = new CommandSystem(this);
// 初始化虚拟摇杆
this.virtualJoystick = new VirtualJoystick(this);
}
update() {
this.inputSystem.update();
}
}
+31
View File
@@ -0,0 +1,31 @@
// systems/CommandSystem.js
import { activeUnit, selectedUnits } from './ControlMode.js';
export class CommandSystem {
constructor(scene) {
this.scene = scene;
scene.events.on('move-relative', this.onMoveRelative, this);
scene.events.on('move-to-point', this.onMoveToPoint, this);
scene.events.on('select-unit', this.onSelectUnit, this);
}
onMoveRelative(dir) {
if (activeUnit) {
activeUnit.moveRelative(dir.x, dir.y);
}
}
onMoveToPoint(x, y) {
const targets = selectedUnits.length > 0 ? selectedUnits : (activeUnit ? [activeUnit] : []);
targets.forEach(unit => unit.moveTo(x, y));
}
onSelectUnit(unit) {
// 切换单位
import('./ControlMode.js').then(mod => {
mod.setActiveUnit(unit);
this.scene.events.emit('unit-activated', unit);
});
}
}
+24
View File
@@ -0,0 +1,24 @@
export const CONTROL_MODE = {
SINGLE: 'single',
GROUP: 'group',
JOYSTICK: 'joystick'
};
export let activeUnit = null;
export let selectedUnits = [];
export function setActiveUnit(unit) {
activeUnit = unit;
selectedUnits = unit ? [unit] : [];
}
export function addToSelection(unit) {
if (!selectedUnits.includes(unit)) {
selectedUnits.push(unit);
}
}
export function clearSelection() {
activeUnit = null;
selectedUnits = [];
}
+86
View File
@@ -0,0 +1,86 @@
// systems/InputSystem.js
import { clearSelection, setActiveUnit } from './ControlMode.js';
export class InputSystem {
constructor(scene) {
this.scene = scene;
this.selectionStart = null;
// 初始化键盘
const kb = scene.input.keyboard;
this.keys = {
up: [kb.addKey(Phaser.Input.Keyboard.KeyCodes.W), kb.addKey(Phaser.Input.Keyboard.KeyCodes.UP)],
down: [kb.addKey(Phaser.Input.Keyboard.KeyCodes.S), kb.addKey(Phaser.Input.Keyboard.KeyCodes.DOWN)],
left: [kb.addKey(Phaser.Input.Keyboard.KeyCodes.A), kb.addKey(Phaser.Input.Keyboard.KeyCodes.LEFT)],
right: [kb.addKey(Phaser.Input.Keyboard.KeyCodes.D), kb.addKey(Phaser.Input.Keyboard.KeyCodes.RIGHT)]
};
// 鼠标/触摸事件
scene.input.on('pointerdown', this.onPointerDown, this);
scene.input.on('pointerup', this.onPointerUp, this);
}
isKeyDown(keyGroup) {
return keyGroup.some(key => key.isDown);
}
update() {
// 键盘相对移动(仅当有激活单位)
const dir = { x: 0, y: 0 };
if (this.isKeyDown(this.keys.left)) dir.x -= 1;
if (this.isKeyDown(this.keys.right)) dir.x += 1;
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);
}
}
onPointerDown(pointer) {
if (pointer.leftButtonDown()) {
const worldX = pointer.worldX;
const worldY = pointer.worldY;
// 检查是否点中某个单位
let clickedUnit = null;
this.scene.unitGroup.getChildren().forEach(unit => {
const bounds = unit.getBounds();
if (worldX >= bounds.x && worldX <= bounds.right &&
worldY >= bounds.y && worldY <= bounds.bottom) {
clickedUnit = unit;
}
});
if (clickedUnit) {
// 切换控制目标
this.scene.events.emit('select-unit', clickedUnit);
} else {
// 开始框选或移动指令
this.selectionStart = { x: pointer.x, y: pointer.y };
this.scene.events.emit('selection-start', this.selectionStart);
}
}
}
onPointerUp(pointer) {
if (this.selectionStart) {
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)
);
// 如果框太小,视为“点击地面移动”
if (rect.width < 10 && rect.height < 10) {
this.scene.events.emit('move-to-point', pointer.worldX, pointer.worldY);
} else {
// 否则执行框选
this.scene.events.emit('selection-end', rect);
}
this.selectionStart = null;
}
}
}
+42
View File
@@ -0,0 +1,42 @@
// systems/SelectionSystem.js
import { clearSelection, addToSelection, setActiveUnit } from './ControlMode.js';
export class SelectionSystem {
constructor(scene) {
this.scene = scene;
this.selectionBox = null;
scene.events.on('selection-start', this.onStart, this);
scene.events.on('selection-end', this.onEnd, this);
}
onStart(pos) {
if (!this.selectionBox) {
this.selectionBox = this.scene.add.rectangle(pos.x, pos.y, 1, 1, 0x00ff00, 0.3)
.setStrokeStyle(1, 0xffffff)
.setVisible(true);
} else {
this.selectionBox.setPosition(pos.x, pos.y).setSize(1, 1).setVisible(true);
}
}
onEnd(rect) {
if (this.selectionBox) {
this.selectionBox.setVisible(false);
}
const units = this.scene.unitGroup.getChildren();
const selected = units.filter(unit => {
const unitRect = unit.getBounds();
return Phaser.Geom.Rectangle.Overlaps(rect, unitRect);
});
clearSelection();
selected.forEach(addToSelection);
if (selected.length > 0) {
setActiveUnit(selected[0]); // 激活第一个
this.scene.events.emit('units-selected', selected);
}
}
}
+78
View File
@@ -0,0 +1,78 @@
// ui/VirtualJoystick.js
export class VirtualJoystick {
constructor(scene) {
this.scene = scene;
this.isActive = false;
this.base = null;
this.knob = null;
this.pointer = null;
this.centerX = 0;
this.centerY = 0;
this.radius = 50;
// 仅在触屏设备显示
if ('ontouchstart' in window) {
this.create();
this.bindEvents();
}
}
create() {
const x = 100;
const y = this.scene.scale.height - 100;
this.centerX = x;
this.centerY = y;
this.base = this.scene.add.circle(x, y, this.radius, 0x444444, 0.5);
this.knob = this.scene.add.circle(x, y, this.radius * 0.5, 0xff0000, 0.8);
this.base.setScrollFactor(0); // 不随相机移动
this.knob.setScrollFactor(0);
}
bindEvents() {
this.scene.input.on('pointerdown', (ptr) => {
if (this.isInBase(ptr)) {
this.pointer = ptr;
this.isActive = true;
}
});
this.scene.input.on('pointermove', (ptr) => {
if (this.isActive && this.pointer === ptr) {
const dx = ptr.x - this.centerX;
const dy = ptr.y - this.centerY;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance > this.radius) {
// 限制在圆内
const angle = Math.atan2(dy, dx);
ptr.x = this.centerX + Math.cos(angle) * this.radius;
ptr.y = this.centerY + Math.sin(angle) * this.radius;
}
this.knob.setPosition(ptr.x, ptr.y);
// 发送相对方向(归一化)
const dirX = dx / this.radius;
const dirY = dy / this.radius;
this.scene.events.emit('move-relative', { x: dirX, y: dirY });
}
});
this.scene.input.on('pointerup', (ptr) => {
if (this.pointer === ptr) {
this.isActive = false;
this.pointer = null;
this.knob.setPosition(this.centerX, this.centerY);
this.scene.events.emit('move-relative', { x: 0, y: 0 }); // 停止
}
});
}
isInBase(pointer) {
const dx = pointer.x - this.centerX;
const dy = pointer.y - this.centerY;
return Math.sqrt(dx * dx + dy * dy) <= this.radius + 20; // 容差
}
}
+90
View File
@@ -0,0 +1,90 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Phaser Top-Down Demo</title>
<style>body { margin: 0; }</style>
</head>
<body>
<!-- <script src="https://cdn.jsdelivr.net/npm/phaser@3/dist/phaser.min.js"></script>-->
<script src="../phaser.min.js"></script>
<script>
const config = {
type: Phaser.AUTO,
width: window.innerWidth,
height: window.innerHeight,
backgroundColor: '#87ceeb',
physics: {
default: 'arcade',
arcade: {
debug: false
}
},
scene: {
preload: preload,
create: create,
update: update
}
};
const game = new Phaser.Game(config);
let player;
let cursors;
function preload() {
// 创建一个简单的圆形纹理作为玩家
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);
}
function create() {
// 添加玩家(启用物理)
player = this.physics.add.sprite(400, 300, 'player');
player.setCollideWorldBounds(true);
player.setBounce(0.1);
player.setDrag(0.9);
// 设置世界大小(模拟开放世界)
this.physics.world.setBounds(-2000, -2000, 4000, 4000);
// 相机跟随
this.cameras.main.startFollow(player, true, 0.1, 0.1);
this.cameras.main.setBounds(-2000, -2000, 4000, 4000);
// 键盘输入
cursors = this.input.keyboard.createCursorKeys();
this.wasd = {
up: this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.W),
down: this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.S),
left: this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.A),
right: this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.D)
};
}
function update() {
const speed = 500;
player.setVelocity(0);
if (cursors.up.isDown || this.wasd.up.isDown) {
player.setVelocityY(-speed);
}
if (cursors.down.isDown || this.wasd.down.isDown) {
player.setVelocityY(speed);
}
if (cursors.left.isDown || this.wasd.left.isDown) {
player.setVelocityX(-speed);
}
if (cursors.right.isDown || this.wasd.right.isDown) {
player.setVelocityX(speed);
}
}
</script>
</body>
</html>