mirror of
http://junepve.top:3000/chicrobot/CINC.git
synced 2026-10-30 09:11:29 +08:00
refactor(SRC) 重构核心场景路径位置
This commit is contained in:
1 parent
942ba567f3
commit
cbbed1519e
18 files changed
+845
-473
No files matched your search
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; // 容差
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
/**
|
||||
* BreatheLife 场景类
|
||||
*
|
||||
* 交互场景,允许用户通过点击屏幕创建角色
|
||||
* 第二次点击设置纹理
|
||||
* 第三次点击赋予动画
|
||||
* 第四次点击移动角色
|
||||
* 当移动角色超过3个后再次点击,切换到下一个场景
|
||||
*/
|
||||
import WorldLand from './WorldLand.js'
|
||||
export default class BreatheLife extends WorldLand {
|
||||
constructor(sceneKey = 'BreatheLife') {
|
||||
// 场景相关
|
||||
super(sceneKey)
|
||||
this.nextScene = 'Control'
|
||||
|
||||
// 层级相关
|
||||
this.SpriteDepth = 10 // 角色层级
|
||||
}
|
||||
|
||||
preload() {
|
||||
super.preload()
|
||||
|
||||
// 加载角色动画和图片资源
|
||||
this.load.image(`player1-0`, `assets/PNG/Players/Tiles/tile_${String(8).padStart(4, '0')}.png`)
|
||||
|
||||
// 批量加载图片用于动画播放
|
||||
for (let i = 1; i < 8; i++) {
|
||||
this.load.image(`player0-${i}`, `assets/PNG/Players/Tiles/tile_${String(i).padStart(4, '0')}.png`);
|
||||
}
|
||||
for (let i = 9; i < 16; i++) {
|
||||
this.load.image(`player1-${i-8}`, `assets/PNG/Players/Tiles/tile_${String(i).padStart(4, '0')}.png`);
|
||||
}
|
||||
|
||||
// 加载音效资源
|
||||
this.load.audio('coin-a', 'assets/Sounds/move-a.ogg');
|
||||
this.load.audio('coin-b', 'assets/Sounds/coin-b.ogg');
|
||||
}
|
||||
|
||||
|
||||
create() {
|
||||
super.create()
|
||||
|
||||
// 覆盖场景切换条件
|
||||
this.initSceneTransition()
|
||||
|
||||
// 覆盖输入
|
||||
this.initInput()
|
||||
|
||||
// 添加随机小草
|
||||
this.addRandomSmallGrass()
|
||||
}
|
||||
initSceneTransition() {
|
||||
// 初始化角色计数器,用于跟踪创建的角色数量
|
||||
this.targetCount = 3 // 目标角色数量
|
||||
this.NPCObjects = [] // 维护角色集合
|
||||
this.smallGrassObjects = [] // 维护小草集合
|
||||
}
|
||||
initInput() {
|
||||
// 移除父类的输入事件监听器,避免重复监听
|
||||
super.removeListener()
|
||||
|
||||
// 监听鼠标点击事件,点击后创建一个新的角色
|
||||
this.input.on('pointerdown', () => {
|
||||
// 获取点击位置的坐标
|
||||
const { x, y } = this.input.activePointer
|
||||
this.handleClick(x, y)
|
||||
})
|
||||
|
||||
// 监听自定义收集完成事件,当所有小草都被收集时触发
|
||||
this.events.on('collectAllSmallGrass', () => {
|
||||
this.scene.start(this.nextScene)
|
||||
})
|
||||
}
|
||||
removeListener() {
|
||||
// 移除鼠标点击事件监听器
|
||||
this.input.off('pointerdown');
|
||||
// 移除自定义收集完成事件监听器
|
||||
this.events.off('collectAllSmallGrass')
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理点击事件
|
||||
* @param {number} x - 点击的 x 坐标
|
||||
* @param {number} y - 点击的 y 坐标
|
||||
*/
|
||||
handleClick(x, y) {
|
||||
// 获取点击位置的精灵
|
||||
const clickedSprite = this.getClickedSprite(x, y)
|
||||
console.log("点击到的精灵:", clickedSprite)
|
||||
// 如果满足条件则切换到下一场景
|
||||
if (clickedSprite && this.NPCObjects.includes(clickedSprite)) { // 如果点击到了角色
|
||||
this.handleSpriteClick(clickedSprite)
|
||||
}else if (this.NPCObjects.length >= this.targetCount) {
|
||||
// 如果角色列表中角色数量超过目标数量,且动态效果的数量也超过目标数量
|
||||
let moveCount = 0
|
||||
for (const sprite of this.NPCObjects) {
|
||||
// 如果角色当前的纹理在player-move的动画帧中,计数加一
|
||||
const moveAnimationFrames = ['player1-1', 'player1-2']
|
||||
if (moveAnimationFrames.includes(sprite.texture.key)) {
|
||||
moveCount++
|
||||
}
|
||||
}
|
||||
if (moveCount >= this.targetCount) {
|
||||
this.scene.start(this.nextScene)
|
||||
}
|
||||
}else if (this.NPCObjects.length < this.targetCount) {
|
||||
// 如果角色数量不足目标数量
|
||||
// 创建一个新的角色 sprite,缩放为1倍,并设置原点为中心底部
|
||||
const sprite = this.physics.add.sprite(x, y, 'player').setScale(1).setOrigin(0.5, 1)
|
||||
sprite.setDepth(this.SpriteDepth) // 设置角色层级
|
||||
|
||||
this.NPCObjects.push(sprite) // 加入角色集合
|
||||
|
||||
// // 设置角色碰撞世界边界
|
||||
// sprite.setCollideWorldBounds(true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取鼠标点击位置的精灵
|
||||
* @param {number} x - 点击位置的 x 坐标
|
||||
* @param {number} y - 点击位置的 y 坐标
|
||||
* @returns {Phaser.GameObjects.Sprite|null} - 点击位置的精灵,如果没有则返回 null
|
||||
*/
|
||||
getClickedSprite(x, y) {
|
||||
// 获取场景中所有的精灵
|
||||
const sprites = this.children.getChildren().filter(child => child instanceof Phaser.GameObjects.Sprite)
|
||||
|
||||
// 检查每个精灵是否被点击
|
||||
for (const sprite of sprites) {
|
||||
// 检查点击位置是否在精灵的边界内
|
||||
if (sprite.getBounds().contains(x, y)) {
|
||||
return sprite
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
handleSpriteClick(clickedSprite) {
|
||||
console.log("当前精灵的纹理:", clickedSprite.texture.key) // 查看当前精灵的纹理
|
||||
// 如果纹理为默认值或纹理缺失
|
||||
if (clickedSprite.texture.key === 'player' || clickedSprite.texture.key === '__MISSING') {
|
||||
clickedSprite.setTexture(`player1-0`).setScale(1) // 设置指定的纹理
|
||||
this.spriteTextureCount++
|
||||
}else if (clickedSprite.texture.key === 'player1-0'){ // 如果点击的角色纹理已经是player1-0,则添加动画
|
||||
this.addSpriteAnimation(clickedSprite) // 添加角色动画
|
||||
}else if (['player1-1', 'player1-2'].includes(clickedSprite.texture.key)){ // 如果点击的角色纹理已经是player0-0,则添加音效
|
||||
this.sound.play('coin-b') // 播放移动音效
|
||||
// 添加搜索逻辑,自动检测最近的小草
|
||||
this.searchAndAddSmallGrass(clickedSprite)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 为角色添加动画
|
||||
* @param {Phaser.GameObjects.Sprite} sprite - 要添加动画的角色精灵
|
||||
*/
|
||||
addSpriteAnimation(sprite) {
|
||||
// 检查角色是否已添加动画
|
||||
if (sprite.anims.exists('player-move')) {
|
||||
return
|
||||
}
|
||||
|
||||
// 创建移动动画
|
||||
this.anims.create({
|
||||
key: 'player-move',
|
||||
frames: [
|
||||
{ key: 'player1-1' },
|
||||
{ key: 'player1-2' }
|
||||
],
|
||||
frameRate: 10,
|
||||
repeat: -1
|
||||
});
|
||||
// 播放角色动画
|
||||
sprite.anims.play('player-move')
|
||||
this.spriteAnimCount++
|
||||
}
|
||||
|
||||
// 重写创建小草方法,移除条件判断和计数器
|
||||
createSmallGrass(x, y) {
|
||||
// 创建
|
||||
const smallGrass = this.add.image(x, y, 'smallGrass');
|
||||
smallGrass.setDepth(this.mapDepth); // 设置深度,确保在UI层级之下
|
||||
smallGrass.setScrollFactor(0); // 固定,不随相机移动
|
||||
return smallGrass
|
||||
}
|
||||
|
||||
// 新增方法:随机添加50个小草
|
||||
addRandomSmallGrass() {
|
||||
for (let i = 0; i < 50; i++) {
|
||||
// 屏幕范围内随机生成小草位置
|
||||
const x = Phaser.Math.Between(0, this.cameras.main.width)
|
||||
const y = Phaser.Math.Between(0, this.cameras.main.height)
|
||||
const smallGrass = this.createSmallGrass(x, y)
|
||||
// 保存小草对象到数组
|
||||
this.smallGrassObjects.push(smallGrass)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 搜索并添加小草
|
||||
* @param {Phaser.GameObjects.Sprite} clickedSprite - 点击的角色精灵
|
||||
*/
|
||||
searchAndAddSmallGrass(clickedSprite) {
|
||||
// 搜索所有小草
|
||||
// const smallGrass = this.children.getChildren().filter(child =>
|
||||
// child instanceof Phaser.GameObjects.Image && child.texture.key === 'smallGrass'
|
||||
// )
|
||||
const smallGrass = this.smallGrassObjects
|
||||
// 过滤未标记的小草
|
||||
const unmarkedGrass = smallGrass.filter(grass => grass.alpha === 1)
|
||||
|
||||
// 如果没有小草,直接返回
|
||||
if (smallGrass.length === 0) {
|
||||
// 所有小草都已收集,触发事件
|
||||
this.events.emit('collectAllSmallGrass')
|
||||
return
|
||||
}
|
||||
// 查找最近的小草
|
||||
let nearestGrass = null // 最近的小草
|
||||
let minDistance = Infinity // 最近距离,初始为无穷大
|
||||
// 遍历所有小草,找到最近的一个
|
||||
for (const grass of unmarkedGrass) {
|
||||
// 计算当前小草与角色的距离
|
||||
const distance = Phaser.Math.Distance.Between(
|
||||
clickedSprite.x, clickedSprite.y,
|
||||
grass.x, grass.y
|
||||
)
|
||||
// 如果当前小草距离更近,更新最近的小草
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance
|
||||
nearestGrass = grass
|
||||
}
|
||||
}
|
||||
// 如果找到最近的小草,移动角色到小草位置
|
||||
if (nearestGrass) {
|
||||
// 标记小草为已收集
|
||||
nearestGrass.setAlpha(0.5)
|
||||
// 角色移动到小草位置
|
||||
this.tweens.add({
|
||||
targets: clickedSprite, // 角色精灵
|
||||
x: nearestGrass.x, // 目标小草位置x
|
||||
y: nearestGrass.y, // 目标小草位置y
|
||||
duration: minDistance * 10, // 移动时间根据距离计算
|
||||
ease: 'Linear', // 匀速移动
|
||||
onComplete: () => {
|
||||
// 从数组中移除已收集的小草
|
||||
this.smallGrassObjects = this.smallGrassObjects.filter(
|
||||
obj => obj !== nearestGrass
|
||||
)
|
||||
nearestGrass.destroy() // 移动完成后销毁小草
|
||||
this.sound.play('coin-a') // 播放收集小草音效
|
||||
// 等待随机时间后,继续搜索下一个小草
|
||||
const delay = Phaser.Math.Between(500, 1000)
|
||||
this.time.delayedCall(delay, () => {
|
||||
this.searchAndAddSmallGrass(clickedSprite) // 递归调用,继续搜索下一个小草
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* Control 场景类
|
||||
*
|
||||
* 允许用户控制角色,NPC 自动搜索并收集小草
|
||||
*/
|
||||
import BreatheLife from './BreatheLife.js'
|
||||
import Unit from '../objects/Unit.js'
|
||||
import SearchBehavior from '../behaviors/SearchBehavior.js'
|
||||
export default class Control extends BreatheLife {
|
||||
constructor(sceneKey = 'Control') {
|
||||
// 场景相关
|
||||
super(sceneKey)
|
||||
this.nextScene = 'InfoBar'
|
||||
}
|
||||
preload() {
|
||||
super.preload()
|
||||
}
|
||||
create() {
|
||||
super.create()
|
||||
|
||||
// 设置 Control 场景的目标角色数量
|
||||
this.targetCount = 2 // 目标角色数量
|
||||
|
||||
// 初始化输入事件
|
||||
this.initInput()
|
||||
|
||||
// 添加随机NPC
|
||||
this.addRandomNPCs()
|
||||
}
|
||||
|
||||
update() {
|
||||
super.update()
|
||||
|
||||
// 更新所有 NPC 的行为
|
||||
this.NPCObjects.forEach(npc => {
|
||||
if (npc.update) {
|
||||
npc.update()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Control 场景不再需要覆盖 initSceneTransition 方法
|
||||
// 数组初始化由父类 BreatheLife.create() 中的 initSceneTransition() 完成
|
||||
// targetCount 通过在 create() 中直接设置来覆盖
|
||||
initInput() {
|
||||
// 移除父类的输入事件监听器,避免重复监听
|
||||
super.removeListener()
|
||||
|
||||
// 监听鼠标点击事件
|
||||
this.input.on('pointerdown', () => {
|
||||
// 获取点击位置的坐标
|
||||
const { x, y } = this.input.activePointer
|
||||
this.handleClick(x, y)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理点击事件
|
||||
* @param {number} x - 点击的 x 坐标
|
||||
* @param {number} y - 点击的 y 坐标
|
||||
*/
|
||||
handleClick(x, y) {
|
||||
// 获取点击位置的精灵
|
||||
const clickedSprite = this.getClickedSprite(x, y)
|
||||
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) // 加入角色集合
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建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)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
Whitespace-only changes.
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* PrimordialDevide 场景类
|
||||
*
|
||||
* 初始引导场景,显示空白背景并在用户交互或自动延迟后
|
||||
* 执行过渡动画切换到下一个场景
|
||||
*/
|
||||
export default class PrimordialDevide extends Phaser.Scene {
|
||||
/**
|
||||
* 构造函数
|
||||
* 初始化场景并设置场景键名为 'PrimordialDevide'
|
||||
*/
|
||||
constructor() {
|
||||
super('PrimordialDevide')
|
||||
this.nextScene = 'WorldTime'
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建场景
|
||||
* 设置背景颜色,添加用户交互监听和自动切换计时器
|
||||
*/
|
||||
create() {
|
||||
// 设置场景背景颜色为深灰色
|
||||
this.cameras.main.setBackgroundColor('#333333')
|
||||
|
||||
// 设置动画播放状态为 false,避免多次触发
|
||||
this.animationPlaying = false
|
||||
|
||||
// 监听鼠标点击事件,点击后执行场景过渡动画
|
||||
this.input.on('pointerdown', () => {
|
||||
// 获取点击位置的坐标
|
||||
const { x, y } = this.input.activePointer
|
||||
this.transitionToScene(x, y)
|
||||
})
|
||||
|
||||
// 监听键盘按键事件,按键后执行场景过渡动画
|
||||
this.input.keyboard.on('keydown', () => {
|
||||
this.transitionToScene()
|
||||
})
|
||||
|
||||
// 设置1秒后自动执行场景过渡动画
|
||||
this.time.delayedCall(1000, () => {
|
||||
this.transitionToScene()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行到下一个场景的过渡动画
|
||||
*
|
||||
* 动画流程:
|
||||
* 1. 从屏幕中心向两侧延伸一条白色直线(横向扩展)
|
||||
* 2. 横向扩展完成后,直线向上下扩展铺满整个屏幕(纵向扩展)
|
||||
* 3. 纵向扩展完成后,切换到MenuScene场景
|
||||
*/
|
||||
transitionToScene(startX,startY) {
|
||||
// 如果动画正在播放,直接返回
|
||||
if (this.animationPlaying) {
|
||||
return
|
||||
}
|
||||
// 设置动画播放状态为 true
|
||||
this.animationPlaying = true
|
||||
|
||||
const graphics = this.add.graphics() // 创建图形对象,用于绘制过渡动画
|
||||
|
||||
const centerX = startX || this.cameras.main.centerX // 获取屏幕中心点的X坐标
|
||||
const centerY = startY || this.cameras.main.centerY // 获取屏幕中心点的Y坐标
|
||||
|
||||
const lineHeight = 2 // 初始线条高度(2像素)
|
||||
|
||||
const maxWidth = this.cameras.main.width // 屏幕最大宽度,用于横向扩展的目标值
|
||||
|
||||
graphics.fillStyle(0xffffff, 1) // 设置初始填充颜色为白色,透明度为1
|
||||
|
||||
// 创建线条对象,用于控制横向扩展动画的属性
|
||||
const line = {
|
||||
width: 0, // 初始宽度为0(仅中心点)
|
||||
x: centerX // 初始X坐标为屏幕中心
|
||||
}
|
||||
|
||||
// 创建横向扩展动画
|
||||
this.tweens.add({
|
||||
targets: line, // 动画目标对象
|
||||
width: maxWidth, // 目标宽度为屏幕宽度
|
||||
x: 0, // 目标X坐标为屏幕左侧
|
||||
duration: 300, // 动画持续时间(300毫秒)
|
||||
ease: 'Power2.easeOut', // 动画缓动函数
|
||||
onUpdate: function() { // 动画更新回调函数
|
||||
graphics.clear() // 清除之前的绘制
|
||||
graphics.fillStyle(0x999999, 1) // 设置填充颜色
|
||||
// 绘制当前状态的线条
|
||||
graphics.fillRect(line.x, centerY - lineHeight / 2, line.width, lineHeight)
|
||||
},
|
||||
onComplete: function() { // 动画完成回调函数
|
||||
// 横向动画完成后,调用纵向扩展动画方法
|
||||
this.expandVerticalAnimation(graphics, centerY, lineHeight, maxWidth)
|
||||
},
|
||||
callbackScope: this // 回调函数的作用域(指向当前场景实例)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行纵向扩展动画
|
||||
*
|
||||
* @param {Phaser.GameObjects.Graphics} graphics - 图形对象,用于绘制动画
|
||||
* @param {number} centerY - 屏幕中心点的Y坐标
|
||||
* @param {number} lineHeight - 初始线条高度
|
||||
* @param {number} maxWidth - 屏幕宽度,用于绘制横向铺满的矩形
|
||||
*/
|
||||
expandVerticalAnimation(graphics, centerY, lineHeight, maxWidth) {
|
||||
// 创建纵向线条对象,用于控制纵向扩展动画的属性
|
||||
const verticalLine = {
|
||||
height: lineHeight, // 初始高度为线条高度
|
||||
y: centerY - lineHeight / 2 // 初始Y坐标为屏幕中心减去线条高度的一半
|
||||
}
|
||||
|
||||
// 屏幕最大高度,用于纵向扩展的目标值
|
||||
const maxHeight = this.cameras.main.height
|
||||
|
||||
// 创建纵向扩展动画
|
||||
this.tweens.add({
|
||||
targets: verticalLine, // 动画目标对象
|
||||
height: maxHeight, // 目标高度为屏幕高度
|
||||
y: 0, // 目标Y坐标为屏幕顶部
|
||||
duration: 300, // 动画持续时间
|
||||
ease: 'Power2.easeOut', // 动画缓动函数
|
||||
onUpdate: function() { // 动画更新回调函数
|
||||
graphics.clear() // 清除之前的绘制
|
||||
graphics.fillStyle(0x999999, 1) // 设置填充颜色
|
||||
// 绘制当前状态的矩形(已横向铺满,正在纵向扩展)
|
||||
graphics.fillRect(0, verticalLine.y, maxWidth, verticalLine.height)
|
||||
},
|
||||
onComplete: function() { // 动画完成回调函数
|
||||
// 纵向动画完成后,切换到指定场景
|
||||
// 加上内置过渡动画
|
||||
this.cameras.main.fadeOut(300, 0, 0, 0);
|
||||
this.cameras.main.once('camerafadeoutcomplete', () => {
|
||||
// 切换到游戏场景
|
||||
this.scene.start(this.nextScene);
|
||||
});
|
||||
},
|
||||
callbackScope: this // 回调函数的作用域(指向当前场景实例)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* WorldLand 场景类
|
||||
*
|
||||
* 初始引导场景,显示空白背景并在用户交互或自动延迟后
|
||||
* 执行过渡动画切换到下一个场景
|
||||
*/
|
||||
import WorldTime from './WorldTime.js'
|
||||
|
||||
export default class WorldLand extends WorldTime {
|
||||
constructor(sceneKey = 'WorldLand') {
|
||||
// 场景相关
|
||||
super(sceneKey)
|
||||
this.nextScene = 'BreatheLife'
|
||||
|
||||
// 层级相关
|
||||
this.mapDepth = 0 // 地图层级
|
||||
|
||||
// 地图相关
|
||||
this.tileCount = 234 // 瓦片总数,从tile_0000到tile_0233
|
||||
this.tileSize = 16 // 瓦片大小
|
||||
this.mapWidth = 18; // 瓦片图行数
|
||||
this.mapHeight = 13; // 瓦片图列数
|
||||
}
|
||||
|
||||
preload() {
|
||||
// 加载草地瓦片图
|
||||
this.load.image(`grass`, `assets/PNG/Tiles/Tiles/tile_0024.png`)
|
||||
this.load.image(`smallGrass`, `assets/PNG/Tiles/Tiles/tile_0044.png`)
|
||||
}
|
||||
|
||||
create() {
|
||||
super.create()
|
||||
|
||||
// 初始化场景切换条件
|
||||
this.initSceneTransition()
|
||||
|
||||
// 覆盖输入
|
||||
this.initInput()
|
||||
|
||||
this.createTilemap() // 创建瓦片地图
|
||||
|
||||
this.initFrameRateText() // 初始化帧率显示文本
|
||||
}
|
||||
initSceneTransition() {
|
||||
// 初始化计数器,用于跟踪创建的小草数量
|
||||
this.smallGrassCount = 0
|
||||
}
|
||||
initInput() {
|
||||
// 移除父类的输入事件监听器,避免重复监听
|
||||
super.removeListener()
|
||||
|
||||
// 监听鼠标点击事件,点击后创建一个新的小草
|
||||
this.input.on('pointerdown', () => {
|
||||
// 获取点击位置的坐标
|
||||
const { x, y } = this.input.activePointer
|
||||
this.createSmallGrass(x, y)
|
||||
})
|
||||
|
||||
// 监听键盘按键事件,按键后执行场景过渡动画
|
||||
this.input.keyboard.on('keydown', () => {
|
||||
this.scene.start(this.nextScene);
|
||||
})
|
||||
}
|
||||
removeListener() {
|
||||
// 移除鼠标点击事件监听器
|
||||
this.input.off('pointerdown');
|
||||
// 移除键盘按键事件监听器
|
||||
this.input.keyboard.off('keydown');
|
||||
}
|
||||
|
||||
|
||||
update() {
|
||||
this.showFrameRate() // 显示当前帧率
|
||||
this.updateGameTime() // 更新游戏时间
|
||||
this.displayTime() // 显示当前游戏时间
|
||||
this.updateDayNight() // 更新昼夜状态
|
||||
}
|
||||
|
||||
|
||||
createTilemap() {
|
||||
// 获取当前屏幕大小
|
||||
const screenWidth = this.cameras.main.width;
|
||||
const screenHeight = this.cameras.main.height;
|
||||
|
||||
// 计算需要的瓦片数量
|
||||
const tilesX = Math.ceil(screenWidth / this.tileSize);
|
||||
const tilesY = Math.ceil(screenHeight / this.tileSize);
|
||||
|
||||
// 草地铺满屏幕
|
||||
for (let y = 0; y < tilesY; y++) {
|
||||
for (let x = 0; x < tilesX; x++) {
|
||||
// 使用草地瓦片
|
||||
const tile = this.add.image(
|
||||
x * this.tileSize + this.tileSize / 2,
|
||||
y * this.tileSize + this.tileSize / 2,
|
||||
'grass'
|
||||
);
|
||||
tile.setDepth(this.mapDepth); // 设置深度,确保在UI层级之下
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`草地铺满屏幕完成,使用了 ${tilesX}x${tilesY} 个瓦片`)
|
||||
}
|
||||
|
||||
createSmallGrass(x, y) {
|
||||
if (this.smallGrassCount >= 5) {
|
||||
this.scene.start(this.nextScene);
|
||||
}
|
||||
// 创建瓦片图实例
|
||||
const tile = this.add.image(x, y, 'smallGrass');
|
||||
tile.setDepth(this.mapDepth); // 设置深度,确保在UI层级之下
|
||||
tile.setScrollFactor(0); // 固定瓦片,不随相机移动
|
||||
// 增加小草计数器
|
||||
this.smallGrassCount++;
|
||||
}
|
||||
initFrameRateText() {
|
||||
// 创建帧率显示文本
|
||||
this.frameRateText = this.add.text(
|
||||
20, 60, '', {
|
||||
fontSize: '20px',
|
||||
fill: '#ffffff',
|
||||
fontFamily: 'Arial'
|
||||
}
|
||||
).setScrollFactor(0).setDepth(this.UIDepth) // 固定文本,不随相机移动,UI层级
|
||||
}
|
||||
showFrameRate() {
|
||||
const frameRate = this.game.loop.actualFps // 获取当前帧率
|
||||
this.frameRateText.setText(`Frame Rate: ${frameRate.toFixed(2)}`) // 更新帧率显示文本
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
/**
|
||||
* WorldLand 场景类
|
||||
*
|
||||
* 初始引导场景,显示空白背景并在用户交互或自动延迟后
|
||||
* 执行过渡动画切换到下一个场景
|
||||
*/
|
||||
export default class WorldTime extends Phaser.Scene {
|
||||
/**
|
||||
* 构造函数
|
||||
* 初始化场景并设置场景键名
|
||||
* @param {string} sceneKey - 场景键名
|
||||
*/
|
||||
constructor(sceneKey = 'WorldTime') {
|
||||
// 场景相关
|
||||
super(sceneKey)
|
||||
this.nextScene = 'WorldLand'
|
||||
|
||||
// 游戏时间相关
|
||||
this.gameTime = 0 // 游戏中的时间戳(单位:秒)
|
||||
// 固定60秒/分钟,60分钟/小时,24小时/天,30天/月,12月/年
|
||||
this.minuteSec = 60
|
||||
this.hourSec = 60 * this.minuteSec
|
||||
this.daySec = 24 * this.hourSec
|
||||
this.monthSec = 30 * this.daySec
|
||||
this.yearSec = 12 * this.monthSec
|
||||
this.dayStartHour = 6 // 每天开始的小时(24小时制)
|
||||
this.dayEndHour = 18 // 每天结束的小时(24小时制)
|
||||
this.gameStartTime = this.toGameTime('1999-12-30 ' + (this.dayStartHour-1).toString().padStart(2, '0') + ':55:00') // 游戏开始时间
|
||||
this.lastRealTime = 0 // 上一次更新的现实时间戳(毫秒)
|
||||
this.gameTimeUpdateInterval = 1000/60 // 游戏中的每秒时间更新间隔(毫秒)
|
||||
this.isDay = true // 昼夜状态
|
||||
|
||||
// 层级相关
|
||||
this.UIDepth = 100 // UI层级深度,确保在其他元素之上
|
||||
this.maskDepth = 99 // 黑夜遮罩层级,低于UI层级
|
||||
|
||||
}
|
||||
create() {
|
||||
// 设置场景背景颜色
|
||||
this.cameras.main.setBackgroundColor('#999999')
|
||||
|
||||
// 初始化输入
|
||||
this.initInput()
|
||||
|
||||
// 初始化时间
|
||||
this.initDayNight(this.gameStartTime)
|
||||
}
|
||||
/**
|
||||
* 初始化输入事件
|
||||
* 监听鼠标点击和键盘按键,触发场景过渡
|
||||
*/
|
||||
initInput() {
|
||||
// 监听鼠标点击事件,点击后执行场景过渡动画
|
||||
this.input.on('pointerdown', () => {
|
||||
this.scene.start(this.nextScene);
|
||||
})
|
||||
|
||||
// 监听键盘按键事件,按键后执行场景过渡动画
|
||||
this.input.keyboard.on('keydown', () => {
|
||||
this.scene.start(this.nextScene);
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 移除输入事件监听器
|
||||
* 场景切换时调用,避免内存泄漏
|
||||
*/
|
||||
removeListener() {
|
||||
// 移除鼠标点击事件监听器
|
||||
this.input.off('pointerdown');
|
||||
// 移除键盘按键事件监听器
|
||||
this.input.keyboard.off('keydown');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 更新游戏时间
|
||||
* 每帧调用,刷新游戏时间
|
||||
*/
|
||||
update() {
|
||||
this.updateGameTime() // 更新游戏时间
|
||||
this.displayTime() // 显示当前游戏时间
|
||||
this.updateDayNight() // 更新昼夜状态
|
||||
}
|
||||
/**
|
||||
* 初始化昼夜状态
|
||||
* 设置初始为白天,添加事件监听切换状态
|
||||
*/
|
||||
initDayNight(gameTime) {
|
||||
// 初始更新时间戳为当前现实时间,注意不要使用this.time.now,它是Phaser的时间戳,不是现实时间
|
||||
this.lastRealTime = Date.now()
|
||||
this.isDay = true // 设置初始为白天
|
||||
this.gameTime = gameTime // 设置游戏初始时间
|
||||
// 创建时间显示文本
|
||||
this.timeText = this.add.text(
|
||||
20, 20, '', {
|
||||
fontSize: '20px',
|
||||
fill: '#ffffff',
|
||||
fontFamily: 'Arial'
|
||||
}
|
||||
).setScrollFactor(0).setDepth(this.UIDepth) // 固定文本,不随相机移动,UI层级
|
||||
// 当场景是WorldTime时,突出,显示在中心
|
||||
if (this.scene.key === 'WorldTime') {
|
||||
this.timeText.setPosition(
|
||||
this.cameras.main.width/2,
|
||||
this.cameras.main.height/2
|
||||
).setOrigin(0.5, 0.5)
|
||||
}
|
||||
// 添加黑夜遮罩
|
||||
this.nightMask = this.add.rectangle(
|
||||
0, 0,
|
||||
this.cameras.main.width,
|
||||
this.cameras.main.height,
|
||||
0x000000
|
||||
).setOrigin(0, 0).setScrollFactor(0).setDepth(this.maskDepth) // 固定遮罩,不随相机移动,遮罩层级
|
||||
this.nightMask.alpha = 0 // 初始设置为透明
|
||||
}
|
||||
/**
|
||||
* 更新游戏时间
|
||||
* 每帧调用,基于现实时间流逝更新游戏时间
|
||||
*/
|
||||
updateGameTime() {
|
||||
// 获取当前现实时间戳
|
||||
const currentRealTime = Date.now()
|
||||
// 计算与上次更新的时间差(毫秒)
|
||||
const deltaRealTime = currentRealTime - this.lastRealTime
|
||||
if (deltaRealTime <= this.gameTimeUpdateInterval) { // 小于更新间隔,不更新时间
|
||||
return
|
||||
}else {
|
||||
let addSec = deltaRealTime / this.gameTimeUpdateInterval // 计算增加的秒数
|
||||
this.gameTime += addSec // 更新游戏时间
|
||||
this.lastRealTime = currentRealTime // 刷新更新时间戳
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取游戏中的日期
|
||||
* 根据游戏时间戳计算游戏年月日时分秒,格式为YYYY-MM-DD HH:mm:ss
|
||||
*/
|
||||
getGameDate() {
|
||||
// 计算当前时间的年、月、日、时、分、秒
|
||||
const year = Math.floor(this.gameTime / this.yearSec)
|
||||
const month = Math.floor((this.gameTime % this.yearSec) / this.monthSec)+1
|
||||
const day = Math.floor((this.gameTime % this.monthSec) / this.daySec)+1
|
||||
const hour = Math.floor((this.gameTime % this.daySec) / this.hourSec)
|
||||
const minute = Math.floor((this.gameTime % this.hourSec) / this.minuteSec)
|
||||
const second = Math.floor(this.gameTime % this.minuteSec)
|
||||
// 格式化日期字符串
|
||||
const dateString = `${year}-${month < 10 ? '0' : ''}${month}-${day < 10 ? '0' : ''}${day} ${hour < 10 ? '0' : ''}${hour}:${minute < 10 ? '0' : ''}${minute}:${second < 10 ? '0' : ''}${second}`
|
||||
const formatDate = {
|
||||
year, month, day, hour, minute, second ,
|
||||
dateString,
|
||||
}
|
||||
return formatDate
|
||||
}
|
||||
/**
|
||||
* 转换游戏时间字符串为游戏时间戳
|
||||
* 接收时间字符串,格式为YYYY-MM-DD HH:mm:ss,返回游戏时间戳(单位:秒)
|
||||
*/
|
||||
toGameTime(gameTimeString) {
|
||||
// 解析时间字符串为秒数
|
||||
const [year, month, day, hour, minute, second] = gameTimeString.split(/[- :]/).map(Number)
|
||||
// 计算游戏时间戳
|
||||
return year * this.yearSec + (month - 1) * this.monthSec + (day - 1) * this.daySec + hour * this.hourSec + minute * this.minuteSec + second
|
||||
}
|
||||
/**
|
||||
* 显示当前时间
|
||||
* 每帧调用,更新显示时间
|
||||
*/
|
||||
displayTime() {
|
||||
const timeString = this.getGameDate().dateString
|
||||
const day = this.isDay ? '白天' : '黑夜'
|
||||
// 更新显示时间
|
||||
this.timeText.setText(`${day} ${timeString}`)
|
||||
}
|
||||
/**
|
||||
* 更新昼夜状态
|
||||
* 每帧调用,根据游戏时间切换白天黑夜状态
|
||||
*/
|
||||
updateDayNight() {
|
||||
const hour = this.getGameDate().hour
|
||||
if (hour >= 6 && hour < 18) {
|
||||
this.isDay = true
|
||||
// 白天:隐藏黑夜遮罩
|
||||
if (this.nightMask) {
|
||||
this.nightMask.alpha = 0
|
||||
}
|
||||
}else {
|
||||
this.isDay = false
|
||||
// 黑夜:显示黑夜遮罩
|
||||
if (this.nightMask) {
|
||||
this.nightMask.alpha = 0.5
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user