398 lines
13 KiB
TypeScript
398 lines
13 KiB
TypeScript
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';
|
||
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';
|
||
const { ccclass, property } = _decorator;
|
||
|
||
/** 玩家状态 */
|
||
enum EPlayerState {
|
||
IDLE,
|
||
WALK,
|
||
RUN,
|
||
DIE,
|
||
VICTORY
|
||
}
|
||
|
||
@ccclass('WoodenManPlayer')
|
||
export class WoodenManPlayer extends Component {
|
||
|
||
private id: number = 0;
|
||
private game: WoodenManGame;
|
||
private alive: boolean = true;
|
||
private passed: boolean = false;
|
||
isLocalPlayer: boolean = false;
|
||
private animator: SkeletalAnimation = null;
|
||
public isMoving: boolean = false;
|
||
private hasPlayedDieAnimation: boolean = false;
|
||
private moveTimer: number = 0; // 添加移动计时器
|
||
private readonly RUN_SPEED = 6; // 跑步速度
|
||
private _playerState: EPlayerState = EPlayerState.IDLE;
|
||
private isButtonPressed: boolean = false;
|
||
|
||
|
||
@property(CameraManager)
|
||
private cameraManager: CameraManager = null;
|
||
init(id: number, isLocal: boolean, game: WoodenManGame) {
|
||
this.id = id;
|
||
this.game = game;
|
||
this.isLocalPlayer = isLocal;
|
||
this.animator = this.getComponentInChildren(SkeletalAnimation);
|
||
if (!this.animator) {
|
||
console.error(`Player ${id} 缺少 SkeletalAnimation 组件`);
|
||
} else {
|
||
const idleState = this.animator.getState('player_idle1');
|
||
if (idleState && this._playerState === EPlayerState.IDLE) {
|
||
this.animator.crossFade('player_idle1', 0.3);
|
||
}
|
||
}
|
||
}
|
||
|
||
getId(): number {
|
||
return this.id;
|
||
}
|
||
|
||
isAlive(): boolean {
|
||
return this.alive;
|
||
}
|
||
|
||
isPassed(): boolean {
|
||
return this.passed;
|
||
}
|
||
|
||
setAlive(alive: boolean) {
|
||
if (this.alive === alive) return;
|
||
|
||
this.alive = alive;
|
||
if (!alive) {
|
||
// 确保停止所有运动和动画
|
||
this.isMoving = false;
|
||
this.isButtonPressed = false;
|
||
|
||
// 停止当前节点的 tween
|
||
tween(this.node).stop();
|
||
|
||
// 重置状态
|
||
this._playerState = EPlayerState.DIE;
|
||
|
||
// 切换到死亡摄像机
|
||
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");
|
||
|
||
}
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
setPassed(value: boolean) {
|
||
this.passed = value;
|
||
}
|
||
|
||
// 播放死亡动画
|
||
playDieAnimation() {
|
||
if (this.hasPlayedDieAnimation) return;
|
||
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)); // 稍微抬高一点,让特效更明显
|
||
|
||
}
|
||
});
|
||
|
||
if (this._getAnimator()) {
|
||
// 转向动画
|
||
const state = this.animator.getState('player_die');
|
||
if (state) {
|
||
state.wrapMode = 1; // 1 表示只播放一次
|
||
this.animator.crossFade('player_die', 0.3);
|
||
|
||
// 动画结束后确保停止
|
||
state.once(SkeletalAnimation.EventType.FINISHED, () => {
|
||
this.animator.stop();
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
// 播放胜利动画
|
||
playVictoryAnimation() {
|
||
// if (this.animator) {
|
||
// // 胜利动画带过渡
|
||
// this.animator.crossFade('victory', 0.5);
|
||
// }
|
||
}
|
||
|
||
// 播放行走动画
|
||
playWalkAnimation() {
|
||
if (this._getAnimator()) {
|
||
const state = this.animator.getState('player_walk');
|
||
if (state) {
|
||
this._playerState = EPlayerState.WALK;
|
||
state.wrapMode = 2; // 2 表示循环模式
|
||
//this.animator.play('player_walk');
|
||
this.animator.crossFade('player_walk', 0.3);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 播放跑步动画
|
||
playRunAnimation() {
|
||
if (this._getAnimator()) {
|
||
const state = this.animator.getState('player_run');
|
||
if (state) {
|
||
this._playerState = EPlayerState.RUN;
|
||
state.wrapMode = 2; // 2 表示循环模式
|
||
//this.animator.play('player_run');
|
||
this.animator.crossFade('player_run', 0.3);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 停止移动c
|
||
stopMoving() {
|
||
this.isMoving = false;
|
||
if (this._getAnimator()) {
|
||
const game = this.game as WoodenManGame;
|
||
|
||
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);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 添加 setter 方法
|
||
setButtonPressed(pressed: boolean) {
|
||
this.isButtonPressed = pressed;
|
||
}
|
||
|
||
private _getAnimator() {
|
||
if (!this.animator) {
|
||
this.animator = this.getComponentInChildren(SkeletalAnimation);
|
||
if (!this.animator) {
|
||
console.error(`Player ${this.id} 缺少 SkeletalAnimation 组件`);
|
||
}
|
||
}
|
||
return this.animator;
|
||
|
||
}
|
||
|
||
|
||
public startMoving() {
|
||
if (!this.isAlive() || this.isPassed()) return;
|
||
|
||
this.isMoving = true;
|
||
if (this._getAnimator()) {
|
||
const game = this.game as WoodenManGame;
|
||
const runState = this.animator.getState('player_run');
|
||
runState.speed = 1;
|
||
if (game.getIsDollTurning()) {
|
||
// 如果娃娃正在转身,需要确保动画正确播放
|
||
this.animator.crossFade('player_run', 0.1);
|
||
|
||
} else {
|
||
// 如果娃娃还没开始转身,正常播放跑步动画
|
||
this.playRunAnimation();
|
||
}
|
||
this._playerState = EPlayerState.RUN;
|
||
}
|
||
}
|
||
|
||
update(dt: number) {
|
||
if (!this.game.getGameStarted()) return;
|
||
|
||
if (this.isMoving) {
|
||
this.moveTimer += dt;
|
||
|
||
// 直接使用跑步速度
|
||
const newPos = this.node.position.clone();
|
||
newPos.z -= this.RUN_SPEED * dt;
|
||
this.node.setPosition(newPos);
|
||
|
||
if (this.node.position.z <= -49.5) {
|
||
this.handleFinishLine();
|
||
}
|
||
}
|
||
}
|
||
|
||
// 添加处理终点线的方法
|
||
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;
|
||
}
|
||
|
||
// 修改清理方法
|
||
public cleanup() {
|
||
// 停止所有定时器
|
||
this.unscheduleAllCallbacks();
|
||
|
||
// 停止所有 tween
|
||
if (this.node) {
|
||
tween(this.node).stop();
|
||
}
|
||
|
||
// 停止动画
|
||
if (this.animator && this.animator.isValid) {
|
||
try {
|
||
this.animator.stop();
|
||
} catch (error) {
|
||
console.warn('Failed to stop animator:', error);
|
||
}
|
||
}
|
||
|
||
// 重置状态
|
||
this.isMoving = false;
|
||
this.isButtonPressed = false;
|
||
this.hasPlayedDieAnimation = false;
|
||
}
|
||
|
||
onDestroy() {
|
||
try {
|
||
this.cleanup();
|
||
} catch (error) {
|
||
console.warn('Error during WoodenManPlayer cleanup:', error);
|
||
}
|
||
}
|
||
}
|