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