2025-03-06 10:50:18 +08:00
|
|
|
|
import { _decorator, Component, Node, SkeletalAnimation, Input, EventTouch, input, Label, Button, Vec3, tween, Animation, Prefab, instantiate } from 'cc';
|
|
|
|
|
import { WoodenManGame } from './WoodenManGame';
|
|
|
|
|
import { CameraManager } from './CameraManager';
|
2025-03-06 22:46:48 +08:00
|
|
|
|
import { resLoader } from 'db://assets/core_tgx/base/utils/ResLoader';
|
|
|
|
|
import { ModuleDef } from 'db://assets/scripts/ModuleDef';
|
|
|
|
|
import { tgxAudioMgr } from 'db://assets/core_tgx/tgx';
|
|
|
|
|
import { EMusicDefine } from 'db://assets/module_basic/scripts/MusicDefine';
|
2025-02-21 10:45:46 +08:00
|
|
|
|
const { ccclass, property } = _decorator;
|
|
|
|
|
|
2025-03-01 16:55:28 +08:00
|
|
|
|
/** 玩家状态 */
|
|
|
|
|
enum EPlayerState {
|
|
|
|
|
IDLE,
|
|
|
|
|
WALK,
|
|
|
|
|
RUN,
|
|
|
|
|
DIE,
|
|
|
|
|
VICTORY
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-21 10:45:46 +08:00
|
|
|
|
@ccclass('WoodenManPlayer')
|
|
|
|
|
export class WoodenManPlayer extends Component {
|
2025-03-01 02:24:46 +08:00
|
|
|
|
|
2025-02-21 10:45:46 +08:00
|
|
|
|
private id: number = 0;
|
2025-03-06 10:50:18 +08:00
|
|
|
|
private game: WoodenManGame;
|
2025-02-21 10:45:46 +08:00
|
|
|
|
private alive: boolean = true;
|
2025-03-01 02:24:46 +08:00
|
|
|
|
private passed: boolean = false;
|
2025-03-06 10:50:18 +08:00
|
|
|
|
isLocalPlayer: boolean = false;
|
2025-03-01 02:24:46 +08:00
|
|
|
|
private animator: SkeletalAnimation = null;
|
|
|
|
|
public isMoving: boolean = false;
|
|
|
|
|
private hasPlayedDieAnimation: boolean = false;
|
|
|
|
|
private moveTimer: number = 0; // 添加移动计时器
|
|
|
|
|
private readonly RUN_SPEED = 6; // 跑步速度
|
2025-03-01 16:55:28 +08:00
|
|
|
|
private _playerState: EPlayerState = EPlayerState.IDLE;
|
2025-03-06 10:50:18 +08:00
|
|
|
|
private isButtonPressed: boolean = false;
|
2025-03-01 02:24:46 +08:00
|
|
|
|
|
|
|
|
|
|
2025-03-06 10:50:18 +08:00
|
|
|
|
@property(CameraManager)
|
|
|
|
|
private cameraManager: CameraManager = null;
|
|
|
|
|
init(id: number, isLocal: boolean, game: WoodenManGame) {
|
2025-02-21 10:45:46 +08:00
|
|
|
|
this.id = id;
|
2025-03-06 10:50:18 +08:00
|
|
|
|
this.game = game;
|
2025-02-21 10:45:46 +08:00
|
|
|
|
this.isLocalPlayer = isLocal;
|
2025-03-01 02:24:46 +08:00
|
|
|
|
this.animator = this.getComponentInChildren(SkeletalAnimation);
|
2025-02-21 10:45:46 +08:00
|
|
|
|
if (!this.animator) {
|
2025-03-01 02:24:46 +08:00
|
|
|
|
console.error(`Player ${id} 缺少 SkeletalAnimation 组件`);
|
|
|
|
|
} else {
|
2025-03-01 16:55:28 +08:00
|
|
|
|
const idleState = this.animator.getState('player_idle1');
|
|
|
|
|
if (idleState && this._playerState === EPlayerState.IDLE) {
|
2025-03-06 10:50:18 +08:00
|
|
|
|
this.animator.crossFade('player_idle1', 0.3);
|
2025-03-01 02:24:46 +08:00
|
|
|
|
}
|
2025-02-21 10:45:46 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getId(): number {
|
|
|
|
|
return this.id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
isAlive(): boolean {
|
|
|
|
|
return this.alive;
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-01 02:24:46 +08:00
|
|
|
|
isPassed(): boolean {
|
|
|
|
|
return this.passed;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-21 10:45:46 +08:00
|
|
|
|
setAlive(alive: boolean) {
|
2025-03-06 10:50:18 +08:00
|
|
|
|
if (this.alive === alive) return;
|
|
|
|
|
|
2025-02-21 10:45:46 +08:00
|
|
|
|
this.alive = alive;
|
2025-03-01 02:24:46 +08:00
|
|
|
|
if (!alive) {
|
2025-03-06 10:50:18 +08:00
|
|
|
|
// 确保停止所有运动和动画
|
2025-03-01 02:24:46 +08:00
|
|
|
|
this.isMoving = false;
|
2025-03-06 10:50:18 +08:00
|
|
|
|
this.isButtonPressed = false;
|
|
|
|
|
|
|
|
|
|
// 停止当前节点的 tween
|
|
|
|
|
tween(this.node).stop();
|
|
|
|
|
|
|
|
|
|
// 重置状态
|
2025-03-01 16:55:28 +08:00
|
|
|
|
this._playerState = EPlayerState.DIE;
|
2025-03-06 10:50:18 +08:00
|
|
|
|
|
|
|
|
|
// 切换到死亡摄像机
|
|
|
|
|
if (this.isLocalPlayer && this.cameraManager) {
|
|
|
|
|
this.cameraManager.switchToDeadCamera(this.node.worldPosition);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 播放死亡 GUI 动画
|
|
|
|
|
if (this.isLocalPlayer) {
|
|
|
|
|
resLoader.load(ModuleDef.Arean, 'common/prefabs/settlement', (err: Error | null, prefab: Prefab) => {
|
|
|
|
|
if (!err && prefab) {
|
|
|
|
|
const n = instantiate(prefab);
|
|
|
|
|
this.game.guiNode.addChild(n);
|
|
|
|
|
n.getComponent(Animation).play("showLose");
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
});
|
2025-03-01 02:24:46 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setPassed(value: boolean) {
|
|
|
|
|
this.passed = value;
|
2025-02-21 10:45:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 播放死亡动画
|
2025-03-06 10:50:18 +08:00
|
|
|
|
playDieAnimation() {
|
2025-03-01 02:24:46 +08:00
|
|
|
|
if (this.hasPlayedDieAnimation) return;
|
2025-03-06 10:50:18 +08:00
|
|
|
|
this.hasPlayedDieAnimation = true;
|
|
|
|
|
|
|
|
|
|
// 播放音效
|
|
|
|
|
tgxAudioMgr.inst.playOneShot(EMusicDefine.GUN, 1, ModuleDef.BASIC);
|
|
|
|
|
tgxAudioMgr.inst.playOneShot(EMusicDefine.WOODENMANDIE, 1, ModuleDef.BASIC);
|
|
|
|
|
|
|
|
|
|
// 加载并播放死亡特效
|
|
|
|
|
resLoader.load(ModuleDef.Arean, 'common/prefabs/hit001', (err: Error | null, prefab: Prefab) => {
|
|
|
|
|
if (!err && prefab) {
|
|
|
|
|
const effectNode = instantiate(prefab);
|
|
|
|
|
this.node.addChild(effectNode);
|
|
|
|
|
effectNode.setPosition(new Vec3(0, 1, 0)); // 稍微抬高一点,让特效更明显
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
});
|
2025-03-01 02:24:46 +08:00
|
|
|
|
|
2025-03-01 16:55:28 +08:00
|
|
|
|
if (this._getAnimator()) {
|
2025-03-06 10:50:18 +08:00
|
|
|
|
// 转向动画
|
2025-03-01 02:24:46 +08:00
|
|
|
|
const state = this.animator.getState('player_die');
|
|
|
|
|
if (state) {
|
2025-03-01 16:55:28 +08:00
|
|
|
|
state.wrapMode = 1; // 1 表示只播放一次
|
2025-03-06 10:50:18 +08:00
|
|
|
|
this.animator.crossFade('player_die', 0.3);
|
|
|
|
|
|
|
|
|
|
// 动画结束后确保停止
|
|
|
|
|
state.once(SkeletalAnimation.EventType.FINISHED, () => {
|
|
|
|
|
this.animator.stop();
|
|
|
|
|
});
|
2025-03-01 02:24:46 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 播放胜利动画
|
|
|
|
|
playVictoryAnimation() {
|
|
|
|
|
// if (this.animator) {
|
|
|
|
|
// // 胜利动画带过渡
|
|
|
|
|
// this.animator.crossFade('victory', 0.5);
|
|
|
|
|
// }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 播放行走动画
|
|
|
|
|
playWalkAnimation() {
|
2025-03-01 16:55:28 +08:00
|
|
|
|
if (this._getAnimator()) {
|
2025-03-01 02:24:46 +08:00
|
|
|
|
const state = this.animator.getState('player_walk');
|
|
|
|
|
if (state) {
|
2025-03-01 16:55:28 +08:00
|
|
|
|
this._playerState = EPlayerState.WALK;
|
2025-03-01 02:24:46 +08:00
|
|
|
|
state.wrapMode = 2; // 2 表示循环模式
|
2025-03-06 10:50:18 +08:00
|
|
|
|
//this.animator.play('player_walk');
|
|
|
|
|
this.animator.crossFade('player_walk', 0.3);
|
2025-03-01 02:24:46 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 播放跑步动画
|
|
|
|
|
playRunAnimation() {
|
2025-03-01 16:55:28 +08:00
|
|
|
|
if (this._getAnimator()) {
|
2025-03-01 02:24:46 +08:00
|
|
|
|
const state = this.animator.getState('player_run');
|
|
|
|
|
if (state) {
|
2025-03-01 16:55:28 +08:00
|
|
|
|
this._playerState = EPlayerState.RUN;
|
2025-03-01 02:24:46 +08:00
|
|
|
|
state.wrapMode = 2; // 2 表示循环模式
|
2025-03-06 10:50:18 +08:00
|
|
|
|
//this.animator.play('player_run');
|
|
|
|
|
this.animator.crossFade('player_run', 0.3);
|
2025-03-01 02:24:46 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 停止移动c
|
|
|
|
|
stopMoving() {
|
|
|
|
|
this.isMoving = false;
|
2025-03-01 16:55:28 +08:00
|
|
|
|
if (this._getAnimator()) {
|
2025-03-06 10:50:18 +08:00
|
|
|
|
const game = this.game as WoodenManGame;
|
2025-03-01 02:24:46 +08:00
|
|
|
|
|
2025-03-06 10:50:18 +08:00
|
|
|
|
if (game.getIsDollTurning()) {
|
|
|
|
|
// 如果娃娃正在转身,只改变动画速度
|
|
|
|
|
const runState = this.animator.getState('player_run');
|
|
|
|
|
if (runState?.isPlaying) {
|
|
|
|
|
runState.speed = 0;
|
|
|
|
|
} else {
|
|
|
|
|
// 如果跑步动画没有播放,可能需要先播放再暂停
|
|
|
|
|
this.animator.crossFade('player_run', 0.1);
|
|
|
|
|
this.scheduleOnce(() => {
|
|
|
|
|
const state = this.animator.getState('player_run');
|
|
|
|
|
if (state) state.speed = 0;
|
|
|
|
|
}, 0.1);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 如果娃娃还没开始转身,切换到待机动画
|
|
|
|
|
this.animator.crossFade('player_idle1', 0.2);
|
2025-03-01 02:24:46 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-06 10:50:18 +08:00
|
|
|
|
// 添加 setter 方法
|
|
|
|
|
setButtonPressed(pressed: boolean) {
|
|
|
|
|
this.isButtonPressed = pressed;
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-01 16:55:28 +08:00
|
|
|
|
private _getAnimator() {
|
|
|
|
|
if (!this.animator) {
|
|
|
|
|
this.animator = this.getComponentInChildren(SkeletalAnimation);
|
|
|
|
|
if (!this.animator) {
|
|
|
|
|
console.error(`Player ${this.id} 缺少 SkeletalAnimation 组件`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return this.animator;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-01 02:24:46 +08:00
|
|
|
|
|
|
|
|
|
public startMoving() {
|
2025-03-06 10:50:18 +08:00
|
|
|
|
if (!this.isAlive() || this.isPassed()) return;
|
|
|
|
|
|
2025-03-01 02:24:46 +08:00
|
|
|
|
this.isMoving = true;
|
2025-03-01 16:55:28 +08:00
|
|
|
|
if (this._getAnimator()) {
|
2025-03-06 10:50:18 +08:00
|
|
|
|
const game = this.game as WoodenManGame;
|
2025-03-07 09:08:47 +08:00
|
|
|
|
const runState = this.animator.getState('player_run');
|
|
|
|
|
runState.speed = 1;
|
2025-03-06 10:50:18 +08:00
|
|
|
|
if (game.getIsDollTurning()) {
|
|
|
|
|
// 如果娃娃正在转身,需要确保动画正确播放
|
2025-03-07 09:08:47 +08:00
|
|
|
|
this.animator.crossFade('player_run', 0.1);
|
|
|
|
|
|
2025-03-06 10:50:18 +08:00
|
|
|
|
} else {
|
|
|
|
|
// 如果娃娃还没开始转身,正常播放跑步动画
|
|
|
|
|
this.playRunAnimation();
|
|
|
|
|
}
|
|
|
|
|
this._playerState = EPlayerState.RUN;
|
2025-03-01 02:24:46 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
update(dt: number) {
|
2025-03-06 10:50:18 +08:00
|
|
|
|
if (!this.game.getGameStarted()) return;
|
|
|
|
|
|
2025-03-01 02:24:46 +08:00
|
|
|
|
if (this.isMoving) {
|
|
|
|
|
this.moveTimer += dt;
|
|
|
|
|
|
2025-03-06 10:50:18 +08:00
|
|
|
|
// 直接使用跑步速度
|
2025-03-01 02:24:46 +08:00
|
|
|
|
const newPos = this.node.position.clone();
|
2025-03-06 10:50:18 +08:00
|
|
|
|
newPos.z -= this.RUN_SPEED * dt;
|
2025-03-01 02:24:46 +08:00
|
|
|
|
this.node.setPosition(newPos);
|
2025-03-06 10:50:18 +08:00
|
|
|
|
|
|
|
|
|
if (this.node.position.z <= -49.5) {
|
|
|
|
|
this.handleFinishLine();
|
|
|
|
|
}
|
2025-02-21 10:45:46 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
2025-03-06 10:50:18 +08:00
|
|
|
|
|
|
|
|
|
// 添加处理终点线的方法
|
|
|
|
|
private handleFinishLine() {
|
|
|
|
|
if (!this.isPassed()) {
|
|
|
|
|
this.game.qualifiedCount++;
|
|
|
|
|
this.setPassed(true);
|
|
|
|
|
|
|
|
|
|
// 同时旋转角色和摄像机
|
|
|
|
|
const duration = 1.0; // 转身动画持续时间
|
|
|
|
|
|
|
|
|
|
// 角色转身动画
|
|
|
|
|
tween(this.node)
|
|
|
|
|
.to(duration, {
|
|
|
|
|
eulerAngles: new Vec3(0, 0, 0)
|
|
|
|
|
}, {
|
|
|
|
|
easing: 'quadOut'
|
|
|
|
|
})
|
|
|
|
|
.start();
|
|
|
|
|
|
|
|
|
|
// 如果是本地玩家,执行摄像机旋转
|
|
|
|
|
if (this.isLocalPlayer && this.cameraManager) {
|
|
|
|
|
const camera = this.cameraManager.defaultCamera;
|
|
|
|
|
if (camera) {
|
|
|
|
|
// 保存摄像机初始位置
|
|
|
|
|
const startPos = camera.position.clone();
|
|
|
|
|
const centerPos = this.node.position.clone();
|
|
|
|
|
centerPos.y += 1.5; // 调整旋转中心点高度
|
|
|
|
|
|
|
|
|
|
// 计算摄像机到角色的距离和高度差
|
|
|
|
|
const radius = Vec3.distance(
|
|
|
|
|
new Vec3(startPos.x, centerPos.y, startPos.z),
|
|
|
|
|
centerPos
|
|
|
|
|
);
|
|
|
|
|
const heightOffset = startPos.y - centerPos.y;
|
|
|
|
|
|
|
|
|
|
// 执行摄像机环绕动画
|
|
|
|
|
tween(camera)
|
|
|
|
|
.to(duration, {
|
|
|
|
|
position: new Vec3(
|
|
|
|
|
centerPos.x,
|
|
|
|
|
centerPos.y + heightOffset,
|
|
|
|
|
centerPos.z - radius
|
|
|
|
|
)
|
|
|
|
|
}, {
|
|
|
|
|
easing: 'quadOut',
|
|
|
|
|
onUpdate: (target: Node, ratio: number) => {
|
|
|
|
|
// 计算当前角度
|
|
|
|
|
const angle = Math.PI * ratio;
|
|
|
|
|
const x = centerPos.x + Math.sin(angle) * radius;
|
|
|
|
|
const z = centerPos.z + Math.cos(angle) * radius;
|
|
|
|
|
|
|
|
|
|
// 更新摄像机位置
|
|
|
|
|
target.setPosition(x, centerPos.y + heightOffset, z);
|
|
|
|
|
// 始终看向角色
|
|
|
|
|
target.lookAt(centerPos);
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.start();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 播放晋级动画
|
|
|
|
|
if (this.isLocalPlayer && this.game.guiNode) {
|
|
|
|
|
resLoader.load(ModuleDef.Arean, 'common/prefabs/settlement', (err: Error | null, prefab: Prefab) => {
|
|
|
|
|
if (!err && prefab) {
|
|
|
|
|
const n = instantiate(prefab);
|
|
|
|
|
this.game.guiNode.addChild(n);
|
|
|
|
|
n.getComponent(Animation).play("showWin");
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.game.updateWinCount();
|
|
|
|
|
|
|
|
|
|
if (this.game.qualifiedCount >= this.game.REQUIRED_QUALIFIED) {
|
|
|
|
|
this.game.endGame('达到晋级人数要求!游戏结束');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 停止移动并播放待机动画
|
|
|
|
|
this.stopMoving();
|
|
|
|
|
if (this._getAnimator()) {
|
|
|
|
|
const idleState = this.animator.getState('player_idle1');
|
|
|
|
|
if (idleState) {
|
|
|
|
|
idleState.speed = 1;
|
|
|
|
|
this.animator.crossFade('player_idle1', 0.3);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 添加 getter 方法
|
|
|
|
|
public getIsLocalPlayer(): boolean {
|
|
|
|
|
return this.isLocalPlayer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 添加一个方法来检查玩家是否应该被击杀
|
|
|
|
|
public shouldBeKilled(): boolean {
|
|
|
|
|
// 如果玩家已经停止移动,就不应该被击杀
|
|
|
|
|
if (!this.isMoving) return false;
|
|
|
|
|
|
|
|
|
|
// 如果玩家按住了按钮,也不应该被击杀
|
|
|
|
|
if (this.isButtonPressed) return false;
|
|
|
|
|
|
|
|
|
|
// 如果玩家的动画速度为0(已经停止),也不应该被击杀
|
|
|
|
|
if (this._getAnimator()) {
|
|
|
|
|
const runState = this.animator.getState('player_run');
|
|
|
|
|
if (runState?.isPlaying && runState.speed === 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 其他情况下,如果在移动就应该被击杀
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-06 22:47:02 +08:00
|
|
|
|
// 修改清理方法
|
2025-03-06 10:50:18 +08:00
|
|
|
|
public cleanup() {
|
|
|
|
|
// 停止所有定时器
|
|
|
|
|
this.unscheduleAllCallbacks();
|
|
|
|
|
|
|
|
|
|
// 停止所有 tween
|
2025-03-06 22:47:02 +08:00
|
|
|
|
if (this.node) {
|
|
|
|
|
tween(this.node).stop();
|
|
|
|
|
}
|
2025-03-06 10:50:18 +08:00
|
|
|
|
|
|
|
|
|
// 停止动画
|
2025-03-06 22:47:02 +08:00
|
|
|
|
if (this.animator && this.animator.isValid) {
|
|
|
|
|
try {
|
|
|
|
|
this.animator.stop();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.warn('Failed to stop animator:', error);
|
|
|
|
|
}
|
2025-03-06 10:50:18 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 重置状态
|
|
|
|
|
this.isMoving = false;
|
|
|
|
|
this.isButtonPressed = false;
|
|
|
|
|
this.hasPlayedDieAnimation = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onDestroy() {
|
2025-03-06 22:47:02 +08:00
|
|
|
|
try {
|
|
|
|
|
this.cleanup();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.warn('Error during WoodenManPlayer cleanup:', error);
|
|
|
|
|
}
|
2025-03-06 10:50:18 +08:00
|
|
|
|
}
|
2025-02-21 10:45:46 +08:00
|
|
|
|
}
|