SDK method to update a Sprite animation frame with new image?

  • Is there an SDK method to update a Sprite animation frame with a new image (from a blob, data URI, JS array, etc.)

    I am looking for alternatives/workarounds to a current stuttering issue seen when using the Sprite Action Load Image from URL.

  • This would inevitably involve a WebGL texture upload, which has a high performance cost. Is there a way to render the image source directly?

  • Ashley Thanks for the reply (and thanks for the comments in the bug report, I understand, that makes sense.)

    I do agree that rendering directly to the C3 canvas would be better and that will be something to explore, however, the render is done through the Spine-TS runtime script and it looks like a lot to dive into to figure out (10K lines). It's already set up to render to a canvas, however, also integrating in the render with other sprite objects could be a challenge? Though I think it would be possible if created a Drawing plugin and did the work to integrate the Spine render into the Drawing Plugin Draw function? Something to explore in the future.

    I was looking for a short cut to use the existing Spine-TS. I have found a way that works and _seems_ to perform pretty well. I definitely tried to use only C3 SDK, but I think I used a little more. In this method, I created a drawing plugin which replaces a sub texture of the drawing plugin Sprite Sheet with the canvas texture on each Draw() call. There are limitations: it only replaces the top MIP level, so if the Sprite is scaled down, the old image starts to blend in. So, for the game, I will force screen resolution and keep Sprite size relatively the same so the other MIP levels do not kick in (this will likely be a desktop nw.js / Electron game, so I will have some control over which chromium engine the game is shipped with and control over full screen vs windowed.) I know this is not bullet proof, but I think I can work around the issues. If you have suggestions on how to make the below use more C3 SDK functions, that would be very appreciated!

    In general for Spine animations, I will just use 1-3 characters animated this way, so 'only' 1-3 texture updates per frame. The rest will be simpler and use traditional sprite frame animation.

     Draw(renderer)
     {
     var gl = renderer._gl
     const imageInfo = this._objectClass.GetImageInfo();
     const texture = imageInfo.GetTexture();
     if (!texture) return; // dynamic texture load which hasn't completed yet; can't draw anything
    
     const wi = this.GetWorldInfo();
     const quad = wi.GetBoundingQuad();
     const rcTex = imageInfo.GetTexRect();
     var myCanvas = document.getElementById(this._elementId)
    
     gl.bindTexture(gl.TEXTURE_2D, texture._texture);
     // webgl2: gl.texSubImage2D(gl.TEXTURE_2D, 0, (rcTex._left * texture.GetWidth() - 0.5).toFixed(0), (rcTex._top * texture.GetHeight() - 0.5).toFixed(0), myCanvas.width, myCanvas.height, gl.RGBA, gl.UNSIGNED_BYTE, myCanvas);
     gl.texSubImage2D(gl.TEXTURE_2D, 0, (rcTex._left * texture.GetWidth()).toFixed(0), (rcTex._top * texture.GetHeight()).toFixed(0), gl.RGBA, gl.UNSIGNED_BYTE, myCanvas);
    
     renderer.SetTexture(texture)
    
     if (this._runtime.IsPixelRoundingEnabled())
     {
     const ox = Math.round(wi.GetX()) - wi.GetX();
     const oy = Math.round(wi.GetY()) - wi.GetY();
     tempQuad.copy(quad);
     tempQuad.offset(ox, oy);
     renderer.Quad3(tempQuad, rcTex);
     }
     else
     {
     renderer.Quad3(quad, rcTex);
     }
     }
    
  • Try Construct 3

    Develop games in your browser. Powerful, performant & highly capable.

    Try Now Construct 3 users don't see these ads
  • I think I found at least one improvement to use more of the C3 SDK, I can use the below to get the rcTex coordinates:

    getLeft()
    getTop()
    getRight()
    getBottom()
    
  • I think the best approach would be to use the same approach as the Text plugin, which similarly renders via a canvas. The steps are roughly:

    1. Draw content to a 2D canvas (text for the Text plugin, or in your case a Spine rendering)
    2. Create a new empty texture managed by the plugin
    3. Whenever the canvas size changes, destroy the texture and create a new one to match the new size
    4. Whenever the canvas content changes, upload it to the texture
    5. Finally draw the texture normally with a quad

    The key points are the texture is owned and managed by the plugin itself, so it's not trying to overwrite existing content used by other parts of the engine; it uses a direct WebGL texture upload so there's no unnecessary compress/decompress cycle; and there is support in the engine for this approach already (as Text uses it already). I also must point out that you cannot safely mix raw WebGL calls and Construct renderer calls, as Construct's renderer queues WebGL commands using a batching system (meaning WebGL calls will happen in an unexpected order) and heavily relies on caching and ignoring redundant changes (and changing WebGL state outside of the renderer with calls like bindTexture will corrupt the internal state of the renderer and probably break the game). This is why there is no documented/supported way to get the WebGL context directly - it's for good reason! I've added to my todo list to document the necessary renderer calls to support that approach.

  • I just updated the WebGLRenderer docs to cover new methods CreateDynamicTexture(), UpdateTexture() and DeleteTexture(). These should be enough to implement the approach I outlined above. If not let me know and I can look in to covering more of the API surface. These methods should already be supported in the runtime, but note if you want to use them in the editor they won't be supported until the next release.

  • Ashley Thanks for the great response, details of the engine and suggested method for implementation. I will take a look at implementing something similar to the text method and thanks for the additional documentation that you just provided. I appreciate the comment on the resize and creating a new texture based on that - very helpful in making this all more robust.

  • Thanks Ashley this worked well, it seems to perform reasonably well (100 instances at 320x240 still ran at 60 fps, I think I will be more in the 1-10 range.) I am also glad to be using only the C3 SDK and no 'raw' webgl which could get out of sync with the engine gl batching and optimizations. Thanks for the help and the docs. I will post the addon (and Spine example which uses it) after some more testing.

    Here's my updated Draw() function.

     Draw(renderer)
     {
     if (this._elementId == "") return; // elementID not set, can't draw the element
    
     var myCanvas = document.getElementById(this._elementId)
     // Create texture if it does not exist (could this be done in constructor or setElementId action)
     if (this._elementTexture === null)
     {
     this._elementTexture = renderer.CreateDynamicTexture(myCanvas.width,myCanvas.height,{mipMap:false})
     // console.log(this._elementTexture)
     }
    
     const wi = this.GetWorldInfo();
     const quad = wi.GetBoundingQuad();
     const rcTex = new C3.Rect(0,0,1,1) // Possible to get from this._texture instead? Not needed, not spritesheeted?
    
     renderer.UpdateTexture(myCanvas, this._elementTexture, {})
    
     renderer.SetTexture(this._elementTexture)
    
     if (this._runtime.IsPixelRoundingEnabled())
     {
     const ox = Math.round(wi.GetX()) - wi.GetX();
     const oy = Math.round(wi.GetY()) - wi.GetY();
     tempQuad.copy(quad);
     tempQuad.offset(ox, oy);
     renderer.Quad3(tempQuad, rcTex);
     }
     else
     {
     renderer.Quad3(quad, rcTex);
     }
     }
    
Jump to:
Active Users
There are 1 visitors browsing this topic (0 users and 1 guests)