Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
674 changes: 346 additions & 328 deletions package-lock.json

Large diffs are not rendered by default.

20 changes: 10 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next2d/player",
"version": "3.11.0",
"version": "3.11.1",
"description": "Experience the fast and beautiful anti-aliased rendering of WebGL/WebGPU. You can create rich, interactive graphics, cross-platform applications and games without worrying about browser or device compatibility.",
"author": "Toshiyuki Ienaga<[email protected]> (https://github.com/ienaga/)",
"license": "MIT",
Expand Down Expand Up @@ -51,25 +51,25 @@
"url": "https://github.com/sponsors/Next2D"
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.6",
"@eslint/eslintrc": "^3.3.7",
"@eslint/js": "^10.0.1",
"@playwright/test": "^1.62.1",
"@rollup/plugin-commonjs": "^29.0.3",
"@rollup/plugin-node-resolve": "^16.0.3",
"@rollup/plugin-terser": "^1.0.0",
"@rollup/plugin-typescript": "^12.3.0",
"@typescript-eslint/eslint-plugin": "^8.67.0",
"@typescript-eslint/parser": "^8.67.0",
"@webgpu/types": "^0.1.71",
"eslint": "^10.8.1",
"@typescript-eslint/eslint-plugin": "^8.69.0",
"@typescript-eslint/parser": "^8.69.0",
"@webgpu/types": "^0.1.72",
"eslint": "^10.9.1",
"eslint-plugin-unused-imports": "^4.4.1",
"globals": "^17.11.0",
"globals": "^17.12.0",
"jsdom": "^30.0.1",
"rollup": "^4.62.4",
"rollup": "^4.63.1",
"tslib": "^2.8.1",
"typescript": "^6.0.3",
"vite": "^8.2.1",
"vitest": "^4.1.10",
"vite": "^8.2.2",
"vitest": "^4.1.11",
"vitest-webgl-canvas-mock": "^1.1.0"
},
"peerDependencies": {
Expand Down
36 changes: 36 additions & 0 deletions specs/cn/display-object.md
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,42 @@ sprite.cacheAsBitmap = null;
- 当 `stage.rendererScale` 更改时,缓存会自动失效
- 同时设置 `filter` 和 `cacheAsBitmap` 时,`cacheAsBitmap` 优先

## 类判定(namespace)

判定类的种类时不要使用 `constructor.name`。生产构建中,类名会因 minify 而改变。

请改用 `namespace` 属性(实例用・static 用)。

```typescript
const { Stage, Sprite } = next2d.display;

// ❌ NG: 类名会因 minify 改变,构建后判定会失效
if (displayObject.constructor.name === "Stage") { /* ... */ }

// ✅ OK: 用实例的 namespace 判定
if (displayObject.namespace === "next2d.display.Stage") { /* ... */ }

// ✅ OK: 与 static 的 namespace 比较还能防止拼写错误
if (displayObject.namespace === Stage.namespace) { /* ... */ }

// ✅ OK: 也可使用 isStage 标志(Stage 独有的 readonly 属性)
if (displayObject.isStage) { /* ... */ }
```

**拥有 namespace 的主要类:**

| 类 | namespace 的值 |
|----|----------------|
| `Stage` | `"next2d.display.Stage"` |
| `Sprite` | `"next2d.display.Sprite"` |
| `MovieClip` | `"next2d.display.MovieClip"` |
| `Shape` | `"next2d.display.Shape"` |
| `Loader` | `"next2d.display.Loader"` |
| `TextField` | `"next2d.display.TextField"` |
| `Video` | `"next2d.media.Video"` |

**补充:** 包含继承在内的功能判定请使用 `isStage` / `isSprite` / `isShape` / `isText` / `isVideo` / `isContainerEnabled` / `isTimelineEnabled` 等各标志。`namespace` 用于完全一致的类判定。

## 相关

- [MovieClip](/cn/reference/player/movie-clip)
Expand Down
58 changes: 58 additions & 0 deletions specs/cn/shape.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,21 @@ classDiagram
| 性能 | 轻量级 | 稍重 |
| 使用场景 | 静态背景、装饰 | 按钮、容器 |

## 图像加载(推荐)

Shape 中使用图像(位图)时的推荐 API:

| 用途 | 推荐 API | 备注 |
|------|----------|------|
| 加载单个图像 | `shape.load(url)` | 从指定 URL 异步加载图像并生成 Graphics |
| 重复绘制图像(平铺) | `graphics.beginBitmapFill(bitmapData, matrix?, repeat?, smooth?)` | `repeat: true` 时以平铺方式重复绘制 |
| 用图像填充 | `graphics.beginBitmapFill(bitmapData, ...)` | 用位图填充矩形、圆形等图形 |

- **Shape 加载 Image 时推荐使用 `load()`**
- **重复绘制 Image 或用作填充时使用 `beginBitmapFill`**

使用示例请参考「[位图填充](#位图填充)」。

## 使用示例

### 基本绘制
Expand Down Expand Up @@ -228,13 +243,56 @@ stage.addChild(frontShape);
2. **最小化绘制**:如果内容不经常更改,只绘制一次
3. **使用 clear()**:动态重绘时始终调用 clear()
4. **缓存复杂形状**:使用 cacheAsBitmap 属性缓存绘制
5. **图像加载**:单个图像使用 `load()`,重复绘制或填充使用 `beginBitmapFill`

```javascript
// 将复杂形状缓存为位图
const { Matrix } = next2d.geom;
shape.cacheAsBitmap = new Matrix(1, 0, 0, 1, 0, 0);
```

### graphics 的路径缓存

Shape 的 `graphics` 会**根据路径信息生成缓存键**。因此,即使 `new Shape()`,拥有相同 graphics 信息(路径信息)的 Shape 也会从缓存中绘制。

```typescript
// 相同的路径信息 → 缓存被复用(无 GPU 负载)
const shape1 = new Shape();
shape1.graphics.beginFill(0xFF0000).drawCircle(0, 0, 50).endFill();

const shape2 = new Shape();
shape2.graphics.beginFill(0xFF0000).drawCircle(0, 0, 50).endFill(); // 缓存命中
```

**缓存有效的属性更改:**

颜色、透明度、x/y 坐标、旋转(`alpha`、`x`、`y`、`rotation`)可以在复用缓存的同时更改,因此渲染负载非常小。

```typescript
// 这些可以在保持缓存的同时更改(低负载)
shape.alpha = 0.5;
shape.x = 100;
shape.y = 200;
shape.rotation = 45;
```

**使用 scale 时的缓存策略:**

使用 `scaleX` / `scaleY` 时,**按最终显示的最大尺寸设置 `cacheAsBitmap`**,并通过 scale 缩小显示该缓存,从而降低渲染负载。

```typescript
const { Shape } = next2d.display;
const { Matrix } = next2d.geom;

const shape = new Shape();
shape.graphics.beginFill(0x3498db).drawRect(0, 0, 100, 100).endFill();

// 按最大尺寸(2倍)缓存并用 scale 调整
shape.cacheAsBitmap = new Matrix(2, 0, 0, 2, 0, 0); // 以 2 倍质量缓存
shape.scaleX = 0.5; // 缩小缓存显示(无渲染负载)
shape.scaleY = 0.5;
```

## Graphics 类

Graphics 类提供用于渲染矢量图形的绘图 API。通过 Shape.graphics 属性访问。
Expand Down
35 changes: 35 additions & 0 deletions specs/cn/text-field.md
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,41 @@ textField.replaceText("Next2D", 6, 11);
stage.addChild(textField);
```

### RPG 游戏风格对话动画(stopIndex)

使用 `stopIndex` 可以实现从开头按顺序显示文本的打字机效果。
适用于 RPG 游戏对话框之类的演出。
`stopIndex` 的默认值为 `-1`(显示全部文字),设置为 `0` 时文字不可见。

```typescript
const { TextField } = next2d.text;
const { Tween, Job } = next2d.ui;

const textField = new TextField();
textField.width = 300;
textField.height = 80;
textField.multiline = true;
textField.wordWrap = true;
textField.text = "勇者啊,请去打倒魔王吧!世界的命运就托付给你了。";

stage.addChild(textField);

// 将 stopIndex 从 0 → text.length 用 5 秒进行动画(含 0.5 秒延迟)
const job = Tween.add(
textField,
{ stopIndex: 0 },
{ stopIndex: textField.text.length },
0.5,
5
);

job.addEventListener(Job.COMPLETE, () => {
console.log("台词显示完成");
});

job.start();
```

## 事件

| 事件 | 说明 |
Expand Down
36 changes: 36 additions & 0 deletions specs/en/display-object.md
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,42 @@ sprite.cacheAsBitmap = null;
- Cache is automatically invalidated when `stage.rendererScale` changes
- When both `filter` and `cacheAsBitmap` are set, `cacheAsBitmap` takes priority

## Class Identification (namespace)

Do not use `constructor.name` to identify the class. In production builds, class names change due to minification.

Instead, use the `namespace` property (instance and static).

```typescript
const { Stage, Sprite } = next2d.display;

// ❌ NG: class names change with minification, so identification breaks after the build
if (displayObject.constructor.name === "Stage") { /* ... */ }

// ✅ OK: identify by the instance's namespace
if (displayObject.namespace === "next2d.display.Stage") { /* ... */ }

// ✅ OK: comparing with the static namespace also prevents typos
if (displayObject.namespace === Stage.namespace) { /* ... */ }

// ✅ OK: the isStage flag can also be used (a readonly property that only Stage has)
if (displayObject.isStage) { /* ... */ }
```

**Main classes that have namespace:**

| Class | namespace value |
|-------|-----------------|
| `Stage` | `"next2d.display.Stage"` |
| `Sprite` | `"next2d.display.Sprite"` |
| `MovieClip` | `"next2d.display.MovieClip"` |
| `Shape` | `"next2d.display.Shape"` |
| `Loader` | `"next2d.display.Loader"` |
| `TextField` | `"next2d.display.TextField"` |
| `Video` | `"next2d.media.Video"` |

**Note:** For capability checks that include inheritance, use the `isStage` / `isSprite` / `isShape` / `isText` / `isVideo` / `isContainerEnabled` / `isTimelineEnabled` flags. Use `namespace` for exact class matching.

## Related

- [MovieClip](/en/reference/player/movie-clip)
Expand Down
58 changes: 58 additions & 0 deletions specs/en/shape.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,21 @@ classDiagram
| Performance | Lightweight | Slightly heavier |
| Use case | Static backgrounds, decorations | Buttons, containers |

## Loading Images (Recommended)

Recommended APIs when using images (bitmaps) with Shape:

| Use case | Recommended API | Notes |
|----------|-----------------|-------|
| Load a single image | `shape.load(url)` | Asynchronously loads an image from the specified URL and generates Graphics |
| Draw an image repeatedly (tiling) | `graphics.beginBitmapFill(bitmapData, matrix?, repeat?, smooth?)` | Pass `repeat: true` to draw repeatedly as a tile |
| Fill with an image | `graphics.beginBitmapFill(bitmapData, ...)` | Fill shapes such as rectangles and circles with a bitmap |

- **`load()` is recommended when loading an Image with Shape**
- **`beginBitmapFill` should be used when drawing an Image repeatedly or using it as a fill**

See [Bitmap Fill](#bitmap-fill) for a usage example.

## Usage Examples

### Basic Drawing
Expand Down Expand Up @@ -228,13 +243,56 @@ stage.addChild(frontShape);
2. **Minimize drawing**: Only draw once if content doesn't change frequently
3. **Use clear()**: Always call clear() when dynamically redrawing
4. **Cache complex shapes**: Cache drawing with cacheAsBitmap property
5. **Loading Images**: Use `load()` for a single image, and `beginBitmapFill` for repeated drawing or fills

```javascript
// Cache complex shapes as bitmap
const { Matrix } = next2d.geom;
shape.cacheAsBitmap = new Matrix(1, 0, 0, 1, 0, 0);
```

### Path Caching in graphics

Shape's `graphics` **generates a cache key from path information**. As a result, even if you create a new `Shape()`, a Shape that has the same graphics information (path information) is drawn from the cache.

```typescript
// Same path information → cache is reused (no GPU load)
const shape1 = new Shape();
shape1.graphics.beginFill(0xFF0000).drawCircle(0, 0, 50).endFill();

const shape2 = new Shape();
shape2.graphics.beginFill(0xFF0000).drawCircle(0, 0, 50).endFill(); // cache hit
```

**Property changes that keep the cache valid:**

Color, opacity, x/y position, and rotation (`alpha`, `x`, `y`, `rotation`) can be changed while reusing the cache, so the rendering load is very small.

```typescript
// These can be changed while keeping the cache (low load)
shape.alpha = 0.5;
shape.x = 100;
shape.y = 200;
shape.rotation = 45;
```

**Cache strategy when using scale:**

When using `scaleX` / `scaleY`, **set `cacheAsBitmap` at the maximum size the object will be displayed at**, and display that cache scaled down. This keeps the rendering load low.

```typescript
const { Shape } = next2d.display;
const { Matrix } = next2d.geom;

const shape = new Shape();
shape.graphics.beginFill(0x3498db).drawRect(0, 0, 100, 100).endFill();

// Cache at the maximum size (2x) and adjust with scale
shape.cacheAsBitmap = new Matrix(2, 0, 0, 2, 0, 0); // cache at 2x quality
shape.scaleX = 0.5; // display the cache scaled down (no rendering load)
shape.scaleY = 0.5;
```

## Graphics Class

The Graphics class provides a drawing API for rendering vector graphics. Access it through the Shape.graphics property.
Expand Down
35 changes: 35 additions & 0 deletions specs/en/text-field.md
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,41 @@ textField.replaceText("Next2D", 6, 11);
stage.addChild(textField);
```

### RPG-Style Dialogue Animation (stopIndex)

Using `stopIndex` lets you implement a typewriter effect that displays text from the beginning in order.
It is suitable for effects such as the dialogue window in an RPG game.
The default value of `stopIndex` is `-1` (display all characters), and setting it to `0` hides the characters.

```typescript
const { TextField } = next2d.text;
const { Tween, Job } = next2d.ui;

const textField = new TextField();
textField.width = 300;
textField.height = 80;
textField.multiline = true;
textField.wordWrap = true;
textField.text = "Hero, please defeat the Demon King! The fate of the world rests with you.";

stage.addChild(textField);

// Animate stopIndex from 0 → text.length over 5 seconds (with a 0.5 second delay)
const job = Tween.add(
textField,
{ stopIndex: 0 },
{ stopIndex: textField.text.length },
0.5,
5
);

job.addEventListener(Job.COMPLETE, () => {
console.log("Dialogue display complete");
});

job.start();
```

## Events

| Event | Description |
Expand Down
Loading
Loading