Entity Object
1. Entity object:
2. Initialize Animation
3. Getting An Animated Region
4. Getting the Animated One Shot Region
5. Extending the Entity
1) Entity object:
Any object that is either animated or moves I have be part of my "entity" object class. This class contains basic functionality that I tend to reuse over and over. The class basically starts defining a group of vars which are described below.
Here's the code for the initial vars that are set:
public class Entity {
Animation<TextureRegion> anim;
Animation<TextureRegion> oneShotAnim;
TextureRegion tempRegion;
Vector2 position = new Vector2();
boolean doOneShot = false;
float oneShotTime = 0; // animation time for one shot animations
boolean animationFinished;
Rectangle bounds = new Rectangle();
}
anim is used to hold an animation, typically one used to render the entity in the renderer. oneShotAnim holds the current one shot animation to be shown. A one shot animation is an animation that plays through one time. This type of animation will be explained more later. tempRegion is the current region that should be rendered that is used by your renderer. position is an x y vector (2 numbers) that represents the current position of this entity on the screen. doOneShot is set in the code when the one shot should be triggered. oneShotTime keeps track of how long the current one shot animation has been shown. This time determines which frame in the one shot to show. animationFinished is set to "true" when the one shot animation has finished. And finally, bounds is the bounds rectangle that is used to determine whether or not this entity has overlaped another entity in the game. As an example in this image the pink portion is the bounds rectangle:
In the code we check to see if another bound intersects that pink rectangle and act accordingly if it does.
There are 5 basic components in this class:
moveX
moveY
init_animation
getAnimatedRegion
getAnimatedOneShotRegion
moveX moves the entity along the x axis. It basically increments the x portion of the position var (position.x). To make the entity go right, enter a positive number, and to make it go left, enter a negative number. moveY moves the entity along the y axis, and works the same way as moveX, except positive numbers move the entity up, and negative numbers move it down. Here are the methods:
public void moveX(float xPos) {
position.x += xPos * Gdx.graphics.getDeltaTime();
} // END moveX
public void moveY(float yPos) {
position.y += yPos * Gdx.graphics.getDeltaTime();
} // END moveY
Now as you can see, each of those methods doesn't simply add or subtract from the original position, but multiplies the amount by the value of "Gdx.graphics.getDeltaTime()". What that does is determine how far to move the entity based on the amount of time that has elapsed since the last time we attempted to move. So, for example, if we have a speed of "10 units", represented as "10",
2) Initialize Animation
init_animation initializes an animation object and returns that object. Basically, you pass into the method the name of the animation sheet, which is basically a sheet with all of the animations on it as shown below:
You also pass in the asset manager, which contains all of the images and sound for your game. The vars "cols" and "Rows" are how many columns and rows are in the animation respectively. The var "frameSpeed" is basically a number that determines how fast the animation runs. Now for this I don't use any "exact" method as I simply use the formula of 1 divided by whatever number you pass in here. This means that the larger number you pass in, the smaller the result will be, which in turn means the shorter time there will be between animations, thereby making the animation move faster. In other words, the larger the number you put in for "frameSpeed", the faster the animation will run.
The final var tells whether this animation will loop indefinitely when called, or if it goes through all of the animations once and then stops.
Now lets go through the code for the init_animation method.
The first thing we go is get the animation sheet from the asset manager.
sheet = assetMgr.getImg(sheet_name);
Next, we create a 2 dimensional array of type TextureRegion to hold each individual frame. We then split the sheet we have according to the number of columns and rows we passed in. So the split function in textureRegion needs the height and width of each frame, which we get by dividing the width of the sheet by the columns we passed in, and dividing the height of the sheet by the rows we passed in. I did it this way because I find it much easier to see how many columns and rows are on a sheet as opposed to constantly looking for the height and width of a sheet.
I then create a new textureregion array with a total capacity equal to the columns times the rows. Then I assign each region that was split into the temporary textureregion array into a spot in my new frames textureregion array.
TextureRegion[][] tmp = TextureRegion.split(sheet, sheet.getWidth()/cols, sheet.getHeight()/rows);
frames = new TextureRegion[cols * rows];
int index = 0;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
frames[index++] = tmp[i][j];
}
}
I then calculate the time in between each animation frame, which I have as 1 / frameSpeed.
seconds = 1 / frameSpeed;
This was just a quick way for me to have larger numbers entered as a var in the method result in a faster speed. I'm not much of a stickler on having "X frame speed" and really just set a number and eyeball how it looks, changing it if necessary until the animation looks ok. Works for me so far.
Now that we have the frames and the times, a new animation object is created with that time and the frames as arguments. We then set the looping mode to "normal" if looping is false. This means that the animation will execute once and stop. The default is for the animation to continuously loop, so if loop is true we don't need to change anything.
tempAnimation = new Animation<TextureRegion>(seconds, frames);
if( !loop ) {
tempAnimation.setPlayMode(PlayMode.NORMAL);
} // END if
Finally, we initialize the bounds rectangle to the size of one frame. If this is not correct, we can easily set this in our individual classes, but I find that I'd constantly forget to do this with frames that needed to have the bounds set to the size of one frame, so I included it here.
bounds.width = sheet.getWidth()/cols;
bounds.height = sheet.getHeight()/rows;
And that's it! Just return the newly created animation and there's your init animation method!
3) Getting An Animated Region
The code for getting a "normal" animated region (in this case "normal" meaning an animation that renders continuously) is pretty simple. Here's the entire method:
public TextureRegion getAnimatedRegion(Animation<TextureRegion> frames, float stateTime) {
tempRegion = frames.getKeyFrame(stateTime, true);
return tempRegion;
}
All this method does is take the animation object and the current time as arguments. It then used the method "getKeyFrame" to get the correct animation region (frame) to display based on the time passed in. It then returns this region to be displayed in the renderer.
4) Getting the Animated One Shot Region
The method "getAnimatedOneShotRegion" renders an animation that only plays through once. Once the animation is finished, it sets a var in the object called "animationFinished" and is a bit more complex than the method to continuously render an animation.
First thing getAnimatedOneShotRegion does is take the frames passed in and gets the correct frame to display based on the oneShotTime var similarly to the continuous animation code above:
image = frames.getKeyFrame(oneShotTime, true);
So we keep track of this image region to return from this method. Next we add the DeltaTime to the oneShotTime var. What this does is add a time interval to the total time the one shot animation has been up. Based on the amount of time the animation has been up, and the current time in the var, we display the appropriate frame.
oneShotTime += Gdx.graphics.getDeltaTime();
We don't use the standard global stateTime as we do for other animation because we want to make sure that this animation that plays through one time displays the correct frame based specifically on when this animation started.
Finally, we check to see if the animation is done. Once the animation has ended, we set some vars which tells the main GameScreen that the one shot has ended, which includes a custom var called "animationFinished". This can then be checked to start another animation or anything else or ignored if it is not needed.
if( frames.isAnimationFinished(oneShotTime) ) {
doOneShot = false;
oneShotTime = 0;
animationFinished = true;
} // END if
Then the method simply returns the image region we stored before and that's the completed method!
5) Extending the Entity
These are the things that I've found I need in just about every game I do. Some games require additional methods that are specific to that game, but are used in many of the game objects. An example is card games, where the cards could all have properties that you need throughout the game that isn't needed in other games. In those instances, you can add methods to this entity class as needed so they are available to all of your game objects.