بنقرة واحدة
galacean-2d-game
使用 Galacean Engine 开发2D游戏的核心模式,包括真2D(Sprite)和伪2D(3D正交投影)实现
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
使用 Galacean Engine 开发2D游戏的核心模式,包括真2D(Sprite)和伪2D(3D正交投影)实现
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
此 Skill 约定了 Galacean Engine 中自定义着色器的编写方式,包含 Shader.create、BaseMaterial 参数绑定、动画驱动、透明混合与常见排错。
此 Skill 包含了使用 Galacean Engine 物理引擎的注意事项、距离检测替代方案以及常见问题解决方案
此 Skill 包含了在 Galacean Engine 中通过 Script 脚本处理物体交互(如点击)的核心模式,基于 onPointerClick 实现
此 Skill 包含了在 Galacean Engine 中创建物理碰撞体的核心模式,涵盖动态碰撞体 (DynamicCollider) 和 静态碰撞体 (StaticCollider) 的实现
此 Skill 包含了 Galacean Engine 相机的配置方法,包括透视/正交投影设置、俯视视角配置、窗口适配等
Galacean Engine 移动端游戏开发指南,包含适配、优化和最佳实践
| name | galacean-2d-game |
| description | 使用 Galacean Engine 开发2D游戏的核心模式,包括真2D(Sprite)和伪2D(3D正交投影)实现 |
Galacean 支持两种2D游戏开发方式:
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 真2D (Sprite) | 真正的2D渲染,性能好 | API相对简单,功能有限 | 简单2D游戏、UI |
| 伪2D (3D正交) | 完整3D功能,灵活 | 略重,需要理解3D概念 | 复杂2D效果、伪3D |
使用 SpriteRenderer 组件实现真正的2D渲染。
import { WebGLEngine, Camera, Vector3, BackgroundMode, Color } from "@galacean/engine";
async function init() {
const engine = await WebGLEngine.create({
canvas: document.getElementById('canvas'),
physics: new LitePhysics(), // 如果需要点击检测
});
const scene = engine.sceneManager.activeScene;
scene.background.mode = BackgroundMode.SolidColor;
scene.background.solidColor = new Color(0.1, 0.1, 0.15, 1);
// 2D相机:关键!相机必须在Z轴方向看向XY平面
const cameraEntity = scene.createRootEntity().createChild('camera');
const camera = cameraEntity.addComponent(Camera);
camera.orthographicSize = 10;
camera.nearClipPlane = 0.1;
camera.farClipPlane = 100;
camera.aspectRatio = window.innerWidth / window.innerHeight;
// ⚠️ 关键:相机必须在Z轴方向,不能放在Y轴!
// 这样XY平面上的物体才能被正确看到
cameraEntity.transform.setPosition(0, 0, 10);
cameraEntity.transform.lookAt(new Vector3(0, 0, 0));
engine.run();
}
import { SpriteRenderer, Sprite, Texture2D, Color } from "@galacean/engine";
function createColorSprite(entity: Entity, color: Color, size: number = 1): void {
const renderer = entity.addComponent(SpriteRenderer);
// 创建1x1像素纹理
const texture = new Texture2D(entity.engine, 1, 1);
const pixel = new Uint8Array([
color.r * 255,
color.g * 255,
color.b * 255,
255
]);
texture.setPixelBuffer(pixel);
// 创建Sprite
renderer.sprite = new Sprite(entity.engine, texture);
renderer.width = size;
renderer.height = size;
}
import { Script, SpriteRenderer, Sprite, Texture2D, Color, StaticCollider, BoxColliderShape } from "@galacean/engine";
export class Tile2D extends Script {
private spriteRenderer: SpriteRenderer;
private tileType: number;
init(type: number, size: number = 0.9): void {
this.tileType = type;
// 添加SpriteRenderer
this.spriteRenderer = this.entity.addComponent(SpriteRenderer);
this.setColor(getTileColor(type));
this.spriteRenderer.width = size;
this.spriteRenderer.height = size;
// 2D点击检测
const collider = this.entity.addComponent(StaticCollider);
const shape = new BoxColliderShape();
shape.size = new Vector3(size, size, 1);
collider.addShape(shape);
}
setColor(color: Color): void {
const texture = new Texture2D(this.engine, 1, 1);
const pixel = new Uint8Array([
color.r * 255, color.g * 255, color.b * 255, 255
]);
texture.setPixelBuffer(pixel);
this.spriteRenderer.sprite = new Sprite(this.engine, texture);
}
setHighlighted(highlight: boolean): void {
const color = getTileColor(this.tileType).clone();
if (highlight) {
color.r = Math.min(1, color.r + 0.2);
color.g = Math.min(1, color.g + 0.2);
color.b = Math.min(1, color.b + 0.2);
this.spriteRenderer.width = 1.0;
this.spriteRenderer.height = 1.0;
} else {
this.spriteRenderer.width = 0.9;
this.spriteRenderer.height = 0.9;
}
this.setColor(color);
}
}
使用扁平的3D几何体模拟2D效果。
const camera = cameraEntity.addComponent(Camera);
camera.orthographicSize = 10;
camera.aspectRatio = window.innerWidth / window.innerHeight;
cameraEntity.transform.setPosition(0, 0, 10);
cameraEntity.transform.lookAt(new Vector3(0, 0, 0));
import { MeshRenderer, PrimitiveMesh, UnlitMaterial } from "@galacean/engine";
function createFlatTile(entity: Entity, color: Color, size: number = 1): void {
const renderer = entity.addComponent(MeshRenderer);
// 厚度很小的立方体
renderer.mesh = PrimitiveMesh.createCuboid(entity.engine, size, size, 0.1);
const material = new UnlitMaterial(entity.engine);
material.baseColor = color;
renderer.setMaterial(material);
}
在3D空间中(相机从Z轴看):
Y+ (屏幕上方)
↑
| row=7 (Y=+3.5)
| row=6
| row=5
| row=4
| row=3
| row=2
| row=1
| row=0 (Y=-3.5)
|
└────────→ X+
关键:消消乐中,row=0 在底部(Y负方向),row=7 在顶部(Y正方向)。
const BOARD_OFFSET_Y = -(GRID_ROWS - 1) * TILE_SPACING / 2; // = -3.5
function getTilePosition(row: number, col: number): Vector3 {
const x = BOARD_OFFSET_X + col * TILE_SPACING;
// 直接映射:row=0 -> Y=-3.5 (底部),row=7 -> Y=+3.5 (顶部)
const y = BOARD_OFFSET_Y + row * TILE_SPACING;
return new Vector3(x, y, 0);
}
陷阱1:以为"下落"是row减小
// ❌ 错误理解
// "下落"应该是 row 从 7->0(减小)
// 实际上 Y 从 +3.5 -> -3.5,视觉上就是向下!
正确理解:
陷阱2:翻转row值
// ❌ 错误:翻转row导致方向相反
const flippedRow = (GRID_ROWS - 1) - row; // row=0变成7
const y = BOARD_OFFSET_Y + flippedRow * TILE_SPACING;
// 结果是row=0显示在屏幕上方!
| 逻辑坐标 | Y坐标 | 视觉效果 | 说明 |
|---|---|---|---|
| row=0 | -3.5 | 底部 | 重力方向 |
| row=7 | +3.5 | 顶部 | 远离重力 |
| 新方块 | +6.5 (row=10) | 从上方进入 | 屏幕外进入 |
// 新方块从屏幕上方(row=10)进入
const startRow = GRID_ROWS + 2; // = 10
const startPos = getTilePosition(startRow, col); // Y = 6.5
tile.position = startPos;
await tile.moveTo(getTilePosition(targetRow, col)); // 下落到目标位置
消消乐的核心:所有列同时下落
// ❌ 错误:按列顺序下落
for (let col = 0; col < GRID_COLS; col++) {
const promises = [];
// ... 收集列内动画
await Promise.all(promises); // 等待这一列完成
}
// ✅ 正确:所有列同时下落
const allPromises: Promise<void>[] = [];
for (let col = 0; col < GRID_COLS; col++) {
// ... 收集列内动画
allPromises.push(...columnPromises); // 添加到总列表
}
await Promise.all(allPromises); // 统一等待
class TileAnimator extends Script {
private isMoving = false;
private startPos = new Vector3();
private targetPos = new Vector3();
private moveTime = 0;
private duration = 0.3;
private onComplete?: () => void;
moveTo(target: Vector3): Promise<void> {
return new Promise((resolve) => {
this.startPos.copyFrom(this.entity.transform.position);
this.targetPos.copyFrom(target);
this.moveTime = 0;
this.isMoving = true;
this.onComplete = resolve;
});
}
onUpdate(deltaTime: number): void {
if (!this.isMoving) return;
this.moveTime += deltaTime;
const t = Math.min(this.moveTime / this.duration, 1);
const easeT = 1 - Math.pow(1 - t, 3);
const pos = this.entity.transform.position;
pos.x = this.startPos.x + (this.targetPos.x - this.startPos.x) * easeT;
pos.y = this.startPos.y + (this.targetPos.y - this.startPos.y) * easeT;
if (t >= 1) {
this.isMoving = false;
this.onComplete?.();
}
}
}
// 选中效果
select() {
this.spriteRenderer.width = 1.0;
this.spriteRenderer.height = 1.0;
}
// 消除效果
async destroyAnimate() {
const startSize = this.spriteRenderer.width;
for (let t = 0; t <= 1; t += 0.1) {
const scale = 1 - t;
this.spriteRenderer.width = startSize * scale;
this.spriteRenderer.height = startSize * scale;
await delay(16);
}
}
消消乐方块频繁创建销毁:
class TilePool {
private pool: Tile2D[] = [];
private active: Set<Tile2D> = new Set();
get(type: number): Tile2D {
const tile = this.pool.pop() || this.createNew();
tile.reset(type);
this.active.add(tile);
return tile;
}
recycle(tile: Tile2D): void {
this.active.delete(tile);
tile.hide();
this.pool.push(tile);
}
private createNew(): Tile2D {
const entity = this.engine.createEntity();
return entity.addComponent(Tile2D);
}
}
// 缓存纯色纹理
const colorTextureCache = new Map<string, Texture2D>();
function getColorTexture(engine: Engine, color: Color): Texture2D {
const key = `${color.r},${color.g},${color.b}`;
if (!colorTextureCache.has(key)) {
const texture = new Texture2D(engine, 1, 1);
const pixel = new Uint8Array([
color.r * 255, color.g * 255, color.b * 255, 255
]);
texture.setPixelBuffer(pixel);
colorTextureCache.set(key, texture);
}
return colorTextureCache.get(key)!;
}
| 场景 | 推荐方案 |
|---|---|
| 简单消消乐 | 真2D Sprite |
| 需要复杂特效 | 伪2D + 粒子系统 |
| 2D角色动画 | 真2D + Sprite动画 |
| 2.5D效果 | 伪2D + 3D旋转 |
问题: 存储颜色无法回收Entity,导致内存泄漏
解决: 存储Entity引用以支持对象池回收
// ❌ 无法复用Entity
private board: (Color | null)[][] = [];
// ✅ 支持对象池回收
private board: (Entity | null)[][] = [];
lockPiece() { this.board[y][x] = entity; } // 存储引用
clearLine() { this.pool.return(this.board[y][x]); } // 回收