588 lines
21 KiB
TypeScript
588 lines
21 KiB
TypeScript
import { _decorator, Component, Node, Prefab, Label, Animation, instantiate, Vec3, tween, macro, Sprite, Color, Button, animation, director, assetManager } from 'cc';
|
||
import { WoodenManPlayer } from './WoodenManPlayer';
|
||
import { tgxAudioMgr } from 'db://assets/core_tgx/tgx';
|
||
import { ModuleDef } from 'db://assets/scripts/ModuleDef';
|
||
import { resLoader } from 'db://assets/core_tgx/base/utils/ResLoader';
|
||
import { EMusicDefine } from 'db://assets/module_basic/scripts/MusicDefine';
|
||
import { SceneDef } from 'db://assets/scripts/SceneDef';
|
||
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(Label)
|
||
winCountLabel: Label = null; // 晋级名额显示
|
||
|
||
@property(Node)
|
||
playersNode: Node = null; // 存放所有玩家的节点
|
||
|
||
@property(Node)
|
||
localPlayerNode: Node = null; // 本地玩家节点引用
|
||
|
||
@property(Node)
|
||
guiNode: Node = null; // ui节点
|
||
|
||
|
||
private turnAnimationDuration: number = 0.3; // 转身动画持续时间
|
||
private readonly TURN_BACK_DELAY = 2; // 转回正面的延迟时间
|
||
private readonly INITIAL_PLAYERS = 36; // 初始玩家数
|
||
|
||
private aiPlayers: Node[] = []; // AI玩家节点列表
|
||
private playerNodes: Map<number, Node> = new Map(); // 所有玩家节点映射
|
||
|
||
private readonly minRoleId: number = 1; // 最小角色ID
|
||
readonly maxRoleId: number = 5;
|
||
readonly REQUIRED_QUALIFIED = 10; // 需要晋级的人数
|
||
qualifiedCount: number = 0; // 已晋级人数
|
||
gameTimer: number = 180; // 游戏时间(3分钟)
|
||
isGameOver: boolean = false; // 游戏是否结束
|
||
|
||
@property(Node)
|
||
warningLightsNode: Node = null; // 三个警示灯的父节点
|
||
|
||
|
||
private readonly WARNING_LIGHT_BASE_INTERVAL = 1; // 修改基础间隔为0.7秒
|
||
|
||
private isGameStarted: boolean = false; // 添加游戏是否已开始的标志
|
||
|
||
|
||
|
||
@property(Node)
|
||
stopButton: Node = null; // 改为停止按钮引用
|
||
@property(Animation)
|
||
goAnim: Animation;
|
||
|
||
private isDollTurning: boolean = false; // 添加标记,表示娃娃是否正在转身
|
||
private turnStartTime: number = 0; // 记录转身开始时间
|
||
|
||
|
||
// 添加公共 getter 方法
|
||
public getGameStarted(): boolean {
|
||
return this.isGameStarted;
|
||
}
|
||
|
||
public getIsDollTurning(): boolean {
|
||
return this.isDollTurning;
|
||
}
|
||
|
||
async start() {
|
||
await this.initPlayers();
|
||
this.schedule(this.updateGameTimer, 1);
|
||
this.updateWinCount();
|
||
tgxAudioMgr.inst.play(EMusicDefine.MUSIC_BGM_BATTLE, 1, ModuleDef.BASIC);
|
||
// 修改按钮事件监听
|
||
if (this.stopButton) {
|
||
this.stopButton.active = false; // 初始隐藏
|
||
this.stopButton.on(Node.EventType.TOUCH_START, this.onStopButtonPress, this);
|
||
this.stopButton.on(Node.EventType.TOUCH_END, this.onStopButtonRelease, this);
|
||
this.stopButton.on(Node.EventType.TOUCH_CANCEL, this.onStopButtonRelease, this);
|
||
}
|
||
|
||
// 延迟3秒后开始第一次转身
|
||
this.scheduleOnce(() => {
|
||
this.isGameStarted = true; // 标记游戏开始
|
||
// 显示停止按钮
|
||
this.guiNode.getComponent(Animation).play("showUI");
|
||
this.executeTurnBackPhase(true);
|
||
}, 12); // 延长等待时间,等摄像机序列播放完
|
||
}
|
||
|
||
onDestroy() {
|
||
// 停止所有定时器
|
||
this.unscheduleAllCallbacks();
|
||
|
||
// 停止所有 tween
|
||
if (this.dollNode) {
|
||
tween(this.dollNode).stop();
|
||
}
|
||
|
||
// 停止所有玩家的 tween 和动画
|
||
this.playerNodes.forEach((playerNode) => {
|
||
if (playerNode && playerNode.isValid) {
|
||
tween(playerNode).stop();
|
||
const playerComp = playerNode.getComponent(WoodenManPlayer);
|
||
if (playerComp && playerComp.isValid) {
|
||
try {
|
||
playerComp.cleanup();
|
||
} catch (error) {
|
||
console.warn('Error cleaning up player:', error);
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
// 停止警示灯相关
|
||
if (this.warningLightsNode && this.warningLightsNode.isValid) {
|
||
this.warningLightsNode.children.forEach(light => {
|
||
if (light && light.isValid) {
|
||
tween(light).stop();
|
||
}
|
||
});
|
||
}
|
||
|
||
// 取消按钮事件监听
|
||
if (this.stopButton && this.stopButton.isValid) {
|
||
this.stopButton.off(Node.EventType.TOUCH_START, this.onStopButtonPress, this);
|
||
this.stopButton.off(Node.EventType.TOUCH_END, this.onStopButtonRelease, this);
|
||
this.stopButton.off(Node.EventType.TOUCH_CANCEL, this.onStopButtonRelease, this);
|
||
}
|
||
|
||
// 停止音频
|
||
try {
|
||
tgxAudioMgr.inst.stop();
|
||
} catch (error) {
|
||
console.warn('Error stopping audio:', error);
|
||
}
|
||
}
|
||
|
||
// 修改按钮事件处理
|
||
private onStopButtonPress() {
|
||
const localPlayer = this.localPlayerNode?.getComponent(WoodenManPlayer);
|
||
if (localPlayer && localPlayer.isAlive() && !localPlayer.isPassed()) {
|
||
localPlayer.setButtonPressed(true);
|
||
localPlayer.stopMoving();
|
||
}
|
||
}
|
||
|
||
private onStopButtonRelease() {
|
||
const localPlayer = this.localPlayerNode?.getComponent(WoodenManPlayer);
|
||
if (localPlayer && localPlayer.isAlive() && !localPlayer.isPassed()) {
|
||
localPlayer.setButtonPressed(false);
|
||
localPlayer.startMoving();
|
||
}
|
||
}
|
||
|
||
private async initPlayers() {
|
||
// 初始化本地玩家
|
||
if (this.localPlayerNode) {
|
||
const playerComp = this.localPlayerNode.getComponent(WoodenManPlayer);
|
||
playerComp.init(0, true, this); // 设置为本地玩家
|
||
|
||
// 添加随机位置设置
|
||
const x = 0;
|
||
//const x = Math.random() * 15 - 7;
|
||
const z = 50 + Math.random() * 5;
|
||
this.localPlayerNode.setPosition(new Vec3(x, 0, z));
|
||
this.localPlayerNode.setRotationFromEuler(0, 180, 0);
|
||
|
||
// 添加本地玩家名字
|
||
const nameLabel = this.localPlayerNode.getChildByName("NameLabel");
|
||
if (nameLabel) {
|
||
const labelComponent = nameLabel.getComponent(Label);
|
||
if (labelComponent) {
|
||
labelComponent.string = "You"; // 本地玩家显示为 "You"
|
||
labelComponent.color = new Color(255, 255, 0, 255); // 黄色
|
||
}
|
||
}
|
||
nameLabel.active = false;
|
||
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): Promise<Node> {
|
||
return new Promise((resolve) => {
|
||
// 随机位置:起点区域
|
||
const x = Math.random() * 11 - 5;
|
||
const z = 50 + Math.random() * 3; // 50-55随机
|
||
|
||
// 随机角色ID(1-5)
|
||
const roleId = Math.floor(Math.random() * (this.maxRoleId - this.minRoleId + 1)) + this.minRoleId;
|
||
const roleIdStr = ('000' + roleId).slice(-3);
|
||
|
||
resLoader.load(ModuleDef.Arean, `common/role/Character${roleIdStr}`, (err: Error | null, prefab: Prefab) => {
|
||
if (!err && prefab) {
|
||
const role = instantiate(prefab);
|
||
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, this);
|
||
|
||
// 添加角色名字NameLabel
|
||
const nameLabel = role.getChildByName("NameLabel");
|
||
if (nameLabel) {
|
||
const labelComponent = nameLabel.getComponent(Label);
|
||
if (labelComponent) {
|
||
labelComponent.string = `player${id}`;
|
||
labelComponent.color = new Color(255, 255, 255, 255);
|
||
}
|
||
}
|
||
nameLabel.active = false;
|
||
resolve(role);
|
||
} else {
|
||
console.error('Failed to load character prefab:', err);
|
||
resolve(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}`;
|
||
}
|
||
|
||
|
||
// 修改 startFirstTurn 方法
|
||
|
||
// 修改 executeTurnBackPhase 方法
|
||
private executeTurnBackPhase(firstTimes: boolean = false) {
|
||
if (!this.isGameStarted || this.isGameOver) return;
|
||
tgxAudioMgr.inst.playOneShot(EMusicDefine.WOODENMAN_GO, 1, ModuleDef.BASIC);
|
||
this.goAnim.getComponent(Animation).play("showGo");
|
||
// 直接执行转身动画
|
||
this.playDollTurnAnimation();
|
||
this.playerNodes.forEach((playerNode) => {
|
||
const playerComp = playerNode.getComponent(WoodenManPlayer);
|
||
if (playerComp && (firstTimes || !playerComp.getIsLocalPlayer()) && playerComp.isAlive() && !playerComp.isPassed()) {
|
||
playerComp.setButtonPressed(false);
|
||
// 添加随机延迟(0-0.3秒)
|
||
this.scheduleOnce(() => {
|
||
playerComp.startMoving();
|
||
}, Math.random() * 0.3 + 0.3);
|
||
}
|
||
});
|
||
// 延迟后开始下一轮
|
||
this.scheduleOnce(() => {
|
||
this.executeTurningPhase();
|
||
}, this.TURN_BACK_DELAY);
|
||
|
||
}
|
||
|
||
private executeTurningPhase() {
|
||
// 先播放警告音效和警示灯
|
||
tgxAudioMgr.inst.playOneShot(EMusicDefine.WOODENMAN, 1, ModuleDef.BASIC);
|
||
|
||
if (this.warningLightsNode) {
|
||
this.warningLightsNode.active = true;
|
||
|
||
// AI玩家在警示灯期间随机停止
|
||
this.playerNodes.forEach((playerNode) => {
|
||
const playerComp = playerNode.getComponent(WoodenManPlayer);
|
||
if (playerComp && !playerComp.getIsLocalPlayer() && playerComp.isAlive() && !playerComp.isPassed()) {
|
||
const stopDelay = Math.random() * 1.8 + 2.5;
|
||
this.scheduleOnce(() => {
|
||
if (playerComp.isAlive() && playerComp.isMoving) {
|
||
playerComp.setButtonPressed(true);
|
||
playerComp.stopMoving();
|
||
}
|
||
}, stopDelay);
|
||
}
|
||
});
|
||
|
||
// 播放警示灯序列
|
||
this.playWarningLights(() => {
|
||
if (this.warningLightsNode) {
|
||
this.warningLightsNode.active = false;
|
||
}
|
||
|
||
// 开始转身
|
||
this.playDollTurnAnimation(() => {
|
||
// 等待0.3秒后开始检查移动状态
|
||
this.scheduleOnce(() => {
|
||
this.schedule(this.checkMovingInterval, 0);
|
||
tgxAudioMgr.inst.playOneShot(EMusicDefine.WOODENMAN_Kill, 1, ModuleDef.BASIC);
|
||
}, 0.3);
|
||
});
|
||
|
||
// 延迟后开始下一轮
|
||
this.scheduleOnce(() => {
|
||
this.unschedule(this.checkMovingInterval);
|
||
this.executeTurnBackPhase();
|
||
}, Math.round((3 + Math.random() * 2) * 10) / 10);
|
||
});
|
||
}
|
||
}
|
||
|
||
checkMovingInterval() {
|
||
const currentTime = Date.now();
|
||
const timeSinceTurnStart = (currentTime - this.turnStartTime) / 1000; // 转换为秒
|
||
|
||
this.playerNodes.forEach((playerNode) => {
|
||
const playerComp = playerNode.getComponent(WoodenManPlayer);
|
||
if (playerComp && playerComp.isAlive() && !playerComp.isPassed()) {
|
||
if (playerComp.isMoving) {
|
||
if (timeSinceTurnStart > 0.3) { // 如果在转身0.3秒后还在移动
|
||
this.killPlayer(playerComp, playerNode);
|
||
}
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// 检查游戏是否结束
|
||
private checkGameEnd(): boolean {
|
||
if (this.isGameOver) return true;
|
||
|
||
let aliveCount = 0;
|
||
let qualifiedCount = 0;
|
||
let totalCount = 0;
|
||
|
||
this.playerNodes.forEach((playerNode) => {
|
||
const playerComp = playerNode.getComponent(WoodenManPlayer);
|
||
if (playerComp) {
|
||
totalCount++;
|
||
if (playerComp.isAlive()) aliveCount++;
|
||
if (playerComp.isPassed()) qualifiedCount++;
|
||
}
|
||
});
|
||
|
||
// 如果所有玩家都要么死亡要么晋级,游戏结束
|
||
if (qualifiedCount + (totalCount - aliveCount) === totalCount) {
|
||
this.endGame('所有玩家已完成比赛!游戏结束');
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
// 游戏主循环
|
||
update(dt: number) {
|
||
if (!this.isGameOver) {
|
||
this.checkGameEnd();
|
||
}
|
||
}
|
||
|
||
endGame(reason: string = '') {
|
||
if (this.isGameOver) return;
|
||
|
||
this.isGameOver = true;
|
||
this.unscheduleAllCallbacks(); // 停止所有定时器
|
||
|
||
|
||
|
||
// 如果是因为达到晋级人数而结束,则击杀所有未晋级的存活玩家
|
||
if (reason.includes('达到晋级人数要求')) {
|
||
this.killAllNonQualifiedPlayers();
|
||
}
|
||
|
||
// 统计最终结果
|
||
let qualifiedPlayers = 0;
|
||
let deadPlayers = 0;
|
||
|
||
this.playerNodes.forEach((playerNode) => {
|
||
const playerComp = playerNode.getComponent(WoodenManPlayer);
|
||
if (playerComp) {
|
||
if (playerComp.isPassed()) qualifiedPlayers++;
|
||
if (!playerComp.isAlive()) deadPlayers++;
|
||
}
|
||
});
|
||
|
||
// 更新最终显示
|
||
this.updateWinCount();
|
||
this.updateAliveCount();
|
||
|
||
this.scheduleOnce(() => {
|
||
resLoader.load(ModuleDef.Arean, 'common/prefabs/settlement', (err: Error | null, prefab: Prefab) => {
|
||
if (!err && prefab) {
|
||
const n = instantiate(prefab);
|
||
this.guiNode.addChild(n);
|
||
n.getComponent(Animation).play("showSettlement");
|
||
// Lives从10滚动到8
|
||
const livesLabel = n.getChildByPath("settlementNode/Node/lives").getComponent(Label);
|
||
let lives = { value: 36 };
|
||
tween(lives)
|
||
.to(1, { value: 10 }, {
|
||
easing: 'linear',
|
||
onUpdate: () => {
|
||
livesLabel.string = Math.floor(lives.value).toString();
|
||
}
|
||
})
|
||
.start();
|
||
|
||
// Money从400w滚动到480w
|
||
const moneyLabel = n.getChildByPath("settlementNode/Node/money").getComponent(Label);
|
||
let money = { value: 420 };
|
||
tween(money)
|
||
.to(1, { value: 446 }, {
|
||
easing: 'linear',
|
||
onUpdate: () => {
|
||
moneyLabel.string = Math.floor(money.value) + "w";
|
||
}
|
||
})
|
||
.start();
|
||
|
||
let isWin = this.localPlayerNode.getComponent(WoodenManPlayer).isPassed();
|
||
n.getChildByPath("settlementNode/btnOK/label").getComponent(Label).string = isWin ? "NEXT" : "QUIT";
|
||
n.getChildByPath("settlementNode/btnOK").on(Node.EventType.TOUCH_START, () => {
|
||
|
||
if (isWin) {
|
||
SceneDef.Next_Scene = "level2/scene/level2Scene";
|
||
// 切换到加载场景
|
||
director.loadScene('LoadingScene');
|
||
// director.loadScene("level2/scene/level2Scene");
|
||
}
|
||
else {
|
||
director.loadScene("lobby_arean");
|
||
}
|
||
});
|
||
}
|
||
});
|
||
}, 3);
|
||
|
||
console.log(`游戏结束!
|
||
原因:${reason}
|
||
晋级人数:${qualifiedPlayers}
|
||
淘汰人数:${deadPlayers}
|
||
剩余时间:${this.gameTimer}秒`);
|
||
|
||
// 触发游戏结束事件
|
||
this.node.emit('gameOver');
|
||
|
||
// 隐藏停止按钮
|
||
if (this.stopButton) {
|
||
this.stopButton.active = false;
|
||
}
|
||
}
|
||
|
||
// 添加新方法:击杀所有未晋级的存活玩家
|
||
private killAllNonQualifiedPlayers() {
|
||
this.playerNodes.forEach((playerNode) => {
|
||
const playerComp = playerNode.getComponent(WoodenManPlayer);
|
||
if (playerComp && playerComp.isAlive() && !playerComp.isPassed()) {
|
||
this.killPlayer(playerComp, playerNode);
|
||
}
|
||
});
|
||
}
|
||
|
||
// 获取游戏状态
|
||
|
||
// 木头人转身动画
|
||
private playDollTurnAnimation(callback?: () => void) {
|
||
const currentRotation = this.dollNode.eulerAngles.clone();
|
||
const targetRotation = new Vec3(
|
||
currentRotation.x,
|
||
currentRotation.y + 180,
|
||
currentRotation.z
|
||
);
|
||
|
||
this.isDollTurning = true;
|
||
this.turnStartTime = Date.now();
|
||
|
||
tween(this.dollNode)
|
||
.to(this.turnAnimationDuration, { eulerAngles: targetRotation }, {
|
||
easing: 'quadOut',
|
||
onStart: () => {
|
||
this.node.parent?.emit('dollTurnStart');
|
||
},
|
||
onComplete: () => {
|
||
this.isDollTurning = false;
|
||
this.node.parent?.emit('dollTurnComplete');
|
||
if (callback) callback();
|
||
}
|
||
})
|
||
.start();
|
||
}
|
||
|
||
// 玩家死亡效果
|
||
private killPlayer(playerComp: WoodenManPlayer, playerNode: Node) {
|
||
if (!playerComp.isAlive()) return; // 防止重复处理
|
||
|
||
// 先设置玩家状态为死亡
|
||
playerComp.setAlive(false);
|
||
playerComp.isMoving = false;
|
||
|
||
// 确保停止所有正在进行的动画和移动
|
||
tween(playerNode).stop();
|
||
|
||
// 延迟一小段时间再播放死亡动画,避免动作冲突
|
||
this.scheduleOnce(() => {
|
||
// 播放死亡动画
|
||
playerComp.playDieAnimation();
|
||
|
||
// 更新存活人数显示
|
||
this.updateAliveCount();
|
||
|
||
// 如果是本地玩家死亡,隐藏停止按钮
|
||
if (playerComp.getIsLocalPlayer() && this.stopButton) {
|
||
this.stopButton.active = false;
|
||
}
|
||
}, 0.1);
|
||
}
|
||
|
||
|
||
|
||
// 更新游戏时间
|
||
private updateGameTimer() {
|
||
if (this.isGameOver) return;
|
||
|
||
this.gameTimer--;
|
||
if (this.gameTimer <= 0) {
|
||
this.endGame('时间到!游戏结束');
|
||
}
|
||
}
|
||
|
||
// 更新晋级名额显示
|
||
updateWinCount() {
|
||
if (this.winCountLabel) {
|
||
const displayCount = Math.min(this.qualifiedCount, this.REQUIRED_QUALIFIED);
|
||
this.winCountLabel.string = `${displayCount}/${this.REQUIRED_QUALIFIED}`;
|
||
}
|
||
}
|
||
|
||
// 修改警示灯动画方法
|
||
private playWarningLights(callback?: () => void) {
|
||
// 生成随机加速倍率 (0.5~1倍)
|
||
const actualInterval = this.WARNING_LIGHT_BASE_INTERVAL;
|
||
|
||
// 先重置所有灯为初始状态
|
||
this.warningLightsNode.children.forEach(light => {
|
||
light.getComponent(Sprite).color = new Color(128, 128, 128, 255);
|
||
});
|
||
|
||
// 依次点亮每个灯
|
||
let currentLight = 0;
|
||
const lightInterval = () => {
|
||
if (currentLight >= this.warningLightsNode.children.length) {
|
||
if (callback) callback();
|
||
return;
|
||
}
|
||
|
||
// 点亮当前灯并播放音效
|
||
const light = this.warningLightsNode.children[currentLight];
|
||
light.getComponent(Sprite).color = new Color(255, 0, 0, 255);
|
||
|
||
|
||
currentLight++;
|
||
|
||
this.scheduleOnce(() => {
|
||
lightInterval();
|
||
}, actualInterval);
|
||
}
|
||
|
||
// 开始点亮第一个灯
|
||
lightInterval();
|
||
}
|
||
}
|