mirror of
http://junepve.top:3000/chicrobot/CINC.git
synced 2026-10-29 19:41:29 +08:00
609 lines
20 KiB
JavaScript
609 lines
20 KiB
JavaScript
/**
|
|
* Control 场景类
|
|
*
|
|
* 允许用户控制角色,NPC 自动搜索并收集小草
|
|
*/
|
|
import BreatheLife from './BreatheLife.js'
|
|
import { generateUUID } from '../utils/String.js'
|
|
export default class Control extends BreatheLife {
|
|
constructor(sceneKey = 'Control') {
|
|
// 场景相关
|
|
super(sceneKey)
|
|
this.nextScene = 'InfoBar'
|
|
}
|
|
async preload() {
|
|
super.preload()
|
|
// 加载仙人掌图片
|
|
this.load.image('cactus', `${this.TilesPath}/tile_0063.png`)
|
|
// 加载物品配置
|
|
await Item.loadItemsConfig(this)
|
|
}
|
|
create() {
|
|
super.create()
|
|
|
|
// 场景切换条件
|
|
this.initSceneTransition()
|
|
|
|
// 输入事件
|
|
this.initInput()
|
|
|
|
// 添加随机小草
|
|
this.addRandomSmallGrass()
|
|
|
|
// 添加随机NPC
|
|
this.addRandomNPCs()
|
|
|
|
// 添加NPC之间的碰撞
|
|
this.physics.add.collider(this.NPCObjects, this.NPCObjects)
|
|
}
|
|
initSceneTransition() {
|
|
// 调用父类的initSceneTransition方法,确保所有必要的数组都被初始化
|
|
super.initSceneTransition()
|
|
// 初始化仙人掌集合
|
|
this.cactusObjects = [] // 维护仙人掌集合
|
|
// 初始化受控队伍
|
|
this.controlledTeam = [] // 维护受控队伍
|
|
// 初始化团队目标点
|
|
this.teamTargetPoint = null // 团队目标移动点
|
|
}
|
|
initInput() {
|
|
// 移除父类的输入事件监听器,避免重复监听
|
|
super.removeListener()
|
|
|
|
// 监听鼠标点击事件,同时处理左右键
|
|
this.input.on('pointerdown', (pointer) => {
|
|
const { x, y } = this.input.activePointer
|
|
if (pointer.rightButtonDown()) {
|
|
// 右键点击
|
|
this.handleRightClick(x, y)
|
|
} else {
|
|
// 左键点击
|
|
this.handleClick(x, y, pointer)
|
|
}
|
|
})
|
|
}
|
|
|
|
update() {
|
|
super.update()
|
|
|
|
// 检查小草是否被收集完
|
|
if (this.smallGrassObjects.length === 0) {
|
|
console.log("所有小草已被收集")
|
|
// 切换到下一个场景
|
|
this.scene.start(this.nextScene)
|
|
}
|
|
// 更新所有NPC
|
|
this.NPCObjects.forEach(npc => {
|
|
if (npc.update) {
|
|
npc.update();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 处理点击事件
|
|
* @param {number} x - 点击的 x 坐标
|
|
* @param {number} y - 点击的 y 坐标
|
|
* @param {Phaser.Input.Pointer} pointer - 鼠标指针对象
|
|
*/
|
|
handleClick(x, y, pointer) {
|
|
// 获取点击位置的精灵
|
|
const clickedSprite = this.getClickedSprite(x, y)
|
|
console.log("点击到的精灵:", clickedSprite)
|
|
|
|
if (clickedSprite && this.NPCObjects.includes(clickedSprite)) { // 如果点击到了NPC
|
|
// 左键点击NPC后加入受控队伍
|
|
if (!this.controlledTeam.includes(clickedSprite)) {
|
|
this.controlledTeam.push(clickedSprite)
|
|
clickedSprite.setState(NPCState.CONTROLLED)
|
|
clickedSprite.setIndicatorColor(IndicatorColor.GREEN)
|
|
console.log("NPC已加入受控队伍,当前队伍数量:", this.controlledTeam.length)
|
|
}
|
|
} else { // 点击空地
|
|
// 左键点击空地清空受控队伍,恢复待机
|
|
if (this.controlledTeam.length > 0) {
|
|
this.controlledTeam.forEach(npc => {
|
|
npc.setState(NPCState.IDLE)
|
|
npc.setIndicatorColor(IndicatorColor.GRAY)
|
|
})
|
|
this.controlledTeam = []
|
|
this.teamTargetPoint = null
|
|
console.log("受控队伍已清空")
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 处理右键点击事件
|
|
* @param {number} x - 点击的 x 坐标
|
|
* @param {number} y - 点击的 y 坐标
|
|
*/
|
|
handleRightClick(x, y) {
|
|
// 右键点击空地为受控队伍设置目标移动点
|
|
if (this.controlledTeam.length > 0) {
|
|
this.teamTargetPoint = { x, y }
|
|
console.log("为受控队伍设置目标点:", x, y)
|
|
}
|
|
}
|
|
/**
|
|
* 重写随机小草添加方法
|
|
*/
|
|
addRandomSmallGrass(count = 5) {
|
|
// 随机生成小草
|
|
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 smallGrass = new Item(this, randomX, randomY, 'smallGrass', 'smallGrass')
|
|
this.smallGrassObjects.push(smallGrass)
|
|
}
|
|
}
|
|
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 NPC(this, randomX, randomY, 'player1-0', 'npc')
|
|
npc.setState(NPCState.SEARCHING); // 初始化为搜索状态
|
|
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
|
|
}
|
|
}
|
|
|
|
// 指示器颜色枚举
|
|
const IndicatorColor = {
|
|
GRAY: 0x808080,
|
|
GREEN: 0x00FF00,
|
|
RED: 0xFF0000,
|
|
BLUE: 0x0000FF,
|
|
YELLOW: 0xFFFF00
|
|
};
|
|
|
|
/**
|
|
* 单位类
|
|
* 用于表示游戏中的角色和敌人
|
|
* @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.direction = 1 // 渲染方向,1 为向右,-1 为向左
|
|
this.setOrigin(0.5, 1) // 设置渲染原点,中心底部
|
|
// 添加物品标签为单位
|
|
this.type.push('unit')
|
|
|
|
scene.physics.add.existing(this)
|
|
const radius = Math.min(this.width, this.height) / 2
|
|
this.body.setCircle(radius) // 设置单位为圆形碰撞体,半径为宽度或高度的一半
|
|
this.body.setBounce(0.2)
|
|
this.body.setCollideWorldBounds(true)
|
|
this.body.setMaxVelocity(200)
|
|
this.body.setFriction(0.95, 0.95)
|
|
this.body.setDrag(0.95, 0.95)
|
|
|
|
this.uuid = generateUUID() // 单位唯一标识符
|
|
|
|
this.mass = 0 // 单位质量
|
|
this.velocity = new Phaser.Math.Vector2(0, 0) // 单位速度向量
|
|
this.acceleration = new Phaser.Math.Vector2(0, 0) // 单位加速度向量
|
|
|
|
this.force = 0 // 单位力量
|
|
this.status = ['alive'] // 单位状态
|
|
|
|
this.target = null // 搜索目标
|
|
this.targetPoint = null // 移动目标点
|
|
|
|
// 状态管理
|
|
this.state = null; // 状态
|
|
this.currentBehavior = null; // 当前行为组件
|
|
|
|
// 椭圆指示光圈
|
|
this.indicator = null
|
|
this.indicatorColor = IndicatorColor.GRAY
|
|
|
|
this.speed = 200
|
|
|
|
this.createIndicator()
|
|
}
|
|
|
|
createIndicator() {
|
|
const graphics = this.scene.add.graphics() // 创建 Graphics 对象
|
|
this.indicator = graphics // 保存 Graphics 对象
|
|
this.scene.children.moveTo(this.indicator, this.scene.children.getIndex(this) - 1) // 将 Graphics 对象移动到单位下方
|
|
this.updateIndicator() // 更新指示器
|
|
}
|
|
|
|
updateIndicator() {
|
|
if (!this.indicator) return // 如果单位未添加到场景中,不更新指示器
|
|
this.indicator.clear() // 清除 Graphics 对象的绘制内容
|
|
|
|
const radius = this.width / 2 // 单位半径
|
|
const ellipseWidth = radius * 1 // 椭圆宽度
|
|
const ellipseHeight = radius * 0.5 // 椭圆高度
|
|
|
|
|
|
const posX = this.body ? this.body.x + this.width / 2 : this.x // 椭圆中心 x 坐标,取单位中心 x 坐标
|
|
const posY = this.body ? this.body.y + this.height : this.y // 椭圆中心 y 坐标,取单位底部 y 坐标
|
|
|
|
this.indicator.fillStyle(0x000000, 0.8) // 填充颜色为黑色,透明度为1
|
|
this.indicator.fillEllipse(posX, posY, ellipseWidth, ellipseHeight) // 填充椭圆
|
|
|
|
// 绘制椭圆边框
|
|
this.indicator.lineStyle(1, this.indicatorColor, 1) // 线宽为1,颜色为 indicatorColor,透明度为1
|
|
this.indicator.strokeEllipse(posX, posY, ellipseWidth + 2, ellipseHeight + 2) // 根据宽高绘制椭圆边框
|
|
}
|
|
|
|
setIndicatorColor(color) {
|
|
this.indicatorColor = color
|
|
this.updateIndicator()
|
|
}
|
|
|
|
destroy(fromScene) {
|
|
if (this.indicator) {
|
|
this.indicator.destroy()
|
|
}
|
|
super.destroy(fromScene)
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
// 设置状态
|
|
setState(state) {
|
|
this.state = state;
|
|
// 切换行为组件
|
|
this.switchBehavior(state)
|
|
}
|
|
// 子类实现切换行为组件,当前类不实现该方法
|
|
switchBehavior(state) {
|
|
// 空实现
|
|
}
|
|
|
|
// 更新方法
|
|
update() {
|
|
if (this.currentBehavior) {
|
|
this.currentBehavior.update();
|
|
}
|
|
this.updateIndicator()
|
|
}
|
|
}
|
|
|
|
// NPC状态枚举
|
|
const NPCState = {
|
|
IDLE: 'idle', // 待机状态
|
|
SEARCHING: 'searching', // 搜索状态
|
|
COLLECTING: 'collecting', // 采集状态
|
|
CONTROLLED: 'controlled' // 受玩家控制状态
|
|
};
|
|
|
|
// NPC类
|
|
export class NPC extends Unit {
|
|
constructor(scene, x, y, texture, frame) {
|
|
super(scene, x, y, texture, frame)
|
|
// 添加物品标签为NPC
|
|
this.type.push('npc')
|
|
this.setIndicatorColor(IndicatorColor.RED)
|
|
}
|
|
// 根据状态切换行为组件
|
|
switchBehavior(state) {
|
|
this.body.setAcceleration(0, 0);
|
|
this.body.setVelocity(0, 0);
|
|
|
|
switch (state) {
|
|
case NPCState.IDLE:
|
|
this.currentBehavior = new IdleBehavior(this);
|
|
break;
|
|
case NPCState.SEARCHING:
|
|
this.currentBehavior = new SearchBehavior(this);
|
|
break;
|
|
case NPCState.COLLECTING:
|
|
this.currentBehavior = new CollectBehavior(this);
|
|
break;
|
|
case NPCState.CONTROLLED:
|
|
this.currentBehavior = new ControlledBehavior(this);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// 待机行为组件
|
|
class IdleBehavior {
|
|
constructor(npc) {
|
|
this.npc = npc; // 关联的NPC实例,用于访问NPC的属性和方法
|
|
}
|
|
|
|
update() {
|
|
this.npc.body.setAcceleration(0, 0);
|
|
this.npc.body.setVelocity(0, 0);
|
|
this.npc.velocity.set(0, 0); // 重置速度
|
|
this.npc.acceleration.set(0, 0); // 重置加速度
|
|
this.npc.target = null; // 重置目标
|
|
this.npc.targetPoint = null; // 重置移动目标点
|
|
// 重置动画
|
|
this.npc.anims.stop(); // 停止当前动画
|
|
this.npc.setTexture(this.npc.texture.key); // 重置纹理为原始状态
|
|
}
|
|
}
|
|
|
|
// 搜索行为组件
|
|
class SearchBehavior {
|
|
constructor(npc, targetType = 'smallGrass') {
|
|
this.npc = npc; // 关联的NPC实例,用于访问NPC的属性和方法
|
|
this.targetType = targetType; // 目标类型,用于指定搜索的物品类型
|
|
}
|
|
|
|
update() {
|
|
// 如果没有目标,寻找新目标
|
|
if (!this.npc.target) {
|
|
this.findTarget();
|
|
} else {
|
|
// 向目标移动
|
|
this.moveToTarget();
|
|
}
|
|
}
|
|
|
|
findTarget() {
|
|
// 简单实现:随机选择一个目标
|
|
const scene = this.npc.scene;
|
|
switch (this.targetType) {
|
|
case 'smallGrass':
|
|
if (scene.smallGrassObjects.length > 0) {
|
|
const randomIndex = Phaser.Math.Between(0, scene.smallGrassObjects.length - 1);
|
|
this.npc.target = scene.smallGrassObjects[randomIndex];
|
|
}else{
|
|
// 如果没有小草,切换到待机状态
|
|
this.npc.setState(NPCState.IDLE);
|
|
}
|
|
break;
|
|
case 'npc':
|
|
if (scene.NPCObjects.length > 1) { // 确保有其他NPC
|
|
// 随机选择一个除自己以外的NPC
|
|
const randomIndex = Phaser.Math.Between(0, scene.NPCObjects.length - 1);
|
|
this.npc.target = scene.NPCObjects[randomIndex];
|
|
// 确保选择的NPC不是自己
|
|
if (this.npc.target === this.npc) {
|
|
this.findTarget(); // 如果是自己,重新选择
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
moveToTarget() {
|
|
// 检查目标是否已被移除
|
|
const scene = this.npc.scene;
|
|
let targetExists = false;
|
|
|
|
switch (this.targetType) {
|
|
case 'smallGrass':
|
|
targetExists = scene.smallGrassObjects.includes(this.npc.target);
|
|
break;
|
|
}
|
|
// 如果目标不存在,切换到搜索状态
|
|
if (!targetExists) {
|
|
this.npc.target = null;
|
|
this.npc.setState(NPCState.SEARCHING);
|
|
return;
|
|
}
|
|
if (this.npc.target) {
|
|
const direction = new Phaser.Math.Vector2(
|
|
this.npc.target.x - this.npc.x,
|
|
this.npc.target.y - this.npc.y
|
|
).normalize();
|
|
|
|
const currentVelocity = this.npc.body.velocity.clone();
|
|
const targetVelocity = direction.clone().scale(this.npc.speed);
|
|
|
|
const blendedVelocity = currentVelocity.lerp(targetVelocity, 0.1);
|
|
|
|
this.npc.body.setVelocity(
|
|
blendedVelocity.x,
|
|
blendedVelocity.y
|
|
);
|
|
|
|
const distance = Phaser.Math.Distance.Between(
|
|
this.npc.x, this.npc.y,
|
|
this.npc.target.x, this.npc.target.y
|
|
);
|
|
if (distance < 30) {
|
|
this.npc.body.setAcceleration(0, 0);
|
|
this.npc.body.setVelocity(0, 0);
|
|
this.npc.setState(NPCState.COLLECTING);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// 采集行为组件
|
|
class CollectBehavior {
|
|
constructor(npc, targetType = 'smallGrass') {
|
|
this.npc = npc;
|
|
this.targetType = targetType;
|
|
this.collectTime = 500; // 采集时间(毫秒)
|
|
this.collectStartTime = 0;
|
|
this.isCollecting = false;
|
|
}
|
|
|
|
update() {
|
|
if (!this.npc.target) {
|
|
// 如果没有目标,切换回搜索状态
|
|
this.npc.setState(NPCState.SEARCHING);
|
|
return;
|
|
}
|
|
|
|
// 检查目标是否还存在
|
|
const scene = this.npc.scene;
|
|
let targetExists = false;
|
|
|
|
switch (this.targetType) {
|
|
case 'smallGrass':
|
|
targetExists = scene.smallGrassObjects.includes(this.npc.target);
|
|
break;
|
|
}
|
|
|
|
if (!targetExists) {
|
|
// 目标不存在,切换回搜索状态
|
|
this.npc.target = null;
|
|
this.npc.setState(NPCState.SEARCHING);
|
|
return;
|
|
}
|
|
|
|
// 开始采集
|
|
if (!this.isCollecting) {
|
|
this.isCollecting = true;
|
|
this.collectStartTime = Date.now();
|
|
}
|
|
|
|
// 检查采集是否完成
|
|
if (Date.now() - this.collectStartTime >= this.collectTime) {
|
|
// 采集完成,移除目标
|
|
this.removeTarget();
|
|
this.isCollecting = false;
|
|
// 切换回搜索状态
|
|
this.npc.setState(NPCState.SEARCHING);
|
|
}
|
|
}
|
|
|
|
removeTarget() {
|
|
const scene = this.npc.scene;
|
|
if (this.npc.target) {
|
|
switch (this.targetType) {
|
|
case 'smallGrass':
|
|
const index = scene.smallGrassObjects.indexOf(this.npc.target);
|
|
if (index > -1) {
|
|
scene.smallGrassObjects.splice(index, 1);
|
|
this.npc.target.destroy();
|
|
}
|
|
break;
|
|
}
|
|
this.npc.target = null;
|
|
}
|
|
}
|
|
}
|
|
// 受控行为组件
|
|
class ControlledBehavior {
|
|
constructor(npc) {
|
|
this.npc = npc;
|
|
}
|
|
|
|
update() {
|
|
const scene = this.npc.scene;
|
|
|
|
if (scene.teamTargetPoint) {
|
|
const distance = Phaser.Math.Distance.Between(
|
|
this.npc.x, this.npc.y,
|
|
scene.teamTargetPoint.x, scene.teamTargetPoint.y
|
|
);
|
|
|
|
if (distance >= 4) {
|
|
this.moveToTarget(scene.teamTargetPoint);
|
|
} else {
|
|
this.npc.body.setVelocity(0, 0);
|
|
this.npc.x = scene.teamTargetPoint.x;
|
|
this.npc.y = scene.teamTargetPoint.y;
|
|
}
|
|
} else {
|
|
this.npc.body.setVelocity(0, 0);
|
|
}
|
|
}
|
|
|
|
moveToTarget(targetPoint) {
|
|
const direction = new Phaser.Math.Vector2(
|
|
targetPoint.x - this.npc.x,
|
|
targetPoint.y - this.npc.y
|
|
).normalize();
|
|
|
|
const currentVelocity = this.npc.body.velocity.clone();
|
|
const targetVelocity = direction.clone().scale(this.npc.speed);
|
|
|
|
const blendedVelocity = currentVelocity.lerp(targetVelocity, 0.1);
|
|
|
|
this.npc.body.setVelocity(
|
|
blendedVelocity.x,
|
|
blendedVelocity.y
|
|
);
|
|
}
|
|
}
|