283 lines
9.1 KiB
TypeScript
283 lines
9.1 KiB
TypeScript
import { _decorator, Component, Node, Prefab, Label, Animation, instantiate, Vec3, tween, macro } from 'cc';
|
||
import { WoodenManPlayer } from './WoodenManPlayer';
|
||
const { ccclass, property } = _decorator;
|
||
|
||
|
||
|
||
@ccclass('WoodenManGame')
|
||
export class WoodenManGame extends Component {
|
||
@property(Prefab)
|
||
rolePrefab: Prefab = null; // 角色预制体
|
||
|
||
@property(Node)
|
||
dollNode: Node = null; // 木头人节点
|
||
|
||
@property(Label)
|
||
aliveCountLabel: Label = null; // 存活人数显示
|
||
|
||
@property(Node)
|
||
playersNode: Node = null; // 存放所有玩家的节点
|
||
|
||
@property(Node)
|
||
localPlayerNode: Node = null; // 本地玩家节点引用,从场景中获取
|
||
|
||
@property
|
||
private turnAnimationDuration: number = 1.0; // 转身动画持续时间
|
||
@property
|
||
private turnWarningDuration: number = 0.5; // 转身前警告时间
|
||
private readonly TURN_BACK_DELAY = 5; // 转回正面的延迟时间
|
||
|
||
// 游戏配置
|
||
private readonly MIN_QUALIFIED = 20; // 最少晋级人数
|
||
private readonly INITIAL_PLAYERS = 36; // 初始玩家数
|
||
|
||
|
||
private aiPlayers: Node[] = []; // AI玩家节点列表
|
||
private playerNodes: Map<number, Node> = new Map(); // 所有玩家节点映射
|
||
|
||
async start() {
|
||
await this.initPlayers();
|
||
this.scheduleDollTurn(); // 开始娃娃转身定时器
|
||
}
|
||
|
||
private async initPlayers() {
|
||
// 初始化本地玩家
|
||
if (this.localPlayerNode) {
|
||
const playerComp = this.localPlayerNode.addComponent(WoodenManPlayer);
|
||
playerComp.init(0, true); // 设置为本地玩家
|
||
this.playerNodes.set(0, this.localPlayerNode);
|
||
}
|
||
|
||
// 创建AI玩家
|
||
for (let i = 1; i < this.INITIAL_PLAYERS; i++) {
|
||
const aiPlayer = await this.createPlayer(i);
|
||
this.playerNodes.set(i, aiPlayer);
|
||
this.aiPlayers.push(aiPlayer);
|
||
}
|
||
|
||
this.updateAliveCount();
|
||
}
|
||
|
||
private async createPlayer(id: number) {
|
||
// 随机位置:起点区域
|
||
const x = Math.random() * 15 - 7;
|
||
const z = 50 + Math.random() * 5; // 50-55随机
|
||
|
||
if (this.rolePrefab) {
|
||
const role = instantiate(this.rolePrefab);
|
||
this.playersNode.addChild(role);
|
||
|
||
// 设置位置和朝向(面向-Z方向)
|
||
role.setPosition(new Vec3(x, 0, z));
|
||
role.setRotationFromEuler(0, 180, 0);
|
||
|
||
// 添加 WoodenManPlayer 组件
|
||
const playerComp = role.addComponent(WoodenManPlayer);
|
||
playerComp.init(id, false);
|
||
|
||
return role;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
// 更新存活人数显示
|
||
private updateAliveCount() {
|
||
let aliveCount = 0;
|
||
this.playerNodes.forEach((node) => {
|
||
const player = node.getComponent(WoodenManPlayer);
|
||
if (player && player.isAlive()) {
|
||
aliveCount++;
|
||
}
|
||
});
|
||
|
||
this.aliveCountLabel.string = `存活: ${aliveCount}/${this.INITIAL_PLAYERS}`;
|
||
}
|
||
|
||
private scheduleDollTurn() {
|
||
const scheduleNextTurn = () => {
|
||
// 随机 5-8 秒后转身
|
||
const nextTurnDelay = Math.random() * 3 + 3;
|
||
this.scheduleOnce(() => {
|
||
this.executeTurnBackPhase();
|
||
}, nextTurnDelay);
|
||
};
|
||
|
||
// 开始第一次转身循环
|
||
scheduleNextTurn();
|
||
}
|
||
|
||
// 执行转身(背对玩家)
|
||
private executeTurnBackPhase() {
|
||
|
||
// 播放转身动画
|
||
this.playDollTurnAnimation(() => {
|
||
// 触发AI决策
|
||
this.aiPlayers.forEach(aiNode => {
|
||
const playerComp = aiNode.getComponent(WoodenManPlayer);
|
||
if (!playerComp || !playerComp.isAlive() || playerComp.isPassed()) return;
|
||
|
||
// 在木头人转身后随机延迟开始移动
|
||
const startDelay = Math.random() * 0.3 + 0.2; // 0.2-0.5秒延迟
|
||
this.scheduleOnce(() => {
|
||
if (playerComp.isAlive()) {
|
||
playerComp.startMoving();
|
||
}
|
||
}, startDelay);
|
||
});
|
||
|
||
|
||
this.aiPlayers.forEach(aiNode => {
|
||
const playerComp = aiNode.getComponent(WoodenManPlayer);
|
||
if (!playerComp || !playerComp.isAlive()) return;
|
||
|
||
const stopDelay = this.TURN_BACK_DELAY + Math.random() * 2 - 1; // 范围改为[-1,1]秒,负值表示转身后触发
|
||
this.scheduleOnce(() => {
|
||
if (playerComp.isAlive() && playerComp.isMoving) {
|
||
playerComp.stopMoving();
|
||
}
|
||
}, stopDelay);
|
||
;
|
||
});
|
||
|
||
this.scheduleOnce(() => {
|
||
this.executeTurningPhase();
|
||
}, this.TURN_BACK_DELAY);
|
||
});
|
||
}
|
||
|
||
// 执行转回(面向玩家)
|
||
private executeTurningPhase() {
|
||
|
||
this.playDollTurnAnimation(() => {
|
||
// 检查并处理被抓到的玩家
|
||
this.playerNodes.forEach((playerNode, id) => {
|
||
const playerComp = playerNode.getComponent(WoodenManPlayer);
|
||
// 只检查存活的玩家,跳过已死亡和已到达终点的玩家
|
||
if (!playerComp || !playerComp.isAlive()) return;
|
||
|
||
if (playerComp.isMoving) {
|
||
this.killPlayer(playerComp, playerNode);
|
||
}
|
||
});
|
||
});
|
||
|
||
this.scheduleDollTurn();
|
||
}
|
||
|
||
// 检查游戏是否结束
|
||
private checkGameEnd(): boolean {
|
||
let qualifiedCount = 0;
|
||
this.playerNodes.forEach((playerNode) => {
|
||
const playerComp = playerNode.getComponent(WoodenManPlayer);
|
||
if (playerComp && playerComp.isPassed()) {
|
||
qualifiedCount++;
|
||
}
|
||
});
|
||
|
||
return qualifiedCount >= this.MIN_QUALIFIED;
|
||
}
|
||
|
||
// 游戏主循环
|
||
update(dt: number) {
|
||
|
||
// 检查游戏结束条件
|
||
if (this.checkGameEnd()) {
|
||
this.endGame();
|
||
}
|
||
}
|
||
|
||
private endGame() {
|
||
|
||
// 淘汰未晋级玩家
|
||
this.playerNodes.forEach((playerNode) => {
|
||
const playerComp = playerNode.getComponent(WoodenManPlayer);
|
||
if (playerComp && !playerComp.isPassed()) {
|
||
playerComp.setAlive(false);
|
||
}
|
||
});
|
||
|
||
// 这里可以触发游戏结束事件
|
||
this.node.emit('gameOver');
|
||
}
|
||
|
||
// 获取游戏状态
|
||
|
||
// 木头人转身动画
|
||
private playDollTurnAnimation(callback: () => void) {
|
||
this.scheduleOnce(() => {
|
||
const currentRotation = this.dollNode.eulerAngles.clone();
|
||
const targetRotation = new Vec3(
|
||
currentRotation.x,
|
||
currentRotation.y + 180,
|
||
currentRotation.z
|
||
);
|
||
|
||
tween(this.dollNode)
|
||
.to(this.turnAnimationDuration, { eulerAngles: targetRotation }, {
|
||
easing: 'quadOut',
|
||
onStart: () => {
|
||
this.node.parent?.emit('dollTurnStart');
|
||
},
|
||
onComplete: () => {
|
||
this.node.parent?.emit('dollTurnComplete');
|
||
if (callback) callback();
|
||
}
|
||
})
|
||
.start();
|
||
}, this.turnWarningDuration);
|
||
}
|
||
|
||
// 玩家死亡效果
|
||
private killPlayer(playerComp: WoodenManPlayer, playerNode: Node) {
|
||
if (!playerComp.isAlive()) return; // 防止重复处理
|
||
|
||
playerComp.setAlive(false);
|
||
this.updateAliveCount(); // 更新存活人数显示
|
||
|
||
// 播放死亡动画
|
||
playerComp.playDieAnimation(() => {
|
||
playerComp.stopMoving();
|
||
});
|
||
}
|
||
|
||
// 添加到 WoodenManGame 类中
|
||
public playerStartMoving(playerId: number) {
|
||
const playerNode = this.playerNodes.get(playerId);
|
||
if (!playerNode) return;
|
||
|
||
const playerComp = playerNode.getComponent(WoodenManPlayer);
|
||
if (!playerComp || !playerComp.isAlive()) return;
|
||
|
||
// 持续移动
|
||
this.schedule(() => {
|
||
if (!playerComp.isAlive()) {
|
||
// 如果玩家死亡,停止移动
|
||
this.playerStopMoving(playerId);
|
||
return;
|
||
}
|
||
// 检查是否到达终点
|
||
if (playerNode.position.z <= -50) {
|
||
playerComp.setPassed(true); // 假设WoodenManPlayer有setPassed方法
|
||
this.playerStopMoving(playerId);
|
||
// AI到达终点后转身
|
||
if (playerId !== 0) {
|
||
playerNode.setRotationFromEuler(0, 0, 0);
|
||
}
|
||
return;
|
||
}
|
||
}, 0, macro.REPEAT_FOREVER, 0);
|
||
}
|
||
|
||
public playerStopMoving(playerId: number) {
|
||
// 停止该玩家的移动调度
|
||
this.unschedule(this.playerStartMoving);
|
||
|
||
const playerNode = this.playerNodes.get(playerId);
|
||
if (playerNode) {
|
||
const playerComp = playerNode.getComponent(WoodenManPlayer);
|
||
if (playerComp) {
|
||
playerComp.stopMoving();
|
||
}
|
||
}
|
||
}
|
||
}
|