mirror of
http://junepve.top:3000/chicrobot/CINC.git
synced 2026-10-29 19:51:29 +08:00
feat(control) 多角色控制
This commit is contained in:
1 parent
be5ee91328
commit
f86e5f3a79
11 files changed
+431
No files matched your search
@@ -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
@@ -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>
|
||||||
@@ -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);
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 = [];
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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; // 容差
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
Reference in new issue
Block a user