mirror of
http://junepve.top:3000/chicrobot/CINC.git
synced 2026-10-30 04:21:29 +08:00
feat(SS) 添加物品类、单位类
This commit is contained in:
1 parent
cbbed1519e
commit
494922246b
11 files changed
+440
-855
No files matched your search
@@ -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() {
|
||||
// 初始化角色计数器,用于跟踪创建的角色数量
|
||||
|
||||
+138
-67
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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() // 创建瓦片地图
|
||||
|
||||
Reference in new issue
Block a user