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>
This commit is contained in:
213
assets/scripts/controller/PlayerController.ts
Normal file
213
assets/scripts/controller/PlayerController.ts
Normal file
@@ -0,0 +1,213 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
assets/scripts/controller/PlayerController.ts.meta
Normal file
9
assets/scripts/controller/PlayerController.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "93a110e7-a99e-4719-b6c2-d2c2d8469d7f",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
59
assets/scripts/controller/PropController.ts
Normal file
59
assets/scripts/controller/PropController.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { _decorator, Component, Vec3 } from 'cc';
|
||||
import { GameManager } from '../manager/GameManager';
|
||||
import { PlayerController } from './PlayerController';
|
||||
|
||||
const { ccclass } = _decorator;
|
||||
|
||||
@ccclass('PropController')
|
||||
export class PropController extends Component {
|
||||
private collected = false;
|
||||
|
||||
update() {
|
||||
if (this.collected || !GameManager.instance) return;
|
||||
const gm = GameManager.instance;
|
||||
const players = gm.curLevel?.children.filter((c) => c.name.includes('Player')) ?? [];
|
||||
const propCell = gm.worldToCell(this.node.worldPosition);
|
||||
for (const p of players) {
|
||||
const pc = p.getComponent(PlayerController);
|
||||
if (!pc) continue;
|
||||
const pcCell = gm.worldToCell(p.worldPosition);
|
||||
if (pcCell.x !== propCell.x || pcCell.y !== propCell.y) continue;
|
||||
this.onCollected(pc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private onCollected(player: PlayerController) {
|
||||
if (this.collected) return;
|
||||
this.collected = true;
|
||||
const gm = GameManager.instance!;
|
||||
player.addCoins();
|
||||
gm.removeProp(this.node.worldPosition);
|
||||
const remaining = (gm.curLevel?.children.filter((c) => c.name.includes('Prop') && c !== this.node).length ?? 0);
|
||||
this.node.destroy();
|
||||
|
||||
if (remaining === 0) {
|
||||
if (gm.multMode) this.resolveMultWin(gm);
|
||||
else player.externalCallResult(true);
|
||||
}
|
||||
}
|
||||
|
||||
private resolveMultWin(gm: GameManager) {
|
||||
const sum = (names: string[]) =>
|
||||
names.reduce((t, n) => t + (gm.findNodeByName(n)?.getComponent(PlayerController)?.coins ?? 0), 0);
|
||||
const totalA = sum(['PlayerA1', 'PlayerA2', 'PlayerA3']);
|
||||
const totalB = sum(['PlayerB1', 'PlayerB2', 'PlayerB3']);
|
||||
const winA = totalA > totalB || (totalA === totalB && gm.stepA1Num + gm.stepA2Num + gm.stepA3Num <
|
||||
gm.stepB1Num + gm.stepB2Num + gm.stepB3Num);
|
||||
const set = (names: string[], win: boolean) => {
|
||||
for (const n of names) gm.findNodeByName(n)?.getComponent(PlayerController)?.externalCallResult(win);
|
||||
};
|
||||
if (winA) {
|
||||
set(['PlayerA1', 'PlayerA2', 'PlayerA3'], true);
|
||||
set(['PlayerB1', 'PlayerB2', 'PlayerB3'], false);
|
||||
} else {
|
||||
set(['PlayerA1', 'PlayerA2', 'PlayerA3'], false);
|
||||
set(['PlayerB1', 'PlayerB2', 'PlayerB3'], true);
|
||||
}
|
||||
}
|
||||
}
|
||||
9
assets/scripts/controller/PropController.ts.meta
Normal file
9
assets/scripts/controller/PropController.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "7b8d3aef-659e-486a-8e93-a08689bed871",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
114
assets/scripts/controller/VehicleController.ts
Normal file
114
assets/scripts/controller/VehicleController.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import { _decorator, Vec3 } from 'cc';
|
||||
import { Direction, GridType } 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 { PlayerController, ExternalDataList } from './PlayerController';
|
||||
import { VisualAssets } from '../visual/VisualAssets';
|
||||
|
||||
const { ccclass } = _decorator;
|
||||
|
||||
@ccclass('VehicleController')
|
||||
export class VehicleController extends Movement {
|
||||
private player: PlayerController | null = null;
|
||||
|
||||
onLoad() {
|
||||
this.moverRole = 'vehicle';
|
||||
this.moveState = 0;
|
||||
}
|
||||
|
||||
start() {
|
||||
super.start();
|
||||
this.setIcon();
|
||||
}
|
||||
|
||||
setPlayer(p: PlayerController | null) {
|
||||
this.player = p;
|
||||
}
|
||||
|
||||
setPosition(pos: Vec3) {
|
||||
this.node.setPosition(pos);
|
||||
}
|
||||
|
||||
setName(name: string) {
|
||||
/* 可挂 Label 显示名称 */
|
||||
}
|
||||
|
||||
override setDirection(dir: Direction) {
|
||||
super.setDirection(dir);
|
||||
this.setIcon();
|
||||
if (Movement.callEach) return;
|
||||
Movement.callEach = true;
|
||||
this.player?.setDirection(dir);
|
||||
Movement.callEach = false;
|
||||
}
|
||||
|
||||
setIcon() {
|
||||
const style = GameManager.instance?.uiStyle ?? 'default';
|
||||
VisualAssets.applyVehicleSprite(this.node, this.direction, style);
|
||||
}
|
||||
|
||||
protected onMoveNextSet(isJump: boolean) {
|
||||
if (Movement.callEach) return;
|
||||
Movement.callEach = true;
|
||||
this.player?.syncFromVehicle(this.targetGridType, isJump);
|
||||
Movement.callEach = false;
|
||||
}
|
||||
|
||||
protected onMoving() {
|
||||
if (Movement.callEach) return;
|
||||
Movement.callEach = true;
|
||||
if (this.player) {
|
||||
this.player.onMoving();
|
||||
this.player.setPosition(this.node.worldPosition);
|
||||
}
|
||||
Movement.callEach = false;
|
||||
}
|
||||
|
||||
protected onMoveToTarget() {
|
||||
GameManager.instance!.removeObj(this.lastPosition);
|
||||
GameManager.instance!.addObj(this.node.worldPosition, GridType.Ride, this.node);
|
||||
if (Movement.callEach) return;
|
||||
Movement.callEach = true;
|
||||
if (this.player) {
|
||||
this.player.setPosition(this.node.worldPosition);
|
||||
this.player.syncMoveToTargetFromVehicle();
|
||||
}
|
||||
Movement.callEach = false;
|
||||
this.externalCall();
|
||||
}
|
||||
|
||||
protected onMoveFail(isJump: boolean) {
|
||||
if (!GameManager.instance!.multMode) {
|
||||
EventManager.dispatch(EventType.InputEnd, GameManager.instance!.gameState);
|
||||
}
|
||||
}
|
||||
|
||||
callVehicleInfo() {
|
||||
this.externalCall();
|
||||
}
|
||||
|
||||
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('processVehicleData', json);
|
||||
}
|
||||
}
|
||||
9
assets/scripts/controller/VehicleController.ts.meta
Normal file
9
assets/scripts/controller/VehicleController.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "aa054a89-5920-470a-aac7-9af275a2d789",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
Reference in New Issue
Block a user