318 lines
9.8 KiB
TypeScript
318 lines
9.8 KiB
TypeScript
import { _decorator, Component, Node, Prefab, Label, Animation, instantiate, Vec3, tween } from 'cc';
|
||
import { WoodenManPlayer } from './WoodenManPlayer';
|
||
import { ModuleDef } from '../../scripts/ModuleDef';
|
||
import { resLoader } from '../../core_tgx/base/utils/ResLoader';
|
||
const { ccclass, property } = _decorator;
|
||
|
||
// 玩家状态
|
||
enum PlayerState {
|
||
ALIVE, // 存活
|
||
DEAD, // 死亡
|
||
QUALIFIED // 已晋级
|
||
}
|
||
|
||
// 游戏阶段
|
||
export enum GamePhase {
|
||
CHOOSE, // 选择阶段
|
||
MOVING, // 前进阶段
|
||
ROUND_END, // 回合结束
|
||
GAME_OVER // 游戏结束
|
||
}
|
||
|
||
// 玩家数据
|
||
interface PlayerData {
|
||
id: number;
|
||
position: number; // 当前位置
|
||
state: PlayerState;
|
||
selectedSteps: number; // 选择的步数
|
||
}
|
||
|
||
@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; // 存放所有玩家的节点
|
||
|
||
// 游戏配置
|
||
private readonly TOTAL_ROUNDS = 15; // 总回合数
|
||
private readonly ROUND_TIME = 20; // 每回合时间(秒)
|
||
private readonly TOTAL_DISTANCE = 12; // 终点距离(米)
|
||
private readonly MIN_QUALIFIED = 20; // 最少晋级人数
|
||
private readonly INITIAL_PLAYERS = 36; // 初始玩家数
|
||
private readonly STEP_OPTIONS = [1, 2, 3, 4, 5, 6]; // 可选步数
|
||
|
||
// 游戏状态
|
||
private currentRound = 0;
|
||
private currentPhase = GamePhase.CHOOSE;
|
||
private remainingTime = 0;
|
||
private dollTurnTime = 0; // 娃娃回头时间
|
||
private players: Map<number, PlayerData> = new Map();
|
||
|
||
private aiPlayers: Node[] = []; // AI玩家节点列表
|
||
private playerNodes: Map<number, Node> = new Map(); // 所有玩家节点映射
|
||
|
||
async start() {
|
||
await this.initPlayers();
|
||
this.initGame();
|
||
}
|
||
|
||
private async initPlayers() {
|
||
// 创建本地玩家
|
||
const localPlayer = await this.createPlayer(0, true);
|
||
this.playerNodes.set(0, localPlayer);
|
||
|
||
// 创建AI玩家
|
||
for (let i = 1; i < this.INITIAL_PLAYERS; i++) {
|
||
const aiPlayer = await this.createPlayer(i, false);
|
||
this.playerNodes.set(i, aiPlayer);
|
||
this.aiPlayers.push(aiPlayer);
|
||
}
|
||
|
||
this.updateAliveCount();
|
||
}
|
||
|
||
private async createPlayer(id: number, isLocal: boolean) {
|
||
// 随机位置:以起点为中心,随机分布
|
||
const x = Math.random() * 10 - 5; // -5到5
|
||
const z = isLocal ? 0 : Math.random() * 4 - 2; // 本地玩家在起点,AI随机分布
|
||
|
||
// 随机角色ID(1-5)
|
||
const roleId = Math.floor(Math.random() * 5) + 1;
|
||
const roleIdStr = ('000' + roleId).slice(-3);
|
||
|
||
const prefab: Prefab = await new Promise<Prefab>((resolve) => {
|
||
resLoader.get(`common/role/Character${roleIdStr}`, Prefab, ModuleDef.Arean);
|
||
});
|
||
|
||
if (prefab) {
|
||
const role = instantiate(prefab);
|
||
this.playersNode.addChild(role);
|
||
role.setPosition(new Vec3(x, 0, z));
|
||
role.setRotationFromEuler(0, 180, 0);
|
||
|
||
// 添加玩家组件
|
||
const playerComp = role.addComponent(WoodenManPlayer);
|
||
playerComp.init(id, isLocal);
|
||
|
||
return role;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
// 更新存活人数显示
|
||
private updateAliveCount() {
|
||
const aliveCount = Array.from(this.players.values())
|
||
.filter(p => p.state === PlayerState.ALIVE).length;
|
||
this.aliveCountLabel.string = `存活: ${aliveCount}/${this.INITIAL_PLAYERS}`;
|
||
}
|
||
|
||
private initGame() {
|
||
// 初始化玩家
|
||
for (let i = 0; i < this.INITIAL_PLAYERS; i++) {
|
||
this.players.set(i, {
|
||
id: i,
|
||
position: 0,
|
||
state: PlayerState.ALIVE,
|
||
selectedSteps: 0
|
||
});
|
||
}
|
||
this.startNewRound();
|
||
}
|
||
|
||
private startNewRound() {
|
||
this.currentRound++;
|
||
this.remainingTime = this.ROUND_TIME;
|
||
this.currentPhase = GamePhase.CHOOSE;
|
||
this.dollTurnTime = Math.floor(Math.random() * 6) + 1; // 1-6秒随机
|
||
|
||
// 重置所有存活玩家的选择
|
||
this.players.forEach(player => {
|
||
if (player.state === PlayerState.ALIVE) {
|
||
player.selectedSteps = 0;
|
||
}
|
||
});
|
||
}
|
||
|
||
// 玩家选择步数
|
||
public playerChooseSteps(playerId: number, steps: number) {
|
||
if (this.currentPhase !== GamePhase.CHOOSE) return;
|
||
|
||
const player = this.players.get(playerId);
|
||
if (!player || player.state !== PlayerState.ALIVE) return;
|
||
|
||
if (this.STEP_OPTIONS.indexOf(steps) !== -1) {
|
||
player.selectedSteps = steps;
|
||
}
|
||
}
|
||
|
||
// 执行移动阶段
|
||
private executeMovingPhase() {
|
||
this.currentPhase = GamePhase.MOVING;
|
||
|
||
// 播放木头人转身动画
|
||
this.playDollTurnAnimation(() => {
|
||
this.players.forEach((player, id) => {
|
||
if (player.state !== PlayerState.ALIVE) return;
|
||
|
||
const steps = player.selectedSteps || 1;
|
||
const playerNode = this.playerNodes.get(id);
|
||
|
||
if (steps > this.dollTurnTime) {
|
||
// 被抓到,播放死亡动画
|
||
this.killPlayer(player, playerNode);
|
||
} else {
|
||
// 移动玩家
|
||
this.movePlayer(player, playerNode, steps);
|
||
}
|
||
});
|
||
|
||
this.updateAliveCount();
|
||
});
|
||
}
|
||
|
||
// 木头人转身动画
|
||
private playDollTurnAnimation(callback: () => void) {
|
||
const duration = 1.0; // 转身动画持续时间
|
||
const currentRotation = this.dollNode.eulerAngles.clone();
|
||
const targetRotation = new Vec3(currentRotation.x, currentRotation.y + 180, currentRotation.z);
|
||
|
||
// 使用 tween 实现平滑旋转
|
||
tween(this.dollNode)
|
||
.to(duration, { eulerAngles: targetRotation })
|
||
.call(callback)
|
||
.start();
|
||
}
|
||
|
||
// 玩家死亡效果
|
||
private killPlayer(player: PlayerData, playerNode: Node) {
|
||
player.state = PlayerState.DEAD;
|
||
|
||
// 获取玩家组件
|
||
const playerComp = playerNode.getComponent(WoodenManPlayer);
|
||
if (playerComp) {
|
||
playerComp.setAlive(false);
|
||
// 播放死亡动画,动画结束后重置位置
|
||
playerComp.playDieAnimation(() => {
|
||
player.position = 0;
|
||
// 重置到起点
|
||
playerNode.setPosition(new Vec3(
|
||
Math.random() * 10 - 5,
|
||
0,
|
||
0
|
||
));
|
||
});
|
||
}
|
||
}
|
||
|
||
// 玩家移动效果
|
||
private movePlayer(player: PlayerData, playerNode: Node, steps: number) {
|
||
const targetPos = new Vec3(
|
||
playerNode.position.x,
|
||
0,
|
||
player.position + steps
|
||
);
|
||
|
||
// 使用 tween 实现平滑移动
|
||
tween(playerNode)
|
||
.to(steps, { position: targetPos })
|
||
.start();
|
||
|
||
player.position += steps;
|
||
|
||
if (player.position >= this.TOTAL_DISTANCE) {
|
||
player.state = PlayerState.QUALIFIED;
|
||
player.position = this.TOTAL_DISTANCE;
|
||
|
||
// 播放胜利动画
|
||
const anim = playerNode.getComponent(Animation);
|
||
anim.play('victory');
|
||
}
|
||
}
|
||
|
||
// AI决策
|
||
private updateAIDecisions() {
|
||
if (this.currentPhase !== GamePhase.CHOOSE) return;
|
||
|
||
this.aiPlayers.forEach(aiNode => {
|
||
const playerComp = aiNode.getComponent(WoodenManPlayer);
|
||
if (!playerComp || !playerComp.isAlive()) return;
|
||
|
||
// 简单AI逻辑:随机选择1-3步
|
||
const steps = Math.floor(Math.random() * 3) + 1;
|
||
this.playerChooseSteps(playerComp.getId(), steps);
|
||
});
|
||
}
|
||
|
||
// 检查游戏是否结束
|
||
private checkGameEnd(): boolean {
|
||
let qualifiedCount = 0;
|
||
this.players.forEach(player => {
|
||
if (player.state === PlayerState.QUALIFIED) {
|
||
qualifiedCount++;
|
||
}
|
||
});
|
||
|
||
return qualifiedCount >= this.MIN_QUALIFIED || this.currentRound >= this.TOTAL_ROUNDS;
|
||
}
|
||
|
||
// 游戏主循环
|
||
update(dt: number) {
|
||
if (this.currentPhase === GamePhase.GAME_OVER) return;
|
||
|
||
this.remainingTime -= dt;
|
||
|
||
if (this.currentPhase === GamePhase.CHOOSE && this.remainingTime <= 0) {
|
||
this.executeMovingPhase();
|
||
}
|
||
|
||
if (this.currentPhase === GamePhase.MOVING) {
|
||
// 移动阶段结束后
|
||
this.currentPhase = GamePhase.ROUND_END;
|
||
|
||
if (this.checkGameEnd()) {
|
||
this.endGame();
|
||
} else {
|
||
this.startNewRound();
|
||
}
|
||
}
|
||
|
||
// 更新AI决策
|
||
this.updateAIDecisions();
|
||
}
|
||
|
||
private endGame() {
|
||
this.currentPhase = GamePhase.GAME_OVER;
|
||
|
||
// 淘汰未晋级玩家
|
||
this.players.forEach(player => {
|
||
if (player.state !== PlayerState.QUALIFIED) {
|
||
player.state = PlayerState.DEAD;
|
||
}
|
||
});
|
||
|
||
// 这里可以触发游戏结束事件
|
||
this.node.emit('gameOver');
|
||
}
|
||
|
||
// 获取游戏状态
|
||
public getGameState() {
|
||
return {
|
||
currentRound: this.currentRound,
|
||
currentPhase: this.currentPhase,
|
||
remainingTime: this.remainingTime,
|
||
players: Array.from(this.players.values())
|
||
};
|
||
}
|
||
|
||
public getCurrentPhase(): GamePhase {
|
||
return this.currentPhase;
|
||
}
|
||
}
|