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:
147
assets/scripts/visual/VisualAssets.ts
Normal file
147
assets/scripts/visual/VisualAssets.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import {
|
||||
Node, Sprite, SpriteFrame, UITransform, resources, Color, Graphics,
|
||||
ImageAsset, Texture2D,
|
||||
} from 'cc';
|
||||
import { Direction } from '../core/Define';
|
||||
import { SpawnKind } from '../level/LevelTypes';
|
||||
|
||||
type SpriteKey = 'player_F' | 'player_B' | 'ship_F' | 'ship_B' | 'coin' | 'tile';
|
||||
|
||||
const PATHS: Record<SpriteKey, string> = {
|
||||
player_F: 'textures/silu/player_F',
|
||||
player_B: 'textures/silu/player_B',
|
||||
ship_F: 'textures/silu/ship_F',
|
||||
ship_B: 'textures/silu/ship_B',
|
||||
coin: 'textures/ui/coin',
|
||||
tile: 'textures/silu/Baseblock',
|
||||
};
|
||||
|
||||
export class VisualAssets {
|
||||
private static frames = new Map<SpriteKey, SpriteFrame>();
|
||||
private static loading: Promise<void> | null = null;
|
||||
|
||||
static async preload(): Promise<void> {
|
||||
if (this.loading) return this.loading;
|
||||
this.loading = (async () => {
|
||||
const keys = Object.keys(PATHS) as SpriteKey[];
|
||||
const results = await Promise.all(keys.map((k) => this.loadOne(k)));
|
||||
const ok = results.filter(Boolean).length;
|
||||
console.log(`[VisualAssets] 贴图加载 ${ok}/${keys.length}`);
|
||||
if (ok === 0) {
|
||||
console.warn('[VisualAssets] 未加载到贴图,将使用色块。请确认 assets/resources/textures 已导入');
|
||||
}
|
||||
})().catch((e) => {
|
||||
console.error('[VisualAssets] preload failed', e);
|
||||
this.loading = null;
|
||||
});
|
||||
return this.loading;
|
||||
}
|
||||
|
||||
private static loadOne(key: SpriteKey): Promise<boolean> {
|
||||
if (this.frames.has(key)) return Promise.resolve(true);
|
||||
const base = PATHS[key];
|
||||
return new Promise((resolve) => {
|
||||
resources.load(`${base}/spriteFrame`, SpriteFrame, (err, sf) => {
|
||||
if (!err && sf) {
|
||||
this.frames.set(key, sf);
|
||||
resolve(true);
|
||||
return;
|
||||
}
|
||||
resources.load(base, SpriteFrame, (err2, sf2) => {
|
||||
if (!err2 && sf2) {
|
||||
this.frames.set(key, sf2);
|
||||
resolve(true);
|
||||
return;
|
||||
}
|
||||
resources.load(base, ImageAsset, (err3, img) => {
|
||||
if (!err3 && img) {
|
||||
const tex = new Texture2D();
|
||||
tex.image = img;
|
||||
const frame = new SpriteFrame();
|
||||
frame.texture = tex;
|
||||
this.frames.set(key, frame);
|
||||
resolve(true);
|
||||
} else {
|
||||
console.warn(`[VisualAssets] 加载失败: ${base}`, err3 || err2 || err);
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static getFrame(key: SpriteKey): SpriteFrame | null {
|
||||
return this.frames.get(key) ?? null;
|
||||
}
|
||||
|
||||
static applyPlayerSprite(node: Node, direction: Direction) {
|
||||
const isFront = direction === Direction.South || direction === Direction.East;
|
||||
const flipX = direction === Direction.West || direction === Direction.East;
|
||||
const key: SpriteKey = isFront ? 'player_F' : 'player_B';
|
||||
this.applySprite(node, key, flipX);
|
||||
}
|
||||
|
||||
static applyVehicleSprite(node: Node, direction: Direction, uiStyle = 'default') {
|
||||
void uiStyle;
|
||||
const isFront = direction === Direction.South || direction === Direction.East;
|
||||
const flipX = direction === Direction.West || direction === Direction.East;
|
||||
const key: SpriteKey = isFront ? 'ship_F' : 'ship_B';
|
||||
this.applySprite(node, key, flipX);
|
||||
}
|
||||
|
||||
static setupEntityVisual(node: Node, kind: SpawnKind, direction?: Direction) {
|
||||
if (kind === 'player') {
|
||||
this.applyPlayerSprite(node, direction ?? Direction.South);
|
||||
return;
|
||||
}
|
||||
if (kind === 'vehicle') {
|
||||
this.applyVehicleSprite(node, direction ?? Direction.North);
|
||||
return;
|
||||
}
|
||||
if (kind === 'prop') {
|
||||
this.applySprite(node, 'coin', false, 0.85);
|
||||
return;
|
||||
}
|
||||
if (kind === 'prop_decor') {
|
||||
this.applySprite(node, 'coin', false, 0.6, 120);
|
||||
}
|
||||
}
|
||||
|
||||
/** Sprite 与 Graphics 不能共存,二选一 */
|
||||
static applySprite(node: Node, key: SpriteKey, flipX: boolean, scale = 1, alpha = 255) {
|
||||
let ui = node.getComponent(UITransform);
|
||||
if (!ui) {
|
||||
ui = node.addComponent(UITransform);
|
||||
ui.setContentSize(48, 48);
|
||||
}
|
||||
|
||||
const sf = this.getFrame(key);
|
||||
const spr = node.getComponent(Sprite);
|
||||
const g = node.getComponent(Graphics);
|
||||
|
||||
if (sf) {
|
||||
if (g) g.destroy();
|
||||
const sprite = spr || node.addComponent(Sprite);
|
||||
sprite.spriteFrame = sf;
|
||||
sprite.sizeMode = Sprite.SizeMode.CUSTOM;
|
||||
const w = ui.contentSize.width * scale;
|
||||
const h = ui.contentSize.height * scale;
|
||||
ui.setContentSize(w, h);
|
||||
sprite.color = new Color(255, 255, 255, alpha);
|
||||
} else {
|
||||
if (spr) spr.destroy();
|
||||
const graphics = g || node.addComponent(Graphics);
|
||||
graphics.fillColor = key === 'coin'
|
||||
? new Color(255, 220, 0, alpha)
|
||||
: new Color(80, 160, 255, alpha);
|
||||
const w = ui.contentSize.width * 0.45 * scale;
|
||||
graphics.clear();
|
||||
graphics.rect(-w, -w, w * 2, w * 2);
|
||||
graphics.fill();
|
||||
}
|
||||
|
||||
const sx = flipX ? -Math.abs(scale) : Math.abs(scale);
|
||||
node.setScale(sx, Math.abs(scale), 1);
|
||||
}
|
||||
}
|
||||
9
assets/scripts/visual/VisualAssets.ts.meta
Normal file
9
assets/scripts/visual/VisualAssets.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "c813fc15-e977-4bb5-9d75-abc460539690",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
Reference in New Issue
Block a user