From 201bf0568fb7799cbf7c39f5970d71e96dd4a69c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=A8=E9=B3=90?= Date: Wed, 15 Jan 2025 11:55:06 +0800 Subject: [PATCH] feat: update doc --- docs/en/graphics/2D/spine/editor.md | 133 ++++---- docs/en/graphics/2D/spine/example.md | 50 ++- docs/en/graphics/2D/spine/other.md | 41 +-- docs/en/graphics/2D/spine/overview.md | 25 +- docs/en/graphics/2D/spine/runtime.md | 437 ++++++++++++++------------ docs/zh/graphics/2D/spine/editor.md | 68 ++-- docs/zh/graphics/2D/spine/example.md | 29 +- docs/zh/graphics/2D/spine/other.md | 25 +- docs/zh/graphics/2D/spine/overview.md | 2 +- docs/zh/graphics/2D/spine/runtime.md | 221 +++++++------ examples/spine-animation.ts | 16 +- examples/spine-change-attachment.ts | 20 +- examples/spine-follow-shoot.ts | 28 +- examples/spine-full-skin-change.ts | 20 +- examples/spine-mix-and-match.ts | 23 +- examples/spine-performance.ts | 17 +- examples/spine-physics.ts | 23 +- 17 files changed, 611 insertions(+), 567 deletions(-) diff --git a/docs/en/graphics/2D/spine/editor.md b/docs/en/graphics/2D/spine/editor.md index 74632ae8c0..078b01168b 100644 --- a/docs/en/graphics/2D/spine/editor.md +++ b/docs/en/graphics/2D/spine/editor.md @@ -6,128 +6,131 @@ group: Spine label: Graphics/2D/Spine/editor --- -The Galacean editor has built-in support for Spine animations, no additional downloads or configurations are needed, making the development process much simpler. This chapter introduces how to use Spine animations in the Galacean editor. +The Galacean editor has built-in support for Spine animations, requiring no additional downloads or configurations, making the development process significantly easier. This chapter introduces how to use Spine animations in the Galacean editor. -> For editor version dependencies, please refer to: [Version/Performance Chapter](/en/docs/graphics/2D/spine/other) +> For editor version dependencies, please refer to: [Version/Performance Section](/docs/graphics/2D/spine/other) -## 1. Export Assets from Spine Editor -The first step is to export your Spine animation assets from the Spine editor. You can find the complete steps in the [Spine User Guide](https://zh.esotericsoftware.com/spine-user-guide), which explains how to: + +## 1. Exporting Assets from the Spine Editor +The first step is to export your Spine animation assets from the Spine editor. You can find the complete steps in the ["Spine User Guide"](https://zh.esotericsoftware.com/spine-user-guide), which explains how to: 1. [Export skeleton and animation data](https://zh.esotericsoftware.com/spine-export) 2. [Export texture atlases containing skeleton images](https://zh.esotericsoftware.com/spine-texture-packer) -Below is a brief process of exporting assets from Spine: +Below is a brief overview of the Spine asset export process: + +1. After completing the animation, click `Spine Menu` > `Export` to open the export window. -1. After completing the animation, click `Spine Menu` > `Export` to open the export window +Export panel in Spine editor -Export panel in Spine editor +2. In the upper left corner of the export window, select **Binary** (recommended to export in binary format instead of JSON format, as it results in smaller file sizes and faster loading). -2. Select **Binary** in the upper left corner of the export window (it is recommended to use binary, exporting in binary format instead of JSON format will make the file size smaller and load faster) +Export window in Spine editor -Export window in Spine editor +3. Check the **Texture Atlas** packing checkbox. -3. Check the **Texture Atlas** packing checkbox +Click packing texture atlas button in Export window -Click packing texture atlas button in Export window +4. Click **Packing Settings**. -4. Click **Packing Settings** +The packing settings refer to the texture packing configurations. Refer to the [official documentation](https://zh.esotericsoftware.com/spine-texture-packer) for detailed parameters. After completing the packing settings, click **OK**. -Here it is recommended to check `Power of 2`; do not check `Premultiply` and `Bleed` -After completing the packing settings, click **OK** -Texture pack window in Spine Editor +Texture pack window in Spine Editor -5. Return to the export window, select the export folder, and click **Export** +5. Return to the export window, select the export folder, and click **Export**. -Click export button in texture pack window +Click export button in texture pack window 6. You will get the following three files: -Spine assets in folder +Spine assets in folder + +- spineboy.skel: Contains skeleton and animation data, the core information for binding animation actions to the skeleton. +- spineboy.atlas: Stores information about the texture atlas, including the position and size of each texture in the atlas. +- Texture images: These may include multiple images, with each representing a page in the texture atlas used for rendering the visual content of animated characters. -spineboy.skel contains skeleton animation data, spineboy.atlas contains texture atlas information, and the exported images may be multiple, each representing a page in the texture atlas. +## 2. Importing Assets into the Galacean Editor +The second step is to import the files exported from the Spine editor into the Galacean editor. -## 2. Import assets into the Galacean editor -After exporting assets from the Spine editor, the second step is to import the assets into the Galacean editor. Open the editor and drag the exported files directly into the [Assets Panel](/en/docs/assets/interface/) to complete the upload. +After opening the editor, drag the exported files directly into the [Assets Panel](/docs/assets/interface/) to upload them, as shown in the following GIF: -Drag spine assets into Galacean editor +Drag spine assets into Galacean editor -You can also click the upload button in the assets panel to upload: +You can also click the upload button in the assets panel and select the files to upload: -Use upload button to upload spine assets +Use upload button to upload spine assets +
-After the upload is complete, you will see the uploaded spine assets in the assets panel. +Once uploaded, you will see the uploaded Spine assets in the assets panel, including: SpineSkeletonData assets, SpineAtlas assets, and texture assets. ### SpineSkeletonData Asset -Spine skeleton data asset icon +Spine skeleton data asset icon -The SpineSkeletonData asset stores skeleton data and references the generated SpineAtlas asset. -After clicking the asset, you can preview the Spine animation in the inspector. In the preview panel, you can switch `skins` and `animation clips`: +The SpineSkeletonData asset stores skeleton data and references to the generated SpineAtlas asset. By clicking the asset, you can preview the Spine animation in the inspector, where you can switch between `Skin` and `Animation Clips` in the preview panel: -Spine skeleton data preview +Spine skeleton data preview ### SpineAtlas Asset -Spine atlas asset - -The SpineAtlas asset stores the texture atlas file and includes references to the required Texture assets. -After clicking the asset, you can view its referenced Texture assets and Spine's atlas information in the inspector. - -Spine atlas preview +Spine atlas asset -### Asset Update -If you need to update your Spine assets, re-export the assets from the Spine editor and re-import them into the Galacean editor to overwrite the original files. +The SpineAtlas asset stores the texture atlas file and contains references to the required texture assets. By clicking the asset, you can view its referenced texture assets and atlas information in the inspector. +Spine atlas preview -## 3. Adding Spine Animation +### Updating Assets +If you need to update your Spine assets, re-export the assets from the Spine editor and import them into the Galacean editor to overwrite the existing files. -After uploading the assets, the third step is to add the Spine animation to the scene. There are three ways to do this: -1. Drag and Drop to Add +## 3. Adding Spine Animations -Drag and drop is the quickest way. Click on the SpineSkeletonData asset, hold it, and drag it into the viewport. This will quickly create an entity with the SpineAnimationRenderer component added, and the asset will be set to the selected SpineSkeletonData asset. +After uploading the assets, you can add Spine animations to the scene using the following three methods: -Drag Spine skeleton data asset into viewport +### Drag-and-Drop -2. Quick Add +Drag-and-drop is the quickest method. Click the SpineSkeletonData asset, hold and drag it into the viewport to quickly create an entity with the SpineAnimationRenderer component and specify the selected SpineSkeletonData asset. -Click the quick add button in the top left corner, select `2D Object`>`SpineAnimationRenderer`, +Drag Spine skeleton data asset into viewport -Quick add Spine animation renderer +### Quick Add -After adding, you will see a new entity with the SpineAnimationRenderer component attached. Click the Resource property and select the uploaded SpineSkeletonData asset to see the Spine animation. +Click the quick add button in the upper left corner and select `2D Object` > `SpineAnimationRenderer`. -Select spine skeleton data asset in component panel +Quick add Spine animation renderer -3. Manual Add +After adding, you will see a new entity with the SpineAnimationRenderer component. Click the Resource property and select the uploaded SpineSkeletonData asset to see the Spine animation. -The manual add method is similar to the quick add method, but you need to manually create a new entity in the node tree and add the SpineAnimationRenderer component via the AddComponent button in the inspector. +Select spine skeleton data asset in component panel -Use add component to add spine animation renderer +### Manual Add -After adding the SpineAnimationRenderer component, you also need to specify the resource of the component, which is the SpineSkeletonData asset that the SpineAnimationRenderer component will render. +The manual method is similar to quick add but requires manually creating a new entity in the node tree and adding the SpineAnimationRenderer component via the AddComponent button in the inspector. -### SpineAnimationRenderer Configuration +Use add component to add spine animation renderer -The three methods of adding Spine animations mentioned above are essentially the same, all achieved by adding the `SpineAnimationRenderer` component to the entity to incorporate Spine animations into the scene. +After adding the SpineAnimationRenderer component, you also need to specify the component's Resource, which is the SpineSkeletonData asset that the SpineAnimationRenderer component will render. -The configuration of the `SpineAnimationRenderer`` component is as follows: +### SpineAnimationRenderer Component Configuration -Spine animation renderer component config +All three methods for adding Spine animations are essentially the same: by adding the SpineAnimationRenderer component to an entity to incorporate Spine animations into the scene. -Through the `SpineAnimationRenderer` component, you can configure the assets and default state of Spine animations. +The SpineAnimationRenderer component configuration is as follows: -- Resource: Resource for Spine animations (SpineSkeletonData asset) -- Animation: Default animation to play -- Loop: Whether the default animation loops -- Skin: Default skin name -- Scale: Default scaling factor -- Priority: Rendering priority +Spine animation renderer component config +Using the SpineAnimationRenderer component, you can configure the Spine animation assets and default states: -## 4. Exporting the Project +- Resource: The resource for the Spine animation (SpineSkeletonData asset) +- Animation: The default animation name to play +- Loop: Whether the default animation plays in a loop +- Skin: The default skin name +- Priority: Render priority +- PremultiplyAlpha: Whether to render the animation in premultiplied alpha mode -Finally, after completing the scene editor, you can refer to the [project export](/en/docs/assets/build/) process to export the editor project. +## 4. Project Export +Finally, after completing scene editing, you can refer to the [Project Export](/docs/assets/build/) process to export the editor project. -Next section: [Using Galacean Spine Runtime in Your Code](/en/docs/graphics/2D/spine/runtime) +



+Next Chapter: [Using in Code](/en/docs/graphics/2D/spine/runtime) diff --git a/docs/en/graphics/2D/spine/example.md b/docs/en/graphics/2D/spine/example.md index aa6b57e5b8..85e882f49b 100644 --- a/docs/en/graphics/2D/spine/example.md +++ b/docs/en/graphics/2D/spine/example.md @@ -1,41 +1,67 @@ --- order: 3 -title: Spine Example +title: Examples and Templates type: Graphics group: Spine label: Graphics/2D/Spine/example --- +## Templates + +The Galacean editor provides a series of tutorial templates to help you quickly get started with using Spine animations. +After entering the [editor](https://galacean.antgroup.com/editor/projects), you can click on the Templates Tab on the left to view these templates. +

+ +**Animation Control** + +This template demonstrates how to use the `setAnimation` and `addAnimation` APIs of `AnimationState` to orchestrate Spine animations: +spine-animation + +**Animation Transition and Blending** + +This template shows how to set transitions and blend animations between different tracks in Spine: +spine-mix-blend + +**Mix and Match Outfits** + +This template demonstrates the capability of Spine to mix and match outfits. By freely combining attachments from different skins, you can mix and match accessories from various skins: +mix-and-match + +**Dynamic Partial Skin Switching** + +This template showcases the ability to dynamically switch partial skins. You can create new attachments based on an additionally uploaded atlas and replace them dynamically. +spine-dynamic-change + +## Examples + **Animation Control** -This example demonstrates how to orchestrate a spine animation queue using the setAnimation and addAnimation APIs: +This example demonstrates how to orchestrate a Spine animation queue using the `setAnimation` and `addAnimation` APIs: **Follow Shooting** -This example demonstrates how to achieve aiming and shooting effects by modifying the IK bone position: +This example shows how to achieve an aiming and shooting effect by modifying the IK bone position: -**Partial Skin Change** +**Partial Skin Switching** -This example demonstrates how to achieve partial skin change by modifying the attachments in the slots: +This example demonstrates how to achieve a partial outfit change by modifying attachments in slots: -**Full Skin Change** +**Full Skin Switching** -This example demonstrates how to achieve a full skin change using the setSkin method: +This example demonstrates how to achieve full skin switching using the `setSkin` method: **Skin Mixing** -This example demonstrates how to achieve mixing effects by combining new skins at runtime: +This example demonstrates how to achieve mix-and-match effects at runtime by combining new skins: **Physics** -This example demonstrates physics-based animation effects in spine version 4.2: +This example showcases the physics-based animation effects in Spine 4.2: - - -Next Chapter: [Version and Performance](/en/docs/graphics/2D/spine/other) +The next chapter: [Versions and Performance](/docs/graphics/2D/spine/other) diff --git a/docs/en/graphics/2D/spine/other.md b/docs/en/graphics/2D/spine/other.md index e4d5764a11..f6b98df2bf 100644 --- a/docs/en/graphics/2D/spine/other.md +++ b/docs/en/graphics/2D/spine/other.md @@ -6,27 +6,32 @@ group: Spine label: Graphics/2D/Spine/other --- -### Spine Version -@galacen/engine-spine has supported spine 4.x versions since version 1.2.
-From version 1.2 onwards, the major version and minor version of the @galacen/engine-spine package correspond exactly to the spine version, as follows:
-- @galacean/engine-spine <= 1.2 corresponds to spine version 3.8 -- @galacean/engine-spine 4.0 corresponds to spine version 4.0 -- @galacean/engine-spine 4.1 corresponds to spine version 4.1 -- @galacean/engine-spine 4.2 corresponds to spine version 4.2 -- ..... +### Upgrades and Changes in 1.4 +After upgrading to Editor version 1.4, besides updating the engine version in the editor's [Project Settings](/docs/interface/menu/#project-settings), please note the following changes to the 1.4 Spine API: -Currently, the 4.2 beta version has been released, and versions 4.1 and 4.0 will be released gradually. +1. We no longer recommend creating Spine animations using the method: adding the `SpineAnimationRenderer` component via `addComponent` + setting the resource using `set SpineResource`.
+In version 1.4, we introduced an instantiation method `instantiate` for SpineResource. The `instantiate` method returns a Spine animation entity that uses the resource, making the creation process much faster and more convenient. -### Version Upgrade -After upgrading to editor version 1.3, besides upgrading the engine version in the editor's [project settings](/en/docs/interface/menu/#项目设置), since the exported JSON or binary Spine editor version needs to [stay consistent](https://zh.esotericsoftware.com/spine-versioning#%E5%90%8C%E6%AD%A5%E7%89%88%E6%9C%AC) with the runtime version, after upgrading the editor to 1.3, `you also need to re-export the Spine assets of version 4.2 and upload them to the editor, completing the asset update by file overwrite`. +2. `defaultState` has been renamed to `defaultConfig`. This parameter represents the default configuration of the Spine animation. The renamed parameter makes its purpose clearer. -### Performance Suggestions -Here are some methods to optimize spine animation performance: +3. The `scale` configuration for default states has been removed. Previously, the `scale` parameter was used to handle the pixel ratio of Spine animations to keep their size consistent with other objects in the engine. In version 1.4, Spine animations no longer require setting a default scale to correct their size. We recommend adjusting the Spine animation scale by modifying the entity's `scale` parameter. + +4. A new `premultipliedAlpha` parameter has been added in version 1.4 for enabling premultiplied alpha rendering. When exporting animations from the Spine Editor, if premultiplication is checked during texture packing, enable the `premultipliedAlpha` option. + + +### Performance Recommendations +Here are some tips to optimize the performance of Spine animations: + +1. Export skeletons in binary file format (.skel), which results in smaller file sizes and faster loading. + +2. Minimize the number of atlas pages by packing attachments into as few atlas pages as possible and grouping attachments in the atlas according to the draw order to avoid unnecessary material switches. Refer to [Spine Texture Packer: Folder Structure](https://zh.esotericsoftware.com/spine-texture-packer#%E6%96%87%E4%BB%B6%E5%A4%B9%E7%BB%93%E6%9E%84) for details on how to arrange atlas regions in your Spine atlas. + +3. Avoid excessive use of clipping. Spine implements clipping by dynamically cutting triangles, which is very resource-intensive. + +4. Minimize the use of atlas page textures. When exporting, aim to keep the number of texture pages as low as possible. + +5. Try to use a single atlas texture for multiple skeletons. For example, add multiple skeletons in the same Spine project and select a single atlas during export. This way, multiple skeletons will share the same atlas texture. -1. Export the skeleton in binary file (.skel) format, as binary files are smaller and load faster. -2. It is recommended to pack attachments into as few atlas pages as possible, and group attachments into atlas pages according to the drawing order to prevent unnecessary material switching. Please refer to: [Spine Texture Packer: Folder Structure](https://zh.esotericsoftware.com/spine-texture-packer#%E6%96%87%E4%BB%B6%E5%A4%B9%E7%BB%93%E6%9E%84) to learn how to arrange atlas regions in your Spine atlas. -3. Use the clipping feature sparingly. Spine's clipping implementation is done through dynamic triangle clipping, which is very performance-intensive. -4. Minimize the use of atlas page textures. That is, try to control the number of textures exported to one. ### Questions -For any questions about Spine, feel free to [create an issue](https://github.com/galacean/engine-spine/issues/new) on @galacean/engine-spine. +If you have any questions about Spine, feel free to [create an issue](https://github.com/galacean/engine-spine/issues/new) in the @galacean/engine-spine repository. diff --git a/docs/en/graphics/2D/spine/overview.md b/docs/en/graphics/2D/spine/overview.md index 80d0c19cc6..38a2220042 100644 --- a/docs/en/graphics/2D/spine/overview.md +++ b/docs/en/graphics/2D/spine/overview.md @@ -6,18 +6,17 @@ group: Spine label: Graphics/2D/Spine/overview --- -Spine animation is a 2D skeletal animation tool designed for game development. It binds images to bones and then controls the bones to create animations. It meets the program's need for control and flexibility over animations, while also providing a more efficient and streamlined workflow for artists and designers. -Compared to traditional frame-by-frame animation, Spine animation has the following advantages: +Spine animation is a 2D skeletal animation tool specifically designed for game development. By binding images to bones and then controlling the bones to create animations, it offers flexibility and control for developers while providing an efficient and streamlined workflow for artists and designers. Compared to traditional frame-by-frame animations, Spine animations offer the following advantages: -- **Smaller size:** Traditional animations require providing an image for each frame. Spine animation only saves the animation data of the bones, which takes up very little space. -- **Art requirements:** Spine animation requires fewer art resources, saving you more manpower and resources to better invest in game development. -- **Smoothness:** Spine animation uses interpolation algorithms to calculate intermediate frames, ensuring that your animations always remain smooth. -- **Equip attachments:** Images are bound to bones to create animations. If needed, you can easily change the character's equipment to meet different requirements. You can even change the character's appearance to achieve animation reuse. -- **Blending:** Animations can be blended. For example, a character can shoot while walking, running, jumping, or swimming. -- **Programmatic animation:** Bones can be controlled through code, allowing for effects such as shooting following the mouse, looking at enemies, or leaning forward when going uphill. +- **Smaller Size:** Traditional animations require every frame to be provided as an image. In contrast, Spine animations only store skeletal animation data, resulting in significantly smaller file sizes. +- **Reduced Art Requirements:** Spine animations require fewer art resources, allowing more manpower and resources to be allocated to other aspects of game development. +- **Smoothness:** Spine animations use interpolation algorithms to calculate in-between frames, ensuring consistently smooth animation playback. +- **Attachment Swapping:** Images are bound to bones to create animations. You can easily swap character equipment or even alter the character's appearance to reuse animations for different purposes. +- **Blending:** Animations can be blended seamlessly. For example, a character can shoot while walking, running, jumping, or swimming. +- **Programmatic Animations:** Bones can be controlled via code, enabling effects such as shooting that follows the mouse, focusing on enemies, or leaning forward when climbing a slope. -This section will introduce: -- [How to use Spine animation in the Galacean editor](/en/docs/graphics/2D/spine/editor) -- [How to use the Galacean spine runtime in code](/en/docs/graphics/2D/spine/runtime) -- [Spine animation examples](/en/docs/graphics/2D/spine/example) -- [Other content (versions, performance)](/en/docs/graphics/2D/spine/other) +This chapter will introduce: +- [How to use Spine animations in the Galacean editor](/en/docs/graphics/2D/spine/editor) +- [How to use the Galacean Spine runtime in code](/en/docs/graphics/2D/spine/runtime) +- [Examples and templates](/en/docs/graphics/2D/spine/example) +- [Additional topics (versions, performance)](/en/docs/graphics/2D/spine/other) diff --git a/docs/en/graphics/2D/spine/runtime.md b/docs/en/graphics/2D/spine/runtime.md index 95f7727d86..684f07484f 100644 --- a/docs/en/graphics/2D/spine/runtime.md +++ b/docs/en/graphics/2D/spine/runtime.md @@ -6,242 +6,200 @@ group: Spine label: Graphics/2D/Spine/runtime --- -This chapter introduces how to use the Galacean Spine runtime in your code. +This chapter introduces how to use Galacean Spine in your code. ## Installation -Whether the project is exported from the editor or a procode project, you need to install `@galacean/engine-spine` (the Galacean Spine runtime) to load and render Spine animations. + +Whether you are working with an exported project from the editor or a procode project, you need to install `@galacean/engine-spine` (the Galacean Spine runtime) to load and render Spine animations. ```typescript npm install @galacean/engine-spine --save ``` -After installation, import it in your code: +After a successful installation, import it in your code: ```typescript import { SpineAnimationRenderer } from "@galacean/engine-spine"; ``` -Once `@galacean/engine-spine` is installed and imported, the editor's `resourceManager` can recognize and load Spine animation assets. -The Galacean Spine loader supports both assets uploaded via the editor and custom-uploaded assets. +After installing and importing `@galacean/engine-spine`, the editor's `ResourceManager` will be able to recognize and load Spine animation assets. -## Load Assets and Add Them to the Scene +## Load Assets and Add to Scene -### Load Assets Uploaded via the Galacean Editor -[After exporting the editor project](/docs/assets/build/), `Spine animations already added to the scene will automatically load when the scene file is loaded`: +### Load assets uploaded via the editor ```typescript -// Spine animations already added to the scene will automatically load when loading the scene file +// When loading scene files, Spine animations already added to the scene will be loaded automatically. await engine.resourceManager.load({ url: projectInfo.url, type: AssetType.Project, -}) +}); ``` -If not added to the scene, you need to load it manually in the code, as follows: -1. First, download the editor project. - -Note: The `Upload Assets to CDN` option determines whether the animation is loaded via a CDN link or using a local file's relative path. - -Project export panel - -2. Locate the Spine asset file. - -After downloading the project locally, open the `project.json` file and find the `url` property. +If not added to the scene, you need to load it manually in the code. Follow these steps: +1. Copy the SkeletonDataAsset link. +Right-click on the SkeletonDataAsset, select `Copy relative path` to copy the asset path. + -If `Upload Assets to CDN` was checked, you can find the Spine asset link in the JSON file: -Find Spine asset +2. Use ResourceManager to load -If not checked, you can find the Spine asset in the local `public` folder. Use the relative path as the link when loading. -Find Spine asset - -2. Load using `resourceManager`. - -After obtaining the Spine skeleton file's asset link, use `resourceManager` to load it. To manually add Spine to the scene, create a new entity and add the `SpineAnimationRenderer` component, as follows: +After obtaining the asset path, use the `resourceManager` to load it as shown below: ```typescript import { SpineAnimationRenderer } from '@galacean/engine-spine'; -// Load and obtain the Spine resource +// Load and obtain the spine resource const spineResource = await engine.resourceManager.load({ - url: 'https://galacean.raptor.json', // Or the relative file path, e.g., '../public/raptor.json"' - type: 'spine', // The loader type must be specified as 'spine' + url: '/raptor.json', // The copied relative path + type: 'Spine', // Specify the loader type as Spine }); -// Create a new entity -const spineEntity = new Entity(engine); -// Add the SpineAnimationRenderer component -const spine = spineEntity.addComponent(SpineAnimationRenderer); -// Set the animation resource -spine.resource = spineResource; +// Instantiate a Spine animation entity +const spineEntity = spineResource.instantiate(); // Add to the scene root.addChild(spineEntity); ``` -### Load Custom Uploaded Assets -1. Load the asset +### Load custom uploaded assets +#### 1. Load assets -If your Spine asset was not uploaded via the Galacean editor but uploaded to a CDN through a third-party platform, you can still load it using the Galacean Spine runtime loader. +If your Spine assets were not uploaded via the Galacean editor but through a third-party platform to a CDN, you can still load them using the Galacean Spine runtime loader. ```typescript const resource = await engine.resourceManager.load({ url: 'https://your.spineboy.json', // Custom uploaded asset - type: 'spine', // The loader type must be specified as 'spine' + type: 'spine', // Specify the loader type as Spine }); ``` + When loading custom uploaded assets: -- If passing a `url`, `ensure that the atlas and texture resources are in the same directory as the skeleton file`, e.g.: -
https://your.spineboy.json
https://your.spineboy.atlas
https://your.spineboy.png
-All three files must be in the same directory. +- If passing the parameter as `url`, ensure the files are in the same directory, such as:
+https://your.spineboy.json
+https://your.spineboy.atlas
+https://your.spineboy.png
-- If passing `urls` (multiple links), the same directory condition is not required: +- If passing the parameter as `urls` (multiple links), the files do not need to be in the same directory: ```typescript const resource = await engine.resourceManager.load({ urls: [ 'https://your.spineboy.json', - 'https://another-path1.spineboy.altas', - 'https://another-path2.spineboy.png', - ], // Custom uploaded asset - type: 'spine', // The loader type must be specified as 'spine' + 'https://ahother-path1.spineboy.atlas', + 'https://ahother-path2.spineboy.png', + ], + type: 'spine', // Specify the loader type as Spine }); ``` -- If no texture address is provided, the loader will read the texture's image name from the atlas file and look for the texture resource relative to the atlas file's path. -
-- If the custom uploaded asset has no file extension (e.g., blob protocol URLs), you can add a URL query parameter, e.g.: -
https://your.spineboyjson?ext=.json
https://your.spineboyatlas?ext=.atlas
-Alternatively, use the `fileExtensions` parameter to specify resource suffix types: + +- If no texture URL is provided, the loader will read the texture image name from the atlas file and look for the texture in the same directory as the atlas file.
+- If the custom uploaded asset lacks file extensions, you can add URL query parameters to the links, e.g.:
+https://your.spineboyjson?ext=.json,
+https://your.spineboyatlas?ext=.atlas
+ +- If the Spine animation atlas includes multiple images (e.g., a.png and b.png), follow the order recorded in the atlas file to pass the image URLs: ```typescript const resource = await engine.resourceManager.load({ urls: [ - 'https://your.spineboyjson', - 'https://another-path1.spineboyatlas', - 'https://another-path2.spineboypng', - ], // Custom uploaded asset - type: 'spine', - fileExtensions: [ - 'json', // Specify the first file as having a '.json' extension - 'atlas', // Specify the second file as having a '.atlas' extension - 'png', // Specify the third file as having a '.png' extension - ] + 'https://your.spineboy.json', + 'https://your.spineboy.atlas', + 'https://your.spineboy1.png', // Corresponds to a.png + 'https://your.spineboy2.png' // Corresponds to b.png + ], + type: 'spine', // Specify the loader type as Spine }); ``` -- If the Spine animation's texture atlas contains multiple images, pass the image addresses in the order they appear in the atlas file. -2. Add to the scene +#### 2. Add to the scene -After loading, manually create an entity and add the `SpineAnimationRenderer` component: +After loading, instantiate a Spine animation entity and add it to the scene: ```typescript import { SpineAnimationRenderer } from '@galacean/engine-spine'; const spineResource = await engine.resourceManager.load({ url: 'https://your.spineboy.json', // Custom uploaded asset - type: 'spine', + type: 'Spine', }); -// Create an entity -const spineEntity = new Entity(engine); -// Add the SpineAnimationRenderer component -const spine = spineEntity.addComponent(SpineAnimationRenderer); -// Set the animation resource -spine.resource = spineResource; +// Instantiate a Spine animation entity +const spineEntity = spineResource.instantiate(); // Add to the scene root.addChild(spineEntity); ``` -## Using the Runtime API +## More Runtime APIs -In the [previous chapter](/docs/graphics/2D/spine/editor), we introduced the configuration options for the `SpineAnimationRenderer` component in the editor. -This section provides a more detailed introduction to using various APIs of the `SpineAnimationRenderer` component in code. +In the [previous chapter](/docs/graphics/2D/spine/editor), we introduced the configuration options of the SpineAnimationRenderer component in the editor. +This section will explain in detail how to use each API of the SpineAnimationRenderer component in code. -The `SpineAnimationRenderer` component inherits from `Renderer`. In addition to exposing general methods of `Renderer`, it provides the following properties: +The SpineAnimationRenderer component inherits from Renderer. In addition to exposing the common methods of Renderer, it provides the following properties: -| Property | Description | -| :---------------- | :------------------------------------------------------------------------------------------------ | -| resource | Spine animation resource. After setting the resource, the `SpineAnimationRenderer` component reads the resource data and renders the Spine animation. | -| setting | Rendering settings. Used to enable clipping and adjust layer spacing. | -| defaultState | Default state. Corresponding to the editor configuration options, used to set the default animation, skin, and scaling for Spine animation. | -| state | Animation state object. Enables advanced animation control, such as queued playback and loop control. | -| skeleton | Skeleton object. Used for advanced skeletal operations, such as attachment replacement and skin switching. | +| Property | Description | +| :---------------- | :-------------------------------------------------------------------------- | +| defaultConfig | Default configuration. Corresponds to the editor's configuration options and is used to set the default animation and skin of Spine | +| state | Animation state object. Used for more complex animation controls, such as queue playback, loop control, etc. | +| skeleton | Skeleton object. Used for more complex skeleton operations, such as attachment replacement, skin switching, etc. | +| premultipliedAlpha | Premultiplied Alpha setting. Controls whether to enable premultiplied alpha mode during rendering | -Below is a more detailed usage introduction: +### Default Configuration -### Resource Setting -First, you need to set the resource. The `SpineAnimationRenderer` component can only render the Spine animation after setting the resource. -In the previous chapter, "Loading Assets and Adding Them to the Scene", we already demonstrated how to set the resource: -```typescript -import { SpineAnimationRenderer } from '@galacean/engine-spine'; - -const spineResource = await engine.resourceManager.load({ - url: 'https://your.spineboy.json', - type: 'spine', -}); -const spineEntity = new Entity(engine); -const spine = spineEntity.addComponent(SpineAnimationRenderer); -spine.resource = spineResource; // Set Spine resource -root.addChild(spineEntity); -``` - -### Rendering Settings -In your script, you can modify the rendering settings of Spine as follows. Generally, the default values are sufficient: +In the script, you can use the `defaultConfig` parameter to set the default animation and skin for Spine: ```typescript class YourAmazingScript { - - onStart() { - const spine = this.entity.getComponent(SpineAnimationRenderer); - spine.setting.zSpacing = 0.01; // Set layer spacing - spine.setting.useClipping = true; // Enable or disable clipping, enabled by default - } - -} -``` - -### Default State -In your script, you can modify the default state of the Spine animation as follows: -```typescript -class YourAmazingScript { - async onStart() { const spineResource = await engine.resourceManager.load({ url: 'https://your.spineboy.json', type: 'spine', }); - const spineEntity = new Entity(engine); - const spine = spineEntity.addComponent(SpineAnimationRenderer); + const spineEntity = spineResource.instantiate(); + const spine = spineEntity.getComponent(SpineAnimationRenderer); spine.defaultState.animationName = 'your-default-animation-name'; // Default animation name - spine.defaultState.loop = true; // Whether the default animation loops - spine.defaultState.skinName = 'default'; // Default skin name - spine.defaultState.scale = 0.02; // Default scaling - spine.resource = spineResource; // Set the resource - rootEntity.addChild(spineEntity); // Add to the scene, activating the component + spine.defaultState.loop = true; // Whether the default animation loops + spine.defaultState.skinName = 'default'; // Default skin name + rootEntity.addChild(spineEntity); // Add to the scene } - } -``` -Note: The default state only takes effect when the `SpineAnimationRenderer` component is activated and the resource is set. To dynamically modify the animation, skin, or scaling, use the `state` and `skeleton` properties (see later sections). +``` + +Note: Default configuration only takes effect when the SpineAnimationRenderer component is active. To dynamically modify animations and skins, use the `state` and `skeleton` properties (explained in the following sections). + +... ### Animation Control -In your script, you can access the [AnimationState](https://zh.esotericsoftware.com/spine-api-reference#AnimationState) object for advanced animation operations: + +In the script, you can obtain the [AnimationState](https://zh.esotericsoftware.com/spine-api-reference#AnimationState) object in the following way, which allows for more complex animation operations: + ```typescript class YourAmazingScript { - onStart() { const spine = this.entity.getComponent(SpineAnimationRenderer); const { state } = spine; // AnimationState object } - } ``` -#### **Playing Animations** -Let's first introduce the most commonly used API: [setAnimation](https://zh.esotericsoftware.com/spine-api-reference#AnimationState-setAnimation) + +#### **Play Animation** + +First, let's introduce the most commonly used API: [setAnimation](https://zh.esotericsoftware.com/spine-api-reference#AnimationState-setAnimation) ```typescript -state.setAnimation(0, 'animationName', true) +state.setAnimation(0, 'animationName', true); ``` -The `setAnimation` function accepts three parameters: +The `setAnimation` function takes three parameters: -- `TrackIndex`: The track index of the animation. -- `animationName`: The name of the animation. -- `loop`: Whether to loop the animation. +- `TrackIndex`: Animation track index +- `animationName`: Name of the animation +- `loop`: Whether to loop the animation -The last two parameters are self-explanatory, but the first parameter introduces the concept of **Track** in Spine animation: -> In Spine animations, you need to specify a track for playing animations. Tracks allow Spine to apply animations in layers. Each track stores animations and playback parameters, with track numbers starting from 0. When applying animations, Spine applies them from the lowest to the highest track. Higher tracks override animations on lower tracks. +The second and third parameters are straightforward, while the first parameter introduces a concept in Spine animations: **Track**. -Animation tracks have many uses. For example, track 0 can hold walking, running, or swimming animations, while track 1 can hold a shooting animation with keyframes for just the arms and shooting action. Additionally, setting the `TrackEntry` alpha for higher tracks allows blending with lower tracks. For instance, track 0 can have a walking animation, and track 1 can have a limping animation. When the player is injured, increasing the alpha value of track 1 intensifies the limp. +> When playing a Spine animation, an animation track must be specified. Using animation tracks, Spine can apply animations in layers. Each track stores animation and playback parameters, with track numbers starting from 0. When applying animations, Spine processes from lower to higher tracks, with higher tracks overriding animations on lower tracks. -#### **Setting Transitions** -Calling `setAnimation` immediately switches the current track's animation. To add a transition effect, set the transition duration using the [AnimationStateData](https://zh.esotericsoftware.com/spine-api-reference#AnimationStateData) API: +#### **Animation Blending** + +The above track override mechanism has many applications. For example, track 0 can have animations for walking, running, or swimming, while track 1 can contain a shooting animation that only has keyframes for the arms and firing. Additionally, setting the `TrackEntry.alpha` for higher tracks can blend them with lower tracks. For instance, track 0 could have a walking animation, and track 1 could have a limping animation. When the player is injured, increasing the `alpha` value of track 1 will intensify the limp. + +For example: ```typescript -class YourAmazingScript { +// The animation will now be walking while shooting +state.setAnimation(0, 'walk', true); +state.setAnimation(1, 'shoot', true); +``` + +#### **Animation Mixing** + +Calling the `setAnimation` method switches the animation on the current track immediately. If you want a transition effect between animations, you need to set the duration of the transition. This can be done using the [AnimationStateData](https://zh.esotericsoftware.com/spine-api-reference#AnimationStateData) API: +```typescript +class YourAmazingScript { onStart() { const spine = this.entity.getComponent(SpineAnimationRenderer); const { state } = spine; // AnimationState object @@ -249,76 +207,84 @@ class YourAmazingScript { data.defaultMix = 0.2; // Set default transition duration data.setMix('animationA', 'animationB', 0.3); // Set transition duration between two specific animations } - } ``` -- `defaultMix` is the default transition duration when no specific duration is defined between two animations. -- The `setMix` function accepts three parameters: the names of the two animations and the transition duration. +- `defaultMix`: Default duration for transitions between animations without a defined duration +- `setMix`: Takes three parameters: the names of the two animations to set the transition duration, and the duration of the animation blend + +... + #### **Animation Queue** -Spine also provides the [addAnimation](https://zh.esotericsoftware.com/spine-api-reference#AnimationState-addAnimation2) method for queuing animations: + +Spine also provides the [addAnimation](https://zh.esotericsoftware.com/spine-api-reference#AnimationState-addAnimation2) method to implement animation queue playback: ```typescript state.setAnimation(0, 'animationA', false); // Play animation A on track 0 -state.addAnimation(0, 'animationB', true, 0); // Queue animation B after animation A and loop it +state.addAnimation(0, 'animationB', true, 0); // After animation A, add animation B and play it in a loop ``` +The `addAnimation` method takes four parameters: -addAnimation accepts four parameters: +- `TrackIndex`: Animation track +- `animationName`: Name of the animation +- `loop`: Whether to play the animation in a loop +- `delay`: Delay time -- `TrackIndex`: The animation track. -- `animationName`: The name of the animation. -- `loop`: Whether to loop the animation. -- `delay`: The delay time. +The first three parameters are easy to understand, so let’s focus on the fourth parameter: +`delay` represents the duration of the preceding animation. -The first three parameters are straightforward; let's explain the fourth parameter, `delay`. -`delay` represents the time duration from when the previous animation starts playing. +When `delay > 0` (e.g., `delay` is 1), the preceding animation switches to the next animation after playing for 1 second, as shown below: -When `delay > 0` (e.g., `delay` is 1), the next animation starts 1 second after the current animation begins, as shown below: animation delay > 0 -If the duration of animation A is less than 1 second, depending on whether it loops, it will either loop until 1 second or stay in its completed state until 1 second. +If animation A’s duration is less than 1 second, it will either loop until 1 second or remain in its finished state until 1 second, depending on whether looping is enabled. + +When `delay = 0`, the next animation plays immediately after the preceding animation finishes, as shown below: -When `delay = 0`, the next animation starts after the current animation finishes, as shown below: animation delay = 0 -Assuming the duration of animation A is 1 second and the transition duration is 0.2 seconds, when `delay` is set to 0, animation B will transition at 0.8 seconds into animation B. +Assuming animation A lasts 1 second and the transition duration is 0.2 seconds, animation B will transition starting at 0.8 seconds (1 - 0.2). + +When `delay < 0`, the next animation begins before the preceding animation finishes, as shown below: +Similarly, if animation A lasts 1 second with a 0.2-second transition, animation B will begin transitioning at 0.6 seconds. -When `delay < 0`, the next animation starts playing before the current animation finishes, as shown below: -Similarly, assuming the duration of animation A is 1 second and the transition duration is 0.2 seconds, animation B will transition from 0.6 seconds into animation B. animation delay < 0 -In addition to `addAnimation`, you can also use the [addEmptyAnimation](https://zh.esotericsoftware.com/spine-api-reference#AnimationState-addEmptyAnimation) method to add an empty animation. -Empty animations allow animations to return to their initial state. +Besides `addAnimation`, the [addEmptyAnimation](https://zh.esotericsoftware.com/spine-api-reference#AnimationState-addEmptyAnimation) method can add an empty animation. Empty animations reset animations to their initial state. + +`addEmptyAnimation` takes three parameters: `TrackIndex`, `mixDuration`, and `delay`. The `TrackIndex` and `delay` parameters are the same as those in `addAnimation`. The `mixDuration` parameter specifies the transition duration, and the animation will reset to its initial state over this duration. As shown below (the brown area on the right represents the empty animation): + +Add empty animation api + +... -`addEmptyAnimation` accepts three parameters: `TrackIndex`, `mixDuration`, and `delay`. -`TrackIndex` and `delay` work the same as in `addAnimation`. -`mixDuration` is the transition duration, during which the animation gradually returns to its initial state. The image below (the brown area on the right represents the empty animation) illustrates this: -Add empty animation API #### **Track Parameters** -Both `setAnimation` and `addAnimation` methods return an object: `TrackEntry`. -`TrackEntry` provides additional parameters for animation control, such as: -- `timeScale`: Controls the playback speed of the animation. -- `animationStart`: Controls the start time of the animation. -- `alpha`: The blending coefficient for applying the current animation to the track. +The `setAnimation` and `addAnimation` methods both return an object called `TrackEntry`. The `TrackEntry` object provides additional parameters for animation control. For example: -For more parameters, refer to the [TrackEntry official documentation](https://zh.esotericsoftware.com/spine-api-reference#TrackEntry). +- `timeScale`: Controls the playback speed of the animation +- `animationStart`: Controls the start time of the animation +- `alpha`: Blending factor for the current animation on the track +- ... + +For more details on these parameters, refer to the [TrackEntry official documentation](https://zh.esotericsoftware.com/spine-api-reference#TrackEntry). #### **Animation Events** + Animation event diagram -When using the `AnimationState` API for animation control, events are triggered as shown in the diagram above. -A `Start` event is triggered when a new animation begins playing. -An `End` event is triggered when an animation is removed or interrupted from the queue. -A `Complete` event is triggered when an animation finishes playing, regardless of whether it loops. +When controlling animations via the `AnimationState` API, various events, as shown above, can be triggered. +- When a new animation starts, the `Start` event is triggered. +- When an animation is removed from the queue or interrupted, the `End` event is triggered. +- When an animation finishes, regardless of whether it loops, the `Complete` event is triggered. + +For a complete list of events and detailed explanations, refer to the [Spine animation events official documentation](https://zh.esotericsoftware.com/spine-unity-events). -For a full list of events and detailed explanations, refer to the [Spine Animation Events official documentation](https://zh.esotericsoftware.com/spine-unity-events). +These events can be listened to using the [AnimationState.addListener](https://zh.esotericsoftware.com/spine-api-reference#AnimationState-addListener) method. -These events can be listened to using the [AnimationState.addListener](https://zh.esotericsoftware.com/spine-api-reference#AnimationState-addListener) method: ```typescript class YourAmazingScript { - onStart() { const spine = this.entity.getComponent(SpineAnimationRenderer); const { state } = spine; // AnimationState object @@ -343,29 +309,30 @@ class YourAmazingScript { }, }); } - } ``` ### Skeleton Operations -In your script, you can access the [Skeleton](https://zh.esotericsoftware.com/spine-api-reference#Skeleton) object to manipulate bones, slots, attachments, and more for advanced skeleton operations. + +In your script, you can access the [Skeleton](https://zh.esotericsoftware.com/spine-api-reference#Skeleton) object to manipulate bones, slots, attachments, etc. + ```typescript class YourAmazingScript { - onStart() { const spine = this.entity.getComponent(SpineAnimationRenderer); const { skeleton } = spine; // Skeleton object } - } ``` -Below are some common operations: -#### **Modifying Bone Positions** -Using the Skeleton API, you can modify the position of Spine bones. A typical use case is setting the target bone of an IK to achieve aiming or tracking effects. +The following are some common operations: + +#### **Modify Bone Position** + +The `Skeleton` API allows you to modify the positions of Spine bones, which can be useful for implementing effects like aiming or following by setting the target bone for IK. + ```typescript class YourAmazingScript { - onStart() { const spine = this.entity.getComponent(SpineAnimationRenderer); const { skeleton } = spine; // Skeleton object @@ -373,57 +340,62 @@ class YourAmazingScript { bone.x = targetX; bone.y = targetY; } - } ``` -Note: Since playing animations can modify bone positions, if Spine is playing an animation, changes to bone positions must be done after the animation is applied, which means performing these operations in the `onLateUpdate` lifecycle of your script. -#### **Attachment Replacement** -Using the Skeleton API, you can replace [attachments](https://zh.esotericsoftware.com/spine-attachments) within [slots](https://zh.esotericsoftware.com/spine-slots). Attachment replacement can be used for effects like equipping different items or changing appearances locally. +Note: Since animations affect bone positions, modifications to bone positions should be made after the animation is applied, such as in the `onLateUpdate` lifecycle of your script. + +#### **Replace Attachments** + +The `Skeleton` API also allows you to replace [attachments](https://zh.esotericsoftware.com/spine-attachments) within [slots](https://zh.esotericsoftware.com/spine-slots). By switching attachments, you can achieve localized outfit changes. + ```typescript class YourAmazingScript { - onStart() { const spine = this.entity.getComponent(SpineAnimationRenderer); const { skeleton } = spine; // Skeleton object - // Find a slot by name + // Find slot by name const slot = skeleton.findSlot('slotName'); - // Get an attachment by name from the skeleton's skin or default skin + // Get attachment by name from the skeleton's skin or default skin const attachment = skeleton.getAttachment(slot.index, 'attachmentName'); - // Set the slot's attachment + // Set the attachment for the slot slot.attachment = attachment; - // Or set the attachment via the skeleton's setAttachment method + // Or set the slot attachment using the skeleton's setAttachment method skeleton.setAttachment('slotName', 'attachmentName'); } } ``` -Note: Since playing animations can modify attachments within slots, attachment replacement must be done after the animation is applied, which means performing these operations in the `onLateUpdate` lifecycle of your script. + +Note: Similar to bone positions, attachment replacement should occur after the animation is applied, such as in the `onLateUpdate` lifecycle. + +... + #### **Skin Switching and Mixing** + **Skin Switching** -You can use the Skeleton's [setSkin](https://zh.esotericsoftware.com/spine-api-reference#Skeleton-setSkin) API to switch the entire skin based on its name. +You can switch the entire skin using the [setSkin](https://zh.esotericsoftware.com/spine-api-reference#Skeleton-setSkin) API of the `Skeleton`. + ```typescript class YourAmazingScript { - onStart() { const spine = this.entity.getComponent(SpineAnimationRenderer); const { skeleton } = spine; // Skeleton object // Set the skin by name skeleton.setSkinByName("full-skins/girl"); - // Reset to the initial pose (must call this to avoid rendering issues) + // Reset to the initial position (this must be called, or rendering might appear incorrect) skeleton.setSlotsToSetupPose(); } - } ``` **Skin Mixing** -In the Spine editor, designers can prepare skins for each appearance and equipment, which can then be combined at runtime to create a new skin. The following code demonstrates how to add selected skins using `addSkin`: +In the Spine editor, designers can prepare skins for each appearance and equipment item, then combine them into a new skin at runtime. The following code demonstrates how to add selected skins using `addSkin`: + ```typescript class YourAmazingScript { - onStart() { const spine = this.entity.getComponent(SpineAnimationRenderer); const { skeleton } = spine; // Skeleton object @@ -439,10 +411,57 @@ class YourAmazingScript { mixAndMatchSkin.addSkin(skeletonData.findSkin("accessories/hat-red-yellow")); this.skeleton.setSkin(mixAndMatchSkin); } - } ``` -The skin names used in the code come from the mix-and-match example. -In the next chapter, we will show you all [Spine Examples](/docs/graphics/2D/spine/example). +The skin names used in the code come from the mix-and-match example, which you can see in the next chapter. + +#### **Dynamically Load Atlases and Replace Attachments** + +In traditional Spine projects, different skins are usually packed into the same atlas. However, as the number of skins increases, the growing number of textures in the atlas can lead to longer loading times. To address this issue, you can dynamically load additional atlas files at runtime and create new attachments based on the new atlas to replace the original attachments. This approach supports large-scale skin expansions while avoiding initial load performance issues. + +For example, you can pack weapons, headgear, and glasses into a separate atlas and replace them at runtime. + +```typescript +class YourAmazingScript { + async onStart() { + // Load additional atlas files + const extraAtlas = await this.engine.resourceManager.load('/extra.atlas') as TextureAtlas; + const { skeleton } = this.entity.getComponent(SpineAnimationRenderer); + // The slot containing the attachment to be replaced + const slot = skeleton.findSlot(slotName); + // The region in the new atlas used to create a new attachment + const region = extraAtlas.findRegion(regionName); + // Clone a new attachment from the original, using the region from the new atlas + const clone = this.cloneAttachmentWithRegion(slot.attachment, region); + // Replace the attachment + slot.attachment = clone; + } + + // Attachment cloning method + cloneAttachmentWithRegion( + attachment: RegionAttachment | MeshAttachment | Attachment, + atlasRegion: TextureAtlasRegion, + ): Attachment { + let newAttachment: RegionAttachment | MeshAttachment; + switch (attachment.constructor) { + case RegionAttachment: + newAttachment = attachment.copy() as RegionAttachment; + newAttachment.region = atlasRegion; + newAttachment.updateRegion(); + break; + case MeshAttachment: + const meshAttachment = attachment as MeshAttachment; + newAttachment = meshAttachment.newLinkedMesh(); + newAttachment.region = atlasRegion; + newAttachment.updateRegion(); + break; + default: + return attachment.copy(); + } + return newAttachment; + } +} +``` +The next chapter will showcase [Spine Examples and Templates](/en/docs/graphics/2D/spine/example). diff --git a/docs/zh/graphics/2D/spine/editor.md b/docs/zh/graphics/2D/spine/editor.md index f1b7b61131..a2037c396a 100644 --- a/docs/zh/graphics/2D/spine/editor.md +++ b/docs/zh/graphics/2D/spine/editor.md @@ -21,60 +21,65 @@ Galacean 编辑器内置了对 Spine 动画的支持,无需额外下载或配 1. 完成动画制作后,单击 `Spine 菜单`>`导出` ,打开导出窗口 -Export panel in Spine editor +Export panel in Spine editor 2. 选择导出窗口左上角的**二进制** ( 推荐使用二进制,以二进制格式而不是JSON格式导出,会使文件体积更小,加载更快 -Export window in Spine editor +Export window in Spine editor -3. 勾选上,**纹理图集**的打包复选框 +3. 勾选上**纹理图集**的打包复选框 -Click packing texture atlas button in Export window +Click packing texture atlas button in Export window 4. 点击 **打包设置** -这里建议勾选 `2 的幂数`;`预乘`和`溢出`两项请勿勾选 -完成打包设置后,点击**确定** -Texture pack window in Spine Editor +这里的打包设置是指纹理的打包设置,打包配置参数可以参考[官方文档](https://zh.esotericsoftware.com/spine-texture-packer),完成打包设置后,点击**确定** + +Texture pack window in Spine Editor 5. 回到导出窗口,选择导出文件夹后,点击**导出** -Click export button in texture pack window +Click export button in texture pack window 6. 将会得到三个如下文件: -Spine assets in folder +Spine assets in folder -spineboy.skel 包含了 skeleton animation 数据,spineboy.atlas 包含了 texture atlas 信息,导出的图片可能有多张,每张图片都代表了 texture altas 中的一页 +- spineboy.skel:包含骨骼结构(skeleton)和动画(animation)数据,是动画动作与骨骼绑定的核心信息。 +- spineboy.atlas:存储纹理图集(texture atlas)的信息,包括每张纹理在图集中的位置、大小等细节。 +- 纹理图片:可能包含多张图片,每张图片代表纹理图集(atlas)中的一页,用于实际渲染动画角色的视觉内容。 ## 2. 在 Galacean 编辑器中导入资产 -从 Spine 编辑器导出资产后,第二步就要将资产导入至 Galacean 编辑器了。打开编辑器后,将导出的文件直接拖入到[资产面板](/docs/assets/interface/)中,即可完成上传 +第二步,就要将 Spine 编辑器导出的文件导入至 Galacean 编辑器了。 + +打开编辑器后,将导出的文件直接拖入到[资产面板](/docs/assets/interface/)中,即可完成上传,如下面的动图所示: -Drag spine assets into Galacean editor +Drag spine assets into Galacean editor -也可以点击资产面板的上传按钮进行上传: +也可以点击资产面板的上传按钮,选择文件进行上传: -Use upload button to upload spine assets +Use upload button to upload spine assets +
-上传完成后,在资产面板中能够看到上传的 spine 素材。 +上传完成后,在资产面板中能够看到上传后的 Spine 资产,包括:SpineSkeletonData 资产SpineAtlas 资产,以及纹理资产 ### SpineSkeletonData 资产 -Spine skeleton data asset icon +Spine skeleton data asset icon SpineSkeletonData 资产存储了 skeleton 数据,以及对生成的 SpineAtlas 资产的引用 点击资产后,能够在检查器中预览 Spine 动画,预览面板中能够切换`皮肤`和`动画片段`: -Spine skeleton data preview +Spine skeleton data preview ### SpineAtlas 资产 -Spine atlas asset +Spine atlas asset SpineAtlas 资产存储了texture atlas 文件,并包含了其对所需 Texture 资产的引用。 -点击资产后,能够在检查器中查看其引用的 Texture 资产,以及 Spine 的图集信息 +点击资产后,能够在检查器中查看其引用的 Texture 资产,以及 Spine 的图集信息。 -Spine atlas preview +Spine atlas preview ### 资产更新 如若需要更新你的 Spine 资产。从 Spine 编辑器中重新导出资产,并再次导入到 Galacean 编辑器中覆盖原有文件即可。 @@ -82,29 +87,29 @@ SpineAtlas 资产存储了texture atlas 文件,并包含了其对所需 Textur ## 3. 添加 Spine 动画 -完成资产上传后,第三步,需要将 Spine 动画添加至场景中。一共有三种方式: +完成资产上传后,将 Spine 添加至场景中。一共有以下三种方式: -1. 拖入添加 +### 拖入添加 拖入添加是最快捷的一种方式。点击 SpineSkeletonData 资产,按住后拖动到视图区,就能快速创建一个添加了 SpineAnimationRenderer 组件的实体,并指定资产为刚刚选中的 SpineSkeletonData 资产。 -Drag Spine skeleton data asset into viewport +Drag Spine skeleton data asset into viewport -2. 快速添加 +### 快速添加 点击左上角的快速添加按钮,选择 `2D Object`>`SpineAnimationRenderer`, -Quick add Spine animation renderer +Quick add Spine animation renderer 添加完成后,能够看到一个新的实体,挂载了 SpineAnimationRenderer 组件;点击 Resource 属性,选择上传的 SpineSkeletonData 资产,就能看到 Spine 动画啦 -Select spine skeleton data asset in component panel +Select spine skeleton data asset in component panel -3. 手动添加 +### 手动添加 手动添加的方式与快速添加类似,不过需要在节点树中手动创建一个新的实体,并通过检查器的 AddComponent 按钮添加 SpineAnimationRenderer 组件 -Use add component to add spine animation renderer +Use add component to add spine animation renderer 添加了 SpineAnimationRenderer 组件后,同样需要指定组件的 Resource,也就是 SpineAnimationRenderer 组件要渲染的 SpineSkeletonData 资产。 @@ -113,7 +118,7 @@ SpineAtlas 资产存储了texture atlas 文件,并包含了其对所需 Textur SpineAnimationRenderer 组件的配置如下: -Spine animation renderer component config +Spine animation renderer component config 通过 SpineAnimationRenderer 组件能够配置 Spine 动画的资产以及默认状态: @@ -121,12 +126,11 @@ SpineAnimationRenderer 组件的配置如下: - Animation:默认播放的动画名称 - Loop:默认播放的动画是否循环 - Skin:默认的皮肤名称 -- Scale:默认的缩放系数 - Priority:渲染优先级 +- PremultiplyAlpha: 是否以预乘alpha的模式渲染动画 ## 4. 项目导出 最终,完成场景编辑器后,可以参考[项目导出](/docs/assets/build/)流程,导出编辑器项目。



-下一章节:[在代码中使用 Galacean Spine 运行时](/docs/graphics/2D/spine/runtime) - +下一章节:[在代码中使用](/docs/graphics/2D/spine/runtime) \ No newline at end of file diff --git a/docs/zh/graphics/2D/spine/example.md b/docs/zh/graphics/2D/spine/example.md index 245ba4c494..94260788c4 100644 --- a/docs/zh/graphics/2D/spine/example.md +++ b/docs/zh/graphics/2D/spine/example.md @@ -1,11 +1,38 @@ --- order: 3 -title: Spine 示例 +title: 示例与模板 type: 图形 group: Spine label: Graphics/2D/Spine/example --- +## 模板 +Galacean 编辑器提供了一系列教学模板,帮助大家更快的上手 Spine 动画的使用。 +进入[编辑器](https://galacean.antgroup.com/editor/projects) 后,你可以点击左侧的 Templates Tab 进入查看这些模板。 +

+ +**动画控制** + +该模板通过展示了如何通过 AnimationState 的 setAnimation 与 addAnimation 两个 API 来编排 spine 动画: +spine-animation + +**动画过渡与混合** + +该模板通过展示了 Spine 动画如何设置过渡以及不同轨道之间的动画混合: +spine-mix-blend + +**混搭换装** + +该模板展示了 Spine 混搭换装的能力,通过自由组合不同皮肤的附件,能够将不同皮肤的配件混搭起来: +mix-and-match + +**动态局部换肤** + +该模板展示了动态局部换肤的能力。我们能够基于一个额外上传的图集创建新的附件并进行替换。 +spine-dynamic-change + +## 示例 + **动画控制** 该示例展示了如何通过 setAnimation 与 addAnimation API 来编排 spine 动画队列: diff --git a/docs/zh/graphics/2D/spine/other.md b/docs/zh/graphics/2D/spine/other.md index b5f6e8262f..7048312142 100644 --- a/docs/zh/graphics/2D/spine/other.md +++ b/docs/zh/graphics/2D/spine/other.md @@ -6,27 +6,24 @@ group: Spine label: Graphics/2D/Spine/other --- -### Spine 版本 -@galacen/engine-spine 自 1.2 版本后开始支持 spine 4.x 版本。
-从 1.2 版本后,@galacen/engine-spine 包的 major version 和 minor version 与 spine 版本完全对应,版本对照如下:
-- @galacean/engine-spine <= 1.2 对应 spine version 3.8 -- @galacean/engine-spine 4.0 对应 spine version 4.0 -- @galacean/engine-spine 4.1 对应 spine version 4.1 -- @galacean/engine-spine 4.2 对应 spine version 4.2 -- ..... +### 版本升级与变更 (1.4) +升级到编辑器 1.4 版本后。除了需要在编辑器的[项目设置](/docs/interface/menu/#项目设置)中升级引擎版本外,还需要注意 1.4 Spine API 的修改: +1. 我们不再推荐使用:添加组件`addComponent(SpineAnimationRenderer)` + 设置资源`set SpineResource` 的方式创建 Spine 动画了。
+1.4 版本,我们给 SpineResource 添加了一个实例化方法 `instantiate`。`instantiate`方法返回一个使用了该资源的 Spine 动画实体。这比过去的创建方式更加快捷方便~ +2. `defaultState` 更名为 `defaultConfig`。该参数的含义是 Spine 动画默认状态下的配置项。我们调整了参数命名,使其更加便于理解。 +3. 删除了默认状态下的缩放`scale`配置。过去 scale 参数的目的其实是为了处理 Spine 动画的像素比例,让 Spine 动画大小与引擎中其他物体保持一致。1.4 版本,Spine 无需再通过设置默认缩放来修正其尺寸了。我们推荐大家通过修改 Entity 的 `scale`参数,来缩放 Spine 动画。 +4. 1.4 新增了 `premultipliedAlpha` 参数用于开启预乘模式渲染。当 Spine Editor 导出动画时,纹理打包勾选了预乘,此时需要开启 `premultipliedAlpha` 开关。 -目前已发布 4.2 beta 版本,4.1, 4.0 版本会陆续发布 - -### 版本升级 -升级到编辑器 1.3 版本后。除了需要在编辑器的[项目设置](/docs/interface/menu/#项目设置)中升级引擎版本外,由于导出 JSON 或者二进制的 Spine 编辑器版本需要与运行时版本[保持一致](https://zh.esotericsoftware.com/spine-versioning#%E5%90%8C%E6%AD%A5%E7%89%88%E6%9C%AC),所以编辑器升级到 1.3 后,`还需要重新导出 4.2 版本的 Spine 资产并上传到编辑器,通过文件覆盖完成资产的更新`。 ### 性能建议 这里提供一些优化 spine 动画性能的方法: 1. 使用二进制文件(.skel)的形式导出 skeleton,二进制文件的体积更小,加载更快。 -2. 建议将附件打包到尽可能少的atlas页中, 并根据绘制顺序将附件分组置入atlas页以防止多余的material切换. 请参考:[Spine 纹理打包:文件夹结构](https://zh.esotericsoftware.com/spine-texture-packer#%E6%96%87%E4%BB%B6%E5%A4%B9%E7%BB%93%E6%9E%84)了解如何在你的Spine atlas中编排 atlas 区域。 +2. 建议将附件打包到尽可能少的atlas页中, 并根据绘制顺序将附件分组置入atlas页以防止多余的 material 切换. 请参考:[Spine 纹理打包:文件夹结构](https://zh.esotericsoftware.com/spine-texture-packer#%E6%96%87%E4%BB%B6%E5%A4%B9%E7%BB%93%E6%9E%84)了解如何在你的Spine atlas中编排 atlas 区域。 3. 少用裁减功能。Spine 的裁减实现是通过动态裁减三角形实现的,性能开销很大。 -4. 尽可能少地使用atlas page textures。即,导出是贴图的数量尽可能控制在一张。 +4. 尽可能少地使用atlas page textures。即,导出是贴图的数量尽可能控制在一张。。 +5. 尽量尝试用一个 atlas texture 覆盖多个骨架。比如,可以在同一个 Spine 工程里添加多个骨架,导出时,选择单一图集。这样多个骨架会对应同一个 atlas texture。 + ### 提问 对于 Spine 有任何问题,欢迎在 @galacean/engine-spine [创建 issue](https://github.com/galacean/engine-spine/issues/new) \ No newline at end of file diff --git a/docs/zh/graphics/2D/spine/overview.md b/docs/zh/graphics/2D/spine/overview.md index 253c7e67de..eefe4f018d 100644 --- a/docs/zh/graphics/2D/spine/overview.md +++ b/docs/zh/graphics/2D/spine/overview.md @@ -20,5 +20,5 @@ Spine 动画是一款针对游戏开发的 2D 骨骼动画,它通过将图片 本章节会为大家介绍, - [如何在 Galacean 编辑器中使用 Spine 动画](/docs/graphics/2D/spine/editor) - [如何在代码中使用 Galacean spine 运行时](/docs/graphics/2D/spine/runtime) -- [Spine 动画示例](/docs/graphics/2D/spine/example) +- [示例与模板](/docs/graphics/2D/spine/example) - [其他内容(版本,性能)](/docs/graphics/2D/spine/other) \ No newline at end of file diff --git a/docs/zh/graphics/2D/spine/runtime.md b/docs/zh/graphics/2D/spine/runtime.md index 7be9984029..727ad761dc 100644 --- a/docs/zh/graphics/2D/spine/runtime.md +++ b/docs/zh/graphics/2D/spine/runtime.md @@ -6,7 +6,7 @@ group: Spine label: Graphics/2D/Spine/runtime --- -本章节为大家介绍如何在代码中使用 Galacean Spine 运行时。 +本章节为大家介绍如何在代码中使用 Galacean Spine ## 安装 @@ -18,13 +18,12 @@ npm install @galacean/engine-spine --save ```typescript import { SpineAnimationRenderer } from "@galacean/engine-spine"; ``` -安装并导入 `@galacean/engine-spine` 后,编辑器的 resourceManager 才能识别并加载 Spine 动画资产。 -Galacean spine 加载器既能加载编辑器上传的资产,也能过加载自定义上传的资产。 +安装并导入 `@galacean/engine-spine` 后,编辑器的 `ResourceManager` 才能识别并加载 Spine 动画资产。 + ## 加载资产并添加至场景 -### 加载 Galacean 编辑器中的上传的资产 -[导出编辑器项目后](/docs/assets/build/),`已添加至场景中的 Spine 动画,会在加载场景文件时,自动完成加载`: +### 加载编辑器中的上传的资产 ```typescript // 加载场景文件时,已添加至场景中的 Spine 动画会自行完成加载 @@ -35,64 +34,47 @@ await engine.resourceManager.load({ ``` 若未添加至场景中,则需要在代码中手动加载,步骤如下: -1. 首先下载编辑器项目 - -注意:`Uploadd Assets to CDN` 选项,如果勾选,则是通过 cdn 链接来加载动画;如果未勾选,则是通过本地文件相对路径加载动画。 - -Project export panel - -2. 找到 spine 资产文件 - -下载项目到本地后,打开 project.json 文件,找到 url 属性并打开。 -如果上一步勾选了`Uploadd Assets to CDN`选项,那么可以在 json 文件中找到 spine 资产链接: - -Find spine assset +1. 拷贝 SkeletonDataAsset 资产链接 +右键点击 SkeletonDataAsset 资产,点选 `Copy relative path` 拷贝资产路径 + -如果上一步未勾选`Uploadd Assets to CDN`选项,可以在本地 `public 文件夹`中找到 spine 资产。加载时,使用相对路径作为链接即可。 -Find spine assset +2. 使用 ResourceManager 加载 - -2. 使用 resourceManager 加载 - -得到 spine 的骨骼文件资产链接后,需要使用 resourceManager 进行加载。手动加载时,添加 Spine 至场景中,需要创建一个新的实体并添加 SpineAnimationRenderer 组件,代码如下: +得到资产路径后,需要使用 resourceManager 进行加载,代码如下: ```typescript import { SpineAnimationRenderer } from '@galacean/engine-spine'; // 加载并得到 spine 资源 const spineResource = await engine.resourceManager.load( { - url: 'https://galacean.raptor.json', // 或者是文件的相对路径 url: '../public/raptor.json"' - type: 'spine', // 必须指定加载器类型为 spine + url: '/raptor.json', // 拷贝得到的相对路径 + type: 'Spine', // 指定加载器类型为 Spine }, ); -// 创建一个新的实体 -const spineEntity = new Entity(engine); -// 添加 SpineAnimationRenderer 组件 -const spine = spineEntity.addComponent(SpineAnimationRenderer); -// 设置动画资源 -spine.resource = spineResource; +// 实例化一个 Spine 动画实体 +const spineEntity = spineResource.instantiate(); // 添加至场景 root.addChild(spineEntity); ``` ### 加载自定义上传的资产 -1. 加载资产 +#### 1. 加载资产 如果你的 Spine 资产未通过 Galacean 编辑器进行上传,而是通过三方平台上传至 CDN,同样能够通过 Galacean Spine 运行时加载器进行加载。 ```typescript const resource = await engine.resourceManager.load( { url: 'https://your.spineboy.json', // 自定义上传的资产 - type: 'spine', // 必须指定加载器类型为 spine + type: 'pine', // 指定加载器类型为 Spine }, ); ``` 加载自定义上传的资产时: -- 当传递参数为 url 时,`需要保证 atlas 和 texture 资源与骨骼文件在相同目录下`,即:
+- 当传递参数为 url 时,需要保证文件在相同目录下,即:
https://your.spineboy.json
https://your.spineboy.atlas
https://your.spineboy.png
-三个文件相同目录 + - 当传递参数为 urls (多链接)时,则无需满足相同目录的条件: ```typescript @@ -102,108 +84,68 @@ const resource = await engine.resourceManager.load( 'https://your.spineboy.json', 'https://ahother-path1.spineboy.altas', 'https://ahother-path2.spineboy.png', - ], // 自定义上传的资产 - type: 'spine',// 必须指定加载器类型为 spine + ], + type: 'spine',// 指定加载器类型为 Spine }, ); ``` -- 若不传递 texture 地址,那么加载器会从 atlas 文件中读取 texture 的图片名称,并从 atlas 文件的相对路径下查找 texture 资源。
-- 若自定上传的资产没有文件后缀(比如 blob 协议的 URL),则可以通过给链接添加 URL query 参数,例如:
-https://your.spineboyjson?ext=.json
+ +- 若不传递 texture 地址,那么加载器会从 atlas 文件中读取 texture 的图片名称,并从 atlas 文件的目录下查找 texture 资源。
+- 若自定上传的资产没有文件后缀,可以通过给链接添加 URL query 参数,例如:
+https://your.spineboyjson?ext=.json, https://your.spineboyatlas?ext=.atlas
-或者添加 fileExtensions 参数来指定资源后缀类型: + +- 如果 Spine 动画的 atlas 包含多张图片(如 a.png 和 b.png),则需要按照 atlas 文件中记录的图片顺序传入图片地址: ```typescript const resource = await engine.resourceManager.load( { urls: [ - 'https://your.spineboyjson', - 'https://ahother-path1.spineboyatlas', - 'https://ahother-path2.spineboypng', - ], // 自定义上传的资产 - type: 'spine', - fileExtensions: [ - 'json', // 指定第一个文件为 json 后缀 - 'atlas', // 指定第二个文件为 atlas 后缀 - 'png', // // 指定第三个文件为 atlas 后缀 - ] + 'https://your.spineboy.json', + 'https://your.spineboy.atlas', + 'https://your.spineboy1.png', // 对应 a.png + 'https://your.spineboy2.png' // 对应 b.png + ], + type: 'spine',// 指定加载器类型为 Spine }, ); ``` -- 若 Spine 动画的 texure atlas 包含多张图片,则需要按照 atlas 文件中图片的顺序传入图片地址。 -2. 添加至场景 +#### 2. 添加至场景 -加载完毕后,需要手动创建实体,并添加 SpineAnimationRenderer 组件: +加载完毕后, 实例化一个 Spine 动画实体并添加至场景: ```typescript import { SpineAnimationRenderer } from '@galacean/engine-spine'; const spineResource = await engine.resourceManager.load( { url: 'https://your.spineboy.json', // 自定义上传的资产 - type: 'spine', + type: 'Spine', }, ); -// 创建实体 -const spineEntity = new Entity(engine); -// 添加 SpineAnimationRenderer 组件 -const spine = spineEntity.addComponent(SpineAnimationRenderer); -// 设置动画资源 -spine.resource = spineResource; +// 实例化一个 Spine 动画实体 +const spineEntity = spineResource.instantiate(); // 添加至场景 root.addChild(spineEntity); ``` -## 使用运行时 API +## 更多运行时 API 在[前一个章节](/docs/graphics/2D/spine/editor)中,为大家介绍了编辑器中 SpineAnimationRenderer 组件的配置项。 本小节会更加详细介绍在代码中如何使用 SpineAnimationRenderer 组件的各个 API。 SpineAnimationRenderer 组件继承于 Renderer,除了暴露 Renderer 的通用方法外,还提供了以下属性: -| 属性 | 解释 | -| :--------------------------------------------------------------------------------------------- | :--------------------- | -| resource | Spine 动画资源。设置了资源后,SpineAnimationRenderer 组件会读取资源数据,并渲染出 Spine 动画 | -| setting | 渲染设置。用于控制开启裁减和调整图层间隔 | -| defaultState | 默认状态。与编辑器的配置项对应,用于设置默认状态下 Spine 动画的动画,皮肤,缩放 | -| state | 动画状态对象。用于进行更加复杂动画控制,如:队列播放,循环控制等 | -| skeleton | 骨架对象。用于进行更加复杂的骨架操作,如:附件替换,换肤等 | - -下面是更详细的使用介绍: - -### 资源设置 -首先是资源的设置。SpineAnimationRenderer 组件需要设置资源后,才能完成 Spine动画的渲染。在上一个章节,「加载资产并添加至场景」中,已经为大家展示了设置资产的方式: -```typescript -import { SpineAnimationRenderer } from '@galacean/engine-spine'; +| 属性 | 解释 | +| :------------- | :------------------------------------------------------------------------------------------------- | +| defaultConfig | 默认配置。与编辑器的配置项对应,用于设置默认状态下 Spine 的动画和皮肤 | +| state | 动画状态对象。用于进行更加复杂的动画控制,例如:队列播放、循环控制等 | +| skeleton | 骨架对象。用于进行更加复杂的骨架操作,例如:附件替换、换肤等 | +| premultipliedAlpha | 预乘 Alpha 设置。用于控制渲染时是否启用预乘 Alpha模式进行渲染 | -const spineResource = await engine.resourceManager.load( - { - url: 'https://your.spineboy.json', - type: 'spine', - }, -); -const spineEntity = new Entity(engine); -const spine = spineEntity.addComponent(SpineAnimationRenderer); -spine.resource = spineResource; // 设置 Spine 资产 -root.addChild(spineEntity); -``` -### 渲染设置 -在脚本中,你可以通过以下方式修改 Spine 的渲染设置,一般情况下,使用默认值即可。 -```typescript -class YourAmazingScript { - - onStart() { - const spine = this.entity.getComponent(SpineAnimationRenderer); - spine.setting.zSpacing = 0.01; // 设置图层间隔 - spine.setting.useClipping = true; // 开启或关闭裁减,默认开启 - } - -} -``` - -### 默认状态 -在脚本中,你可以通过以下方式修改 Spine 动画的默认状态: +### 默认配置 +在脚本中,你可以通过 `defaultConfig`参数设置默认状态下 Spine 的动画和皮肤 ```typescript class YourAmazingScript { @@ -214,19 +156,17 @@ class YourAmazingScript { type: 'spine', }, ); - const spineEntity = new Entity(engine); - const spine = spineEntity.addComponent(SpineAnimationRenderer); + const spineEntity = spineResource.instantiate(); + const spine = spineEntity.getComponent(SpineAnimationRenderer); spine.defaultState.animationName = 'your-default-animation-name'; // 默认播放的动画名称 spine.defaultState.loop = true; // 默认播放的动画是否循环 spine.defaultState.skinName = 'default'; // 默认皮肤名称 - spine.defaultState.scale = 0.02; // 默认缩放 - spine.resource = spineResource; // 设置资源 - rootEntity.addChild(spineEntity); // 添加至场景,此时组件激活 + rootEntity.addChild(spineEntity); // 添加至场景 } } ``` -注意:默认状态仅在 SpineAnimationRenderer 组件激活和资源设置时生效。动态修改动画、皮肤、缩放请使用 state 与 skeleton 属性中的方法(见下面的章节)。 +注意:默认配置仅在 SpineAnimationRenderer 组件激活时生效。动态修改动画、皮肤请使用 state 与 skeleton 属性中的方法(见下面的章节)。 ### 动画控制 @@ -254,10 +194,17 @@ setAnimation 函数接受三个参数: 后两个参数很好理解,第一个参数则包含了 Spine 动画的一个概念:**Track** (轨道) > Spine 动画在播放时,需要指定一个动画轨道。借助动画轨道,Spine 能够分层应用动画,每一个轨道都能够存储动画与播放参数,轨道的编号从 0 开始累加。在动画应用后,Spine 会从低轨道到高轨道依次应用动画,高轨道上的动画将会覆盖低轨道上的动画。
-动画轨道有很多用途,例如,轨道 0 可以有行走、奔跑、游泳或其他动画,轨道 1 可以有一个只为手臂和开枪设置了关键帧的射击动画。此外,为高层轨道设置TrackEntry alpha可使其与下面的轨道混合。例如,轨道 0 可以有一个行走动画,轨道 1 可以有一个跛行动画。当玩家受伤时,增加轨道 1 的alpha值,跛行就会加重。 +#### **动画混合** +上面提到的轨道覆盖机制,大有用途。例如,轨道 0 可以有行走、奔跑、游泳或其他动画,轨道 1 可以有一个只为手臂和开枪设置了关键帧的射击动画。此外,为高层轨道设置TrackEntry alpha可使其与下面的轨道混合。例如,轨道 0 可以有一个行走动画,轨道 1 可以有一个跛行动画。当玩家受伤时,增加轨道 1 的alpha值,跛行就会加重。 +比如: +```typescript +// 此时动画会边走路,边射击 +state.setAnimation(0, 'walk', true); +state.setAnimation(1, 'shoot', true); +``` -#### **设置过渡** +#### **动画过渡** 调用 setAnimation 方法后,会立即切换当前轨道的动画。如果你需要动画切换时有过渡效果,需要设置过渡的持续时间。可以通过 [AnimationStateData](https://zh.esotericsoftware.com/spine-api-reference#AnimationStateData) 的 API 来进行设置: ```typescript class YourAmazingScript { @@ -457,6 +404,54 @@ class YourAmazingScript { } ``` -代码中皮肤的名称来自 mix-and-match 示例。 +代码中皮肤的名称来自 mix-and-match 示例,在下一个章节中能够看到。 + + +#### **动态加载图集并替换附件** +在传统 Spine 项目中,不同的皮肤通常会被打包到同一个图集中。但是,随着皮肤数量的不断增加,图集纹理数量的增长会导致加载耗时不断上涨。为了解决这一问题,可以通过在运行时加载额外的 Atlas 文件,并基于新图集创建附件并替换原有附件,从而灵活支持大规模皮肤扩展,同时避免对初始加载性能的影响。

+比如,我们可以把不同皮肤的武器,头饰,眼镜等配件打包到一个额外的图集中,在运行时进行替换。 + +```typescript +class extends YourAmazingScript { + async onStart() + // 加载额外的图集文件 + const extraAtlas = await this.engine.resourceManager.load('/extra.atlas') as TextureAtlas; + const { skeleton } = this.entity.getComponent(SpineAnimationRenderer); + // 待替换附件所在的插槽 + const slot = skeleton.findSlot(slotName); + // 用于创建新附件的图集区域 + const region = extraAtlas.findRegion(regionName); + // 基于原本的附件进行克隆出新附件,新附件的图集区域来自于额外的图集文件 + const clone = this.cloneAttachmentWithRegion(slot.attachment, region); + // 替换附件 + slot.attachment = clone; + } + + // 附件克隆方法 + cloneAttachmentWithRegion( + attachment: RegionAttachment | MeshAttachment | Attachment, + atlasRegion: TextureAtlasRegion, + ): Attachment { + let newAttachment: RegionAttachment | MeshAttachment; + switch (attachment.constructor) { + case RegionAttachment: + newAttachment = attachment.copy() as RegionAttachment; + newAttachment.region = atlasRegion; + newAttachment.updateRegion(); + break; + case MeshAttachment: + const meshAttachment = attachment as MeshAttachment; + newAttachment = meshAttachment.newLinkedMesh(); + newAttachment.region = atlasRegion; + newAttachment.updateRegion(); + break; + default: + return attachment.copy(); + } + return newAttachment; + } +``` +

+ -下一个章节会给大家展示全部的 [Spine 示例](/docs/graphics/2D/spine/example) \ No newline at end of file +下一个章节会给大家展示 [Spine的示例与模板](/docs/graphics/2D/spine/example) \ No newline at end of file diff --git a/examples/spine-animation.ts b/examples/spine-animation.ts index 69326c6d1c..54e62a7be4 100644 --- a/examples/spine-animation.ts +++ b/examples/spine-animation.ts @@ -3,7 +3,7 @@ * @category 2D * @thumbnail https://mdn.alipayobjects.com/merchant_appfe/afts/img/A*IALeTYOXMXwAAAAAAAAAAAAADiR2AQ/original */ -import { Camera, Entity, Logger, Vector3, WebGLEngine } from "@galacean/engine"; +import { Camera, Logger, Vector3, WebGLEngine } from "@galacean/engine"; import { SpineAnimationRenderer } from "@galacean/engine-spine"; Logger.enable(); @@ -18,9 +18,7 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { // camera const cameraEntity = rootEntity.createChild("camera_node"); const camera = cameraEntity.addComponent(Camera); - cameraEntity.transform.position = new Vector3(0, 0, 100); - camera.nearClipPlane = 0.001; - camera.farClipPlane = 20000; + cameraEntity.transform.position = new Vector3(0, 0, 20); engine.resourceManager .load({ @@ -28,11 +26,9 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { type: "spine", }) .then((spineResource: any) => { - const spineEntity = new Entity(engine); - spineEntity.transform.setPosition(0, -18, 0); - const spine = spineEntity.addComponent(SpineAnimationRenderer); - spine.resource = spineResource; - spine.defaultState.scale = 0.05; + const spineEntity = spineResource.instantiate(); + spineEntity.transform.setPosition(0, -1.8, 0); + const spine = spineEntity.getComponent(SpineAnimationRenderer); rootEntity.addChild(spineEntity); const { state } = spine; state.data.defaultMix = 0.3; @@ -58,4 +54,4 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { }); engine.run(); -}); +}); \ No newline at end of file diff --git a/examples/spine-change-attachment.ts b/examples/spine-change-attachment.ts index d8303df57f..8bcd645b3b 100644 --- a/examples/spine-change-attachment.ts +++ b/examples/spine-change-attachment.ts @@ -3,7 +3,7 @@ * @category 2D * @thumbnail https://mdn.alipayobjects.com/merchant_appfe/afts/img/A*IALeTYOXMXwAAAAAAAAAAAAADiR2AQ/original */ -import { Camera, Entity, Logger, Vector3, WebGLEngine } from "@galacean/engine"; +import { Camera, Logger, Vector3, WebGLEngine } from "@galacean/engine"; import { SpineAnimationRenderer } from "@galacean/engine-spine"; Logger.enable(); @@ -18,9 +18,7 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { // camera const cameraEntity = rootEntity.createChild("camera_node"); const camera = cameraEntity.addComponent(Camera); - cameraEntity.transform.position = new Vector3(0, 0, 100); - camera.nearClipPlane = 0.001; - camera.farClipPlane = 20000; + cameraEntity.transform.position = new Vector3(0, 0, 20); engine.resourceManager .load({ @@ -28,13 +26,11 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { type: "spine", }) .then((spineResource: any) => { - const spineEntity = new Entity(engine); - spineEntity.transform.setPosition(0, -18, 0); - const spine = spineEntity.addComponent(SpineAnimationRenderer); - spine.resource = spineResource; - spine.defaultState.scale = 0.05; - spine.defaultState.skinName = 'full-skins/girl'; - spine.defaultState.animationName = 'idle'; + const spineEntity = spineResource.instantiate(); + spineEntity.transform.setPosition(0, -1.8, 0); + const spine = spineEntity.getComponent(SpineAnimationRenderer); + spine.defaultConfig.skinName = 'full-skins/girl'; + spine.defaultConfig.animationName = 'idle'; rootEntity.addChild(spineEntity); const { skeleton } = spine; const slot = skeleton.findSlot('body')!; @@ -48,4 +44,4 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { }); engine.run(); -}); +}); \ No newline at end of file diff --git a/examples/spine-follow-shoot.ts b/examples/spine-follow-shoot.ts index 59fc5c3369..fb7370fa8b 100644 --- a/examples/spine-follow-shoot.ts +++ b/examples/spine-follow-shoot.ts @@ -3,7 +3,7 @@ * @category 2D * @thumbnail https://mdn.alipayobjects.com/merchant_appfe/afts/img/A*IALeTYOXMXwAAAAAAAAAAAAADiR2AQ/original */ -import { Camera, Entity, Logger, Script, Vector3, WebGLEngine } from "@galacean/engine"; +import { Camera, Logger, Script, Vector3, WebGLEngine } from "@galacean/engine"; import { Bone, SpineAnimationRenderer } from "@galacean/engine-spine"; Logger.enable(); @@ -18,9 +18,7 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { // camera const cameraEntity = rootEntity.createChild("camera_node"); const camera = cameraEntity.addComponent(Camera); - cameraEntity.transform.position = new Vector3(0, 0, 100); - camera.nearClipPlane = 0.001; - camera.farClipPlane = 20000; + cameraEntity.transform.position = new Vector3(0, 0, 20); engine.resourceManager .load({ @@ -28,11 +26,9 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { type: "spine", }) .then((spineResource: any) => { - const spineEntity = new Entity(engine); - spineEntity.transform.setPosition(0, -18, 0); - const spine = spineEntity.addComponent(SpineAnimationRenderer); - spine.resource = spineResource; - spine.defaultState.scale = 0.05; + const spineEntity = spineResource.instantiate(); + spineEntity.transform.setPosition(0, -1.8, 0); + const spine = spineEntity.getComponent(SpineAnimationRenderer); rootEntity.addChild(spineEntity); const { state, skeleton } = spine; state.setAnimation(0, 'idle', true); @@ -49,16 +45,16 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { const { position } = pointers[0]; const worldPos = this._vec3; camera.screenToWorldPoint( - new Vector3(position.x, position.y, 2000), + new Vector3(position.x, position.y, 20), worldPos, ); - const targetBone = skeleton.findBone('crosshair') as Bone;targetBone.y = worldPos.y + 380; - targetBone.y = worldPos.y + 380; + const targetBone = skeleton.findBone('crosshair') as Bone; + targetBone.y = worldPos.y + 1.9; if (worldPos.x < 0) { - skeleton.scaleX = -0.05; + skeleton.scaleX = -1; targetBone.x = -worldPos.x; } else { - skeleton.scaleX = 0.05; + skeleton.scaleX = 1; targetBone.x = worldPos.x; } } @@ -70,6 +66,4 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { }); engine.run(); -}); - - +}); \ No newline at end of file diff --git a/examples/spine-full-skin-change.ts b/examples/spine-full-skin-change.ts index fce1e9b3c8..a5b45e26fa 100644 --- a/examples/spine-full-skin-change.ts +++ b/examples/spine-full-skin-change.ts @@ -3,7 +3,7 @@ * @category 2D * @thumbnail https://mdn.alipayobjects.com/merchant_appfe/afts/img/A*IALeTYOXMXwAAAAAAAAAAAAADiR2AQ/original */ -import { Camera, Entity, Logger, Vector3, WebGLEngine } from "@galacean/engine"; +import { Camera, Logger, Vector3, WebGLEngine } from "@galacean/engine"; import { SpineAnimationRenderer } from "@galacean/engine-spine"; import * as dat from "dat.gui"; @@ -21,9 +21,7 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { // camera const cameraEntity = rootEntity.createChild("camera_node"); const camera = cameraEntity.addComponent(Camera); - cameraEntity.transform.position = new Vector3(0, 0, 100); - camera.nearClipPlane = 0.001; - camera.farClipPlane = 20000; + cameraEntity.transform.position = new Vector3(0, 0, 20); engine.resourceManager .load({ @@ -31,13 +29,11 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { type: "spine", }) .then((spineResource: any) => { - const spineEntity = new Entity(engine); - spineEntity.transform.setPosition(0, -18, 0); - const spine = spineEntity.addComponent(SpineAnimationRenderer); - spine.resource = spineResource; - spine.defaultState.scale = 0.05; - spine.defaultState.skinName = 'full-skins/girl'; - spine.defaultState.animationName = 'idle'; + const spineEntity = spineResource.instantiate(); + spineEntity.transform.setPosition(0, -1.8, 0); + const spine = spineEntity.getComponent(SpineAnimationRenderer); + spine.defaultConfig.skinName = 'full-skins/girl'; + spine.defaultConfig.animationName = 'idle'; rootEntity.addChild(spineEntity); const { skeleton, state } = spine; const info = { @@ -61,4 +57,4 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { }); engine.run(); -}); +}); \ No newline at end of file diff --git a/examples/spine-mix-and-match.ts b/examples/spine-mix-and-match.ts index e74cd308ba..3052aea587 100644 --- a/examples/spine-mix-and-match.ts +++ b/examples/spine-mix-and-match.ts @@ -3,7 +3,7 @@ * @category 2D * @thumbnail https://mdn.alipayobjects.com/merchant_appfe/afts/img/A*IALeTYOXMXwAAAAAAAAAAAAADiR2AQ/original */ -import { Camera, Entity, Logger, Script, Vector3, WebGLEngine } from "@galacean/engine"; +import { Camera, Logger, Script, Vector3, WebGLEngine } from "@galacean/engine"; import { Skin, SpineAnimationRenderer } from "@galacean/engine-spine"; import * as dat from "dat.gui"; @@ -21,9 +21,7 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { // camera const cameraEntity = rootEntity.createChild("camera_node"); const camera = cameraEntity.addComponent(Camera); - cameraEntity.transform.position = new Vector3(0, 0, 100); - camera.nearClipPlane = 0.001; - camera.farClipPlane = 20000; + cameraEntity.transform.position = new Vector3(0, 0, 20); engine.resourceManager .load({ @@ -31,13 +29,11 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { type: "spine", }) .then((spineResource: any) => { - const spineEntity = new Entity(engine); - spineEntity.transform.setPosition(0, -18, 0); - const spine = spineEntity.addComponent(SpineAnimationRenderer); - spine.resource = spineResource; - spine.defaultState.scale = 0.05; - spine.defaultState.skinName = 'full-skins/girl'; - spine.defaultState.animationName = 'idle'; + const spineEntity = spineResource.instantiate(); + spineEntity.transform.setPosition(0, -1.8, 0); + const spine = spineEntity.getComponent(SpineAnimationRenderer); + spine.defaultConfig.skinName = 'full-skins/girl'; + spine.defaultConfig.animationName = 'idle'; const mixAndMatch = spineEntity.addComponent(MixAndMatch); rootEntity.addChild(spineEntity); const { state } = spine; @@ -207,7 +203,4 @@ class MixAndMatch extends Script { combinedSkin.addSkin(skeletonData.findSkin(this.hatSkin)!); } } -} - - - +} \ No newline at end of file diff --git a/examples/spine-performance.ts b/examples/spine-performance.ts index 36ae289188..cfc74711f1 100644 --- a/examples/spine-performance.ts +++ b/examples/spine-performance.ts @@ -3,7 +3,7 @@ * @category Benchmark * @thumbnail https://mdn.alipayobjects.com/merchant_appfe/afts/img/A*6xrGR6nr1c0AAAAAAAAAAAAADiR2AQ/original */ -import { Camera, Entity, Vector3, WebGLEngine } from "@galacean/engine"; +import { Camera, Vector3, WebGLEngine } from "@galacean/engine"; import { SpineAnimationRenderer } from "@galacean/engine-spine"; import { Stats } from "@galacean/engine-toolkit-stats"; @@ -15,8 +15,7 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { // camera const cameraEntity = rootEntity.createChild("camera_node"); const camera = cameraEntity.addComponent(Camera); - cameraEntity.transform.position = new Vector3(0, 0, 110); - camera.farClipPlane = 200; + cameraEntity.transform.position = new Vector3(0, 0, 50); cameraEntity.addComponent(Stats); engine.resourceManager @@ -25,19 +24,17 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { type: "spine", }) .then((resource: any) => { - const spineEntity = new Entity(engine); - const spine = spineEntity.addComponent(SpineAnimationRenderer); - spine.resource = resource; - spine.defaultState.scale = 0.02; - spine.defaultState.animationName = "walk"; + const spineEntity = resource.instantiate(); + const spine = spineEntity.getComponent(SpineAnimationRenderer); + spine.defaultConfig.animationName = "walk"; for (let i = -5; i < 5; i++) { for (let j = -5; j < 5; j++) { const clone = spineEntity.clone(); - clone.transform.setPosition(8 * i, 8 * j, 0); + clone.transform.setPosition(-3 + i * 4, -1 + j * 4, 0); rootEntity.addChild(clone); } } }); engine.run(); -}); +}); \ No newline at end of file diff --git a/examples/spine-physics.ts b/examples/spine-physics.ts index f88727a53c..747db43592 100644 --- a/examples/spine-physics.ts +++ b/examples/spine-physics.ts @@ -3,7 +3,7 @@ * @category 2D * @thumbnail https://mdn.alipayobjects.com/merchant_appfe/afts/img/A*IALeTYOXMXwAAAAAAAAAAAAADiR2AQ/original */ -import { Camera, Entity, Logger, Script, Vector3, WebGLEngine } from "@galacean/engine"; +import { Camera, Logger, Script, Vector3, WebGLEngine } from "@galacean/engine"; import { SpineAnimationRenderer } from "@galacean/engine-spine"; Logger.enable(); @@ -18,9 +18,7 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { // camera const cameraEntity = rootEntity.createChild("camera_node"); const camera = cameraEntity.addComponent(Camera); - cameraEntity.transform.position = new Vector3(0, 0, 2000); - camera.nearClipPlane = 0.001; - camera.farClipPlane = 20000; + cameraEntity.transform.position = new Vector3(0, 0, 50); engine.resourceManager .load({ @@ -32,12 +30,11 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { type: "spine", }) .then((spineResource: any) => { - const spineEntity = new Entity(engine); - spineEntity.transform.setPosition(0, -250, 0); - const spine = spineEntity.addComponent(SpineAnimationRenderer); - spine.resource = spineResource; - spine.defaultState.animationName = 'wind-idle'; - spine.defaultState.scale = 0.5; + const spineEntity = spineResource.instantiate(); + spineEntity.transform.setPosition(0, -2.5, 0); + const spine = spineEntity.getComponent(SpineAnimationRenderer); + spine.premultipliedAlpha = true; + spine.defaultConfig.animationName = 'wind-idle'; rootEntity.addChild(spineEntity); const { skeleton } = spine; spineEntity.addComponent(class extends Script { @@ -49,10 +46,10 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { const { position } = pointers[0]; const worldPos = this._vec3; camera.screenToWorldPoint( - new Vector3(position.x, position.y, 2000), + new Vector3(position.x, position.y, 50), worldPos, ); - skeleton.y = worldPos.y - 480; + skeleton.y = worldPos.y - 13; skeleton.x = worldPos.x; } } @@ -60,4 +57,4 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { }); engine.run(); -}); +}); \ No newline at end of file