Spine 4.0 (2D Animated Characters)

This page contains the expression type description for Spine 4.0 as well as information about how we've used Spine 4.0 to create our flagship game Duro Dogs.

2D Animated Character

Spine

The Duro Dog characters were created using the animation engine Spine. It is an animation tool that focuses specifically on 2D animation for games. Animation in Spine is done by attaching images to bones, then animating the bones. This is called skeletal or cutout animation and has numerous benefits over traditional, frame-by-frame animation. You can read about it here Spine.

The Sample App provides an example of how to render 2D Spine animations in React. The code that renders a Duro Dog can be found in the Sample App at: /src/components/PixiNFT.js. The two main libraries used to render the dogs are pixi.js & pixi-spine.js.

Spine Expression Type

The Spine expression type includes the following expression attributes:

  • .json

  • .png

  • .atlas

All three of these attribute values (file locations in this case) are required to render a dog in Spine.

Getting The Expression Values

We get the expression values for each dog (NFT) with the following API Route: /pages/api/collection/nfts with "collectionId": "638665d8506da1c31926beef" as the input parameter. This API Route is calling the Asset Layer API with the following: You can also make calls directly to the API by using Postman.

GET https://api.assetlayer.com/api/v1/collection/nfts

The API response for the Duro Dog collection and Duro Dog serial number '0' will look like this:

"statusCode": 200,
    "success": true,
    "body": {
        "collection": {
            "collectionId": "638665d8506da1c31926beef",
            "collectionName": "Duro Dog",
            "collectionImage": "https://www.durodogs.com/static/home/logos/logo1_light.png",
            "slotId": "633b31a709d1ac7280c50dfc",
            "maximum": 10000000000,
            "minted": 1030000,
            "type": "Unique",
            "createdAt": 1669752280444,
            "updatedAt": 1670949060750,
            "nfts": [
                {
                    "expressionValues": [
                        {
                            "value": "https://asset-api-files-bucket.s3.amazonaws.com/487b8e2c-9cff-4e94-aca8-7b25896e96c7.atlas",
                            "expressionValueId": "6397fb6306ea8bc8c4bd3140",
                            "expressionAttribute": {
                                "expressionAttributeName": "Atlas",
                                "expressionAttributeId": "6340b0caa2b2f44da248211e"
                            },
                            "expression": {
                                "expressionName": "Front View",
                                "expressionId": "6340b22b0fd33e9fd518af70"
                            }
                        },
                        {
                            "value": "https://asset-api-files-bucket.s3.amazonaws.com/0f79c80e-c199-4e25-b2b3-5a44508e9cf6.atlas",
                            "expressionValueId": "6397fb6506ea8bc8c4bd3205",
                            "expressionAttribute": {
                                "expressionAttributeName": "Atlas",
                                "expressionAttributeId": "6340b0caa2b2f44da248211e"
                            },
                            "expression": {
                                "expressionName": "Three Quarter View",
                                "expressionId": "6340b21f0fd33e538d18af67"
                            }
                        },
                        {
                            "value": "https://asset-api-files-bucket.s3.amazonaws.com/0c8d3e6a-beba-442e-bb90-3789e79b627b.png",
                            "expressionValueId": "6397fb6606ea8bc8c4bd3289",
                            "expressionAttribute": {
                                "expressionAttributeName": "PNG",
                                "expressionAttributeId": "6340b0d1a2b2f44da248211f"
                            },
                            "expression": {
                                "expressionName": "Three Quarter View",
                                "expressionId": "6340b21f0fd33e538d18af67"
                            }
                        },
                        {
                            "value": "https://asset-api-files-bucket.s3.amazonaws.com/e75e0cdb-54b6-43a8-a879-b6636a7c819f.png",
                            "expressionValueId": "6397fb6606ea8bc8c4bd32a5",
                            "expressionAttribute": {
                                "expressionAttributeName": "Image",
                                "expressionAttributeId": "62f83b2482081d6f89953fa7"
                            },
                            "expression": {
                                "expressionName": "Menu View",
                                "expressionId": "6340abde0fd33e330f18af38"
                            }
                        },
                        {
                            "value": "https://asset-api-files-bucket.s3.amazonaws.com/f258c125-0de1-40a5-a3d7-104e4537252a.png",
                            "expressionValueId": "6397fb6606ea8bc8c4bd32bb",
                            "expressionAttribute": {
                                "expressionAttributeName": "PNG",
                                "expressionAttributeId": "6340b0d1a2b2f44da248211f"
                            },
                            "expression": {
                                "expressionName": "Front View",
                                "expressionId": "6340b22b0fd33e9fd518af70"
                            }
                        },
                        {
                            "value": "https://asset-api-files-bucket.s3.amazonaws.com/72dc2111-2e03-4204-bdac-66f5111f429b.json",
                            "expressionValueId": "6397fb6706ea8bc8c4bd32d4",
                            "expressionAttribute": {
                                "expressionAttributeName": "JSON",
                                "expressionAttributeId": "6340b0bfa2b2f44da248211d"
                            },
                            "expression": {
                                "expressionName": "Three Quarter View",
                                "expressionId": "6340b21f0fd33e538d18af67"
                            }
                        },
                        {
                            "value": "https://asset-api-files-bucket.s3.amazonaws.com/69da4d4a-6ac2-4016-90d0-da1cf6ad1bf4.json",
                            "expressionValueId": "6397fb6706ea8bc8c4bd32fa",
                            "expressionAttribute": {
                                "expressionAttributeName": "JSON",
                                "expressionAttributeId": "6340b0bfa2b2f44da248211d"
                            },
                            "expression": {
                                "expressionName": "Front View",
                                "expressionId": "6340b22b0fd33e9fd518af70"
                            }
                        }
                    ],
                    "location": "a198b520bdb2adff4b3ac57aec2ac08d7fb9dd732b4bedfef54e697325f829d8_o2",
                    "serial": 0,
                    "properties": {
                        "633b30ca09d1acacd0c50df4": {
                            "rarities": {
                                "fur": {
                                    "rarity": "Common",
                                    "color": "443F3F",
                                    "secondaryColor": "FFFFFF"
                                },
                                "eyes": {
                                    "rarity": "Common",
                                    "type": 0,
                                    "color": "7D4402"
                                },
                                "nose": {
                                    "rarity": "Common",
                                    "type": 2,
                                    "color": "77787B"
                                },
                                "head": {
                                    "rarity": "Normal",
                                    "type": 4,
                                    "spots": false
                                },
                                "tail": {
                                    "rarity": "Normal",
                                    "type": 4,
                                    "spots": false
                                },
                                "body": {
                                    "rarity": "Normal",
                                    "type": 4,
                                    "spots": false
                                },
                                "ears": {
                                    "rarity": "Normal",
                                    "type": 2
                                }
                            }
                        }
                    },
                    "nftId": "75641d9dcf78c4a40e16f283c3282cb6"
                },

NFT Expressions

As you can see in the .json response above, each dog has 3 expressions: Front View, Three Quarter View, and Menu View. That's the power of the Asset Layer platform - one NFT can be expressed in multiple ways. Even though they both use the same NFTs, the game Duro Dogs uses the Front View expression whereas the game Ruff Runner uses the Three Quarter View. The Menu View is just a simple image of each NFT that is used in the Marketplace.

Parsing Out Each Duro Dog

The following code parses out the expression attribute values which are received from the API.

function getExpressionValue(expressionValues, expressionName, expressionAttributeName, showAnimations) {
    return String(expressionValues?.find((expressionVal) => expressionVal?.expression?.expressionName === expressionName && expressionVal?.expressionAttribute?.expressionAttributeName === expressionAttributeName)?.value);
}

function parseNFT(nft, expression) {
    if (!nft) {
        return { parsedJson: null, parsedAtlas: null, parsedPng: null}
    }
    const parsedJson = getExpressionValue(nft.expressionValues || [], expression, 'JSON');
    const parsedAtlas = getExpressionValue(nft.expressionValues || [], expression, 'Atlas');
    const parsedImage = getExpressionValue(nft.expressionValues || [], expression, 'PNG');
    return { 
        parsedJson, 
        parsedAtlas,
        parsedImage, 
    };
}

useEffect(()=>{
        const { parsedJson, parsedAtlas, parsedImage } = parseNFT(assetlayerNFT, expression);
        if (parsedJson) {
            // setSpineJson('jsonNamehere.json'); // setting this do a local copy of any nfts, they are not nft specific, only the image is
            setSpineJson(parsedJson);  // using the parse Json from the nft.
        }
        if (parsedAtlas) {
            // setSpineAtlas('atlasNamehere.atlas');  // setting this do a local copy of any nfts, they are not nft specific, only the image is
            setSpineAtlas(parsedAtlas); // using the parse Atlas from the nft.
        }
        if (parsedImage) {
            setSpinePng(parsedImage); 
        }
    

}, [assetlayerNFT, expression])

Rendering Each Duro Dog

Each dog is then rendered using the following code:

function resourcesLoaded (loader, resources) {
        const nftSpine = new PIXISPINE.Spine(resources[(assetlayerNFT.nftId + expression)].spineData);
        if (nftSpine) {
            adjustSizeOfSpine(nftSpine);
            app.stage.addChild(nftSpine); 
            setAnimations(parseAnimations(nftSpine.spineData));
            setNftLoaded(true);
            setSpine(nftSpine);
            setCurrentAnimation(defaultAnimation);
        }
        }

    async function loadPixiNFT() {
        clearStage();
        const texture = await PIXI.Texture.fromURL(spinePng);
        let spineLoaderOptions = { metadata: { 
                    spineAtlasFile: spineAtlas,
                    image: texture }}; 
        // now load json skeleton
        if(!app.loader.resources[(assetlayerNFT.nftId + expression)]) {
            app.loader.add((assetlayerNFT.nftId + expression), spineJson , spineLoaderOptions);
            app.loader.load(resourcesLoaded);
        } else {
            resourcesLoaded(app.loader, app.loader.resources);
        }
    
  
    }

Create Your Own Animated Character

You can use the Duro Dog Spine output files in your app (by using Duro Dogs in your app as a foreign slot) or create your very own characters in Spine, and then upload the files using the No-code Tool. If you do not have the skills to create your own spine character & animations, then you can hire one of the talented animators here Fiverr or here ArtStation.

There will also be an area in the marketplace where Spine animators will be able to sell their own 2D character collections consisting of unique or common NFTs.

Last updated