Animated GUISprites

Started by DMWarrior, Mar 31, 2020, 02:11 PM

Previous topic - Next topic

DMWarrior

Animated GUISprites

These functions can be used to create spritesheets and set animation frames to a GUISprite. Includes example and explanation below.
/**
 * Create a spritesheet.
 *
 * @param {number} imageWidth Image width.
 * @param {number} imageHeight Image height.
 * @param {number} width Frame width.
 * @param {number} height Frame height.
 * @param {number} x Horizontal unit position (from 0.0 to 1.0).
 * @param {number} y Vertical unit position (from 0.0 to 1.0).
 *
 * @return {table[]}
 */
function createSpritesheet(imageWidth, imageHeight, width, height, x, y) {
  // Spritesheet to be returned:
  local spritesheet = [];

  // Rows and columns of spritesheet:
  local rows    = imageHeight / height;
  local columns = imageWidth / width;

  // Frame index counter:
  local index = 0;

  // Iterate through rows and columns...
  for(local row = 0; row < rows; row += 1) {
    for(local column = 0; column < columns; column += 1) {
      spritesheet.push({
        // Frame index.
        index = index,

        // Initial cut position (unit value).
        topLeft = {
          x = x * column,
          y = y * row
        },

        // Final cut position (unit value).
        bottomRight = {
          x = (x * column) + x,
          y = (y * row) + y
        }
      });

      index += 1;
    }
  }

  return spritesheet;
}

/**
 * Set animation frame for one image.
 *
 * @param {GUISprite} image Image.
 * @param {table} frame Animation frame.
 */
function setSpriteFrame(image, frame) {
  image.TopLeftUV.X = frame.topLeft.x;
  image.TopLeftUV.Y = frame.topLeft.y;

  image.BottomRightUV.X = frame.bottomRight.x;
  image.BottomRightUV.Y = frame.bottomRight.y;
}

Example

This will animate a GUISprite using this image (name it "spritesheet.png"): https://i.ibb.co/8dVGb3v/spritesheet.png
// Animated sprite. It does have a size of 256x320:
local sprite_anim = GUISprite("spritesheet.png", VectorScreen(0, 0));
sprite_anim.Size = VectorScreen(128, 128);

// Create spritesheet:
local spritesheet = createSpritesheet(256, 320, 64, 64, 0.25, 0.2);
setSpriteFrame(sprite_anim, spritesheet[0]);

// Animation control:
local sprite_frame = 0;
local sprite_delay = 0;

/**
 * @event Script::ScriptProcess
 */
function Script::ScriptProcess() {
  // Increase delay counter...
  sprite_delay += 1;

  // Change the frame and reset the delay:
  if(sprite_delay >= 20) {
    sprite_delay = 0;

    // Set and advance frame:
    setSpriteFrame(sprite_anim, spritesheet[sprite_frame]);
    sprite_frame += 1;

    // Loop frames:
    if(sprite_frame >= spritesheet.len()) {
      sprite_frame = 0;
    }
  }
}

How it works?

While GUISprites are typically used to display one full-sized image, it's possible to display only one part of it, too. If used alongside a spritesheet or texture atlas, this can be used for a lot of things, like animations. This is achieved by using "TopLeftUV" and "BottomRightUV". So how does that work?

Basically, these properties determine where your image starts and ends. When a GUISprite is created, the default properties are always set to display the whole image. The first position (the top-left) will always be at "0,0", and the last position (the bottom-right) will always be at "<width of image - 1>,<height of image - 1>".

Let's take the "logo.png" from Blank Server, for example:



The image size is 500x247, so the top-left position will be at "0,0" and the bottom-right position will be at "499,246". Simple enough, right?

Well, not really. Take a look at this:
// This is the logo image from Blank Server (500x247):
sprite_logo <- GUISprite("logo.png", VectorScreen(0, 0));

// Top-left position: 0,0 (as expected).
Console.Print("Top-left position: " + sprite_logo.TopLeftUV.X + "," + sprite_logo.TopLeftUV.Y);

// Bottom-right position: 1,1 (wait... what?).
Console.Print("Bottom-right position: " + sprite_logo.BottomRightUV.X + "," + sprite_logo.BottomRightUV.Y);

What went wrong?

Turns out the "UV" properties don't use pixels, but percentages! These properties will go from 0.0 (0%) to 1.0 (100%) and can wrap or flip the image if you go lower or greater than these values. This can give you interesting effects, like a moving background.

We can also take advantage of percentages to make simple cuts. Let's say we want to display only half of the logo:

// This is the logo image from Blank Server (500x247):
sprite_logo <- GUISprite("logo.png", VectorScreen(0, 0));
sprite_logo.BottomRightUV.X = 0.5; // This will remove 50% of the right side.

// The image has been cut, but the original size of the GUISprite will
// remain the same. We need to resize it too if we don't want the result
// to appear stretched:
sprite_logo.Size.X = 250;

Remember to resize the image, too. You should see something like this:



Spritesheets

Now let's move onto spritesheets. Take this image, and name it "spritesheet.png":



The image size is 256x320, but every frame is 64x64. Just to get started, let's split this into rows and columns and see if we can figure something out:



We have 5 rows (20% per row) and 4 columns (25% per column). If we want to display the frame where the character is scared, we need to move the top-left position to "25%,80%" and the bottom-right position to "50%,100%".

// This is the sprite (256x320):
sprite_anim <- GUISprite("spritesheet.png", VectorScreen(0, 0));
sprite_anim.Size = VectorScreen(64, 64);

// Set the top-left position:
sprite_anim.TopLeftUV.X = 0.25;
sprite_anim.TopLeftUV.Y = 0.8;

// Set the bottom-right position:
sprite_anim.BottomRightUV.X = 0.5;
sprite_anim.BottomRightUV.Y = 1.0;

And that's how it's done.

Sebastian

Just wow! You have explained everything very well, and the discovery itself is wow!
I didn't even know we have such UV functions.

I remember some guys asking for such a thing back in the days, where client-side just came in.
Well, it was always here, after all.
Good job, @DMWarrior !

Xmair

Fucking beast. A job well fucking done!

Credits to Boystang!

VU Full Member | VCDC 6 Coordinator & Scripter | EG A/D Contributor | Developer of VCCNR | Developer of KTB | Ex-Scripter of EAD

Murdock


AroliS^

Lemme love ya

DizzasTeR

A prime example of a well documented and coded snippet. Just like very few other people on this forum have done it this way.

Luckshya

Damn, you nailed it. Awesome!