diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 796cb0fc98..0e218ea198 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -105,19 +105,19 @@ jobs: browser: chrome - name: Upload Diff if: failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: cypress-diff path: e2e/diff/ - name: Upload Origin if: failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: cypress-origin path: e2e/fixtures/originImage - name: Upload Screenshots if: failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: cypress-screenshots path: e2e/downloads/ diff --git a/docs/en/UI/_meta.json b/docs/en/UI/_meta.json new file mode 100644 index 0000000000..963d63d40e --- /dev/null +++ b/docs/en/UI/_meta.json @@ -0,0 +1,11 @@ +{ + "overall": { + "title": "Overview" + }, + "quickStart": { + "title": "QuickStart" + }, + "system": { + "title": "Architecture" + } +} diff --git a/docs/en/UI/overall.md b/docs/en/UI/overall.md new file mode 100644 index 0000000000..1dc8f574de --- /dev/null +++ b/docs/en/UI/overall.md @@ -0,0 +1,26 @@ +--- +order: 0 +title: Overview +type: UI +label: UI +--- + +UI is a system used to build user interfaces. It provides a range of tools and components to help developers create interactive interface elements. Here are its key features: + +- **Visual Editing**: With the basic nodes and component creation capabilities in the [editor](https://galacean.antgroup.com/editor/projects), combined with the RectTool (shortcut key T), developing interactive interfaces becomes more intuitive and efficient. +- **Rendering and Interactive Components**: Supports rendering components like Image, Text, etc., as well as basic interactive components such as Button. +- **Transferable Opacity and Interactivity Attributes**: Through the UIGroup component, properties such as **opacity** and **interactivity** can be inherited or ignored. +- **Events**: In addition to supporting original Pointer events, UI components also support **event bubbling** for interactions triggered by the UI. + +In this section, you will: + +- Learn how to quickly develop a UI interface: + - Create a [Root Canvas](/en/docs/UI/quickStart/canvas) + - Familiarize yourself with [UITransform](/en/docs/UI/quickStart/transform) + - Create [Image](/en/docs/UI/quickStart/image) + - Create [Text](/en/docs/UI/quickStart/text) + - Create [Button](/en/docs/UI/quickStart/button) + - Create [UIGroup](/en/docs/UI/quickStart/group) +- Understand the [Overall Architecture and Module Management](/en/docs/UI/system) of UI +- Learn about [UI Rendering Order](/en/docs/UI/quickStart/order) +- Understand the [UI Event Mechanism](/en/docs/UI/quickStart/event) diff --git a/docs/en/UI/quickStart/button.md b/docs/en/UI/quickStart/button.md new file mode 100644 index 0000000000..20cb753101 --- /dev/null +++ b/docs/en/UI/quickStart/button.md @@ -0,0 +1,44 @@ +--- +order: 4 +title: Button +type: UI +label: UI +--- + +`Button` can be used to create interactive buttons within a UICanvas. + +## Editor Usage + +### Add Button Node + +Add a `Button` node in the **[Hierarchy Panel](/docs/interface/hierarchy/)**. + + + +> If the parent or ancestor node does not have a Canvas component, a root Canvas node will be automatically added. + +### Set Transition + +In the editor, you can easily set the button's transition effects for different states. + + + +## Properties + +| Property Name | Description | +| :-------------- | :-------------------------------- | +| `transitions` | All the transition effects of the button | +| `interactive` | Whether the button is interactive | + +## Methods + +| Method Name | Description | +| :------------------ | :------------------------------------ | +| `addTransition` | Add a transition effect to the button | +| `removeTransition` | Remove a transition effect from the button | +| `addClicked` | Add a click callback function | +| `removeClicked` | Remove a click callback function | + +## Script Development + + \ No newline at end of file diff --git a/docs/en/UI/quickStart/canvas.md b/docs/en/UI/quickStart/canvas.md new file mode 100644 index 0000000000..7bb4b12c1b --- /dev/null +++ b/docs/en/UI/quickStart/canvas.md @@ -0,0 +1,62 @@ +--- +order: 0 +title: Canvas +type: UI +label: UI +--- + +The root canvas is the foundation of the UI, but not all `UICanvas` nodes are root canvases. Here’s how to create a root canvas in your scene. + +## Editor Usage + +### Add UICanvas Node + +Add a Canvas node in the **[Hierarchy Panel](/docs/interface/hierarchy/)**. + + + +### Set Properties + +Select the node that has the `Canvas` component and you can set its properties in the **[Inspector Panel](/docs/interface/inspector)**. + + + +### Root Canvas + +If the parent or ancestor node of the newly added canvas node already contains an active `UICanvas` component, this canvas will not have rendering or interaction functionality. + + + +## Properties + +| Property Name | Description | +| :------------------------------ | :------------------------------------------------------------ | +| `renderMode` | The rendering mode of the canvas | +| `renderCamera` | The camera used for rendering when the canvas is in `ScreenSpaceCamera` mode | +| `distance` | The distance of the canvas relative to the camera when in `ScreenSpaceCamera` mode | +| `resolutionAdaptationMode` | The adaptation mode of the canvas, such as width adaptation or height adaptation | +| `referenceResolution` | The reference resolution for size adaptation of the canvas | +| `referenceResolutionPerUnit` | The ratio of canvas units to world space units | +| `sortOrder` | The rendering priority of the canvas | + +## Script Development + +```typescript +// Add camera +const cameraEntity = root.createChild("Camera"); +const camera = cameraEntity.addComponent(Camera); + +// Add UICanvas +const canvasEntity = root.createChild("canvas"); +const canvas = canvasEntity.addComponent(UICanvas); + +// Set renderMode to `ScreenSpaceOverlay` +canvas.renderMode = CanvasRenderMode.ScreenSpaceOverlay; +// Set renderMode to `ScreenSpaceCamera` +canvas.renderMode = CanvasRenderMode.ScreenSpaceCamera; +canvas.renderCamera = camera; +// Set Reference Resolution +canvas.referenceResolution.set(750, 1624); +// Set Adaptation Mode +canvas.resolutionAdaptationMode = ResolutionAdaptationMode.WidthAdaptation; +``` \ No newline at end of file diff --git a/docs/en/UI/quickStart/event.md b/docs/en/UI/quickStart/event.md new file mode 100644 index 0000000000..0d6db41457 --- /dev/null +++ b/docs/en/UI/quickStart/event.md @@ -0,0 +1,40 @@ +--- +order: 3 +title: Event +type: UI +label: UI +--- + +UI events follow the engine's event system, with the additional support for event bubbling in UI components. + +## Bubbling + +The current version only supports the following bubbling flow: + +| Interface | Bubbles | +| :----------------------------------------------------------- | :--------- | +| [onPointerEnter](/apis/core/#Script-onPointerEnter) | Does not bubble | +| [onPointerExit](/apis/core/#Script-onPointerExit) | Does not bubble | +| [onPointerDown](/apis/core/#Script-onPointerDown) | Bubbles | +| [onPointerUp](/apis/core/#Script-onPointerUp) | Bubbles | +| [onPointerClick](/apis/core/#Script-onPointerClick) | Bubbles | +| [onPointerBeginDrag](/apis/core/#Script-onPointerBeginDrag) | Bubbles | +| [onPointerDrag](/apis/core/#Script-onPointerDrag) | Bubbles | +| [onPointerEndDrag](/apis/core/#Script-onPointerEndDrag) | Bubbles | +| [onPointerDrop](/apis/core/#Script-onPointerDrop) | Bubbles | + +As shown in the diagram below, if node C triggers the `pointerup` event, the event will bubble along the path C --> B --> A --> RootCanvas. + +```mermaid +stateDiagram + RootCanvas --> A + RootCanvas --> F + A --> B + A --> E + B --> C + B --> D +``` + +## Script Development + + \ No newline at end of file diff --git a/docs/en/UI/quickStart/group.md b/docs/en/UI/quickStart/group.md new file mode 100644 index 0000000000..8ec0cae9b5 --- /dev/null +++ b/docs/en/UI/quickStart/group.md @@ -0,0 +1,28 @@ +--- +order: 5 +title: UIGroup +type: UI +label: UI +--- + +The `UIGroup` component allows you to inherit or ignore properties such as **opacity** and **interactivity**. + +## Editor Usage + +Select the node, then in the **[Inspector Panel](/docs/interface/inspector)**, click **Add Component** and choose **UIGroup**. You can control the opacity of multiple UI elements by modifying the settings. + + + +## Properties + +| Property Name | Description | +| :------------------ | :--------------------------------- | +| `alpha` | Opacity | +| `interactive` | Whether the element is interactive | +| `ignoreParentGroup` | Whether to ignore the settings of the parent group | + +> UIGroup resolves the issue where UI element properties cannot be passed from parent to child. + +## Script Development + + \ No newline at end of file diff --git a/docs/en/UI/quickStart/image.md b/docs/en/UI/quickStart/image.md new file mode 100644 index 0000000000..f4851aed68 --- /dev/null +++ b/docs/en/UI/quickStart/image.md @@ -0,0 +1,48 @@ +--- +order: 2 +title: Image +type: UI +label: UI +--- + +The `Image` component is used to display images within a `UICanvas`. + +## Editor Usage + +### Add Image Node + +Add an `Image` node in the **[Hierarchy Panel](/docs/interface/hierarchy/)**. + + + +> If the parent or ancestor node does not have a Canvas component, a root canvas node will be automatically added. + +### Set Sprite + +The content displayed by the `Image` depends on the selected [Sprite asset](). Select the node with the `Image` component, and in the **[Inspector Panel](/docs/interface/inspector)**, choose the corresponding sprite asset in the Sprite property to change the displayed content. + + + +### Modify Draw Mode + +The `Image` component currently provides three draw modes: Normal, Nine-Patch, and Tiled (the default is Normal). You can visually feel the rendering differences between modes by modifying the width and height in each mode. + + + +### Adjust Size + +For adjusting the size of UI elements, refer to [Quickly Adjust UI Element Size](/docs/UI/quickStart/transform). + +## Properties + +| Property Name | Description | +| :----------------- | :-------------------------------------------------- | +| `sprite` | The sprite to render | +| `color` | The color of the sprite | +| `drawMode` | The draw mode, supports Normal, Nine-Patch, and Tiled modes | +| `raycastEnabled` | Whether the image can be detected by raycasting | +| `raycastPadding` | Custom padding for raycasting, representing the distance from the edges of the collision area. This is a normalized value, where X, Y, Z, and W represent the distances from the left, bottom, right, and top edges respectively. | + +## Script Development + + \ No newline at end of file diff --git a/docs/en/UI/quickStart/order.md b/docs/en/UI/quickStart/order.md new file mode 100644 index 0000000000..803b97bc10 --- /dev/null +++ b/docs/en/UI/quickStart/order.md @@ -0,0 +1,61 @@ +--- +order: 6 +title: Rendering Order +type: UI +label: UI +--- + +The rendering order of UI components follows two rules: + +- Different `UICanvas` instances follow a specific rendering order based on their `RendererMode` type. +- `UIRenderer` components under a `UICanvas` are rendered according to a **depth-first** order, from parent to child, and from left to right. + +## UICanvas + +Assume the current runtime: +- There is a scene `Scene` +- The scene `Scene` contains two cameras, `Camera1` and `Camera2` +- The scene `Scene` contains three canvases: + - `Canvas1` with `WorldSpace` render mode + - `Canvas2` with `ScreenSpace-Overlay` render mode + - `Canvas3` with `ScreenSpace-Camera` render mode, using `Camera1` as the render camera + +```mermaid +journey + title Scene Rendering Cycle + section Camera1.render + Canvas1.render: 5 + Canvas3.render: 5 + section Camera2.render + Canvas1.render: 5 + section Ending + Canvas2.render: 5 +``` + +It's important to note: +- Canvases with `ScreenSpace-Camera` render mode will only render with their corresponding camera, and they follow the general camera clipping rules, just like canvases with `ScreenSpace-Overlay` render mode. +- Canvases with `ScreenSpace-Overlay` render mode can still be rendered without a camera. +- Within the same camera, the rendering order of `UICanvas` follows these rules: canvases in the overlay mode have their rendering order determined only by `sortOrder`. + +```mermaid +flowchart TD + A[Sort rendering data] --> B{canvas.sortOrder} + B -->|Not equal| C[Return comparison result] + B -->|Equal| D{Canvas and camera distance} + D -->|Not equal| E[Return comparison result] + D -->|Equal| F[Return comparison result] +``` + +## UIRenderer Rendering Order + +```mermaid +stateDiagram + RootCanvas --> A + RootCanvas --> F + A --> B + A --> E + B --> C + B --> D +``` + +As shown in the diagram above, the rendering order under the root canvas follows A -> B -> C -> D -> E -> F. It is important to note that setting `UIRenderer.priority` does not change its rendering order. \ No newline at end of file diff --git a/docs/en/UI/quickStart/text.md b/docs/en/UI/quickStart/text.md new file mode 100644 index 0000000000..8a30a56a37 --- /dev/null +++ b/docs/en/UI/quickStart/text.md @@ -0,0 +1,60 @@ +--- +order: 3 +title: Text +type: UI +label: UI +--- + +The `Text` component is used to display text within a `UICanvas`. + +## Editor Usage + +### Add Text Node + +Add a `Text` node in the **[Hierarchy Panel](/docs/interface/hierarchy/)**. + + + +> If the parent or ancestor node does not have a Canvas component, a root canvas node will be automatically added. + +### Set Text Content + +Select the node with the `Text` component, and in the **[Inspector Panel](/docs/interface/inspector)**, modify the `text` property to change the content of the `Text` element. + + + +### Set Font + +Select the node with the `Text` component, and in the **[Inspector Panel](/docs/interface/inspector)**, modify the `font` property to change the font type of the `Text` element. + + + +### Modify Font Size + +The `Text` component can modify the rendering size by adjusting the `FontSize`. + + + +> Changing the `size` in `UITransform` will not affect the rendering size of `Text`. + +## Properties + +| Property Name | Description | +| :------------------ | :------------------------------------------------------------------------------------------------ | +| `Text` | The text to be displayed | +| `Color` | The color of the text | +| `FontSize` | The font size of the text | +| `Font` | Custom font for the text | +| `LineSpacing` | The line spacing between text lines | +| `FontStyle` | Font style settings: whether to apply bold or italic styles | +| `HorizontalAlignment` | Horizontal alignment of the text, with options: Left/Center/Right | +| `VerticalAlignment` | Vertical alignment of the text, with options: Top/Center/Bottom | +| `EnableWrapping` | Whether to enable word wrapping; when enabled, text will wrap based on the set width. If width is set to 0, the text will not render | +| `OverflowMode` | How to handle overflow when the text exceeds the set height: options are Overflow/Truncate. Overflow means the text will overflow and display; Truncate means only the content within the set height will be displayed, depending on the vertical alignment of the text. | +| `Mask Interaction` | Mask type for text, specifying whether the text needs masking, and whether the content inside or outside the mask should be shown | +| `Mask Layer` | The mask layer for the text, used for matching with SpriteMask. Default is Everything, meaning it can interact with any SpriteMask | +| `priority` | Rendering priority; the lower the value, the higher the priority, meaning it will be rendered first | + +## Script Development + + \ No newline at end of file diff --git a/docs/en/UI/quickStart/transform.md b/docs/en/UI/quickStart/transform.md new file mode 100644 index 0000000000..95c05f739c --- /dev/null +++ b/docs/en/UI/quickStart/transform.md @@ -0,0 +1,34 @@ +--- +order: 1 +title: UITransform +type: UI +label: UI +--- + +The `UITransform` component is specifically designed to represent the size and position of UI elements. It inherits from the [Transform](/apis/core/#Transform) component. + +## Editor Usage + +When a node with a UI component is added, the `UITransform` component will automatically be added (replacing the previous [Transform](/apis/core/#Transform) component). In the editor, you can select the node and use the `RectTool` (shortcut key `T`) to quickly adjust properties, or you can set precise properties in the **[Inspector Panel](/docs/interface/inspector)**. + + + +> When the main canvas's render mode is `Screen`, the editor will prohibit modifications to its `transform` to avoid screen adaptation issues. Therefore, in scripts, **developers should avoid modifying the `transform` properties of the main canvas in screen space.** + +## Properties + +| Property Name | Description | +| :------------ | :--------------------------------------------------------------------------- | +| `size` | The size of the UI element. `x` represents width, and `y` represents height. The default is `100` for both. | +| `pivot` | The anchor point of the UI element. It is a normalized 2D vector with the origin at the bottom-left corner, with the default value being the center (0.5, 0.5). | + +## Script Usage + +```typescript +// Add UICanvas +const canvasEntity = root.createChild("canvas"); +const canvas = canvasEntity.addComponent(UICanvas); +const imageEntity = canvasEntity.create("Image"); +(imageEntity.transform).size.set(200, 200); +(imageEntity.transform).pivot.set(0, 0); +``` \ No newline at end of file diff --git a/docs/en/UI/system.md b/docs/en/UI/system.md new file mode 100644 index 0000000000..f6fe6df877 --- /dev/null +++ b/docs/en/UI/system.md @@ -0,0 +1,108 @@ +--- +order: 2 +title: Architecture +type: UI +label: UI +--- + +## System Design + +```mermaid +--- +title: UI System +--- +classDiagram + Component <|-- Transform + Script <|-- Component + Transform <|-- UITransform + Component <|-- Renderer + Renderer <|-- UIRenderer + UIRenderer <|-- Image + UIRenderer <|-- Text + UICanvas <|-- Component + UIInteractive <|-- Script + Button <|-- UIInteractive + UIGroup <|-- Component + + class Script { + +void onUpdate() + } + + class Transform{ + +Vector3 position + +Vector3 rotation + +Vector3 scale + ... + } + + class UITransform{ + <> + +Vector2 size + +Vector2 pivot + } + + class Renderer{ + +BoundingBox bounds + +Material getMaterial() + +Material setMaterial() + } + + class UIRenderer{ + <> + +Color color + +boolean raycastEnabled + +vec4 raycastPadding + } + + class Image{ + <> + +Sprite sprite + +SpriteDrawMode drawMode + ... + } + + class Text{ + <> + +string text + +Font font + ... + } + + class UICanvas { + <> + +CanvasRenderMode renderMode + +Camera renderCamera + ... + } + + class UIInteractive { + <> + +boolean interactive + +Transition transitions + ... + } + + class Button { + <> + +void addClicked() + +void removeClicked() + ... + } + + class UIGroup { + <> + +boolean interactive + +number alpha + +boolean ignoreParentGroup + } +``` + +## Module Management + +| Package | Description | Related Documentation | +| :--------------------------------------------------------------------- | :------------- | ----------------------- | +| [@galacean/engine-ui](https://www.npmjs.com/package/@galacean/engine-xr) | Core architecture logic | [API](/apis/galacean) | + +> `@galacean/engine-ui` is a dependency that must be included to implement **UI**. + +> The [version dependency rules](/docs/basics/version/#version-dependency) must be followed, meaning the version of `@galacean/engine-ui` should match the version of `@galacean/engine`. \ No newline at end of file diff --git a/docs/en/_meta.json b/docs/en/_meta.json index c4284d4e74..6706832105 100644 --- a/docs/en/_meta.json +++ b/docs/en/_meta.json @@ -47,6 +47,12 @@ "collapse": true } }, + "UI": { + "title": "UI", + "theme": { + "collapse": true + } + }, "xr": { "title": "XR", "theme": { diff --git a/docs/en/basics/overview.md b/docs/en/basics/overview.md index 8e440e88db..b8c02d2db5 100644 --- a/docs/en/basics/overview.md +++ b/docs/en/basics/overview.md @@ -35,7 +35,7 @@ Includes the following sub-packages: | [@galacean/engine](https://www.npmjs.com/package/@galacean/engine) | Core architecture logic and core functionalities | [API](/apis/galacean) | | [@galacean/engine-physics-lite](https://www.npmjs.com/package/@galacean/engine-physics-lite) | Lightweight physics engine | [Doc](/en/docs/physics/overall) | | [@galacean/engine-physics-physx](https://www.npmjs.com/package/@galacean/engine-physics-physx) | Full-featured physics engine | [Doc](/en/docs/physics/overall) | -| [@galacean/engine-shader-lab](https://www.npmjs.com/package/@galacean/engine-shader-lab) | Galacean Shader compiler | [Doc](/en/docs/graphics/shader/lab) | +| [@galacean/engine-shaderlab](https://www.npmjs.com/package/@galacean/engine-shaderlab) | Galacean Shader compiler | [Doc](/en/docs/graphics/shader/lab) | | [@galacean/engine-xr](https://www.npmjs.com/package/@galacean/engine-xr) | XR logic package | [Doc](/en/docs/xr/overall) | | [@galacean/engine-xr-webxr](https://www.npmjs.com/package/@galacean/engine-xr-webxr) | WebXR backend | [Doc](/en/docs/xr/overall) | diff --git a/docs/en/graphics/2D/lottie.md b/docs/en/graphics/2D/lottie.md index ea52ea830b..20400c634f 100644 --- a/docs/en/graphics/2D/lottie.md +++ b/docs/en/graphics/2D/lottie.md @@ -145,7 +145,8 @@ Thanks to the unified architecture of the Galacean Engine 2D/3D engine, 3D trans | Engine Version | Lottie Version | | :--- | :--- | | 1.2.x | 1.1.0-beta.0 | -| 1.3.x | 1.1.0-beta.0 | +| 1.3.x | engine-1.3 | +| 1.4.x | engine-1.4 | ## Performance Recommendations diff --git a/docs/zh/UI/_meta.json b/docs/zh/UI/_meta.json new file mode 100644 index 0000000000..522ed42a57 --- /dev/null +++ b/docs/zh/UI/_meta.json @@ -0,0 +1,11 @@ +{ + "overall": { + "title": "总览" + }, + "quickStart": { + "title": "快速上手" + }, + "system": { + "title": "架构" + } +} diff --git a/docs/zh/UI/overall.md b/docs/zh/UI/overall.md new file mode 100644 index 0000000000..763bf8ab82 --- /dev/null +++ b/docs/zh/UI/overall.md @@ -0,0 +1,26 @@ +--- +order: 0 +title: 总览 +type: UI +label: UI +--- + +UI 是用于构建用户界面的系统,它提供了一系列工具和组件方便开发者搭建交互界面元素。以下是它的主要能力: + +- 可视化编辑:通过[编辑器](https://galacean.antgroup.com/editor/projects)中的基础节点与组件创建能力,搭配 `RectTool` (快捷键 `T` ),让交互界面的开发更加直观高效。 +- 渲染与交互组件:支持 `Image`,`Text` 等渲染组件,以及基础的交互组件,如 `Button` +- 可传递的透明度与交互属性:通过 `UIGroup` 组件,可以继承或忽略**透明度**,**是否可交互**等属性 +- 事件:在兼容原有的 Pointer 事件的基础上,UI 组件触发的交互事件还支持**冒泡传递**。 + +在本章节,您可以: + +- 学会如何快速开发 UI 界面: + - 创建 [根画布](/docs/UI/quickStart/canvas) + - 熟悉 [UITransform](/docs/UI/quickStart/transform) + - 创建 [Image](/docs/UI/quickStart/image) + - 创建 [Text](/docs/UI/quickStart/text) + - 创建 [Button](/docs/UI/quickStart/button) + - 创建 [UIGroup](/docs/UI/quickStart/group) +- 了解 [UI 的整体架构与模块管理](/docs/UI/system) +- 了解 [UI 的渲染顺序](/docs/UI/quickStart/order) +- 了解 [UI 的事件机制](/docs/UI/quickStart/event) diff --git a/docs/zh/UI/quickStart/button.md b/docs/zh/UI/quickStart/button.md new file mode 100644 index 0000000000..50444dbe5c --- /dev/null +++ b/docs/zh/UI/quickStart/button.md @@ -0,0 +1,44 @@ +--- +order: 4 +title: 创建按钮 +type: UI +label: UI +--- + +`Button` 可以在 UICanvas 中构建交互按钮。 + +## 编辑器使用 + +### 添加 Button 节点 + +在 **[层级面板](/docs/interface/hierarchy/)** 添加 `Button` 节点 + + + +> 若父亲或祖先节点没有画布组件,会自动添加上根画布节点。 + +### 设置 Transition + +在编辑器中可以方便地设置按钮在不同状态下的过渡表现 + + + +## 属性 + +| 属性名 | 描述 | +| :------------ | :----------------- | +| `transitions` | 按钮的所有过渡表现 | +| `interactive` | 按钮是否可交互 | + +## 方法 + +| 方法名 | 描述 | +| :----------------- | :------------------- | +| `addTransition` | 添加一个按钮过渡表现 | +| `removeTransition` | 移除某个按钮过渡表现 | +| `addClicked` | 添加一个点击回调函数 | +| `removeClicked` | 移除某个点击回调函数 | + +## 脚本开发 + + diff --git a/docs/zh/UI/quickStart/canvas.md b/docs/zh/UI/quickStart/canvas.md new file mode 100644 index 0000000000..0172440f7a --- /dev/null +++ b/docs/zh/UI/quickStart/canvas.md @@ -0,0 +1,62 @@ +--- +order: 0 +title: 创建根画布 +type: UI +label: UI +--- + +根画布是 UI 的基础,但不是所有的 `UICanvas` 都是根画布,接下来为大家演示如何在场景中创建根画布。 + +## 编辑器使用 + +### 添加 UICanvas 节点 + +在 **[层级面板](/docs/interface/hierarchy/)** 添加 Canvas 节点 + + + +### 设置属性 + +选中添加了 `Canvas` 组件的节点,可以在 **[检查器面板](/docs/interface/inspector)** 设置相关的属性 + + + +### 根画布 + +如果新添加的画布节点的父亲或祖先节点已经包含激活的 `UICanvas` 组件,那么此画布不包含任何渲染与交互的功能。 + + + +## 属性 + +| 属性名 | 描述 | +| :--------------------------- | :------------------------------------------------------------ | +| `renderMode` | 画布的渲染模式 | +| `renderCamera` | 当画布设置为 `ScreenSpaceCamera` 模式时,对应的渲染相机 | +| `distance` | 当画布设置为 `ScreenSpaceCamera` 模式时,画布相较于相机的距离 | +| `resolutionAdaptationMode` | 画布的适配模式,可以按需选择宽度适配或者高度适配等模式 | +| `referenceResolution` | 画布在做尺寸适配时,依据的设计分辨率 | +| `referenceResolutionPerUnit` | 画布中的单位与世界空间中单位的比例关系 | +| `sortOrder` | 画布的渲染排序优先级 | + +## 脚本开发 + +```typescript +// Add camera +const cameraEntity = root.createChild("Camera"); +const camera = cameraEntity.addComponent(Camera); + +// Add UICanvas +const canvasEntity = root.createChild("canvas"); +const canvas = canvasEntity.addComponent(UICanvas); + +// Set renderMode to `ScreenSpaceOverlay` +canvas.renderMode = CanvasRenderMode.ScreenSpaceOverlay; +// Set renderMode to `ScreenSpaceCamera` +canvas.renderMode = CanvasRenderMode.ScreenSpaceCamera; +canvas.renderCamera = camera; +// Set Reference Resolution +canvas.referenceResolution.set(750, 1624); +// Set Adaptation Mode +canvas.resolutionAdaptationMode = ResolutionAdaptationMode.WidthAdaptation; +``` diff --git a/docs/zh/UI/quickStart/event.md b/docs/zh/UI/quickStart/event.md new file mode 100644 index 0000000000..fd3ab23243 --- /dev/null +++ b/docs/zh/UI/quickStart/event.md @@ -0,0 +1,40 @@ +--- +order: 3 +title: 事件系统 +type: UI +label: UI +--- + +UI 事件遵循引擎的事件系统,在此基础之上,UI 组件派发的事件还额外支持冒泡机制。 + +## 冒泡 + +当前版本仅支持冒泡流程: + +| 接口 | 是否冒泡率 | +| :---------------------------------------------------------- | :--------- | +| [onPointerEnter](/apis/core/#Script-onPointerEnter) | 不冒泡 | +| [onPointerExit](/apis/core/#Script-onPointerExit) | 不冒泡 | +| [onPointerDown](/apis/core/#Script-onPointerDown) | 冒泡 | +| [onPointerUp](/apis/core/#Script-onPointerUp) | 冒泡 | +| [onPointerClick](/apis/core/#Script-onPointerClick) | 冒泡 | +| [onPointerBeginDrag](/apis/core/#Script-onPointerBeginDrag) | 冒泡 | +| [onPointerDrag](/apis/core/#Script-onPointerDrag) | 冒泡 | +| [onPointerEndDrag](/apis/core/#Script-onPointerEndDrag) | 冒泡 | +| [onPointerDrop](/apis/core/#Script-onPointerDrop) | 冒泡 | + +如下图所示,如果 C 节点触发 `pointerup` 事件, 将会沿着 C --> B --> A --> RootCanvas 路径一路派发此事件。 + +```mermaid +stateDiagram + RootCanvas --> A + RootCanvas --> F + A --> B + A --> E + B --> C + B --> D +``` + +## 脚本开发 + + diff --git a/docs/zh/UI/quickStart/group.md b/docs/zh/UI/quickStart/group.md new file mode 100644 index 0000000000..08a51af701 --- /dev/null +++ b/docs/zh/UI/quickStart/group.md @@ -0,0 +1,28 @@ +--- +order: 5 +title: UIGroup +type: UI +label: UI +--- + +通过 `UIGroup` 组件,可以继承或忽略**透明度**,**是否可交互**等属性。 + +## 编辑器使用 + +选中节点,在 **[检查器面板](/docs/interface/inspector)** 点击 **添加组件** 并选择 **UIGroup**,即可通过修改设置控制多个 UI 元素的透明度。 + + + +## 属性 + +| 属性名 | 描述 | +| :------------------ | :------------------------ | +| `alpha` | 透明度 | +| `interactive` | 是否可交互 | +| `ignoreParentGroup` | 是否忽略上层 Group 的设置 | + +> UIGroup 解决了 UI 元素的属性无法由父传递给子的问题。 + +## 脚本开发 + + diff --git a/docs/zh/UI/quickStart/image.md b/docs/zh/UI/quickStart/image.md new file mode 100644 index 0000000000..18528a6fc6 --- /dev/null +++ b/docs/zh/UI/quickStart/image.md @@ -0,0 +1,48 @@ +--- +order: 2 +title: 创建图片 +type: UI +label: UI +--- + +`Image` 组件用于在 `UICanvas` 中显示图片。 + +## 编辑器使用 + +### 添加 Image 节点 + +在 **[层级面板](/docs/interface/hierarchy/)** 添加 `Image` 节点 + + + +> 若父亲或祖先节点没有画布组件,会自动添加上根画布节点。 + +### 设置 Sprite + +Image 的显示内容取决于设置的 [Sprite 资产]() ,选中添加了 `Image` 组件的节点,在 **[检查器面板](/docs/interface/inspector)** 的 Sprite 属性选择对应的精灵资产即可替换显示内容。 + + + +### 修改渲染模式 + +`Image` 目前提供三种绘制模式,分别是普通绘制,九宫绘制与平铺绘制(默认为普通绘制),在不同的绘制模式下,修改绘制宽高可以直观地感受到各种模式之间的渲染差异。 + + + +### 调整尺寸 + +请参照[快速调整 UI 元素的尺寸](/docs/UI/quickStart/transform) + +## 属性 + +| 属性名 | 描述 | +| :-- | :-- | +| `sprite` | 渲染的精灵 | +| `color` | 精灵颜色 | +| `drawMode` | 绘制模式,支持普通,九宫和平铺绘制模式 | +| `raycastEnabled` | 是否可以被射线检测到 | +| `raycastPadding` | 射线检测的自定义边界与他的碰撞区域的距离,它是归一化的值并且 X,Y,Z,W 分别代表距离左下右上四条边的距离 | + +## 脚本开发 + + diff --git a/docs/zh/UI/quickStart/order.md b/docs/zh/UI/quickStart/order.md new file mode 100644 index 0000000000..2e22e6a590 --- /dev/null +++ b/docs/zh/UI/quickStart/order.md @@ -0,0 +1,63 @@ +--- +order: 6 +title: 渲染顺序 +type: UI +label: UI +--- + +UI 组件的渲染顺序遵从两个规则: + +- 不同的 `UICanvas` 依据 `RendererMode` 类型遵从特定的渲染顺序 +- 从属于 `UICanvas` 的 `UIRenderer` 则依据**深度优先且从父到子、从左到右**的次序进行渲染 + +## UICanvas + +假设当前运行时: +- 包含一个场景 `Scene` +- 场景 `Scene ` 包含两个相机 `Camera1` 与 `Camera2` +- 场景 `Scene ` 包含三个画布,其中 + - `Canvas1` 的渲染模式为 `WorldSpace` + - `Canvas2` 的渲染模式为 `ScreenSpace-Overlay` + - `Canvas3` 的渲染模式为 `ScreenSpace-Camera`,且对应的渲染相机为 `Camera1` + +```mermaid +journey + title Scene Rendering Cycle + section Camera1.render + Canvas1.render: 5 + Canvas3.render: 5 + section Camera2.render + Canvas1.render: 5 + section Ending + Canvas2.render: 5 +``` + +需要注意的是: +- 渲染模式为 `ScreenSpace-Camera` 和的画布只会在其对应的相机中渲染,且它与渲染模式为 `ScreenSpace-Overlay` 的画布一样,都遵循通用的相机裁剪规则。 +- 渲染模式为 `ScreenSpace-Overlay` 的画布在没有相机的情况下依旧可以被渲染。 +- 同个相机下,`UICanvas` 之间的渲染排序遵循如下规则,overlay 的画布仅通过 `sortOrder` 决定渲染顺序。 + +```mermaid +flowchart TD + A[渲染数据排序] --> B{canvas.sortOrder} + B -->|不相等| C[返回比较结果] + B -->|相等| D{canvas 与相机的距离} + D -->|不相同| E[返回比较结果] + D -->|相同| F[返回比较结果] +``` + +## UIRenderer 渲染顺序 + +```mermaid +stateDiagram + RootCanvas --> A + RootCanvas --> F + A --> B + A --> E + B --> C + B --> D +``` + +如上图所示,根画布下的渲染顺序依次为 A -> B -> C -> D -> E -> F 需要注意的是,设置`UIRenderer.priority` 并不会改变它的渲染顺序。 + + diff --git a/docs/zh/UI/quickStart/text.md b/docs/zh/UI/quickStart/text.md new file mode 100644 index 0000000000..5fb43b01f3 --- /dev/null +++ b/docs/zh/UI/quickStart/text.md @@ -0,0 +1,57 @@ +--- +order: 3 +title: 创建文字 +type: UI +label: UI +--- + +`Text` 组件用于在 `UICanvas` 中显示文字。 + +## 编辑器使用 + +### 添加 Text 节点 + +在 **[层级面板](/docs/interface/hierarchy/)** 添加 `Text` 节点 + + + +> 若父亲或祖先节点没有画布组件,会自动添加上根画布节点。 + +### 设置文本内容 + +选中添加了 `Text` 组件的节点,在 **[检查器面板](/docs/interface/inspector)** 修改 `text` 属性可以改变 `Text` 元素的显示内容。 + + + +### 设置字体 + +选中添加了 `Text` 组件的节点,在 **[检查器面板](/docs/interface/inspector)** 修改 `font` 属性可以改变 `Text` 元素的字体类型。 + + + +### 修改字体大小 + +`Text` 组件可以通过调整 `FontSize` 修改渲染尺寸 + + + +> 修改 `UITransform` 的 `size` 不会改变 `Text` 的渲染尺寸。 + +## 属性 + +| 属性名 | 描述 | +| :-- | :-- | +| `text` | 需要显示的文本 | +| `color` | 文本颜色 | +| `font` | 自定义字体 | +| `fontSize` | 文本的字体大小 | +| `fontStyle` | 字体样式设置:是否加粗/是否斜体 | +| `lineSpacing` | 行间距 | +| `horizontalAlignment` | 水平对齐方式,可选值有:Left/Center/Right | +| `verticalAlignment` | 竖直对齐方式,可选值有:Top/Center/Bottom | +| `enableWrapping` | 是否开启换行模式,打开换行模式后,会根据设置的宽来进行换行,如果这时候宽设置为 0,那么文本将不渲染 | +| `overflowMode` | 当文本总高度超出设置的高的时候的处理方式,可选值有:Overflow/Truncate, Overflow 表示直接溢出显示, Truncate 表示只保留设置高度以内的内容显示,具体显示内容还和文本在竖直方向上的对齐方式有关 | + +## 脚本开发 + + diff --git a/docs/zh/UI/quickStart/transform.md b/docs/zh/UI/quickStart/transform.md new file mode 100644 index 0000000000..37622e28bd --- /dev/null +++ b/docs/zh/UI/quickStart/transform.md @@ -0,0 +1,34 @@ +--- +order: 1 +title: UITransform +type: UI +label: UI +--- + +`UITransform` 组件是专门设计用来表示 UI 元素的尺寸和位置的,它继承于 [Transform](/apis/core/#Transform) 。 + +## 编辑器使用 + +添加了 UI 组件的节点,会自动添加 `UITransform` 组件(替换原先旧的 [Transform](/apis/core/#Transform) 组件),在编辑器中,可以选中节点可以使用 `RectTool` (快捷键 `T` )快速设置属性,也可以在在 **[检查器面板](/docs/interface/inspector)** 设置精确属性。 + + + +> 当主画布的渲染模式为 `Screen` 时,编辑器侧会禁止修改它的 `transform` 避免屏幕适配异常,因此在脚本中,**开发者应当自己避免去篡改屏幕空间主画布 `transform` 的属性**。 + +## 属性 + +| 属性名 | 描述 | +| :------ | :----------------------------------------------------------------------------------- | +| `size` | UI 元素的尺寸,`x` 代表宽度,`y` 代表高度,初始化都默认为 `100` | +| `pivot` | UI 元素的锚点,它是一个以左下角为原点的归一化的二元向量,默认值为中心点,即(0.5,0.5) | + +## 脚本使用 + +```typescript +// Add UICanvas +const canvasEntity = root.createChild("canvas"); +const canvas = canvasEntity.addComponent(UICanvas); +const imageEntity = canvasEntity.create("Image"); +(imageEntity.transform).size.set(200, 200); +(imageEntity.transform).pivot.set(0, 0); +``` diff --git a/docs/zh/UI/system.md b/docs/zh/UI/system.md new file mode 100644 index 0000000000..357f559a19 --- /dev/null +++ b/docs/zh/UI/system.md @@ -0,0 +1,108 @@ +--- +order: 2 +title: 架构 +type: UI +label: UI +--- + +## 系统设计 + +```mermaid +--- +title: UI System +--- +classDiagram + Component <|-- Transform + Script <|-- Component + Transform <|-- UITransform + Component <|-- Renderer + Renderer <|-- UIRenderer + UIRenderer <|-- Image + UIRenderer <|-- Text + UICanvas <|-- Component + UIInteractive <|-- Script + Button <|-- UIInteractive + UIGroup <|-- Component + + class Script { + +void onUpdate() + } + + class Transform{ + +Vector3 position + +Vector3 rotation + +Vector3 scale + ... + } + + class UITransform{ + <> + +Vector2 size + +Vector2 pivot + } + + class Renderer{ + +BoundingBox bounds + +Material getMaterial() + +Material setMaterial() + } + + class UIRenderer{ + <> + +Color color + +boolean raycastEnabled + +vec4 raycastPadding + } + + class Image{ + <> + +Sprite sprite + +SpriteDrawMode drawMode + ... + } + + class Text{ + <> + +string text + +Font font + ... + } + + class UICanvas { + <> + +CanvasRenderMode renderMode + +Camera renderCamera + ... + } + + class UIInteractive { + <> + +boolean interactive + +Transition transitions + ... + } + + class Button { + <> + +void addClicked() + +void removeClicked() + ... + } + + class UIGroup { + <> + +boolean interactive + +number alpha + +boolean ignoreParentGroup + } +``` + +## 模块管理 + +| 包 | 解释 | 相关文档 | +| :----------------------------------------------------------------------- | :----------- | --------------------- | +| [@galacean/engine-ui](https://www.npmjs.com/package/@galacean/engine-xr) | 核心架构逻辑 | [API](/apis/galacean) | + +> `@galacean/engine-ui` 是实现 **UI** 必须引入的依赖 + +> 需遵守[版本依赖规则](/docs/basics/version/#版本依赖),即 `@galacean/engine-ui` 的版本需与 `@galacean/engine` 保持一致。 diff --git a/docs/zh/_meta.json b/docs/zh/_meta.json index 0d959575b7..293f77643a 100644 --- a/docs/zh/_meta.json +++ b/docs/zh/_meta.json @@ -47,6 +47,12 @@ "collapse": true } }, + "UI": { + "title": "UI", + "theme": { + "collapse": true + } + }, "xr": { "title": "XR", "theme": { diff --git a/docs/zh/basics/overview.md b/docs/zh/basics/overview.md index fd60853717..377559df99 100644 --- a/docs/zh/basics/overview.md +++ b/docs/zh/basics/overview.md @@ -35,7 +35,7 @@ flowchart LR | [@galacean/engine](https://www.npmjs.com/package/@galacean/engine) | 核心架构逻辑和核心功能 | [API](/apis/galacean) | | [@galacean/engine-physics-lite](https://www.npmjs.com/package/@galacean/engine-physics-lite) | 轻量级物理引擎 | [Doc](/docs/physics/overall) | | [@galacean/engine-physics-physx](https://www.npmjs.com/package/@galacean/engine-physics-physx) | 全功能物理引擎 | [Doc](/docs/physics/overall) | -| [@galacean/engine-shader-lab](https://www.npmjs.com/package/@galacean/engine-shader-lab) | Galacean Shader 编译器 | [Doc](/docs/graphics/shader/shaderLab/intro/) | +| [@galacean/engine-shaderlab](https://www.npmjs.com/package/@galacean/engine-shaderlab) | Galacean Shader 编译器 | [Doc](/docs/graphics/shader/shaderLab/intro/) | | [@galacean/engine-xr](https://www.npmjs.com/package/@galacean/engine-xr) | XR 逻辑包 | [Doc](/docs/xr/overall) | | [@galacean/engine-xr-webxr](https://www.npmjs.com/package/@galacean/engine-xr-webxr) | WebXR 后端 | [Doc](/docs/xr/overall) | diff --git a/docs/zh/graphics/2D/lottie.md b/docs/zh/graphics/2D/lottie.md index 2c4a6f1ca5..6a45ab54ce 100644 --- a/docs/zh/graphics/2D/lottie.md +++ b/docs/zh/graphics/2D/lottie.md @@ -148,7 +148,8 @@ engine.resourceManager.load({ | 引擎版本 | Lottie 版本 | | :--- | :--- | | 1.2.x | 1.1.0-beta.0 | -| 1.3.x | 1.1.0-beta.0 | +| 1.3.x | engine-1.3 | +| 1.4.x | engine-1.4 | ## 性能方面的建议 diff --git a/docs/zh/input/pointer.md b/docs/zh/input/pointer.md index b2ded4840d..cfd9bb78c0 100644 --- a/docs/zh/input/pointer.md +++ b/docs/zh/input/pointer.md @@ -64,12 +64,15 @@ timeline | 接口 | 触发时机与频率 | | :------------------------------------------------- | :------------------------------------------------------------------------- | -| [onPointerEnter](/apis/core/#Script-onPointerEnter) | 当触控点进入 Entity 的碰撞体范围时触发一次 | -| [onPointerExit](/apis/core/#Script-onPointerExit) | 当触控点离开 Entity 的碰撞体范围时触发一次 | -| [onPointerDown](/apis/core/#Script-onPointerDown) | 当触控点在 Entity 的碰撞体范围内按下时触发一次 | -| [onPointerUp](/apis/core/#Script-onPointerUp) | 当触控点在 Entity 的碰撞体范围内松开时触发一次 | -| [onPointerClick](/apis/core/#Script-onPointerClick) | 当触控点在 Entity 的碰撞体范围内按下并松开,在松开时触发一次 | -| [onPointerDrag](/apis/core/#Script-onPointerDrag) | 当触控点在 Entity 的碰撞体范围内按下时**持续**触发,直至触控点解除按下状态 | +| [onPointerEnter](/apis/core/#Script-onPointerEnter) | 当触控点进入 Entity 的碰撞范围时触发一次 | +| [onPointerExit](/apis/core/#Script-onPointerExit) | 当触控点离开 Entity 的碰撞范围时触发一次 | +| [onPointerDown](/apis/core/#Script-onPointerDown) | 当触控点在 Entity 的碰撞范围内按下时触发一次 | +| [onPointerUp](/apis/core/#Script-onPointerUp) | 当触控点在 Entity 的碰撞范围内松开时触发一次 | +| [onPointerClick](/apis/core/#Script-onPointerClick) | 当触控点在 Entity 的碰撞范围内按下并松开,在松开时触发一次 | +| [onPointerBeginDrag](/apis/core/#Script-onPointerBeginDrag) | 当触控点在 Entity 的碰撞范围内按下时触发,表示开始拖动 | +| [onPointerDrag](/apis/core/#Script-onPointerDrag) | 开始拖动后,触控点发生移动时触发 | +| [onPointerEndDrag](/apis/core/#Script-onPointerEndDrag) | 开始拖动后,触控点松开时触发 | +| [onPointerDrop](/apis/core/#Script-onPointerDrop) | 开始拖动后,当触控点在某个 Entity 的碰撞范围内松开时触发 | > ⚠️ 触控回调**依赖物理引擎**,使用此功能前请确保物理引擎已初始化完毕。 diff --git a/e2e/.dev/vite.config.js b/e2e/.dev/vite.config.js index 459bd71db3..b6d811f464 100644 --- a/e2e/.dev/vite.config.js +++ b/e2e/.dev/vite.config.js @@ -56,6 +56,7 @@ module.exports = { "@galacean/engine-draco", "@galacean/engine-lottie", "@galacean/engine-spine", + "@galacean/engine-shaderlab", "@galacean/tools-baker", "@galacean/engine-toolkit", "@galacean/engine-toolkit-auxiliary-lines", diff --git a/examples/draw-lines.ts b/examples/draw-lines.ts index 7c0d53dac8..9cf0a8939b 100644 --- a/examples/draw-lines.ts +++ b/examples/draw-lines.ts @@ -131,7 +131,7 @@ class DrawScript extends Script { } } - onPointerDown(): void { + onPointerBeginDrag(): void { // Screen pointer to world pointer. this._preDrawTime = this.engine.time.elapsedTime; const { x: screenX, y: screenY } = diff --git a/examples/input-log.ts b/examples/input-log.ts index 18d6c528b6..39a4afff94 100644 --- a/examples/input-log.ts +++ b/examples/input-log.ts @@ -21,7 +21,7 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { Pointer: true, Keyboard: true, Size: 1, - Color: [255, 255, 255], + Color: [255, 0, 0], OffsetX: 0, OffsetY: 0, }; @@ -53,5 +53,6 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { inputLogger.color.set(v[0] / 255, v[1] / 255, v[2] / 255, 1); }); + inputLogger.color.set(1, 0, 0, 1); textFolder.open(); }); diff --git a/examples/input-pointer.ts b/examples/input-pointer.ts index fd366d388d..8a79e1d523 100644 --- a/examples/input-pointer.ts +++ b/examples/input-pointer.ts @@ -17,149 +17,112 @@ import { Entity, Vector3, WebGLEngine, + PointerEventData } from "@galacean/engine"; import { LitePhysics } from "@galacean/engine-physics-lite"; // Create engine -WebGLEngine.create({ canvas: "canvas", physics: new LitePhysics() }).then( - (engine) => { - engine.canvas.resizeByClientSize(); - const invCanvasWidth = 1 / engine.canvas.width; - const invCanvasHeight = 1 / engine.canvas.height; - // @ts-ignore - const inputManager = engine.inputManager; - const scene = engine.sceneManager.activeScene; - const rootEntity = scene.createRootEntity("root"); - - scene.ambientLight.diffuseSolidColor.set(1, 1, 1, 1); - scene.ambientLight.diffuseIntensity = 1.2; - - // init camera - const cameraEntity = rootEntity.createChild("camera"); - const camera = cameraEntity.addComponent(Camera); - cameraEntity.transform.setPosition(10, 10, 10); - cameraEntity.transform.lookAt(new Vector3(0, 0, 0)); - - // init point light - let light = rootEntity.createChild("light1"); - light.transform.setPosition(-8, -2, 8); - light.addComponent(PointLight).intensity = 0.12; - - light = rootEntity.createChild("light2"); - light.transform.setPosition(8, -2, 0); - light.addComponent(PointLight).intensity = 0.12; - - class PanScript extends Script { - private startPointerPos = new Vector3(); - private tempVec2: Vector2 = new Vector2(); - private tempVec3: Vector3 = new Vector3(); - private zValue: number = 0; - - onPointerDown(pointer: Pointer) { - this.zValue = camera.worldToViewportPoint( - this.entity.transform.worldPosition, - this.tempVec3 - ).z; - const { tempVec2, tempVec3 } = this; - tempVec2.copyFrom(pointer.position); - tempVec3.set( - tempVec2.x * invCanvasWidth, - tempVec2.y * invCanvasHeight, - this.zValue - ); - camera.viewportToWorldPoint(tempVec3, this.startPointerPos); - } - - onPointerDrag(pointer: Pointer) { - const { tempVec2, tempVec3, startPointerPos } = this; - const { transform } = this.entity; - tempVec2.copyFrom(pointer.position); - tempVec3.set( - tempVec2.x * invCanvasWidth, - tempVec2.y * invCanvasHeight, - this.zValue - ); - camera.viewportToWorldPoint(tempVec3, tempVec3); - Vector3.subtract(tempVec3, startPointerPos, startPointerPos); - transform.worldPosition.add(startPointerPos); - startPointerPos.copyFrom(tempVec3); - } +WebGLEngine.create({ canvas: "canvas", physics: new LitePhysics() }).then((engine) => { + engine.canvas.resizeByClientSize(); + const invCanvasWidth = 1 / engine.canvas.width; + const invCanvasHeight = 1 / engine.canvas.height; + // @ts-ignore + const inputManager = engine.inputManager; + const scene = engine.sceneManager.activeScene; + const rootEntity = scene.createRootEntity("root"); + + scene.ambientLight.diffuseSolidColor.set(1, 1, 1, 1); + scene.ambientLight.diffuseIntensity = 1.2; + + // init camera + const cameraEntity = rootEntity.createChild("camera"); + const camera = cameraEntity.addComponent(Camera); + cameraEntity.transform.setPosition(10, 10, 10); + cameraEntity.transform.lookAt(new Vector3(0, 0, 0)); + + // init point light + let light = rootEntity.createChild("light1"); + light.transform.setPosition(-8, -2, 8); + light.addComponent(PointLight).intensity = 0.12; + + light = rootEntity.createChild("light2"); + light.transform.setPosition(8, -2, 0); + light.addComponent(PointLight).intensity = 0.12; + + class PanScript extends Script { + private startPointerPos = new Vector3(); + private tempVec2: Vector2 = new Vector2(); + private tempVec3: Vector3 = new Vector3(); + private zValue: number = 0; + + onPointerBeginDrag(eventData: PointerEventData) { + this.zValue = camera.worldToViewportPoint(this.entity.transform.worldPosition, this.tempVec3).z; + const { tempVec2, tempVec3 } = this; + tempVec2.copyFrom(eventData.pointer.position); + tempVec3.set(tempVec2.x * invCanvasWidth, tempVec2.y * invCanvasHeight, this.zValue); + camera.viewportToWorldPoint(tempVec3, this.startPointerPos); } - class ClickScript extends Script { - private material: BlinnPhongMaterial; - onStart() { - this.material = ( - this.entity.getComponent(MeshRenderer).getInstanceMaterial() - ); - } - - onPointerClick() { - this.material.baseColor.set( - Math.random(), - Math.random(), - Math.random(), - 1.0 - ); - } + onPointerDrag(eventData: PointerEventData) { + const { tempVec2, tempVec3, startPointerPos } = this; + const { transform } = this.entity; + tempVec2.copyFrom(eventData.pointer.position); + tempVec3.set(tempVec2.x * invCanvasWidth, tempVec2.y * invCanvasHeight, this.zValue); + camera.viewportToWorldPoint(tempVec3, tempVec3); + Vector3.subtract(tempVec3, startPointerPos, startPointerPos); + transform.worldPosition.add(startPointerPos); + startPointerPos.copyFrom(tempVec3); } + } + + class ClickScript extends Script { + private material: BlinnPhongMaterial; + onStart() { + this.material = this.entity.getComponent(MeshRenderer).getInstanceMaterial(); + } + + onPointerClick() { + this.material.baseColor.set(Math.random(), Math.random(), Math.random(), 1.0); + } + } - class EnterExitScript extends Script { - private material: BlinnPhongMaterial; - onStart() { - this.material = ( - this.entity.getComponent(MeshRenderer).getInstanceMaterial() - ); - } - - onPointerEnter() { - this.material.baseColor.set( - Math.random(), - Math.random(), - Math.random(), - 1.0 - ); - } - - onPointerExit() { - this.material.baseColor.set( - Math.random(), - Math.random(), - Math.random(), - 1.0 - ); - } + class EnterExitScript extends Script { + private material: BlinnPhongMaterial; + onStart() { + this.material = this.entity.getComponent(MeshRenderer).getInstanceMaterial(); } - function createBox(x: number, y: number, z: number): Entity { - // create box test entity - const cubeSize = 2.0; - const boxEntity = rootEntity.createChild("BoxEntity"); - boxEntity.transform.setPosition(x, y, z); - - const boxMtl = new BlinnPhongMaterial(engine); - const boxRenderer = boxEntity.addComponent(MeshRenderer); - boxMtl.baseColor.set(0.6, 0.3, 0.3, 1.0); - boxRenderer.mesh = PrimitiveMesh.createCuboid( - engine, - cubeSize, - cubeSize, - cubeSize - ); - boxRenderer.setMaterial(boxMtl); - - const boxCollider: StaticCollider = - boxEntity.addComponent(StaticCollider); - const boxColliderShape = new BoxColliderShape(); - boxColliderShape.size.set(cubeSize, cubeSize, cubeSize); - boxCollider.addShape(boxColliderShape); - return boxEntity; + onPointerEnter() { + this.material.baseColor.set(Math.random(), Math.random(), Math.random(), 1.0); } - createBox(0, 0, 0).addComponent(PanScript); - createBox(3, 0, -3).addComponent(ClickScript); - createBox(-3, 0, 3).addComponent(EnterExitScript); - // Run engine - engine.run(); + onPointerExit() { + this.material.baseColor.set(Math.random(), Math.random(), Math.random(), 1.0); + } } -); + + function createBox(x: number, y: number, z: number): Entity { + // create box test entity + const cubeSize = 2.0; + const boxEntity = rootEntity.createChild("BoxEntity"); + boxEntity.transform.setPosition(x, y, z); + + const boxMtl = new BlinnPhongMaterial(engine); + const boxRenderer = boxEntity.addComponent(MeshRenderer); + boxMtl.baseColor.set(0.6, 0.3, 0.3, 1.0); + boxRenderer.mesh = PrimitiveMesh.createCuboid(engine, cubeSize, cubeSize, cubeSize); + boxRenderer.setMaterial(boxMtl); + + const boxCollider: StaticCollider = boxEntity.addComponent(StaticCollider); + const boxColliderShape = new BoxColliderShape(); + boxColliderShape.size.set(cubeSize, cubeSize, cubeSize); + boxCollider.addShape(boxColliderShape); + return boxEntity; + } + createBox(0, 0, 0).addComponent(PanScript); + createBox(3, 0, -3).addComponent(ClickScript); + createBox(-3, 0, 3).addComponent(EnterExitScript); + + // Run engine + engine.run(); +}); diff --git a/examples/physx-select.ts b/examples/physx-select.ts index 0144e5955e..a2ce78b9c4 100644 --- a/examples/physx-select.ts +++ b/examples/physx-select.ts @@ -20,6 +20,7 @@ import { PBRMaterial, PlaneColliderShape, Pointer, + PointerEventData, PrimitiveMesh, Quaternion, Script, @@ -29,7 +30,7 @@ import { Texture2D, Vector2, Vector3, - WebGLEngine, + WebGLEngine } from "@galacean/engine"; import { PhysXPhysics } from "@galacean/engine-physics-physx"; @@ -47,35 +48,23 @@ class PanScript extends Script { this.collider = this.entity.getComponent(DynamicCollider); } - onPointerDown(pointer: Pointer) { + onPointerDown(eventData: PointerEventData) { // get depth - this.camera.worldToViewportPoint( - this.entity.transform.worldPosition, - this.tempVec3 - ); - this.zValue = this.camera.worldToViewportPoint( - this.entity.transform.worldPosition, - this.tempVec3 - ).z; + this.camera.worldToViewportPoint(this.entity.transform.worldPosition, this.tempVec3); + this.zValue = this.camera.worldToViewportPoint(this.entity.transform.worldPosition, this.tempVec3).z; const { tempVec3 } = this; - tempVec3.set( - pointer.position.x * this.invCanvasWidth, - pointer.position.y * this.invCanvasHeight, - this.zValue - ); + const { position } = eventData.pointer; + tempVec3.set(position.x * this.invCanvasWidth, position.y * this.invCanvasHeight, this.zValue); this.camera.viewportToWorldPoint(tempVec3, this.startPointerPos); this.collider.linearVelocity = new Vector3(); this.collider.angularVelocity = new Vector3(); } - onPointerDrag(pointer: Pointer) { + onPointerDrag(eventData: PointerEventData) { const { tempVec3, startPointerPos } = this; const { transform } = this.entity; - this.tempVec3.set( - pointer.position.x * this.invCanvasWidth, - pointer.position.y * this.invCanvasHeight, - this.zValue - ); + const { position } = eventData.pointer; + this.tempVec3.set(position.x * this.invCanvasWidth, position.y * this.invCanvasHeight, this.zValue); this.camera.viewportToWorldPoint(tempVec3, tempVec3); Vector3.subtract(tempVec3, startPointerPos, startPointerPos); transform.worldPosition = transform.worldPosition.add(startPointerPos); @@ -83,19 +72,9 @@ class PanScript extends Script { } } -function addPlane( - rootEntity: Entity, - size: Vector2, - position: Vector3, - rotation: Quaternion -): Entity { +function addPlane(rootEntity: Entity, size: Vector2, position: Vector3, rotation: Quaternion): Entity { const mtl = new PBRMaterial(rootEntity.engine); - mtl.baseColor.set( - 0.2179807202597362, - 0.2939682161541871, - 0.31177952549087604, - 1 - ); + mtl.baseColor.set(0.2179807202597362, 0.2939682161541871, 0.31177952549087604, 1); mtl.roughness = 0.0; mtl.metallic = 0.0; @@ -135,13 +114,7 @@ function addVerticalBox( const boxRenderer = entity.addComponent(MeshRenderer); boxMtl.baseTexture = texture; boxMtl.baseTexture.anisoLevel = 12; - boxRenderer.mesh = PrimitiveMesh.createCuboid( - rootEntity.engine, - 0.5, - 0.33, - 2, - false - ); + boxRenderer.mesh = PrimitiveMesh.createCuboid(rootEntity.engine, 0.5, 0.33, 2, false); boxRenderer.setMaterial(boxMtl); const physicsBox = new BoxColliderShape(); @@ -153,8 +126,7 @@ function addVerticalBox( const boxCollider = entity.addComponent(DynamicCollider); boxCollider.addShape(physicsBox); boxCollider.mass = 1; - boxCollider.collisionDetectionMode = - CollisionDetectionMode.ContinuousSpeculative; + boxCollider.collisionDetectionMode = CollisionDetectionMode.ContinuousSpeculative; const pan = entity.addComponent(PanScript); pan.camera = camera; @@ -181,12 +153,7 @@ function addHorizontalBox( const boxRenderer = entity.addComponent(MeshRenderer); boxMtl.baseTexture = texture; boxMtl.baseTexture.anisoLevel = 12; - boxRenderer.mesh = PrimitiveMesh.createCuboid( - rootEntity.engine, - 2, - 0.33, - 0.5 - ); + boxRenderer.mesh = PrimitiveMesh.createCuboid(rootEntity.engine, 2, 0.33, 0.5); boxRenderer.setMaterial(boxMtl); const physicsBox = new BoxColliderShape(); @@ -198,8 +165,7 @@ function addHorizontalBox( const boxCollider = entity.addComponent(DynamicCollider); boxCollider.addShape(physicsBox); boxCollider.mass = 0.5; - boxCollider.collisionDetectionMode = - CollisionDetectionMode.ContinuousSpeculative; + boxCollider.collisionDetectionMode = CollisionDetectionMode.ContinuousSpeculative; const pan = entity.addComponent(PanScript); pan.camera = camera; @@ -216,36 +182,9 @@ function addBox( invCanvasHeight: number ): void { for (let i: number = 0; i < 8; i++) { - addVerticalBox( - rootEntity, - texture1, - -0.65, - 0.165 + i * 0.33 * 2, - 0, - camera, - invCanvasWidth, - invCanvasHeight - ); - addVerticalBox( - rootEntity, - texture1, - 0, - 0.165 + i * 0.33 * 2, - 0, - camera, - invCanvasWidth, - invCanvasHeight - ); - addVerticalBox( - rootEntity, - texture1, - 0.65, - 0.165 + i * 0.33 * 2, - 0, - camera, - invCanvasWidth, - invCanvasHeight - ); + addVerticalBox(rootEntity, texture1, -0.65, 0.165 + i * 0.33 * 2, 0, camera, invCanvasWidth, invCanvasHeight); + addVerticalBox(rootEntity, texture1, 0, 0.165 + i * 0.33 * 2, 0, camera, invCanvasWidth, invCanvasHeight); + addVerticalBox(rootEntity, texture1, 0.65, 0.165 + i * 0.33 * 2, 0, camera, invCanvasWidth, invCanvasHeight); addHorizontalBox( rootEntity, @@ -257,16 +196,7 @@ function addBox( invCanvasWidth, invCanvasHeight ); - addHorizontalBox( - rootEntity, - texture2, - 0, - 0.165 + 0.33 + i * 0.33 * 2, - 0, - camera, - invCanvasWidth, - invCanvasHeight - ); + addHorizontalBox(rootEntity, texture2, 0, 0.165 + 0.33 + i * 0.33 * 2, 0, camera, invCanvasWidth, invCanvasHeight); addHorizontalBox( rootEntity, texture2, @@ -281,69 +211,60 @@ function addBox( } //-------------------------------------------------------------------------------------------------------------------- -WebGLEngine.create({ canvas: "canvas", physics: new PhysXPhysics() }).then( - (engine) => { - engine.canvas.resizeByClientSize(); - const invCanvasWidth = 1 / engine.canvas.width; - const invCanvasHeight = 1 / engine.canvas.height; - const scene = engine.sceneManager.activeScene; - scene.shadowDistance = 20; - const rootEntity = scene.createRootEntity("root"); +WebGLEngine.create({ canvas: "canvas", physics: new PhysXPhysics() }).then((engine) => { + engine.canvas.resizeByClientSize(); + const invCanvasWidth = 1 / engine.canvas.width; + const invCanvasHeight = 1 / engine.canvas.height; + const scene = engine.sceneManager.activeScene; + scene.shadowDistance = 20; + const rootEntity = scene.createRootEntity("root"); - scene.ambientLight.diffuseSolidColor.set(0.5, 0.5, 0.5, 1); - scene.ambientLight.diffuseIntensity = 1.2; + scene.ambientLight.diffuseSolidColor.set(0.5, 0.5, 0.5, 1); + scene.ambientLight.diffuseIntensity = 1.2; - // init camera - const cameraEntity = rootEntity.createChild("camera"); - const camera = cameraEntity.addComponent(Camera); - cameraEntity.transform.setPosition(7, 7, 7); - cameraEntity.transform.lookAt(new Vector3(0, 2, 0), new Vector3(0, 1, 0)); + // init camera + const cameraEntity = rootEntity.createChild("camera"); + const camera = cameraEntity.addComponent(Camera); + cameraEntity.transform.setPosition(7, 7, 7); + cameraEntity.transform.lookAt(new Vector3(0, 2, 0), new Vector3(0, 1, 0)); - const entity = cameraEntity.createChild("text"); - entity.transform.position = new Vector3(0, 3.5, -10); - const renderer = entity.addComponent(TextRenderer); - renderer.color = new Color(); - renderer.text = "Use mouse to move the bricks"; - renderer.font = Font.createFromOS(entity.engine, "Arial"); - renderer.fontSize = 40; + const entity = cameraEntity.createChild("text"); + entity.transform.position = new Vector3(0, 3.5, -10); + const renderer = entity.addComponent(TextRenderer); + renderer.color = new Color(); + renderer.text = "Use mouse to move the bricks"; + renderer.font = Font.createFromOS(entity.engine, "Arial"); + renderer.fontSize = 40; - // init point light - const light = rootEntity.createChild("light"); - light.transform.setPosition(0, 5, 8); - light.transform.lookAt(new Vector3(0, 2, 0), new Vector3(0, 1, 0)); - const directLight = light.addComponent(DirectLight); - directLight.shadowType = ShadowType.SoftLow; + // init point light + const light = rootEntity.createChild("light"); + light.transform.setPosition(0, 5, 8); + light.transform.lookAt(new Vector3(0, 2, 0), new Vector3(0, 1, 0)); + const directLight = light.addComponent(DirectLight); + directLight.shadowType = ShadowType.SoftLow; - addPlane(rootEntity, new Vector2(30, 30), new Vector3(), new Quaternion()); + addPlane(rootEntity, new Vector2(30, 30), new Vector3(), new Quaternion()); - Promise.all([ - engine.resourceManager.load({ - url: "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*Wkn5QY0tpbcAAAAAAAAAAAAAARQnAQ", - type: AssetType.Texture2D, - }), - engine.resourceManager.load({ - url: "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*W5azT5DjDAEAAAAAAAAAAAAAARQnAQ", - type: AssetType.Texture2D, - }), - ]).then((asset: Texture2D[]) => { - addBox( - rootEntity, - asset[0], - asset[1], - camera, - invCanvasWidth, - invCanvasHeight - ); + Promise.all([ + engine.resourceManager.load({ + url: "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*Wkn5QY0tpbcAAAAAAAAAAAAAARQnAQ", + type: AssetType.Texture2D + }), + engine.resourceManager.load({ + url: "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*W5azT5DjDAEAAAAAAAAAAAAAARQnAQ", + type: AssetType.Texture2D + }) + ]).then((asset: Texture2D[]) => { + addBox(rootEntity, asset[0], asset[1], camera, invCanvasWidth, invCanvasHeight); - engine.resourceManager - .load({ - type: AssetType.Env, - url: "https://gw.alipayobjects.com/os/bmw-prod/89c54544-1184-45a1-b0f5-c0b17e5c3e68.bin", - }) - .then((ambientLight) => { - scene.ambientLight = ambientLight; - engine.run(); - }); - }); - } -); + engine.resourceManager + .load({ + type: AssetType.Env, + url: "https://gw.alipayobjects.com/os/bmw-prod/89c54544-1184-45a1-b0f5-c0b17e5c3e68.bin" + }) + .then((ambientLight) => { + scene.ambientLight = ambientLight; + engine.run(); + }); + }); +}); diff --git a/examples/ui-Button.ts b/examples/ui-Button.ts new file mode 100644 index 0000000000..3d2ab95f6a --- /dev/null +++ b/examples/ui-Button.ts @@ -0,0 +1,63 @@ +/** + * @title UI Button + * @category UI + */ +import { AssetType, FontStyle, Sprite, SpriteDrawMode, Texture2D, WebGLEngine } from "@galacean/engine"; +import { + Button, + CanvasRenderMode, + ColorTransition, + Image, + ScaleTransition, + Text, + UICanvas, + UITransform +} from "@galacean/engine-ui"; + +// Create engine +WebGLEngine.create({ canvas: "canvas" }).then((engine) => { + engine.canvas.resizeByClientSize(); + + const scene = engine.sceneManager.activeScene; + const rootEntity = scene.createRootEntity(); + + // Add canvas + const canvasEntity = rootEntity.createChild("canvas"); + const canvas = canvasEntity.addComponent(UICanvas); + canvas.renderMode = CanvasRenderMode.ScreenSpaceOverlay; + canvas.referenceResolutionPerUnit = 50; + + // Add button + const buttonEntity = canvasEntity.createChild("Image"); + const image = buttonEntity.addComponent(Image); + image.drawMode = SpriteDrawMode.Sliced; + (buttonEntity.transform).size.set(200, 70); + const text = buttonEntity.createChild("Text").addComponent(Text); + text.text = "Button"; + text.fontStyle |= FontStyle.Bold; + text.color.set(0, 0, 0, 1); + text.fontSize = 60; + const button = buttonEntity.addComponent(Button); + const scaleTransition = new ScaleTransition(); + scaleTransition.target = image; + const colorTransition = new ColorTransition(); + colorTransition.target = image; + button.addTransition(scaleTransition); + button.addTransition(colorTransition); + button.addClicked(() => { + window.alert("button clicked"); + }); + + engine.resourceManager + .load({ + url: "https://mdn.alipayobjects.com/huamei_yo47yq/afts/img/A*mFpSS502qUYAAAAAAAAAAAAAehuCAQ/original", + type: AssetType.Texture2D + }) + .then((texture) => { + const sprite = new Sprite(engine, texture); + sprite.border.set(0.49, 0.49, 0.49, 0.49); + image.sprite = sprite; + }); + + engine.run(); +}); diff --git a/examples/ui-Event.ts b/examples/ui-Event.ts new file mode 100644 index 0000000000..27d15c91c1 --- /dev/null +++ b/examples/ui-Event.ts @@ -0,0 +1,85 @@ +/** + * @title UI Event + * @category UI + */ +import { AssetType, PointerEventData, Script, Sprite, Texture2D, WebGLEngine } from "@galacean/engine"; +import { CanvasRenderMode, Image, UICanvas, UITransform } from "@galacean/engine-ui"; + +// Create engine +WebGLEngine.create({ canvas: "canvas" }).then((engine) => { + engine.canvas.resizeByClientSize(); + + const scene = engine.sceneManager.activeScene; + const rootEntity = scene.createRootEntity(); + + // Add canvas + const canvasEntity = rootEntity.createChild("canvas"); + const canvas = canvasEntity.addComponent(UICanvas); + canvas.renderMode = CanvasRenderMode.ScreenSpaceOverlay; + canvas.referenceResolutionPerUnit = 50; + + // Add ImageOne + const imageEntity1 = canvasEntity.createChild("Image1"); + const image1 = imageEntity1.addComponent(Image); + image1.color.set(1, 0, 0, 1); + (imageEntity1.transform).size.set(300, 300); + imageEntity1.addComponent(EventScript); + + const imageEntity2 = imageEntity1.createChild("Image2"); + const image2 = imageEntity2.addComponent(Image); + image2.color.set(0, 1, 0, 1); + (imageEntity2.transform).size.set(200, 200); + imageEntity2.addComponent(EventScript); + + const imageEntity3 = imageEntity2.createChild("Image3"); + const image3 = imageEntity3.addComponent(Image); + image3.color.set(0, 0, 1, 1); + (imageEntity3.transform).size.set(100, 100); + imageEntity3.addComponent(EventScript); + + engine.resourceManager + .load({ + url: "https://mdn.alipayobjects.com/huamei_yo47yq/afts/img/A*wW-5TYANcJAAAAAAAAAAAAAADhuCAQ/original/Image.png", + type: AssetType.Texture2D + }) + .then((texture) => { + const sprite = new Sprite(engine, texture); + image1.sprite = image2.sprite = image3.sprite = sprite; + }); + + engine.run(); +}); + +class EventScript extends Script { + onPointerEnter(eventData: PointerEventData): void { + console.log(this.entity.name, "onPointerEnter"); + } + + onPointerDown(eventData: PointerEventData): void { + console.log(this.entity.name, "onPointerDown"); + } + + onPointerUp(eventData: PointerEventData): void { + console.log(this.entity.name, "onPointerUp"); + } + + onPointerClick(eventData: PointerEventData): void { + console.log(this.entity.name, "onPointerClick"); + } + + onPointerExit(eventData: PointerEventData): void { + console.log(this.entity.name, "onPointerExit"); + } + + onPointerBeginDrag(eventData: PointerEventData): void { + console.log(this.entity.name, "onPointerBeginDrag"); + } + + onPointerEndDrag(eventData: PointerEventData): void { + console.log(this.entity.name, "onPointerEndDrag"); + } + + onPointerDrop(eventData: PointerEventData): void { + console.log(this.entity.name, "onPointerDrop"); + } +} diff --git a/examples/ui-Image.ts b/examples/ui-Image.ts new file mode 100644 index 0000000000..a43bb9a2ca --- /dev/null +++ b/examples/ui-Image.ts @@ -0,0 +1,74 @@ +/** + * @title UI Image + * @category UI + * @thumbnail https://mdn.alipayobjects.com/merchant_appfe/afts/img/A*t4cXTbFa6kkAAAAAAAAAAAAADiR2AQ/original + */ + +import { AssetType, Sprite, SpriteDrawMode, Texture2D, WebGLEngine } from "@galacean/engine"; +import { CanvasRenderMode, Image, Text, UICanvas, UITransform } from "@galacean/engine-ui"; + +// Create engine +WebGLEngine.create({ canvas: "canvas" }).then((engine) => { + engine.canvas.resizeByClientSize(); + const rootEntity = engine.sceneManager.scenes[0].createRootEntity(); + + // Add canvas + const canvasEntity = rootEntity.createChild("canvas"); + const canvas = canvasEntity.addComponent(UICanvas); + + canvas.renderMode = CanvasRenderMode.ScreenSpaceOverlay; + canvas.referenceResolutionPerUnit = 50; + + // Add Image + const simpleImageEntity = canvasEntity.createChild("Image"); + const simpleImage = simpleImageEntity.addComponent(Image); + simpleImage.drawMode = SpriteDrawMode.Simple; + const simpleImageTransform = simpleImageEntity.transform; + simpleImageTransform.position.set(-300, 0, 0); + simpleImageTransform.size.set(200, 70); + + const simpleTextEntity = canvasEntity.createChild("Text"); + simpleTextEntity.transform.setPosition(-300, 70, 0); + const simpleText = simpleTextEntity.addComponent(Text); + simpleText.text = "Simple Image"; + + const slicedImageEntity = canvasEntity.createChild("Image"); + const slicedImage = slicedImageEntity.addComponent(Image); + slicedImage.drawMode = SpriteDrawMode.Sliced; + const slicedImageTransform = slicedImageEntity.transform; + slicedImageTransform.size.set(200, 70); + + const slicedTextEntity = canvasEntity.createChild("Text"); + slicedTextEntity.transform.setPosition(0, 70, 0); + const slicedText = slicedTextEntity.addComponent(Text); + slicedText.text = "Sliced Image"; + + const tiledImageEntity = canvasEntity.createChild("Image"); + const tiledImage = tiledImageEntity.addComponent(Image); + tiledImage.drawMode = SpriteDrawMode.Tiled; + const tiledImageTransform = tiledImageEntity.transform; + tiledImageTransform.position.set(300, 0, 0); + tiledImageTransform.size.set(200, 70); + + const tiledTextEntity = canvasEntity.createChild("Text"); + tiledTextEntity.transform.setPosition(300, 70, 0); + const tiledText = tiledTextEntity.addComponent(Text); + tiledText.text = "Tiled Image"; + + engine.resourceManager + .load({ + url: "https://mdn.alipayobjects.com/huamei_yo47yq/afts/img/A*mFpSS502qUYAAAAAAAAAAAAAehuCAQ/original", + type: AssetType.Texture2D + }) + .then((texture) => { + const simpleSprite = new Sprite(engine, texture); + const slicedSprite = new Sprite(engine, texture); + slicedSprite.border.set(0.49, 0.49, 0.49, 0.49); + const tiledSprite = new Sprite(engine, texture); + simpleImage.sprite = simpleSprite; + slicedImage.sprite = slicedSprite; + tiledImage.sprite = tiledSprite; + }); + + engine.run(); +}); diff --git a/examples/ui-Text.ts b/examples/ui-Text.ts new file mode 100644 index 0000000000..fadafb4168 --- /dev/null +++ b/examples/ui-Text.ts @@ -0,0 +1,88 @@ +/** + * @title UI Text + * @category UI + * @thumbnail https://mdn.alipayobjects.com/merchant_appfe/afts/img/A*t4cXTbFa6kkAAAAAAAAAAAAADiR2AQ/original + */ + +import { Color, Entity, Font, FontStyle, Vector3, WebGLEngine } from "@galacean/engine"; +import { CanvasRenderMode, Text, UICanvas } from "@galacean/engine-ui"; + +// Create engine +WebGLEngine.create({ canvas: "canvas" }).then((engine) => { + engine.canvas.resizeByClientSize(); + const rootEntity = engine.sceneManager.scenes[0].createRootEntity(); + + // Add canvas + const canvasEntity = rootEntity.createChild("canvas"); + const canvas = canvasEntity.addComponent(UICanvas); + + canvas.renderMode = CanvasRenderMode.ScreenSpaceOverlay; + + // The position of text + const pos = new Vector3(); + // The color of text + const color = new Color(); + + // Create text with default params + pos.set(0, 125, 0); + color.set(1, 1, 1, 1); + createText(); + // Create text with cursive font family + pos.set(0, 75, 0); + color.set(1, 1, 1, 1); + const entity = createText("cursive"); + for (let i = 0; i < 10; i++) { + entity.clone(); + } + // Create text with font size 36 + pos.set(0, 25, 0); + color.set(1, 0.5, 0.5, 1); + createText("Arial", 36); + // Create text with bold + pos.set(0, -25, 0); + color.set(1, 1, 1, 1); + createText("Arial", 26, true); + // Create text with italic + pos.set(0, -75, 0); + color.set(1, 1, 1, 1); + createText("Arial", 26, false, true); + // Create text with bold and italic + pos.set(0, -125, 0); + color.set(1, 1, 1, 1); + createText("Arial", 26, true, true); + + engine.run(); + + /** + * Create text to display by params. + * @param fontFamily - The font family + * @param fontSize - The size of font + * @param bold - The text whether bold + * @param italic - The text whether italic + */ + function createText( + fontFamily: string = "Arial", + fontSize: number = 26, + bold: boolean = false, + italic: boolean = false + ): Entity { + // Create text entity + const entity = canvasEntity.createChild("text"); + entity.transform.position = pos; + // Add text renderer for text entity + const text = entity.addComponent(Text); + // Set text color + text.color = color; + // Set text to render + text.text = "The quick brown fox jumps over the lazy dog"; + // Set font with font family + text.font = Font.createFromOS(entity.engine, fontFamily); + // Set font size + text.fontSize = fontSize; + // Set font whether bold + bold && (text.fontStyle |= FontStyle.Bold); + // Set font whether italic + italic && (text.fontStyle |= FontStyle.Italic); + return entity; + } +}); diff --git a/tests/src/core/audio/AudioSource.test.ts b/tests/src/core/audio/AudioSource.test.ts index af85d28032..9699e988c1 100644 --- a/tests/src/core/audio/AudioSource.test.ts +++ b/tests/src/core/audio/AudioSource.test.ts @@ -1,4 +1,4 @@ -import { AssetType, AudioClip, AudioSource, Engine } from "@galacean/engine-core"; +import { AssetType, AudioClip, AudioManager, AudioSource, Engine } from "@galacean/engine-core"; import "@galacean/engine-loader"; import { WebGLEngine } from "@galacean/engine-rhi-webgl"; import { beforeAll, describe, expect, it } from "vitest"; @@ -54,7 +54,11 @@ describe("AudioSource", () => { audioSource.stop(); audioSource.play(); - // Because the audio play should interaction - expect(audioSource.isPlaying).to.be.false; + if (AudioManager.isAudioContextRunning()) { + expect(audioSource.isPlaying).to.be.true; + } else { + // Because the audio play should interaction + expect(audioSource.isPlaying).to.be.false; + } }); });