From cbbed1519e48b0b66d666f9dd20c06b583183243 Mon Sep 17 00:00:00 2001 From: chicrobot Date: Thu, 12 Feb 2026 18:26:16 +0800 Subject: [PATCH] =?UTF-8?q?refactor(SRC)=20=E9=87=8D=E6=9E=84=E6=A0=B8?= =?UTF-8?q?=E5=BF=83=E5=9C=BA=E6=99=AF=E8=B7=AF=E5=BE=84=E4=BD=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.js | 28 +- scenes/storeScenes/Battle.js | 0 scenes/storeScenes/InfoBar.js | 0 scenes/storeScenes/Joystick.js | 79 ---- scenes/storeScenes/Terrain.js | 0 src/behaviors/AIBehavior.js | 72 ++++ src/behaviors/PlayerControlBehavior.js | 107 +++++ src/behaviors/SearchBehavior.js | 161 ++++++++ src/objects/Joystick.js | 72 ++++ src/objects/Unit.js | 368 ++++++++++++++++++ .../storeScenes => src/scenes}/BreatheLife.js | 0 {scenes/storeScenes => src/scenes}/Control.js | 70 ++-- {scenes/storeScenes => src/scenes}/Farming.js | 0 .../scenes}/PrimordialDevide.js | 0 .../storeScenes => src/scenes}/WorldLand.js | 0 .../storeScenes => src/scenes}/WorldTime.js | 0 src/utils/String.js | 7 + test/movementTests.js | 354 ----------------- 18 files changed, 845 insertions(+), 473 deletions(-) delete mode 100644 scenes/storeScenes/Battle.js delete mode 100644 scenes/storeScenes/InfoBar.js delete mode 100644 scenes/storeScenes/Joystick.js delete mode 100644 scenes/storeScenes/Terrain.js create mode 100644 src/behaviors/AIBehavior.js create mode 100644 src/behaviors/PlayerControlBehavior.js create mode 100644 src/behaviors/SearchBehavior.js create mode 100644 src/objects/Joystick.js create mode 100644 src/objects/Unit.js rename {scenes/storeScenes => src/scenes}/BreatheLife.js (100%) rename {scenes/storeScenes => src/scenes}/Control.js (61%) rename {scenes/storeScenes => src/scenes}/Farming.js (100%) rename {scenes/storeScenes => src/scenes}/PrimordialDevide.js (100%) rename {scenes/storeScenes => src/scenes}/WorldLand.js (100%) rename {scenes/storeScenes => src/scenes}/WorldTime.js (100%) create mode 100644 src/utils/String.js delete mode 100644 test/movementTests.js diff --git a/main.js b/main.js index 29778df..f83edb6 100644 --- a/main.js +++ b/main.js @@ -51,23 +51,25 @@ import WeaponTestScene from './scenes/testScenes/WeaponTestScene.js'; // 故事场景 // 开天辟地,创建场景 -import PrimordialDevide from './scenes/storeScenes/PrimordialDevide.js'; +import PrimordialDevide from './src/scenes/PrimordialDevide.js'; // 日月山川,创建时间,地图、物品 -import WorldTime from './scenes/storeScenes/WorldTime.js'; -import WorldLand from './scenes/storeScenes/WorldLand.js'; +import WorldTime from './src/scenes/WorldTime.js'; +import WorldLand from './src/scenes/WorldLand.js'; // 女娲造人,创建角色 -import BreatheLife from './scenes/storeScenes/BreatheLife.js'; -// 伏羲画卦,创建角色控制与信息栏 -import Control from './scenes/storeScenes/Control.js'; -// import InfoBar from './scenes/storeScenes/InfoBar.js'; -// // 钻木取火,添加合成系统 -// import Synthesis from './scenes/storeScenes/Synthesis.js'; +import BreatheLife from './src/scenes/BreatheLife.js'; +// 伏羲画卦,创建角色控制、信息栏与多语言 +import Control from './src/scenes/Control.js'; +// import InfoBar from './src/scenes/InfoBar.js'; +// import MultiLanguage from './src/scenes/MultiLanguage.js'; +// // 钻木取火,添加光影与合成系统 +// import Shadow from './src/scenes/Shadow.js'; +// import Synthesis from './src/scenes/Synthesis.js'; // // 神农百草,创建耕种系统 -// import Farming from './scenes/storeScenes/Farming.js'; +// import Farming from './src/scenes/Farming.js'; // // 涿鹿之战,创建战斗系统 -// import Battle from './scenes/storeScenes/Battle.js'; -// // 大禹治水,创建地形系统 -// import Terrain from './scenes/storeScenes/Terrain.js'; +// import Battle from './src/scenes/Battle.js'; +// // 大禹治水,创建建造,含地形改造 +// import Building from './src/scenes/Building.js'; // 创建游戏的最终配置对象 const config = { diff --git a/scenes/storeScenes/Battle.js b/scenes/storeScenes/Battle.js deleted file mode 100644 index e69de29..0000000 diff --git a/scenes/storeScenes/InfoBar.js b/scenes/storeScenes/InfoBar.js deleted file mode 100644 index e69de29..0000000 diff --git a/scenes/storeScenes/Joystick.js b/scenes/storeScenes/Joystick.js deleted file mode 100644 index b388dc0..0000000 --- a/scenes/storeScenes/Joystick.js +++ /dev/null @@ -1,79 +0,0 @@ -export default class Joystick { - constructor(scene, x, y, radius, baseColor, thumbColor) { - this.scene = scene - this.x = x - this.y = y - this.radius = radius - this.baseColor = baseColor - this.thumbColor = thumbColor - } - /** - * 创建摇杆,设置层级 - */ - createJoystick(layer = 100) { - // 创建基座 - this.joystickBase = this.scene.add.circle(0, 0, this.radius, this.baseColor) - this.joystickBase.setPosition(this.x, this.y) - // 创建按钮 - this.joystickButton = this.scene.add.circle(0, 0, this.radius / 2, this.thumbColor) - this.joystickButton.setPosition(this.x, this.y) - // 设置层级 - this.joystickBase.setDepth(layer) - this.joystickButton.setDepth(layer) - } - /** - * 获取摇杆归一化方向与力度 - * @returns {Object} 归一化方向 {x, y} 与力度 magnitude - */ - getJoystickDirection() { - // 获取摇杆方向,归一化 - const direction = { - x: this.joystickButton.x - this.joystickBase.x, - y: this.joystickButton.y - this.joystickBase.y, - } - // 计算力度 - const magnitude = Math.sqrt(direction.x ** 2 + direction.y ** 2) - // 归一化方向 - direction.x /= magnitude - direction.y /= magnitude - return {direction, magnitude} - } - /** - * 重置摇杆位置 - */ - resetJoystick() { - this.joystickButton.setPosition(this.x, this.y) - } - /** - * 监听摇杆输入 - */ - listenJoystickInput() { - // 监听按钮按下事件 - this.joystickButton.on('pointerdown', (pointer) => { - this.joystickButton.setPosition(pointer.x, pointer.y) - }) - // 监听按钮移动事件 - this.joystickButton.on('pointermove', (pointer) => { - // 计算摇杆按钮到基座中心的距离 - const dx = pointer.x - this.x - const dy = pointer.y - this.y - const distance = Math.sqrt(dx * dx + dy * dy) - - if (distance > this.radius) { - // 限制在基座范围内 - const angle = Math.atan2(dy, dx) - this.joystickButton.setPosition( - this.x + this.radius * Math.cos(angle), - this.y + this.radius * Math.sin(angle) - ) - } else { - // 在基座范围内,跟随手指 - this.joystickButton.setPosition(pointer.x, pointer.y) - } - }) - // 监听按钮松开事件 - this.joystickButton.on('pointerup', () => { - this.resetJoystick() - }) - } -} \ No newline at end of file diff --git a/scenes/storeScenes/Terrain.js b/scenes/storeScenes/Terrain.js deleted file mode 100644 index e69de29..0000000 diff --git a/src/behaviors/AIBehavior.js b/src/behaviors/AIBehavior.js new file mode 100644 index 0000000..bcad854 --- /dev/null +++ b/src/behaviors/AIBehavior.js @@ -0,0 +1,72 @@ +/** + * AI 行为基类 + * + * 定义单位的行为模式,用于实现不同的 AI 逻辑 + */ +export default class AIBehavior { + /** + * 构造函数 + * @param {Unit} unit - 单位对象 + */ + constructor(unit) { + this.unit = unit; + this.isActive = false; + this.target = null; + } + + /** + * 激活行为 + */ + activate() { + this.isActive = true; + this.onStart(); + } + + /** + * 停用行为 + */ + deactivate() { + this.isActive = false; + this.onStop(); + } + + /** + * 更新行为 + * @param {number} delta - 时间增量 + */ + update(delta) { + if (!this.isActive) return; + this.onUpdate(delta); + } + + /** + * 行为开始时的回调 + */ + onStart() { + // 子类重写 + } + + /** + * 行为停止时的回调 + */ + onStop() { + // 子类重写 + this.target = null; + } + + /** + * 行为更新时的回调 + * @param {number} delta - 时间增量 + */ + onUpdate(delta) { + // 子类重写 + } + + /** + * 设置目标 + * @param {Object} target - 目标对象 + */ + setTarget(target) { + this.target = target; + } +} diff --git a/src/behaviors/PlayerControlBehavior.js b/src/behaviors/PlayerControlBehavior.js new file mode 100644 index 0000000..cbb95c3 --- /dev/null +++ b/src/behaviors/PlayerControlBehavior.js @@ -0,0 +1,107 @@ +/** + * 玩家控制行为类 + * + * 实现玩家通过摇杆或键盘控制单位的行为 + */ +import AIBehavior from './AIBehavior.js'; + +export default class PlayerControlBehavior extends AIBehavior { + /** + * 构造函数 + * @param {Unit} unit - 单位对象 + * @param {Object} options - 配置选项 + */ + constructor(unit, options = {}) { + super(unit); + + this.speed = options.speed || 200; + this.useJoystick = options.useJoystick !== false; + this.useKeyboard = options.useKeyboard !== false; + + this.moveDirection = { x: 0, y: 0 }; + this.joystickEventName = options.joystickEventName || 'move-relative'; + } + + /** + * 行为开始 + */ + onStart() { + if (this.useJoystick) { + this.setupJoystickControl(); + } + if (this.useKeyboard) { + this.setupKeyboardControl(); + } + } + + /** + * 行为停止 + */ + onStop() { + this.moveDirection = { x: 0, y: 0 }; + this.unit.stopMovement(); + + if (this.useJoystick) { + this.teardownJoystickControl(); + } + if (this.useKeyboard) { + this.teardownKeyboardControl(); + } + } + + /** + * 行为更新 + * @param {number} delta - 时间增量 + */ + onUpdate(delta) { + if (this.moveDirection.x !== 0 || this.moveDirection.y !== 0) { + this.unit.setMovementDirection(this.moveDirection); + } else { + this.unit.stopMovement(); + } + } + + /** + * 设置摇杆控制 + */ + setupJoystickControl() { + this.joystickHandler = (data) => { + this.moveDirection = { x: data.x, y: data.y }; + }; + + this.unit.scene.events.on(this.joystickEventName, this.joystickHandler); + } + + /** + * 移除摇杆控制 + */ + teardownJoystickControl() { + if (this.joystickHandler) { + this.unit.scene.events.off(this.joystickEventName, this.joystickHandler); + this.joystickHandler = null; + } + } + + /** + * 设置键盘控制 + */ + setupKeyboardControl() { + this.cursors = this.unit.scene.input.keyboard.createCursorKeys(); + } + + /** + * 移除键盘控制 + */ + teardownKeyboardControl() { + this.cursors = null; + } + + /** + * 设置移动速度 + * @param {number} speed - 移动速度 + */ + setSpeed(speed) { + this.speed = speed; + this.unit.setSpeed(speed); + } +} diff --git a/src/behaviors/SearchBehavior.js b/src/behaviors/SearchBehavior.js new file mode 100644 index 0000000..ef6cab0 --- /dev/null +++ b/src/behaviors/SearchBehavior.js @@ -0,0 +1,161 @@ +/** + * 搜索行为类 + * + * 实现单位自动搜索并收集目标物品的行为 + */ +import AIBehavior from './AIBehavior.js'; + +export default class SearchBehavior extends AIBehavior { + /** + * 构造函数 + * @param {Unit} unit - 单位对象 + * @param {Array} targets - 目标物品数组 + * @param {Object} options - 配置选项 + */ + constructor(unit, targets, options = {}) { + super(unit); + + this.targets = targets; + this.searchDelay = options.searchDelay || 500; + this.moveSpeed = options.moveSpeed || 200; + this.onCollect = options.onCollect || null; + this.onComplete = options.onComplete || null; + + this.currentTarget = null; + this.isMoving = false; + this.delayTimer = null; + } + + /** + * 行为开始 + */ + onStart() { + this.searchNearestTarget(); + } + + /** + * 行为停止 + */ + onStop() { + if (this.delayTimer) { + this.delayTimer.remove(); + this.delayTimer = null; + } + this.currentTarget = null; + this.isMoving = false; + } + + /** + * 行为更新 + * @param {number} delta - 时间增量 + */ + onUpdate(delta) { + if (this.isMoving) { + return; + } + + if (!this.currentTarget) { + this.searchNearestTarget(); + } + } + + /** + * 搜索最近的目标 + */ + searchNearestTarget() { + if (!this.targets || this.targets.length === 0) { + if (this.onComplete) { + this.onComplete(); + } + return; + } + + let nearestTarget = null; + let minDistance = Infinity; + + for (const target of this.targets) { + if (target.alpha === 0.5) continue; + + const distance = Phaser.Math.Distance.Between( + this.unit.x, + this.unit.y, + target.x, + target.y + ); + + if (distance < minDistance) { + minDistance = distance; + nearestTarget = target; + } + } + + if (nearestTarget) { + this.moveToTarget(nearestTarget, minDistance); + } else { + if (this.onComplete) { + this.onComplete(); + } + } + } + + /** + * 移动到目标位置 + * @param {Phaser.GameObjects.Sprite} target - 目标对象 + * @param {number} distance - 距离 + */ + moveToTarget(target, distance) { + this.currentTarget = target; + this.isMoving = true; + + const duration = distance * 10; + + this.unit.scene.tweens.add({ + targets: this.unit, + x: target.x, + y: target.y, + duration: duration, + ease: 'Linear', + onComplete: () => { + this.collectTarget(target); + } + }); + } + + /** + * 收集目标 + * @param {Phaser.GameObjects.Sprite} target - 目标对象 + */ + collectTarget(target) { + target.setAlpha(0.5); + + if (this.onCollect) { + this.onCollect(target); + } + + const index = this.targets.indexOf(target); + if (index > -1) { + this.targets.splice(index, 1); + } + + target.destroy(); + + this.currentTarget = null; + this.isMoving = false; + + const delay = Phaser.Math.Between(this.searchDelay / 2, this.searchDelay * 1.5); + this.delayTimer = this.unit.scene.time.delayedCall(delay, () => { + this.searchNearestTarget(); + }); + } + + /** + * 更新目标数组 + * @param {Array} targets - 新的目标数组 + */ + updateTargets(targets) { + this.targets = targets; + if (!this.currentTarget) { + this.searchNearestTarget(); + } + } +} diff --git a/src/objects/Joystick.js b/src/objects/Joystick.js new file mode 100644 index 0000000..0e69a38 --- /dev/null +++ b/src/objects/Joystick.js @@ -0,0 +1,72 @@ +export default class Joystick { + constructor(scene, x, y) { + this.scene = scene // 场景上下文 + this.centerX = x // 摇杆中心x坐标 + this.centerY = y // 摇杆中心y坐标 + this.radius = 50 // 摇杆半径 + this.baseColor = 0x888888 + this.thumbColor = 0xcccccc + + this.activePointer = null; // 保持单一激活指针,但允许不同指针触发 + this.isActive = false; // 是否激活 + } + /** + * 创建摇杆,设置层级 + */ + createJoystick(layer = 100) { + // 创建基座 + this.joystickBase = this.scene.add.circle(this.centerX, this.centerY, this.radius, this.baseColor) + // 创建按钮 + this.joystickButton = this.scene.add.circle(this.centerX, this.centerY, this.radius / 2, this.thumbColor) + // 设置层级 + this.joystickBase.setDepth(layer) + this.joystickButton.setDepth(layer) + // 不随相机移动 + this.joystickBase.setScrollFactor(0) + this.joystickButton.setScrollFactor(0) + } + /** + * 获取摇杆归一化方向与力度 + * @returns {Object} 归一化方向 {x, y} 与力度 magnitude + */ + getJoystickDirection() { + // 获取摇杆方向,归一化 + const direction = { + x: this.joystickButton.x - this.joystickBase.x, + y: this.joystickButton.y - this.joystickBase.y, + } + // 计算力度 + const magnitude = Math.sqrt(direction.x ** 2 + direction.y ** 2) + // 归一化方向 + direction.x /= magnitude + direction.y /= magnitude + return {direction, magnitude} + } + /** + * 重置摇杆位置 + */ + resetJoystick() { + this.joystickButton.setPosition(this.centerX, this.centerY) + } + /** + * 监听摇杆输入 + */ + startJoystickListen() { + // 监听指针按下事件 + this.scene.input.on('pointerdown', (ptr) => { + if (this.isInBase(ptr)) { + // 只有在没有激活指针时才设置新指针 + // 这样可以避免不同触摸点之间的干扰 + if (!this.isActive) { + this.activePointer = ptr; + this.isActive = true; + } + } + }); + } + isInBase(pointer) { + const dx = pointer.x - this.centerX; + const dy = pointer.y - this.centerY; + return Math.sqrt(dx * dx + dy * dy) <= this.radius + 20; // 容差 + } +} \ No newline at end of file diff --git a/src/objects/Unit.js b/src/objects/Unit.js new file mode 100644 index 0000000..e75c52e --- /dev/null +++ b/src/objects/Unit.js @@ -0,0 +1,368 @@ +/** + * Unit 类 + * + * 游戏中的基础单位类,用于创建和管理游戏角色、敌人等实体 + * 包含位置、移动、渲染、碰撞检测等核心功能 + * 支持行为系统,可灵活配置不同的 AI 或玩家控制行为 + */ +export default class Unit extends Phaser.GameObjects.Sprite { + /** + * 构造函数 + * @param {Phaser.Scene} scene - 游戏场景 + * @param {number} x - 初始 x 坐标 + * @param {number} y - 初始 y 坐标 + * @param {string} texture - 单位纹理 + * @param {string} name - 单位名称 + */ + constructor(scene, x, y, texture, name = 'unit') { + super(scene, x, y, texture); + + // 场景引用 + this.scene = scene; + + // 基础属性 + this.name = name; + this.type = 'unit'; // 单位类型 + + // 移动属性 + this.speed = 100; // 移动速度 + this.velocity = { x: 0, y: 0 }; // 速度向量 + this.canMove = true; // 是否可以移动 + + // 状态属性 + this.isSelected = false; // 是否被选中 + this.isMoving = false; // 是否正在移动 + this.isAlive = true; // 是否存活 + + // 健康属性 + this.maxHealth = 100; + this.health = this.maxHealth; + + // 渲染属性 + this.depth = 10; // 渲染层级 + + // 碰撞属性 + this.collider = null; // 碰撞器 + + // 行为系统 + this.behavior = null; // 当前行为 + this.behaviorStack = []; // 行为栈 + + // 初始化单位 + this.create(); + } + + /** + * 创建单位精灵和物理体 + */ + create() { + this.setOrigin(0.5, 1); // 设置原点为中心底部 + this.setScale(1); + + // 启用物理 + this.scene.physics.add.existing(this); + this.setCollideWorldBounds(true); + + // 同步位置 + this.x = this.x; + this.y = this.y; + } + + /** + * 更新单位状态 + * @param {number} delta - 时间增量 + */ + update(delta) { + if (!this.isAlive) return; + + // 更新行为 + if (this.behavior) { + this.behavior.update(delta); + } + + // 更新移动 + this.updateMovement(); + } + + /** + * 更新移动状态 + */ + updateMovement() { + if (!this.canMove || !this.isMoving) { + this.setVelocity(0, 0); + return; + } + + // 设置速度 + this.setVelocity( + this.velocity.x * this.speed, + this.velocity.y * this.speed + ); + } + + /** + * 设置单位移动方向 + * @param {Object} direction - 归一化方向向量 {x, y} + */ + setMovementDirection(direction) { + if (!this.canMove) return; + + // 归一化方向向量 + const magnitude = Math.sqrt(direction.x ** 2 + direction.y ** 2); + if (magnitude > 0) { + this.velocity.x = direction.x / magnitude; + this.velocity.y = direction.y / magnitude; + this.isMoving = true; + } else { + this.velocity.x = 0; + this.velocity.y = 0; + this.isMoving = false; + } + } + + /** + * 停止移动 + */ + stopMovement() { + this.velocity.x = 0; + this.velocity.y = 0; + this.isMoving = false; + this.setVelocity(0, 0); + } + + /** + * 设置选中状态 + * @param {boolean} selected - 是否选中 + */ + setSelected(selected) { + this.isSelected = selected; + // 可以在这里添加选中视觉效果 + if (this.isSelected) { + this.setTint(0x00ff00); + } else { + this.clearTint(); + } + } + + /** + * 受到伤害 + * @param {number} damage - 伤害值 + */ + takeDamage(damage) { + if (!this.isAlive) return; + + this.health -= damage; + if (this.health <= 0) { + this.health = 0; + this.die(); + } + + // 可以在这里添加受伤视觉效果 + this.setTint(0xff0000); + this.scene.tweens.add({ + targets: this, + alpha: 0.5, + duration: 100, + yoyo: true, + onComplete: () => { + this.clearTint(); + } + }); + } + + /** + * 恢复健康 + * @param {number} amount - 恢复量 + */ + heal(amount) { + if (!this.isAlive) return; + + this.health += amount; + if (this.health > this.maxHealth) { + this.health = this.maxHealth; + } + } + + /** + * 死亡 + */ + die() { + this.isAlive = false; + this.isSelected = false; + this.isMoving = false; + this.canMove = false; + + // 停止移动 + this.stopMovement(); + + // 停止行为 + if (this.behavior) { + this.behavior.deactivate(); + this.behavior = null; + } + + // 死亡视觉效果 + this.setTint(0x888888); + this.scene.tweens.add({ + targets: this, + alpha: 0, + scale: 0, + duration: 1000, + onComplete: () => { + this.destroy(); + } + }); + } + + /** + * 销毁单位 + */ + destroy() { + if (this.behavior) { + this.behavior.deactivate(); + this.behavior = null; + } + if (this.collider) { + this.collider.destroy(); + this.collider = null; + } + super.destroy(); + } + + /** + * 添加碰撞检测 + * @param {Phaser.GameObjects.Group|Phaser.GameObjects.Sprite} target - 碰撞目标 + * @param {Function} callback - 碰撞回调 + */ + addCollider(target, callback) { + this.collider = this.scene.physics.add.collider( + this, + target, + callback, + null, + this.scene + ); + } + + /** + * 播放动画 + * @param {string} animationKey - 动画键值 + */ + playAnimation(animationKey) { + if (this.anims) { + this.anims.play(animationKey); + } + } + + /** + * 设置速度 + * @param {number} speed - 移动速度 + */ + setSpeed(speed) { + this.speed = speed; + } + + /** + * 检查与其他单位的距离 + * @param {Unit} otherUnit - 其他单位 + * @returns {number} 距离 + */ + distanceTo(otherUnit) { + const dx = this.x - otherUnit.x; + const dy = this.y - otherUnit.y; + return Math.sqrt(dx * dx + dy * dy); + } + + /** + * 检查是否与其他单位碰撞 + * @param {Unit} otherUnit - 其他单位 + * @returns {boolean} 是否碰撞 + */ + collidesWith(otherUnit) { + const distance = this.distanceTo(otherUnit); + const combinedRadius = (this.width + otherUnit.width) / 4; + return distance < combinedRadius; + } + + /** + * 设置单位类型 + * @param {string} type - 单位类型 + */ + setType(type) { + this.type = type; + } + + /** + * 设置单位名称 + * @param {string} name - 单位名称 + */ + setName(name) { + this.name = name; + } + + /** + * 获取单位信息 + * @returns {Object} 单位信息 + */ + getInfo() { + return { + name: this.name, + type: this.type, + x: this.x, + y: this.y, + health: this.health, + maxHealth: this.maxHealth, + speed: this.speed, + isSelected: this.isSelected, + isMoving: this.isMoving, + isAlive: this.isAlive + }; + } + + /** + * 暂停单位 + */ + pause() { + this.canMove = false; + this.stopMovement(); + } + + /** + * 恢复单位 + */ + resume() { + this.canMove = true; + } + + /** + * 设置行为 + * @param {AIBehavior} behavior - 行为对象 + */ + setBehavior(behavior) { + if (this.behavior) { + this.behavior.deactivate(); + } + this.behavior = behavior; + if (this.behavior) { + this.behavior.activate(); + } + } + + /** + * 移除行为 + */ + removeBehavior() { + if (this.behavior) { + this.behavior.deactivate(); + this.behavior = null; + } + } + + /** + * 获取当前行为 + * @returns {AIBehavior} 当前行为 + */ + getBehavior() { + return this.behavior; + } +} diff --git a/scenes/storeScenes/BreatheLife.js b/src/scenes/BreatheLife.js similarity index 100% rename from scenes/storeScenes/BreatheLife.js rename to src/scenes/BreatheLife.js diff --git a/scenes/storeScenes/Control.js b/src/scenes/Control.js similarity index 61% rename from scenes/storeScenes/Control.js rename to src/scenes/Control.js index 418dee6..c7ea9fd 100644 --- a/scenes/storeScenes/Control.js +++ b/src/scenes/Control.js @@ -1,10 +1,11 @@ /** * Control 场景类 * - * 允许用户控制角色 + * 允许用户控制角色,NPC 自动搜索并收集小草 */ import BreatheLife from './BreatheLife.js' -import Joystick from './Joystick.js' +import Unit from '../objects/Unit.js' +import SearchBehavior from '../behaviors/SearchBehavior.js' export default class Control extends BreatheLife { constructor(sceneKey = 'Control') { // 场景相关 @@ -26,6 +27,18 @@ export default class Control extends BreatheLife { // 添加随机NPC this.addRandomNPCs() } + + update() { + super.update() + + // 更新所有 NPC 的行为 + this.NPCObjects.forEach(npc => { + if (npc.update) { + npc.update() + } + }) + } + // Control 场景不再需要覆盖 initSceneTransition 方法 // 数组初始化由父类 BreatheLife.create() 中的 initSceneTransition() 完成 // targetCount 通过在 create() 中直接设置来覆盖 @@ -39,9 +52,6 @@ export default class Control extends BreatheLife { const { x, y } = this.input.activePointer this.handleClick(x, y) }) - - // 添加摇杆 - this.addJoystick() } /** @@ -55,28 +65,42 @@ export default class Control extends BreatheLife { console.log("点击到的精灵:", clickedSprite) if (clickedSprite && this.NPCObjects.includes(clickedSprite)) { // 如果点击到了角色 this.handleSpriteClick(clickedSprite) - }else { - // 创建玩家可控制角色 - // this.createCharacter(x, y) + }else if (this.NPCObjects.length < this.targetCount) { + // 如果角色数量不足目标数量,创建新的角色 + const sprite = this.physics.add.sprite(x, y, 'player').setScale(1).setOrigin(0.5, 1) + sprite.setDepth(this.SpriteDepth) // 设置角色层级 + this.NPCObjects.push(sprite) // 加入角色集合 } } + /** * 创建NPC * @param {number} x - NPC的 x 坐标 * @param {number} y - NPC的 y 坐标 */ createNPC(x, y) { - // 创建一个新的角色 sprite,缩放为1倍,并设置原点为中心底部 - const sprite = this.physics.add.sprite(x, y, `player1-0`).setScale(1).setOrigin(0.5, 1) - sprite.setDepth(this.SpriteDepth) // 设置角色层级 - - this.NPCObjects.push(sprite) // 加入角色集合 - - this.addSpriteAnimation(sprite) // 添加角色动画 - this.searchAndAddSmallGrass(sprite) // 添加搜索逻辑,自动检测最近的小草 - // // 设置角色碰撞世界边界 - // sprite.setCollideWorldBounds(true) + // 使用 Unit 类创建 NPC + const npc = new Unit(this, x, y, 'player1-0') + npc.setType('npc') + npc.setDepth(this.SpriteDepth) // 设置NPC渲染层级 + + // 为 NPC 添加搜索行为 + const searchBehavior = new SearchBehavior(npc, this.smallGrassObjects, { + searchDelay: 500, + moveSpeed: 200, + onCollect: (grass) => { + this.sound.play('coin-a') + }, + onComplete: () => { + console.log('NPC 收集完所有小草') + } + }) + npc.setBehavior(searchBehavior) + + // 添加到NPC数组 + this.NPCObjects.push(npc) } + /** * 添加随机NPC */ @@ -93,12 +117,4 @@ export default class Control extends BreatheLife { }) } } - /** - * 添加摇杆 - */ - addJoystick() { - this.joystick = new Joystick(this, 100, this.game.config.height - 100, 50, 0x888888, 0xcccccc) - this.joystick.createJoystick(this.UIDepth) // 创建并设置摇杆层级为UI层 - this.joystick.listenJoystickInput() - } -} \ No newline at end of file +} diff --git a/scenes/storeScenes/Farming.js b/src/scenes/Farming.js similarity index 100% rename from scenes/storeScenes/Farming.js rename to src/scenes/Farming.js diff --git a/scenes/storeScenes/PrimordialDevide.js b/src/scenes/PrimordialDevide.js similarity index 100% rename from scenes/storeScenes/PrimordialDevide.js rename to src/scenes/PrimordialDevide.js diff --git a/scenes/storeScenes/WorldLand.js b/src/scenes/WorldLand.js similarity index 100% rename from scenes/storeScenes/WorldLand.js rename to src/scenes/WorldLand.js diff --git a/scenes/storeScenes/WorldTime.js b/src/scenes/WorldTime.js similarity index 100% rename from scenes/storeScenes/WorldTime.js rename to src/scenes/WorldTime.js diff --git a/src/utils/String.js b/src/utils/String.js new file mode 100644 index 0000000..84e4612 --- /dev/null +++ b/src/utils/String.js @@ -0,0 +1,7 @@ +export function generateUUID() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + const r = Math.random() * 16 | 0; + const v = c === 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); +} \ No newline at end of file diff --git a/test/movementTests.js b/test/movementTests.js deleted file mode 100644 index 7a050f8..0000000 --- a/test/movementTests.js +++ /dev/null @@ -1,354 +0,0 @@ -// 移动逻辑测试场景 -// 本文件将当前移动逻辑拆分为不同的测试场景 - -// 测试场景1: 相对移动(键盘控制) -// 测试内容: -// 1. 键盘方向键输入检测 -// 2. 方向向量归一化 -// 3. 移动速度控制 -// 4. 停止移动逻辑 -function testRelativeMovement() { - console.log("=== 测试场景1: 相对移动(键盘控制) ==="); - - // 模拟键盘输入 - const testCases = [ - { dir: { x: 1, y: 0 }, description: "向右移动" }, - { dir: { x: 0, y: 1 }, description: "向下移动" }, - { dir: { x: -1, y: 0 }, description: "向左移动" }, - { dir: { x: 0, y: -1 }, description: "向上移动" }, - { dir: { x: 1, y: 1 }, description: "向右下对角线移动" }, - { dir: { x: 0, y: 0 }, description: "停止移动" } - ]; - - testCases.forEach(testCase => { - console.log(`测试: ${testCase.description}`); - console.log(`输入方向: (${testCase.dir.x}, ${testCase.dir.y})`); - - // 计算归一化向量 - const magnitude = Math.sqrt(testCase.dir.x * testCase.dir.x + testCase.dir.y * testCase.dir.y); - let normalizedDir = { x: 0, y: 0 }; - if (magnitude > 0) { - normalizedDir = { - x: testCase.dir.x / magnitude, - y: testCase.dir.y / magnitude - }; - } - - console.log(`归一化方向: (${normalizedDir.x.toFixed(2)}, ${normalizedDir.y.toFixed(2)})`); - - // 计算速度向量 - const speed = 150; - const velocity = { - x: normalizedDir.x * speed, - y: normalizedDir.y * speed - }; - - console.log(`速度向量: (${velocity.x.toFixed(2)}, ${velocity.y.toFixed(2)})`); - console.log("------------------------"); - }); -} - -// 测试场景2: 目标点移动 -// 测试内容: -// 1. 目标点设置 -// 2. 世界边界限制 -// 3. 移动路径计算 -// 4. 到达目标点检测 -function testMoveToPoint() { - console.log("=== 测试场景2: 目标点移动 ==="); - - // 模拟世界边界 - const worldBounds = { x: 0, y: 0, width: 1000, height: 800 }; - - // 测试用例 - const testCases = [ - { - unitPos: { x: 100, y: 100 }, - targetPos: { x: 500, y: 400 }, - description: "正常范围内移动" - }, - { - unitPos: { x: 100, y: 100 }, - targetPos: { x: 1200, y: 900 }, - description: "超出世界边界的目标点" - }, - { - unitPos: { x: 500, y: 400 }, - targetPos: { x: 505, y: 405 }, - description: "接近当前位置的目标点" - } - ]; - - testCases.forEach(testCase => { - console.log(`测试: ${testCase.description}`); - console.log(`单位位置: (${testCase.unitPos.x}, ${testCase.unitPos.y})`); - console.log(`目标位置: (${testCase.targetPos.x}, ${testCase.targetPos.y})`); - - // 应用世界边界限制 - const clampedTarget = { - x: Math.max(worldBounds.x, Math.min(worldBounds.width, testCase.targetPos.x)), - y: Math.max(worldBounds.y, Math.min(worldBounds.height, testCase.targetPos.y)) - }; - - console.log(`限制后目标: (${clampedTarget.x}, ${clampedTarget.y})`); - - // 计算距离和方向 - const dx = clampedTarget.x - testCase.unitPos.x; - const dy = clampedTarget.y - testCase.unitPos.y; - const distance = Math.sqrt(dx * dx + dy * dy); - - console.log(`距离: ${distance.toFixed(2)}`); - - // 检查是否到达目标 - const arrived = distance < 5; - console.log(`是否到达目标: ${arrived}`); - - if (!arrived) { - // 计算移动方向和速度 - const angle = Math.atan2(dy, dx); - const speed = 150; - const velocity = { - x: Math.cos(angle) * speed, - y: Math.sin(angle) * speed - }; - console.log(`移动速度: (${velocity.x.toFixed(2)}, ${velocity.y.toFixed(2)})`); - } - - console.log("------------------------"); - }); -} - -// 测试场景3: 单位选择与移动命令 -// 测试内容: -// 1. 单个单位选择 -// 2. 多个单位选择 -// 3. 移动命令分发 -function testSelectionAndCommand() { - console.log("=== 测试场景3: 单位选择与移动命令 ==="); - - // 模拟单位和选择状态 - const mockUnits = [ - { id: 1, name: "单位1", moveRelative: () => {}, setTarget: () => {} }, - { id: 2, name: "单位2", moveRelative: () => {}, setTarget: () => {} }, - { id: 3, name: "单位3", moveRelative: () => {}, setTarget: () => {} } - ]; - - // 测试用例 - const testCases = [ - { - selectedUnits: [], - activeUnit: mockUnits[0], - command: "move-relative", - dir: { x: 1, y: 0 }, - description: "无选中单位,使用激活单位" - }, - { - selectedUnits: [mockUnits[0], mockUnits[1]], - activeUnit: mockUnits[0], - command: "move-to-point", - target: { x: 500, y: 400 }, - description: "多个选中单位,执行目标点移动" - }, - { - selectedUnits: [mockUnits[2]], - activeUnit: mockUnits[0], - command: "move-relative", - dir: { x: 0, y: -1 }, - description: "单个选中单位,执行相对移动" - } - ]; - - testCases.forEach(testCase => { - console.log(`测试: ${testCase.description}`); - console.log(`激活单位: ${testCase.activeUnit.name}`); - console.log(`选中单位: ${testCase.selectedUnits.map(u => u.name).join(", ")}`); - - // 确定命令目标 - const targets = testCase.selectedUnits.length > 0 ? testCase.selectedUnits : [testCase.activeUnit]; - console.log(`命令目标: ${targets.map(u => u.name).join(", ")}`); - - if (testCase.command === "move-relative") { - console.log(`执行相对移动: (${testCase.dir.x}, ${testCase.dir.y})`); - } else if (testCase.command === "move-to-point") { - console.log(`执行目标点移动: (${testCase.target.x}, ${testCase.target.y})`); - } - - console.log("------------------------"); - }); -} - -// 测试场景4: 虚拟摇杆控制 -// 测试内容: -// 1. 摇杆激活检测 -// 2. 摇杆方向计算 -// 3. 归一化方向输出 -// 4. 停止移动处理 -function testVirtualJoystick() { - console.log("=== 测试场景4: 虚拟摇杆控制 ==="); - - // 模拟摇杆配置 - const joystickConfig = { centerX: 100, centerY: 100, radius: 50 }; - - // 测试用例 - const testCases = [ - { - pointerPos: { x: 120, y: 100 }, - description: "向右轻微拖动" - }, - { - pointerPos: { x: 160, y: 100 }, - description: "向右拖动超出摇杆范围" - }, - { - pointerPos: { x: 135, y: 135 }, - description: "向右下对角线拖动" - }, - { - pointerPos: { x: 100, y: 100 }, - description: "摇杆中心位置(停止)" - } - ]; - - testCases.forEach(testCase => { - console.log(`测试: ${testCase.description}`); - console.log(`摇杆中心: (${joystickConfig.centerX}, ${joystickConfig.centerY})`); - console.log(`指针位置: (${testCase.pointerPos.x}, ${testCase.pointerPos.y})`); - - // 计算方向和距离 - const dx = testCase.pointerPos.x - joystickConfig.centerX; - const dy = testCase.pointerPos.y - joystickConfig.centerY; - const distance = Math.sqrt(dx * dx + dy * dy); - - console.log(`距离: ${distance.toFixed(2)}`); - - // 限制在摇杆范围内 - let limitedDx = dx; - let limitedDy = dy; - if (distance > joystickConfig.radius) { - const angle = Math.atan2(dy, dx); - limitedDx = Math.cos(angle) * joystickConfig.radius; - limitedDy = Math.sin(angle) * joystickConfig.radius; - console.log(`限制后偏移: (${limitedDx.toFixed(2)}, ${limitedDy.toFixed(2)})`); - } - - // 计算归一化方向 - const dirX = limitedDx / joystickConfig.radius; - const dirY = limitedDy / joystickConfig.radius; - console.log(`归一化方向: (${dirX.toFixed(2)}, ${dirY.toFixed(2)})`); - - console.log("------------------------"); - }); -} - -// 测试场景5: 移动更新逻辑 -// 测试内容: -// 1. 每帧移动更新 -// 2. 位置更新计算 -// 3. 目标到达检测 -function testMovementUpdate() { - console.log("=== 测试场景5: 移动更新逻辑 ==="); - - // 模拟单位和时间增量 - const mockUnit = { - x: 100, - y: 100, - target: { x: 500, y: 400 }, - setVelocity: (vx, vy) => { mockUnit.velocity = { x: vx, y: vy }; }, - setPosition: (x, y) => { mockUnit.x = x; mockUnit.y = y; } - }; - - const speed = 150; - const deltaTime = 1/60; // 60fps - - console.log("初始状态:"); - console.log(`单位位置: (${mockUnit.x}, ${mockUnit.y})`); - console.log(`目标位置: (${mockUnit.target.x}, ${mockUnit.target.y})`); - - // 模拟几帧的移动更新 - for (let i = 0; i < 5; i++) { - console.log(`\n第${i+1}帧更新:`); - - const dx = mockUnit.target.x - mockUnit.x; - const dy = mockUnit.target.y - mockUnit.y; - const distance = Math.sqrt(dx * dx + dy * dy); - - console.log(`距离目标: ${distance.toFixed(2)}`); - - if (distance < 5) { - // 到达目标 - mockUnit.setPosition(mockUnit.target.x, mockUnit.target.y); - mockUnit.setVelocity(0, 0); - mockUnit.target = null; - console.log("到达目标,停止移动"); - break; - } else { - // 继续移动 - const angle = Math.atan2(dy, dx); - const velocity = { - x: Math.cos(angle) * speed, - y: Math.sin(angle) * speed - }; - - mockUnit.setVelocity(velocity.x, velocity.y); - - // 更新位置 - mockUnit.x += velocity.x * deltaTime; - mockUnit.y += velocity.y * deltaTime; - - console.log(`速度: (${velocity.x.toFixed(2)}, ${velocity.y.toFixed(2)})`); - console.log(`新位置: (${mockUnit.x.toFixed(2)}, ${mockUnit.y.toFixed(2)})`); - } - } - - console.log("\n最终状态:"); - console.log(`单位位置: (${mockUnit.x}, ${mockUnit.y})`); - console.log(`目标状态: ${mockUnit.target ? "有目标" : "无目标"}`); -} - -// 运行所有测试场景 -function runAllTests() { - console.log("开始运行移动逻辑测试场景\n"); - - testRelativeMovement(); - console.log("\n" + "=".repeat(50) + "\n"); - - testMoveToPoint(); - console.log("\n" + "=".repeat(50) + "\n"); - - testSelectionAndCommand(); - console.log("\n" + "=".repeat(50) + "\n"); - - testVirtualJoystick(); - console.log("\n" + "=".repeat(50) + "\n"); - - testMovementUpdate(); - - console.log("\n" + "=".repeat(50)); - console.log("所有测试场景运行完成"); -} - -// 导出测试函数 -if (typeof module !== 'undefined' && module.exports) { - module.exports = { - testRelativeMovement, - testMoveToPoint, - testSelectionAndCommand, - testVirtualJoystick, - testMovementUpdate, - runAllTests - }; - // Node.js环境下自动运行测试 - runAllTests(); -} else { - // 浏览器环境下自动运行测试 - window.movementTests = { - runAllTests, - testRelativeMovement, - testMoveToPoint, - testSelectionAndCommand, - testVirtualJoystick, - testMovementUpdate - }; - // 浏览器环境下自动运行测试 - runAllTests(); -}