The more animation and movement you have in your games, the more flashy and appealing they can be to players. In this tutorial I’ll be showing you how to build animations that run in their own self-contained functions.
This allows them to be interrupted at any time, allows for simultaneous user input and anything else you wish to occur in your game loop without having to wait for the animation script to finish. In our most recent game, Sudoku In Space, we make extensive use of this for background animations, main menu animations, and particle animations when you make a completion move. Since we use a timer in Sudoku In Space, it is important to not prevent the user from interacting with the game while an animation is happening for a row, column, or block completion.
To keep things simple this tutorial will only show the background animations for the larger stars that wink in and out. If you’re interested in seeing how I did the particle animations just let me know and I’ll do it in a future continuation of this article.
First, we’ll need a user defined type and a variable of that type. In Sudoku In Space I actually made this variable global so that all of our screens could have the background animation, however, it is not necessary if you are using the animation in only one area of your game since the function to handle the animation has a feedback loop. A feedback loop is when a function accepts input of a specific variable and then outputs to that same variable.
The following setup will make a star appear in a random location on the screen, color it one of three different colors at random, and make it grow and then shrink.
Our type:
1 2 3 4 5 6 7 8 |
type star_type max_W# as float dir as integer spriteID as integer abs_max_W# as float abs_min_W# as float time as integer endtype |
- max_W# will contain a random size that we want the star to grow to.
- dir is the direction of the sprite’s growth.
- spriteID is the sprite ID of the star.
- abs_max_W# is the maximum width the star should ever grow to so that the sprite’s image doesn’t become fuzzy or larger than we want it to appear.
- abs_min_W# is the minimum width the star should ever shrink to and serves as a starting and stopping point for the shrink/grow animation.
- time will allow us to control when the star reappears.
Now let’s look at the self-contained feedback loop function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
<span style="line-height: 1.5;">function HandleStar(star as star_type , fFT# , tT#)</span> // fFT# is frame time as obtained from GetFrameTime() // in the main loop // tT# is the total time we want the growing or shrinking to take //the depth of the sprite depth = 2000 // If the direction is 0 then this is the first time we're // running the function so we need to set direction to 1 (grow). if star.dir = 0 star.dir = 1 endif //Instantiate the sprite if it doesn't already exist. if GetSpriteExists(star.spriteID) = 0 img = LoadImage(“star.png”) star.spriteID = CreateSprite(img) // We'll allow the star to grow up to 5% // of the screen width. star.abs_max_W# = 5.0 // The star image was designed for an 800px wide screen // We want to prevent it from being smaller // than 10px at 800px resolution star.abs_min_W# = 100.0 * 10.0 / 800.0 // Initialize the size at the minimum size // so that it will grow SetSpriteSize(star.spriteID , star.abs_min_W# , -1) // Initialize the max_W# variable to a negative so that // we can set it to a random value star.max_W# = -1.0 endif // Initialize the animation variables – this happens every time the // star is shrunk to its minimum size as well // as the first run of the function // // If the max_W# variable is negative then we need to // determine a random size we'll allow the star to grow to // as well set it's initial position, color, and angle if star.max_W# < 0.001 // Determine a random value for the max width to grow to. // Here I multiply the maximum and minimum by 100 so that I can // make use of the random() function which only works on integers // and then divide by 100. This way the star can have a max width // with fractional percentages. r_low = floor(100.0 * (star.abs_max_W# - 0.4 * star.abs_max_W#)) r_high = floor(100.0 * (star.abs_max_W#)) star.max_W# = random(r_low , r_high) / 100.0 // Similar is done to find the initial position r_x_low = 100 r_x_high = floor(100.0 * (100.0 - star.max_W#)) x# = random(r_x_low , r_x_high) / 100.0 r_y_low = 100 maxY# = 100.0 – GetSpriteHeight(star.spriteID) r_y_high = floor(100.0 * maxY#) y# = random(r_y_low , r_y_high) / 100.0 // Now we can position the star SetSpritePosition(star.spriteID , x# , y#) // Give it a random angle for more randomness r_angle = random(1,360) SetSpriteAngle(star.spriteID , r_angle) // Set the time at which we initialized the star and make it visible. star.time = GetMilliseconds() SetSpriteVisible(star.spriteID , 1) // Set random color r_color_num = random(1,4) if r_color_num = 1 color$ = "200,255,255" //blue-ish elseif r_color_num = 2 color$ = "255,255,200" //purple-ish else color$ = “255,255,255” //white endif // This is a home brewed function I use that makes it a // Little easier to set sprite colors – // included after this function _SetSpriteColor(star.spriteID , color$) endif // Now we can actually animate the growth and shrinking of the star. // Determine the distance between the random width we grow to // and the minimum allowable width. dist# = star.max_W# - star.abs_min_W# // Get the current widht of the star so we can see if it should still // grow or shrink current_W# = GetSpriteWidth(star.spriteID) // Initialize this to a negative so that we can skip the sizing of // the star when we're pausing between appearances. new_W# = -1.0 // If the direction is 1 then we are growing the star if star.dir = 1 // If the current width is less than the maximum // width we want the star to grow to then grow it! // Otherwise stop growing and negate the direction // So that it will start shrinking. if current_W# < star.max_W# // Here we use a linear equation based on frame time (fFT#) // to increase the size of the sprite over time tT# new_W# = current_W# + dist# * fFT# / tT# else // We're done growing, so negate the direction new_W# = star.max_W# star.dir = star.dir * -1 endif elseif star.dir = -1 //Otherwise we're shrinking the star // If the current width of the star is still greater // than the allowable minimum width then grow it. // Otherwise stop. if current_W# > star.abs_min_W# // Use the same linear equation for growth based on frame time, // but negate the distance variable // so we're making the width smaller. new_W# = current_W# - dist# * fFT# / tT# else // We're done growing so set the new width to the minimum width // Doing this helps if a large number of frames were skipped. new_W# = star.abs_min_W# // Hide the star for now because we're going to wait to make // it reappear SetSpriteVisible(star.spriteID , 0) // Now when it is time, set max_W# to a negative to // reinitialize the star's animation (random max w, color, position) // The calculation here is tT# in seconds which needed to be // converted to milliseconds – so basically we're adding 3 seconds // before the star will restart the animation. if GetMilliseconds() > star.time + 3000.0 * tT# star.max_W# = -1.0 star.dir = star.dir * -1 endif endif endif // Only grow and reposition the star if new_W# if positive. // This allows us to skip over the sizing when the star is waiting // to be reinitialized if new_W# > 0.0 x# = GetSpriteXByOffset(star.spriteID) y# = GetSpriteYByOffset(star.spriteID) SetSpriteSize(star.spriteID , new_W# , -1) SetSpritePositionByOffset(star.spriteID , x# , y#) endif endfunction star |
This is used to color sprites with only a comma separated string for the R, G, and B values.
1 2 3 4 5 6 7 8 |
function _SetSpriteColor(iID , sColor$) if GetSpriteExists(iID) = 1 r = val(GetStringToken(sColor$ , "," , 1)) g = val(GetStringToken(sColor$ , "," , 2)) b = val(GetStringToken(sColor$ , "," , 3)) SetSpriteColor(iID , r , g , b , 255) endif endfunction |
Now all that needs to be done is run this function in a loop like so:
1 2 3 4 5 6 7 |
<span style="line-height: 1.5;">myStar as star_type</span>shrink_grow_time# = 0.50 do ft# = GetFrameTime() myStar = HandleStar(myStar , ft# , shrink_grow_time#) Sync() loop |
Hopefully you can also see how this can be extended for more complex animations where there are various states for the animation. One example you can see is in Sudoku In Space on the home screen. Our game character, Allen the Alien, flies onto the screen, appears to hover with some up and down tweening, and continues to do so until the player presses the play button, at which time he flies away. I also built that animation so it can be interrupted and the player does not need to wait for Allen to be in the center of the screen before they can tap a button. Tapping a button will simply allow Allen to keep moving across the screen until he’s gone.
In Sudoku In Space we take advantage of this method for many of the animations so that user input is not hampered. Please take a look at the game to see these animations in action. The most complex of them are the particle animations for when you complete a puzzle. If you’d like to see an example of that in a future tutorial, just let me know (naplandgames@gmail.com)! Sudoku In Space is free on Google Play and the App Store.