squidGame/tgx-games-client/assets/module_arean/level1/Script/WoodenManGame.ts

588 lines
21 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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随机
// 随机角色ID1-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();
}
}