From 494922246bbaf13b0f219f57fcd15936f80d6b5d Mon Sep 17 00:00:00 2001 From: chicrobot Date: Fri, 13 Feb 2026 18:18:32 +0800 Subject: [PATCH] =?UTF-8?q?feat(SS)=20=E6=B7=BB=E5=8A=A0=E7=89=A9=E5=93=81?= =?UTF-8?q?=E7=B1=BB=E3=80=81=E5=8D=95=E4=BD=8D=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.js | 2 +- src/behaviors/AIBehavior.js | 72 ----- src/behaviors/PlayerControlBehavior.js | 107 ------- src/behaviors/SearchBehavior.js | 161 ----------- src/data/items.json | 24 ++ src/objects/Joystick.js | 72 ----- src/objects/Unit.js | 368 ------------------------- src/scenes/BreatheLife.js | 18 +- src/scenes/Control.js | 205 +++++++++----- src/scenes/Human.js | 259 +++++++++++++++++ src/scenes/WorldLand.js | 7 +- 11 files changed, 440 insertions(+), 855 deletions(-) delete mode 100644 src/behaviors/AIBehavior.js delete mode 100644 src/behaviors/PlayerControlBehavior.js delete mode 100644 src/behaviors/SearchBehavior.js create mode 100644 src/data/items.json delete mode 100644 src/objects/Joystick.js delete mode 100644 src/objects/Unit.js create mode 100644 src/scenes/Human.js diff --git a/main.js b/main.js index f83edb6..a0e04bb 100644 --- a/main.js +++ b/main.js @@ -78,11 +78,11 @@ const config = { ...gameConfig, // 指定游戏使用的场景列表,包括主场景和所有测试场景 scene: [ + Control, PrimordialDevide, WorldTime, WorldLand, BreatheLife, - Control, // 测试场景 MenuScene, diff --git a/src/behaviors/AIBehavior.js b/src/behaviors/AIBehavior.js deleted file mode 100644 index bcad854..0000000 --- a/src/behaviors/AIBehavior.js +++ /dev/null @@ -1,72 +0,0 @@ -/** - * 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 deleted file mode 100644 index cbb95c3..0000000 --- a/src/behaviors/PlayerControlBehavior.js +++ /dev/null @@ -1,107 +0,0 @@ -/** - * 玩家控制行为类 - * - * 实现玩家通过摇杆或键盘控制单位的行为 - */ -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 deleted file mode 100644 index ef6cab0..0000000 --- a/src/behaviors/SearchBehavior.js +++ /dev/null @@ -1,161 +0,0 @@ -/** - * 搜索行为类 - * - * 实现单位自动搜索并收集目标物品的行为 - */ -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/data/items.json b/src/data/items.json new file mode 100644 index 0000000..21b466c --- /dev/null +++ b/src/data/items.json @@ -0,0 +1,24 @@ +{ + "items": { + "smallGrass": { + "name": "小草", + "type": ["plant"], + "weight": 0.1, + "volume": 0.5, + "hardness": 1, + "elasticity": 0.2, + "physicalState": "solid", + "description": "普通的小草,可用于制作简单的物品" + }, + "cactus": { + "name": "仙人掌", + "type": ["plant", "spiky"], + "weight": 2, + "volume": 3, + "hardness": 3, + "elasticity": 0.1, + "physicalState": "solid", + "description": "带刺的仙人掌,小心处理" + } + } +} \ No newline at end of file diff --git a/src/objects/Joystick.js b/src/objects/Joystick.js deleted file mode 100644 index 0e69a38..0000000 --- a/src/objects/Joystick.js +++ /dev/null @@ -1,72 +0,0 @@ -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 deleted file mode 100644 index e75c52e..0000000 --- a/src/objects/Unit.js +++ /dev/null @@ -1,368 +0,0 @@ -/** - * 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/src/scenes/BreatheLife.js b/src/scenes/BreatheLife.js index a9cf888..1962e40 100644 --- a/src/scenes/BreatheLife.js +++ b/src/scenes/BreatheLife.js @@ -16,6 +16,14 @@ export default class BreatheLife extends WorldLand { // 层级相关 this.SpriteDepth = 10 // 角色层级 + + // 碰撞层级 + this.collisionLayers = { + NPC: 0b0001, // NPC 碰撞层 + CACTUS: 0b0010, // 仙人掌碰撞层 + OBSTACLE: 0b0100, // 障碍物碰撞层 + ALL: 0b1111 // 所有碰撞层 + } } preload() { @@ -41,14 +49,16 @@ export default class BreatheLife extends WorldLand { create() { super.create() - // 覆盖场景切换条件 + // 场景切换条件 this.initSceneTransition() - // 覆盖输入 + // 输入 this.initInput() - // 添加随机小草 - this.addRandomSmallGrass() + // 当前场景为'BreatheLife'时添加随机小草 + if (this.scene.key === 'BreatheLife') { + this.addRandomSmallGrass() + } } initSceneTransition() { // 初始化角色计数器,用于跟踪创建的角色数量 diff --git a/src/scenes/Control.js b/src/scenes/Control.js index c7ea9fd..6fba72a 100644 --- a/src/scenes/Control.js +++ b/src/scenes/Control.js @@ -4,44 +4,37 @@ * 允许用户控制角色,NPC 自动搜索并收集小草 */ import BreatheLife from './BreatheLife.js' -import Unit from '../objects/Unit.js' -import SearchBehavior from '../behaviors/SearchBehavior.js' +import { generateUUID } from '../utils/String.js' export default class Control extends BreatheLife { constructor(sceneKey = 'Control') { // 场景相关 super(sceneKey) this.nextScene = 'InfoBar' } - preload() { + async preload() { super.preload() + // 加载仙人掌图片 + this.load.image('cactus', `${this.TilesPath}/tile_0063.png`) + // 加载物品配置 + await Item.loadItemsConfig(this) } create() { super.create() - - // 设置 Control 场景的目标角色数量 - this.targetCount = 2 // 目标角色数量 - // 初始化输入事件 + // 场景切换条件 + this.initSceneTransition() + + // 输入事件 this.initInput() // 添加随机NPC this.addRandomNPCs() } - - update() { - super.update() - - // 更新所有 NPC 的行为 - this.NPCObjects.forEach(npc => { - if (npc.update) { - npc.update() - } - }) + initSceneTransition() { + this.cactusObjects = [] // 维护仙人掌集合 } - // Control 场景不再需要覆盖 initSceneTransition 方法 // 数组初始化由父类 BreatheLife.create() 中的 initSceneTransition() 完成 - // targetCount 通过在 create() 中直接设置来覆盖 initInput() { // 移除父类的输入事件监听器,避免重复监听 super.removeListener() @@ -53,6 +46,10 @@ export default class Control extends BreatheLife { this.handleClick(x, y) }) } + + update() { + super.update() + } /** * 处理点击事件 @@ -65,56 +62,130 @@ export default class Control extends BreatheLife { console.log("点击到的精灵:", clickedSprite) if (clickedSprite && this.NPCObjects.includes(clickedSprite)) { // 如果点击到了角色 this.handleSpriteClick(clickedSprite) - }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) // 加入角色集合 + }else{ } } - - /** - * 创建NPC - * @param {number} x - NPC的 x 坐标 - * @param {number} y - NPC的 y 坐标 - */ - createNPC(x, y) { - // 使用 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 - */ - addRandomNPCs() { - // 随机生成NPC数量 - const randomCount = 5 - for (let i = 0; i < randomCount; i++) { - // 随机间隔随机位置生成NPC - const interval = Phaser.Math.Between(0, 500) - this.time.delayedCall(interval, () => { - const x = Phaser.Math.Between(0, this.game.config.width) - const y = Phaser.Math.Between(0, this.game.config.height) - this.createNPC(x, y) - }) + addRandomNPCs(count = 3) { + // 随机生成NPC + for (let i = 0; i < count; i++) { + const randomX = Phaser.Math.Between(100, this.sys.game.config.width - 100) + const randomY = Phaser.Math.Between(100, this.sys.game.config.height - 100) + const npc = new Human(this, randomX, randomY, 'human') + this.NPCObjects.push(npc) } } } +/** + * 物品类 + * 用于表示游戏中的可收集物品 + * @param {Phaser.Scene} scene - 场景实例 + * @param {number} x - 物品的 x 坐标 + * @param {number} y - 物品的 y 坐标 + * @param {string} texture - 物品的纹理键 + */ +export class Item extends Phaser.GameObjects.Sprite { + // 静态属性,存储所有物品的配置数据 + static itemsConfig = null + + // 静态方法,加载物品配置 + static loadItemsConfig(scene) { + return new Promise((resolve, reject) => { + scene.load.json('itemsConfig', 'src/data/items.json') + scene.load.once('complete', () => { + Item.itemsConfig = scene.cache.json.get('itemsConfig') + resolve() + }) + scene.load.start() + }) + } + + constructor(scene, x, y, texture, itemInfoName = null, frame = 0) { + // 调用父类 Sprite 构造函数 + super(scene, x, y, texture, frame) + // 渲染属性 + this.setOrigin(0.5, 0.5) // 设置渲染原点,中心 + this.scene.add.existing(this) + + // 设置自定义额外属性 + this.type = [] // 物品类型标签 + this.weight = 0 // 物品重量 + this.volume = 0 // 物品体积 + this.hardness = 0 // 硬度 + this.elasticity = 0 // 弹性 + this.physicalState = 'solid' // 物理状态,solid 为固体,liquid 为液体,gas 为气体 + this.name = '' // 物品名称 + this.description = '' // 物品描述 + + // 是否被AI锁定,用于防止AI重复拾取 + this.locked = false + + // 如果指定了物品类型,从配置中加载属性 + if (itemInfoName && Item.itemsConfig) { + this.loadFromConfig(itemInfoName) + } + } + + // 从配置中加载物品属性 + loadFromConfig(itemInfoName) { + if (Item.itemsConfig && Item.itemsConfig.items[itemInfoName]) { + const config = Item.itemsConfig.items[itemInfoName] + this.name = config.name || '' + this.type = config.type || [] + this.weight = config.weight || 0 + this.volume = config.volume || 0 + this.hardness = config.hardness || 0 + this.elasticity = config.elasticity || 0 + this.physicalState = config.physicalState || 'solid' + this.description = config.description || '' + } + } + + setLocked(lockState) { // 设置物品是否被AI锁定 + this.locked = lockState + } +} + +/** + * 单位类 + * 用于表示游戏中的角色和敌人 + * @param {Phaser.Scene} scene - 场景实例 + * @param {number} x - 单位的 x 坐标 + * @param {number} y - 单位的 y 坐标 + * @param {string} texture - 单位的纹理键 + * @param {string|number} frame - 单位的初始帧 + */ +export class Unit extends Item { + constructor(scene, x, y, texture, frame) { + // 调用父类 Sprite 构造函数 + super(scene, x, y, texture, frame) + // 添加物品标签为单位 + this.type.push('unit') + + // 设置自定义额外属性 + this.uuid = generateUUID() // 单位唯一标识符 + this.direction = 1 // 渲染方向,1 为向右,-1 为向左 + + this.mass = 0 // 单位质量 + this.velocity = new Phaser.Math.Vector2(0, 0) // 单位速度向量 + this.acceleration = new Phaser.Math.Vector2(0, 0) // 单位加速度向量 + + this.force = 0 // 单位力量 + this.target = null // 单位目标点 + this.status = ['alive'] // 单位状态 + } + + hasStatus(status) { // 单位状态检查 + return this.status.includes(status) + } + addStatus(status) { // 单位状态添加 + if (!this.hasStatus(status)) { + this.status.push(status) + } + } + removeStatus(status) { // 单位状态移除 + if (this.hasStatus(status)) { + this.status = this.status.filter(s => s !== status) + } + } + +} \ No newline at end of file diff --git a/src/scenes/Human.js b/src/scenes/Human.js new file mode 100644 index 0000000..a8b4a2c --- /dev/null +++ b/src/scenes/Human.js @@ -0,0 +1,259 @@ +/** + * 人类类 + * 继承自Unit类,用于表示游戏中的人类角色 + * @param {Phaser.Scene} scene - 场景实例 + * @param {number} x - 单位的 x 坐标 + * @param {number} y - 单位的 y 坐标 + * @param {string} texture - 单位的纹理键 + * @param {string|number} frame - 单位的初始帧 + */ +import { Unit } from './Control.js' + +export default class Human extends Unit { + constructor(scene, x, y, texture, frame) { + // 调用父类 Unit 构造函数 + super(scene, x, y, texture, frame) + + // 初始化穿戴系统 + this.initWearSystem() + + // 初始化携带系统 + this.initCarrySystem() + } + + /** + * 初始化穿戴系统 + */ + initWearSystem() { + // 穿戴部位结构 + this.wearParts = { + // 头部 + head: { + eyes: null, // 眼睛部位穿戴物 + mouth: null, // 嘴部穿戴物 + ears: null, // 耳朵部位穿戴物 + top: null // 头顶部位穿戴物 + }, + // 身体 + body: { + chest: null, // 胸部穿戴物 + waist: null, // 腰部穿戴物 + abdomen: null, // 腹部穿戴物 + back: null, // 背部穿戴物,用于背包、可背物品等,也可用于携带其他单位 + }, + // 四肢 + limbs: { + leftHand: null, // 左手穿戴物 + rightHand: null, // 右手穿戴物 + + leftArm: null, // 左臂穿戴物 + rightArm: null, // 右臂穿戴物 + + leftLeg: null, // 左腿穿戴物 + rightLeg: null, // 右腿穿戴物 + + leftFoot: null, // 左脚穿戴物 + rightFoot: null // 右脚穿戴物 + } + } + } + + /** + * 初始化携带系统 + */ + initCarrySystem() { + // 携带部位,无穿戴时可携带物品 + this.carryParts = { + leftHand: { + items: [], // 左手携带的物品 + maxWeight: 10, // 左手最大负重 + maxVolume: 5, // 左手最大体积 + currentWeight: 0, // 左手当前负重 + currentVolume: 0 // 左手当前体积 + }, + rightHand: { + items: [], // 右手携带的物品 + maxWeight: 10, // 右手最大负重 + maxVolume: 5, // 右手最大体积 + currentWeight: 0, // 右手当前负重 + currentVolume: 0 // 右手当前体积 + }, + mouth: { + items: [], // 嘴部携带的物品 + maxWeight: 2, // 嘴部最大负重 + maxVolume: 1, // 嘴部最大体积 + currentWeight: 0, // 嘴部当前负重 + currentVolume: 0 // 嘴部当前体积 + } + } + } + + /** + * 穿戴物品到指定部位 + * @param {string} partType - 部位类型:head, body, limbs + * @param {string} partName - 具体部位名称 + * @param {Item} item - 要穿戴的物品 + * @returns {boolean} - 是否穿戴成功 + */ + wearItem(partType, partName, item) { + // 检查物品是否有对应部位的服装标签 + if (!item.type.includes(`${partType}-${partName}-clothing`)) { + return false + } + + // 检查部位是否存在 + if (!this.wearParts[partType] || !this.wearParts[partType].hasOwnProperty(partName)) { + return false + } + + // 穿戴物品 + this.wearParts[partType][partName] = item + return true + } + + /** + * 卸下指定部位的物品 + * @param {string} partType - 部位类型:head, body, limbs + * @param {string} partName - 具体部位名称 + * @returns {Item|null} - 卸下的物品,如果没有则返回null + */ + removeItem(partType, partName) { + // 检查部位是否存在 + if (!this.wearParts[partType] || !this.wearParts[partType].hasOwnProperty(partName)) { + return null + } + + // 获取并卸下物品 + const item = this.wearParts[partType][partName] + this.wearParts[partType][partName] = null + return item + } + + /** + * 携带物品到指定部位 + * @param {string} carryPart - 携带部位:leftHand, rightHand, mouth + * @param {Item} item - 要携带的物品 + * @returns {boolean} - 是否携带成功 + */ + carryItem(carryPart, item) { + // 检查携带部位是否存在 + if (!this.carryParts[carryPart]) { + return false + } + + // 检查负重和体积是否超出限制 + const carryInfo = this.carryParts[carryPart] + if (carryInfo.currentWeight + item.weight > carryInfo.maxWeight || + carryInfo.currentVolume + item.volume > carryInfo.maxVolume) { + return false + } + + // 携带物品 + carryInfo.items.push(item) + carryInfo.currentWeight += item.weight + carryInfo.currentVolume += item.volume + return true + } + + /** + * 卸下指定部位的携带物品 + * @param {string} carryPart - 携带部位:leftHand, rightHand, mouth + * @param {number} index - 物品索引 + * @returns {Item|null} - 卸下的物品,如果没有则返回null + */ + removeCarryItem(carryPart, index) { + // 检查携带部位是否存在 + if (!this.carryParts[carryPart]) { + return null + } + + const carryInfo = this.carryParts[carryPart] + + // 检查索引是否有效 + if (index < 0 || index >= carryInfo.items.length) { + return null + } + + // 获取并卸下物品 + const item = carryInfo.items[index] + carryInfo.items.splice(index, 1) + carryInfo.currentWeight -= item.weight + carryInfo.currentVolume -= item.volume + return item + } + + /** + * 向穿戴物品中存放物品(如果穿戴物品是容器) + * @param {string} partType - 部位类型:head, body, limbs + * @param {string} partName - 具体部位名称 + * @param {Item} item - 要存放的物品 + * @returns {boolean} - 是否存放成功 + */ + storeItemInWear(partType, partName, item) { + // 检查部位是否存在 + if (!this.wearParts[partType] || !this.wearParts[partType].hasOwnProperty(partName)) { + return false + } + + const wearItem = this.wearParts[partType][partName] + + // 检查是否有穿戴物品且是否是容器 + if (!wearItem || !wearItem.type.includes('container')) { + return false + } + + // 检查容器是否有存放物品的能力 + if (!wearItem.hasOwnProperty('items')) { + wearItem.items = [] + wearItem.maxWeight = wearItem.maxWeight || 20 + wearItem.maxVolume = wearItem.maxVolume || 10 + wearItem.currentWeight = 0 + wearItem.currentVolume = 0 + } + + // 检查负重和体积是否超出限制 + if (wearItem.currentWeight + item.weight > wearItem.maxWeight || + wearItem.currentVolume + item.volume > wearItem.maxVolume) { + return false + } + + // 存放物品 + wearItem.items.push(item) + wearItem.currentWeight += item.weight + wearItem.currentVolume += item.volume + return true + } + + /** + * 从穿戴物品中取出物品 + * @param {string} partType - 部位类型:head, body, limbs + * @param {string} partName - 具体部位名称 + * @param {number} index - 物品索引 + * @returns {Item|null} - 取出的物品,如果没有则返回null + */ + retrieveItemFromWear(partType, partName, index) { + // 检查部位是否存在 + if (!this.wearParts[partType] || !this.wearParts[partType].hasOwnProperty(partName)) { + return null + } + + const wearItem = this.wearParts[partType][partName] + + // 检查是否有穿戴物品且是否是容器 + if (!wearItem || !wearItem.type.includes('container') || !wearItem.items) { + return null + } + + // 检查索引是否有效 + if (index < 0 || index >= wearItem.items.length) { + return null + } + + // 获取并取出物品 + const item = wearItem.items[index] + wearItem.items.splice(index, 1) + wearItem.currentWeight -= item.weight + wearItem.currentVolume -= item.volume + return item + } +} \ No newline at end of file diff --git a/src/scenes/WorldLand.js b/src/scenes/WorldLand.js index ceea1b5..96a44d6 100644 --- a/src/scenes/WorldLand.js +++ b/src/scenes/WorldLand.js @@ -23,9 +23,10 @@ export default class WorldLand extends WorldTime { } preload() { + this.TilesPath = 'assets/PNG/Tiles/Tiles' // 加载草地瓦片图 - this.load.image(`grass`, `assets/PNG/Tiles/Tiles/tile_0024.png`) - this.load.image(`smallGrass`, `assets/PNG/Tiles/Tiles/tile_0044.png`) + this.load.image(`grass`, `${this.TilesPath}/tile_0024.png`) + this.load.image(`smallGrass`, `${this.TilesPath}/tile_0044.png`) } create() { @@ -34,7 +35,7 @@ export default class WorldLand extends WorldTime { // 初始化场景切换条件 this.initSceneTransition() - // 覆盖输入 + // 输入 this.initInput() this.createTilemap() // 创建瓦片地图