Skip to content
Draft
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions examples/src/2d-01-sprite-mask.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/**
* @title 2D 01 - SpriteMask 遮罩
* @category 2D 教程
*/
import {
Camera,
Color,
Entity,
Logger,
Script,
Sprite,
SpriteMask,
SpriteMaskInteraction,
SpriteMaskLayer,
SpriteRenderer,
Texture2D,
Vector3,
WebGLEngine
} from "@galacean/engine";

function createCircleTexture(engine: WebGLEngine, size: number): Texture2D {
const canvas = document.createElement("canvas");
canvas.width = canvas.height = size;
const ctx = canvas.getContext("2d")!;
const center = size / 2;
ctx.beginPath();
ctx.arc(center, center, center - 2, 0, Math.PI * 2);
ctx.fillStyle = "#ffffff";
ctx.fill();
const texture = new Texture2D(engine, size, size);
texture.setImageSource(canvas);
texture.generateMipmaps();
return texture;
}

function createColorTexture(engine: WebGLEngine, color: string, size: number = 128): Texture2D {
const canvas = document.createElement("canvas");
canvas.width = canvas.height = size;
const ctx = canvas.getContext("2d")!;
ctx.fillStyle = color;
ctx.fillRect(0, 0, size, size);
const texture = new Texture2D(engine, size, size);
texture.setImageSource(canvas);
texture.generateMipmaps();
return texture;
}

class OscillateScript extends Script {
amplitude = 2;
speed = 1;
private _time = 0;
private _startX = 0;

onStart() {
this._startX = this.entity.transform.position.x;
}

onUpdate(dt: number) {
this._time += dt;
const x = this._startX + Math.sin(this._time * this.speed) * this.amplitude;
const pos = this.entity.transform.position;
this.entity.transform.setPosition(x, pos.y, pos.z);
}
}

Logger.enable();
WebGLEngine.create({ canvas: "canvas" }).then((engine) => {
engine.canvas.resizeByClientSize();
const scene = engine.sceneManager.activeScene;
const rootEntity = scene.createRootEntity();

const cameraEntity = rootEntity.createChild("camera");
cameraEntity.transform.setPosition(0, 0, 10);
cameraEntity.addComponent(Camera);

const circleTexture = createCircleTexture(engine, 256);
const redTexture = createColorTexture(engine, "#e74c3c");
const blueTexture = createColorTexture(engine, "#3498db");
const greenTexture = createColorTexture(engine, "#2ecc71");

// --- SpriteMask (circle shape, oscillating) ---
const maskEntity = rootEntity.createChild("SpriteMask");
maskEntity.transform.setPosition(0, 0, 0);
const mask = maskEntity.addComponent(SpriteMask);
mask.sprite = new Sprite(engine, circleTexture);
mask.alphaCutoff = 0.5;
mask.influenceLayers = SpriteMaskLayer.Layer0;
mask.width = 3;
mask.height = 3;
const oscillate = maskEntity.addComponent(OscillateScript);
oscillate.amplitude = 1.5;
oscillate.speed = 2;

// --- Sprite: VisibleInsideMask ---
const insideEntity = rootEntity.createChild("InsideMask");
insideEntity.transform.setPosition(-2, 0, 0);
const insideRenderer = insideEntity.addComponent(SpriteRenderer);
insideRenderer.sprite = new Sprite(engine, redTexture);
insideRenderer.width = 3;
insideRenderer.height = 3;
insideRenderer.maskInteraction = SpriteMaskInteraction.VisibleInsideMask;
insideRenderer.maskLayer = SpriteMaskLayer.Layer0;

// --- Sprite: VisibleOutsideMask ---
const outsideEntity = rootEntity.createChild("OutsideMask");
outsideEntity.transform.setPosition(2, 0, 0);
const outsideRenderer = outsideEntity.addComponent(SpriteRenderer);
outsideRenderer.sprite = new Sprite(engine, blueTexture);
outsideRenderer.width = 3;
outsideRenderer.height = 3;
outsideRenderer.maskInteraction = SpriteMaskInteraction.VisibleOutsideMask;
outsideRenderer.maskLayer = SpriteMaskLayer.Layer0;

// --- Sprite: No mask interaction (always visible) ---
const normalEntity = rootEntity.createChild("NoMask");
normalEntity.transform.setPosition(0, -3, 0);
const normalRenderer = normalEntity.addComponent(SpriteRenderer);
normalRenderer.sprite = new Sprite(engine, greenTexture);
normalRenderer.width = 2;
normalRenderer.height = 2;
normalRenderer.color = new Color(1, 1, 1, 0.8);

engine.run();

console.log("2D 01 - SpriteMask 遮罩");
console.log("- 圆形 SpriteMask 左右摆动");
console.log("- 红色: VisibleInsideMask (只在 mask 内可见)");
console.log("- 蓝色: VisibleOutsideMask (只在 mask 外可见)");
console.log("- 绿色: 无遮罩交互 (始终可见)");
console.log("- 2D 遮罩使用 maskInteraction + maskLayer,与 UI 层级式遮罩不同");
});
96 changes: 96 additions & 0 deletions examples/src/ui-01-basic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* @title UI 01 - 基础 UI 组件
* @category UI 教程
*/
import {
Camera,
Color,
Entity,
Font,
Logger,
Sprite,
Texture2D,
Vector2,
Vector3,
WebGLEngine
} from "@galacean/engine";
import { CanvasRenderMode, Image, Text, UICanvas, UITransform } from "@galacean/engine-ui";

function createColorTexture(engine: WebGLEngine, r: number, g: number, b: number, a: number = 255): Texture2D {
const texture = new Texture2D(engine, 4, 4);
const data = new Uint8Array(4 * 4 * 4);
for (let i = 0; i < 64; i += 4) {
data[i] = r;
data[i + 1] = g;
data[i + 2] = b;
data[i + 3] = a;
}
texture.setPixelBuffer(data);
return texture;
}

Logger.enable();
WebGLEngine.create({ canvas: "canvas" }).then((engine) => {
engine.canvas.resizeByClientSize();
const scene = engine.sceneManager.activeScene;
const rootEntity = scene.createRootEntity();

// Camera
const cameraEntity = rootEntity.createChild("camera");
cameraEntity.transform.setPosition(0, 0, 10);
const camera = cameraEntity.addComponent(Camera);

// UI Canvas (ScreenSpace Overlay)
const canvasEntity = rootEntity.createChild("Canvas");
const canvas = canvasEntity.addComponent(UICanvas);
canvas.renderMode = CanvasRenderMode.ScreenSpaceOverlay;
canvas.referenceResolution = new Vector2(800, 600);
canvas.renderCamera = camera;

// --- Background Panel ---
const panelEntity = canvasEntity.createChild("Panel");
const panelTransform = <UITransform>panelEntity.transform;
panelTransform.size = new Vector2(400, 300);
const panelImage = panelEntity.addComponent(Image);
panelImage.sprite = new Sprite(engine, createColorTexture(engine, 40, 40, 60));
panelImage.color = new Color(1, 1, 1, 0.9);

// --- Title Text ---
const titleEntity = panelEntity.createChild("Title");
const titleTransform = <UITransform>titleEntity.transform;
titleTransform.size = new Vector2(300, 50);
titleEntity.transform.setPosition(0, 100, 0);
const title = titleEntity.addComponent(Text);
title.text = "Galacean UI";
title.fontSize = 32;
title.color = new Color(1, 1, 1, 1);

// --- Colored Images ---
const colors = [
{ r: 231, g: 76, b: 60 },
{ r: 46, g: 204, b: 113 },
{ r: 52, g: 152, b: 219 }
];
const labels = ["Red", "Green", "Blue"];
for (let i = 0; i < 3; i++) {
const itemEntity = panelEntity.createChild(`Item${i}`);
const itemTransform = <UITransform>itemEntity.transform;
itemTransform.size = new Vector2(100, 100);
itemEntity.transform.setPosition(-120 + i * 120, -20, 0);

const img = itemEntity.addComponent(Image);
const { r, g, b } = colors[i];
img.sprite = new Sprite(engine, createColorTexture(engine, r, g, b));

const labelEntity = itemEntity.createChild("Label");
const labelTransform = <UITransform>labelEntity.transform;
labelTransform.size = new Vector2(100, 30);
labelEntity.transform.setPosition(0, -70, 0);
const label = labelEntity.addComponent(Text);
label.text = labels[i];
label.fontSize = 18;
label.color = new Color(0.8, 0.8, 0.8, 1);
}

engine.run();
});
131 changes: 131 additions & 0 deletions examples/src/ui-02-mask.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/**
* @title UI 02 - Mask 遮罩
* @category UI 教程
*/
import {
Camera,
Color,
Entity,
Logger,
Script,
Sprite,
Texture2D,
Vector2,
Vector3,
WebGLEngine
} from "@galacean/engine";
import { CanvasRenderMode, Image, Mask, UICanvas, UITransform } from "@galacean/engine-ui";

function createCircleTexture(engine: WebGLEngine, size: number): Texture2D {
const canvas = document.createElement("canvas");
canvas.width = canvas.height = size;
const ctx = canvas.getContext("2d")!;
const center = size / 2;
const radius = center - 2;
ctx.beginPath();
ctx.arc(center, center, radius, 0, Math.PI * 2);
ctx.fillStyle = "#ffffff";
ctx.fill();
const texture = new Texture2D(engine, size, size);
texture.setImageSource(canvas);
texture.generateMipmaps();
return texture;
}

function createGradientTexture(engine: WebGLEngine, size: number): Texture2D {
const canvas = document.createElement("canvas");
canvas.width = canvas.height = size;
const ctx = canvas.getContext("2d")!;
const gradient = ctx.createLinearGradient(0, 0, size, size);
gradient.addColorStop(0, "#e74c3c");
gradient.addColorStop(0.5, "#f39c12");
gradient.addColorStop(1, "#2ecc71");
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, size, size);
const texture = new Texture2D(engine, size, size);
texture.setImageSource(canvas);
texture.generateMipmaps();
return texture;
}

function createCheckerTexture(engine: WebGLEngine, size: number): Texture2D {
const canvas = document.createElement("canvas");
canvas.width = canvas.height = size;
const ctx = canvas.getContext("2d")!;
const tileSize = size / 8;
for (let x = 0; x < 8; x++) {
for (let y = 0; y < 8; y++) {
ctx.fillStyle = (x + y) % 2 === 0 ? "#3498db" : "#2c3e50";
ctx.fillRect(x * tileSize, y * tileSize, tileSize, tileSize);
}
}
const texture = new Texture2D(engine, size, size);
texture.setImageSource(canvas);
texture.generateMipmaps();
return texture;
}

class RotateScript extends Script {
speed = 30;
onUpdate(dt: number) {
this.entity.transform.rotate(new Vector3(0, 0, this.speed * dt));
}
}

Logger.enable();
WebGLEngine.create({ canvas: "canvas" }).then((engine) => {
engine.canvas.resizeByClientSize();
const scene = engine.sceneManager.activeScene;
const rootEntity = scene.createRootEntity();

const cameraEntity = rootEntity.createChild("camera");
cameraEntity.transform.setPosition(0, 0, 10);
const camera = cameraEntity.addComponent(Camera);

const canvasEntity = rootEntity.createChild("Canvas");
const canvas = canvasEntity.addComponent(UICanvas);
canvas.renderMode = CanvasRenderMode.ScreenSpaceOverlay;
canvas.referenceResolution = new Vector2(800, 600);
canvas.renderCamera = camera;

const circleTexture = createCircleTexture(engine, 256);
const gradientTexture = createGradientTexture(engine, 256);
const checkerTexture = createCheckerTexture(engine, 256);

// --- Example 1: Circle mask clipping a gradient ---
const mask1Entity = canvasEntity.createChild("CircleMask");
const mask1Transform = <UITransform>mask1Entity.transform;
mask1Transform.size = new Vector2(200, 200);
mask1Entity.transform.setPosition(-200, 50, 0);
const mask1 = mask1Entity.addComponent(Mask);
mask1.sprite = new Sprite(engine, circleTexture);

const content1 = mask1Entity.createChild("Content");
const content1Transform = <UITransform>content1.transform;
content1Transform.size = new Vector2(250, 250);
const content1Image = content1.addComponent(Image);
content1Image.sprite = new Sprite(engine, gradientTexture);
content1.addComponent(RotateScript).speed = 20;

// --- Example 2: Circle mask clipping a checker pattern ---
const mask2Entity = canvasEntity.createChild("CheckerMask");
const mask2Transform = <UITransform>mask2Entity.transform;
mask2Transform.size = new Vector2(200, 200);
mask2Entity.transform.setPosition(200, 50, 0);
const mask2 = mask2Entity.addComponent(Mask);
mask2.sprite = new Sprite(engine, circleTexture);

const content2 = mask2Entity.createChild("Content");
const content2Transform = <UITransform>content2.transform;
content2Transform.size = new Vector2(300, 300);
const content2Image = content2.addComponent(Image);
content2Image.sprite = new Sprite(engine, checkerTexture);
content2.addComponent(RotateScript).speed = -15;

engine.run();

console.log("UI 02 - Mask 遮罩");
console.log("- 左: 圆形 Mask 裁剪渐变图片 (旋转)");
console.log("- 右: 圆形 Mask 裁剪棋盘格 (旋转)");
console.log("- 子节点自动被 Mask 裁剪,无需手动配置 maskInteraction");
});
Loading
Loading