[SOLVED] How can I create an animated sprite on 2D screen UI in a 3D application?

Hi,

I just want to have an animated image on 2D screen UI (in HUD) for a 3D application.
I thought that I just need to add an animated GIF but it’s not supported.

So I have searched and found two ways to create an animated images.

1 - By using the animated sprite like this: 2D Support Added to PlayCanvas Game Engine - YouTube
2 - By using an animated material like this: Animated Textures | Learn PlayCanvas (The title of this tutorial is misleading because it doesn’t animated the texture here but the material by changing his texture)

Both methods work well in the 3D space but not in 2D.

The first one uses an animated sprite, which can’t be child of a Screen element as specifed by @yaustar:

Sprites weren’t supposed to be supported as children of the screen

Source from a similar question: Display sprite animations on 2D screen - #2 by Mal_Duffin

The second one uses a model of type plane, which still stay on 3D position even if it’s a child of a 2D screen object.

So, my goal is to have an animated texture, in the 2D space, on top of everything in a 3D application. Can you help me with that?

Thank you in advance.

I have found an hacky way to get this works. I will document this here but please, tell me if there’s a better solution for that.

So I managed this with theses steps:

1 - By creating an image element and set this as a child of a 2D screen element.

image

2 - I have created an animated material like this tutoriel: https://developer.playcanvas.com/en/tutorials/animated-textures/

3 - I applied this material to my image (instead of a model from the tutorial).
image

4 - I modified the function updateMaterial of animated-texture.js script from the tutoriel because it was working only on model with meshInstances. Here my modified version:

AnimatedTexture.prototype.updateMaterial = function (frame) {
    // calculate how much to change UV to go to next frame
    var dx = 1 / this.width;
    var dy = 1 / this.height;

    // Convert frame number into UV co-ordinate
    var x = frame % this.width;
    var y = Math.floor(frame / this.width);

    // create the tiling vector (tilingx, tilingy, offsetx, offsety)
    var tiling = new pc.Vec2(dx, dy); 
    
    // create the offset vector (offsetx, offsety)
    var offset = new pc.Vec2(x * dx, (1 - dy) - (y * dy)); 

    //Override the material properties for this material
    this.entity.element.material.diffuseMapTiling  = tiling;
    this.entity.element.material.diffuseMapOffset = offset;
    this.entity.element.material.emissiveMapTiling  = tiling;
    this.entity.element.material.emissiveMapOffset = offset;
    this.entity.element.material.opacityMapTiling  = tiling;
    this.entity.element.material.opacityMapOffset = offset;
    
    //Applies any changes made to the material's properties.
    this.entity.element.material.update();
    
};

So, it’s working but I hope there’s a better/easier way.

1 Like

Not to my knowledge. I was thinking of using a sprite asset and advancing the frame by code or going through a series of sprite assets.

You can also use a MP4 and play the video in a texture.

1 Like

It would make sense for the developers to just add animated sprite support to the UI - this has been brought up a few times now by different customers.

2 Likes

I agree with @Mal_Duffin. Here, I managed to reach the result I was looking for but for a non-developper, it would have been difficult to find a solution.

1 Like

Sadly, I still got a problem with my solution. The material is influenced by the Skybox and the Ambiant color of my scene. So, my animated images are not rendered with their truly colors and are impacted by the ligths… :disappointed_relieved:

I still hope that one day, I will be able to drag and drop an animated gif as an image that is unlit and set that on the 2D screen space. :pray:

1 Like

Ok, here another technique that fixed my problem described in my last post. The technique uses the sprite element on 2D screen with a script.

0 - First, you need to have an animated sprite. This tutorial explained how to do that: https://youtu.be/x4xMP-J7WlU

1 - Create an image element and set this as a child of a 2D screen element.

image

2 - This image element are like this:

3 - Here the script I made and put on that image element. You can controle the start frame, the number of frames and the FPS.

var AnimateSprite = pc.createScript('animateSprite');

AnimateSprite.attributes.add('startFrame', {
    type: 'number',
    default: 0,
    description: 'Frame to start animation from'
});

AnimateSprite.attributes.add('numFrames', {
    type: 'number',
    default: 1,
    description: 'Number of frames to play before looping'
});

AnimateSprite.attributes.add('frameRate', {
    type: 'number',
    default: 1,
    description: 'Playback frames per second'
});

// initialize code called once per entity
AnimateSprite.prototype.initialize = function() {
    this.timer = 1/this.frameRate;
    this.frame = this.startFrame;
};

// update code called every frame
AnimateSprite.prototype.update = function(dt) {
            
    // calculate when to animate to next frame
    this.timer -= dt;            
    if (this.timer < 0) {
        // move to next frame
        this.frame++;
        if (this.frame >= (this.numFrames + this.startFrame)) {
            this.frame = this.startFrame;
        }

        this.entity.element.spriteFrame = this.frame;
        
         // reset the timer
        this.timer = 1/this.frameRate;
    }
};

So, it’s working well even if:

@yaustar:

Sprites weren’t supposed to be supported as children of the screen

1 Like

I get these warnings in the log when using this technique:

image

Any problems with that?

What’s the code?

Any instance of pc.Vec3/Vec4/Color that is accessed through its .data array has been made a private api.

You will get this in many older shader examples which use the .data property to pass the array to shader uniforms (quite handy I have to say).

Now you will have to access the object values through their respected properties only:

this.vec.x;
this.vec.y;
this.color.r;
// etc
2 Likes

will this help A [

pc.SpriteComponent](https://developer.playcanvas.com/en/api/pc.SpriteComponent.html) that renders sprite animations.

Yes, I was going through the sprite component aswell and I think it does what I need. Perhaps this thread was written before the animated sprite system was in place?

The only thing this code above with animated-texture.js has which I haven’t found in an animated sprite is the possibility to further adjust the material properties of the plane. For example I can use the emissive-channel on a standard material (icon on the right side),where as a sprite component (left icon) only presents a color to use. Is that right?


image

It’s this code here: https://developer.playcanvas.com/en/tutorials/animated-textures/

    // This allows us to use different settings for different Entities, but share the same material
    this.transform.set(dx, dy, x * dx, (1 - dy) - (y * dy));
    meshes[0].setParameter("texture_diffuseMapTransform", this.transform.data);
    meshes[0].setParameter("texture_emissiveMapTransform", this.transform.data);
    meshes[0].setParameter("texture_opacityMapTransform", this.transform.data);
};

Ah yes, that project needs to be updated The .data API no longer exists so you would have to copy the X, Y, Z, W values into your own float array and pass that instead to get rid of the warning.

You can see it being done here: https://github.com/playcanvas/engine/blob/master/src/deprecated.js#L278

1 Like

I see, thanks!

But would you also say the strategy is outdated aswell? Should I just use an animated sprite?

Either is fine to be honest. Whatever you are more comfortable with in this case.

1 Like

then how did i find it if it didnt exist?

Erm, what do you mean by ‘how did I find it’?

@bjorn.syse perhaps this could be of use:

1 Like

Why not use sprite & spriteFrame of Image element for animation? That’s more easy, than animate material.

1 Like