Adds level prefabs, theme assets, audio, extensions, and deployment scripts for the Unity WebGL migration. Co-authored-by: Cursor <cursoragent@cursor.com>
185 lines
5.8 KiB
TypeScript
185 lines
5.8 KiB
TypeScript
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 { scaledMoveSpeed, UNITY_VEHICLE_MOVE_SPEED } from '../core/GridConstants';
|
||
import { Movement } from '../gameplay/Movement';
|
||
import { PlayerController, ExternalDataList } from './PlayerController';
|
||
import { syncRidePositions } from './RideLink';
|
||
import { VisualAssets } from '../visual/VisualAssets';
|
||
|
||
const { ccclass } = _decorator;
|
||
|
||
/**
|
||
* 载具移动(对齐 Unity VehicleController)
|
||
* - 仅能在空地移动(None↔None、Ride→None)
|
||
* - 有骑手时每帧同步玩家;落格更新 Ride 注册
|
||
*/
|
||
@ccclass('VehicleController')
|
||
export class VehicleController extends Movement {
|
||
private player: PlayerController | null = null;
|
||
|
||
onLoad() {
|
||
this.moverRole = 'vehicle';
|
||
this.moveSpeed = scaledMoveSpeed(UNITY_VEHICLE_MOVE_SPEED);
|
||
this.moveState = 0;
|
||
}
|
||
|
||
/** 不调用 super.start(),避免 component.start 晚于 LevelInit 绑定后再次用 spawn 方向刷贴图 */
|
||
start() {
|
||
this.syncCommittedCellFromPosition();
|
||
if (this.player) {
|
||
this.syncFacingFromRider();
|
||
}
|
||
this.setIcon();
|
||
}
|
||
|
||
setPlayer(p: PlayerController | null) {
|
||
this.player = p;
|
||
if (!p) return;
|
||
if (p.direction !== this.direction) {
|
||
super.setDirection(p.direction);
|
||
}
|
||
this.setIcon();
|
||
}
|
||
|
||
getPlayer(): PlayerController | null {
|
||
return this.player;
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
private entityVisualOptions() {
|
||
const gm = GameManager.instance;
|
||
const levelTheme = gm?.getCurLevel()?.theme?.trim();
|
||
return {
|
||
...(gm?.getEntityVisualOptions() ?? {}),
|
||
theme: levelTheme ?? gm?.uiStyle ?? 'default',
|
||
};
|
||
}
|
||
|
||
/** 载具四向贴图:有骑手时跟随骑手 direction,转向时 setIcon 直接换图 */
|
||
private iconDirection(): Direction {
|
||
return this.player?.direction ?? this.direction;
|
||
}
|
||
|
||
setIcon() {
|
||
const dir = this.iconDirection();
|
||
if (this.player && dir !== this.direction) {
|
||
super.setDirection(dir);
|
||
}
|
||
VisualAssets.applyVehicleIconForDirection(
|
||
this.node,
|
||
dir,
|
||
this.entityVisualOptions(),
|
||
);
|
||
this.player?.syncRideMountVisual();
|
||
}
|
||
|
||
/** 骑乘时逻辑朝向与骑手保持一致(Scratch 回传等) */
|
||
private syncFacingFromRider() {
|
||
if (!this.player || this.player.direction === this.direction) return;
|
||
super.setDirection(this.player.direction);
|
||
}
|
||
|
||
/** 主题 / 软重置后按当前逻辑朝向重刷贴图 */
|
||
refreshIcon() {
|
||
this.syncFacingFromRider();
|
||
this.setIcon();
|
||
}
|
||
|
||
override callJump() {
|
||
/* 载具不可跳跃 */
|
||
}
|
||
|
||
protected override syncRideAfterMoveStep() {
|
||
if (this.player) syncRidePositions('vehicle', this.player, this);
|
||
}
|
||
|
||
protected onMoveNextSet(isJump: boolean) {
|
||
if (Movement.callEach) return;
|
||
Movement.callEach = true;
|
||
if (this.player) {
|
||
this.player.setTargetGridType(this.targetGridType);
|
||
this.player.syncFromVehicle(this.targetGridType, isJump);
|
||
}
|
||
Movement.callEach = false;
|
||
}
|
||
|
||
protected onMoving() {
|
||
if (this.player) syncRidePositions('vehicle', this.player, this);
|
||
if (Movement.callEach) return;
|
||
Movement.callEach = true;
|
||
this.player?.onMoving();
|
||
Movement.callEach = false;
|
||
}
|
||
|
||
protected onMoveToTarget() {
|
||
this.commitLandingCell();
|
||
const gm = GameManager.instance!;
|
||
gm.removeObj(this.lastPosition);
|
||
if (this.committedCell) {
|
||
gm.addObjAtCell(this.committedCell, GridType.Ride, this.node);
|
||
} else {
|
||
gm.addObj(this.node.position, GridType.Ride, this.node);
|
||
}
|
||
|
||
if (!Movement.callEach && this.player) {
|
||
Movement.callEach = true;
|
||
this.player.setRideBaseFromVehicle(this.node.position);
|
||
if (this.committedCell) this.player.shareCommittedCell(this.committedCell);
|
||
this.player.syncMoveToTargetFromVehicle();
|
||
Movement.callEach = false;
|
||
}
|
||
|
||
if (this.step === 0) this.externalCall();
|
||
}
|
||
|
||
protected onMoveFail(_isJump: boolean) {
|
||
this.externalCall();
|
||
EventManager.dispatch(EventType.InputEnd, GameManager.instance!.gameState);
|
||
}
|
||
|
||
callVehicleInfo() {
|
||
this.externalCall();
|
||
}
|
||
|
||
externalCall() {
|
||
const gm = GameManager.instance!;
|
||
const list: ExternalDataList = { direction: this.direction, externalDatas: [] };
|
||
const sample = this.getGridSamplePosition();
|
||
const self = gm.worldToCell(sample);
|
||
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(sample, d);
|
||
const cell = gm.worldToCell(wp);
|
||
list.externalDatas.push({
|
||
position: { x: cell.x, y: cell.y, z: 0 },
|
||
gridType: gm.calculateNextGridType(sample, d),
|
||
direction: gm.getRelativePosition(this.direction, d),
|
||
});
|
||
}
|
||
JsBridge.call('processVehicleData', JSON.stringify(list));
|
||
}
|
||
}
|