Files
cocos/assets/scripts/controller/PlayerController.ts
刘宇飞 cba5105908 Initial Cocos Creator port of main-site Unity WebGL game.
Includes core gameplay, 600 exported levels, visual assets, web bridge, and bootstrap scene.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 14:57:46 +08:00

214 lines
7.1 KiB
TypeScript

import { _decorator, Vec3 } from 'cc';
import { Direction, GameState, GridType, Skin } from '../core/Define';
import { EventManager, EventType } from '../core/EventManager';
import { JsBridge } from '../bridge/JsBridge';
import { GameManager } from '../manager/GameManager';
import { Movement } from '../gameplay/Movement';
import { VehicleController } from './VehicleController';
import { VisualAssets } from '../visual/VisualAssets';
const { ccclass } = _decorator;
export interface ExternalData {
position: { x: number; y: number; z: number };
gridType: number;
direction: string;
}
export interface ExternalDataList {
direction: number;
externalDatas: ExternalData[];
}
export interface ExternalResult {
isWin: boolean;
stepNum: number;
direction: number;
isInputEnd: boolean;
}
@ccclass('PlayerController')
export class PlayerController extends Movement {
coins = 0;
private vehicle: VehicleController | null = null;
private sendFinally = false;
onLoad() {
this.moverRole = 'player';
EventManager.register(EventType.LevelInit, this.onLevelInit);
EventManager.register(EventType.InputEnd, this.onInputEnd);
this.coins = 0;
}
onDestroy() {
EventManager.remove(EventType.LevelInit, this.onLevelInit);
EventManager.remove(EventType.InputEnd, this.onInputEnd);
}
start() {
super.start();
VisualAssets.applyPlayerSprite(this.node, this.direction);
if (this.node.name === 'Player' && GameManager.instance) {
this.callChangeSkin(GameManager.instance.playerSkin);
}
}
override setDirection(dir: Direction) {
super.setDirection(dir);
VisualAssets.applyPlayerSprite(this.node, dir);
}
private onLevelInit = () => {
this.checkRide();
};
private onInputEnd = () => {
this.externalCallResult(false);
};
protected onMoveNextSet(isJump: boolean) {
if (isJump && this.targetGridType === GridType.Jump) {
const p = this.node.worldPosition.clone();
p.y += 0.15;
this.targetPosition.set(p);
}
}
onMoving() {
if (this.targetGridType === GridType.None && this.vehicle && !Movement.callEach) {
this.vehicle.setPosition(this.node.worldPosition);
}
}
protected onMoveFail(isJump: boolean) {
this.externalCallResult(false);
const gm = GameManager.instance!;
if (gm.multMode) {
const other = gm.findNodeByName(this.node.name === 'Player' ? 'Enemy' : 'Player');
other?.getComponent(PlayerController)?.externalCallResult(true);
}
console.log(`${this.node.name} 无法移动`, isJump);
}
protected onMoveToTarget() {
if (this.curGrid === GridType.Ride) {
const obj = GameManager.instance!.getGameObject(this.node.worldPosition);
if (obj) {
this.vehicle = obj.getComponent(VehicleController);
this.vehicle?.setPlayer(this);
if (this.vehicle) this.vehicle.setDirection(this.direction);
}
} else if (this.curGrid !== GridType.None && this.vehicle) {
this.vehicle.setPlayer(null);
this.vehicle = null;
}
if (this.vehicle) {
this.vehicle.setPosition(this.node.worldPosition);
GameManager.instance!.removeObj(this.lastPosition);
GameManager.instance!.addObj(this.node.worldPosition, GridType.Ride, this.vehicle.node);
}
this.externalCall();
}
callChangeSkin(n: number) {
if (n < 0 || n > Skin.sanxing) return;
if (GameManager.instance) GameManager.instance.playerSkin = n as Skin;
}
callPlayerInfo() {
this.externalCall();
}
setName(name: string) {
this.vehicle?.setName(name);
}
getCoinsNum() {
JsBridge.call('coinsData', JSON.stringify({ coinsNum: this.coins, gameObjectName: this.node.name }));
}
addCoins() {
this.coins++;
}
setPosition(pos: Vec3) {
this.node.setPosition(pos);
}
/** 供 VehicleController 同步 */
syncFromVehicle(targetGridType: GridType, isJump: boolean) {
this.targetGridType = targetGridType;
this.onMoveNextSet(isJump);
}
syncMoveToTargetFromVehicle() {
this.onMoveToTarget();
}
externalCall() {
const gm = GameManager.instance!;
const list: ExternalDataList = { direction: this.direction, externalDatas: [] };
const self = gm.worldToCell(this.node.worldPosition);
list.externalDatas.push({
position: { x: self.x, y: self.y, z: 0 },
gridType: this.curGrid,
direction: 'self',
});
for (let d = Direction.North; d <= Direction.West; d++) {
const wp = gm.nextGridPosition(this.node.worldPosition, d);
const cell = gm.worldToCell(wp);
list.externalDatas.push({
position: { x: cell.x, y: cell.y, z: 0 },
gridType: gm.calculateNextGridType(this.node.worldPosition, d),
direction: gm.getRelativePosition(this.direction, d),
});
}
const json = JSON.stringify(list);
if (gm.multMode) JsBridge.call(`process${this.node.name}`, json);
else JsBridge.call('processData', json);
}
externalCallResult(isWin: boolean) {
const gm = GameManager.instance!;
if (isWin && (this.node.name === 'Player' || this.node.name === gm.multPlayerRole)) {
/* success audio */
} else if (!isWin && (this.node.name === 'Player' || this.node.name === gm.multPlayerRole)) {
/* fail audio */
}
let myStep = gm.stepNum;
if (gm.multMode) {
switch (gm.multPlayerRole) {
case 'PlayerA1': myStep = gm.stepA1Num; break;
case 'PlayerA2': myStep = gm.stepA2Num; break;
case 'PlayerA3': myStep = gm.stepA3Num; break;
case 'PlayerB1': myStep = gm.stepB1Num; break;
case 'PlayerB2': myStep = gm.stepB2Num; break;
case 'PlayerB3': myStep = gm.stepB3Num; break;
}
}
if ((this.node.name === 'Player' || this.node.name === gm.multPlayerRole) && !this.sendFinally) {
const payload: ExternalResult = {
isWin,
stepNum: myStep,
direction: this.direction,
isInputEnd: gm.isInputEnd,
};
JsBridge.call('externalResult', JSON.stringify(payload));
this.sendFinally = true;
}
if (gm.gameState !== GameState.Run) return;
gm.setGameState(isWin ? GameState.ResultWin : GameState.ResultFail);
}
private checkRide() {
if (this.curGrid === GridType.Ride) {
const obj = GameManager.instance!.getGameObject(this.node.worldPosition);
if (obj) {
this.vehicle = obj.getComponent(VehicleController);
this.vehicle?.setDirection(this.direction);
this.vehicle?.setPlayer(this);
}
}
}
}