Vice City: Multiplayer

Server Development => Scripting and Server Management => Client Scripting => Topic started by: DMWarrior on Mar 31, 2020, 02:11 PM

Title: Animated GUISprites
Post by: DMWarrior on Mar 31, 2020, 02:11 PM
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:

(https://i.ibb.co/2jZHPBS/logo-sizes.png)

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:

(https://i.ibb.co/1X77ccg/logo-result.png)

Spritesheets

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

(https://i.ibb.co/8dVGb3v/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:

(https://i.ibb.co/Lh4v91W/spritesheet-sizes.png)

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.
Title: Re: Animated GUISprites
Post by: Sebastian on Mar 31, 2020, 03:24 PM
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 !
Title: Re: Animated GUISprites
Post by: Xmair on Mar 31, 2020, 03:32 PM
Fucking beast. A job well fucking done!
Title: Re: Animated GUISprites
Post by: Murdock on Mar 31, 2020, 03:35 PM
Looks great!
Title: Re: Animated GUISprites
Post by: AroliS^ on Mar 31, 2020, 03:41 PM
Awesome!
Title: Re: Animated GUISprites
Post by: DizzasTeR on Mar 31, 2020, 03:51 PM
A prime example of a well documented and coded snippet. Just like very few other people on this forum have done it this way.
Title: Re: Animated GUISprites
Post by: Luckshya on Mar 31, 2020, 04:42 PM
Damn, you nailed it. Awesome!