In the previous project the folder with all our class files was getting a bit crowded. This project will have even more classes, so we will help ourselves a little by separating things out into different packages. We will have a folder(package) for all the GameObjectSpec
related classes and a folder for all the Level
related classes. Everything else will remain in the usual folder.
When you create a new package, it is important to do so in the correct folder. The correct folder is the same one that we have been putting all the Java files in throughout this book. If you called this project Platformer
it will be the app/java/yourdomain.platformer
folder.
In the Android Studio solution explorer, right-click the app/java/yourdomain.platformer
(NOT yourdomain.platformer(androidTest)
and NOT yourdomain.platformer(test)
), select New | Package and name it GOSpec
.
We will now add a whole bunch of classes that will define all the objects of the game; their sizes, tags (for collision detection mainly), speed, starting location, associated graphic file, frames of animation and a list of components that define their behaviour.
There is one parent class, GameObjectSpec
from which all the other specification classes extend. There are lots of them, 23 in total and I don't recommend you sit there and type them all in. They can all be found in the download bundle in the Chapter 22/java/GOSpec
folder. You can highlight all the files, copy them and then right-click and Paste them all into the GOSpec
folder/package you just created. What you might need to do manually is click into them and check that the package declaration at the top of each file was correctly auto-adjusted from my package name to yours.
If you used Platformer
as the project title your package name should be:
package yourdomain.platformer.GOSpec;
While the specification classes are very similar, I thought it still advantageous to show all the code because they are worth examining. Flick through the next few pages of specification classes. When you look at them take note of the different sizes, speeds, graphics files, frames of animation and especially the different component names in the components
array. Here is the complete listing from all the specification classes with a few notes on each.
In a professionally built game it would be more typical to load such things from text files with a single line of text per component or attribute. If you want to upgrade your project (wait until you have it all working in chapter 25 first) you can read about parsing a text file using a simple Web search. However, sorting the required data from the text file is an in-depth process (but not beyond a determined beginner). A simple Web search will provide good example code to get started. By doing things this way we keep all our work inside Android Studio and get to play with making our own packages.
The following list of 23 classes appears in the same order they will appear in Android Studio with the exception that I present the base/parent class to the other 22 classes first to aid understanding.
This is the class that all the …Spec
classes will extend. It provides a related member variable/object instance for each of the specifications a game object can have.
import android.graphics.PointF; public abstract class GameObjectSpec { private String mTag; private String mBitmapName; private float mSpeed; private PointF mSize; private String[] mComponents; private int mFramesAnimation; GameObjectSpec(String tag, String bitmapName, float speed, PointF size, String[] components, int framesAnimation ){ mTag = tag; mBitmapName = bitmapName; mSpeed = speed; mSize = size; mComponents = components; mFramesAnimation = framesAnimation; } public int getNumFrames(){ return mFramesAnimation; } public String getTag(){ return mTag; } public String getBitmapName(){ return mBitmapName; } public float getSpeed(){ return mSpeed; } public PointF getSize(){ return mSize; } public String[] getComponents(){ return mComponents; } }
The main method (the constructor) has the job of receiving the specifications from all the child classes and copying the values to the member variables/instances. Our code will then be able to handle instances of GameObjectSpec
regardless of what the actual game object is; animated, controllable player through to dumb, unmovable tree.
All the other methods of this class are simple getter methods that the various parts of our program can call to find out more about the game object.
Review the code for this class and then we can talk about it.
import android.graphics.PointF; public class BackgroundCitySpec extends GameObjectSpec { // This is all the specifications for the city background private static final String tag = "Background"; private static final String bitmapName = "city"; private static final int framesOfAnimation = 1; private static final float speed = 3f; private static final PointF size = new PointF(100, 70); private static final String[] components = new String [] { "BackgroundGraphicsComponent", "BackgroundUpdateComponent"}; public BackgroundCitySpec() { super(tag, bitmapName, speed, size, components, framesOfAnimation); } }
The tag
is mainly used by the collision detection code we will write in chapter 25. Even the background which doesn't collide needs to be identified so that we can discount it from collision detection.
The bitmapName
variable holds the name of the graphics file that is used to represent this game object.
The framesOfAnimation
variable is set to 1
because this is not a sprite sheet. The player object on the other hand will be set to 5 because that's how many frames there are and the fire object's framesOfAnimation
will be set to 3.
The speed
variable in this project is different to that in the previous project because it is the speed in virtual metres per second rather than some convoluted measure relative to the player as it was in the last. Now we are making the jump to game-world based coordinates using a camera we can use these more real-world based size and speed measures.
Next up is size
and you can consider the city to be 100 metres by 70 metres. When we code the Camera
class in the next chapter we will see how these virtual metres translate to pixels and how we know which parts to show on the screen of the game at any given frame.
We also have a String
array for a list of components just like we did in the previous project. The actual components themselves, however will be different to the previous project.
Notice the two actual components we will need to code to bring this game object to life:
BackgroundGraphicsComponent BackgroundUpdateComponent
Finally notice the constructor that does nothing other than pass all the specifications into the parent class constructor which as we saw stashes the values away in the appropriate variables.
This is the same as the previous accept it uses a different graphic, mountain.png
.
import android.graphics.PointF; public class BackgroundMountainSpec extends GameObjectSpec { // This is all the specifications for the mountain private static final String tag = "Background"; private static final String bitmapName = "mountain"; private static final int framesOfAnimation = 1; private static final float speed = 3f; private static final PointF size = new PointF(100, 70); private static final String[] components = new String [] { "BackgroundGraphicsComponent", "BackgroundUpdateComponent"}; public BackgroundMountainSpec() { super(tag, bitmapName, speed, size, components, framesOfAnimation); } }
This is the same as the previous accept it uses a different graphic.
import android.graphics.PointF; public class BackgroundUndergroundSpec extends GameObjectSpec { // This is all the specifications for the underground private static final String tag = "Background"; private static final String bitmapName = "underground"; private static final int framesOfAnimation = 1; private static final float speed = 3f; private static final PointF size = new PointF(100, 70f); private static final String[] components = new String [] { "BackgroundGraphicsComponent", "BackgroundUpdateComponent"}; public BackgroundUndergroundSpec() { super(tag, bitmapName, speed, size, components, framesOfAnimation); } }
Review the specifications and move on.
This is a regular platform tile. Bob will be able to walk on it, so we will need to perform collision detection on it. Its position in the game world never changes so it is technically inanimate. Note though that the relative position of the block on the screen to the camera will change and the brick graphic will need to be drawn accordingly but the Camera
class will take care of all of that.
import android.graphics.PointF; public class BrickTileSpec extends GameObjectSpec { private static final String tag = "Inert Tile"; private static final String bitmapName = "brick"; private static final int framesOfAnimation = 1; private static final float speed = 0f; private static final PointF size = new PointF(1f, 1f); private static final String[] components = new String[]{ "InanimateBlockGraphicsComponent", "InanimateBlockUpdateComponent"}; public BrickTileSpec() { super(tag, bitmapName, speed, size, components, framesOfAnimation); } }
Finally notice the size is just one virtual metre wide and high.
This specification has the same components as the previous one but note we can assign any value for size
we like. This mine cart object is two metres by one metre. The graphic must of course be drawn to suit the size otherwise it will appear squashed or stretched.
import android.graphics.PointF; public class CartTileSpec extends GameObjectSpec { private static final String tag = "Inert Tile"; private static final String bitmapName = "cart"; private static final int framesOfAnimation = 1; private static final float speed = 0f; private static final PointF size = new PointF(2f, 1f); private static final String[] components = new String[]{ "InanimateBlockGraphicsComponent", "InanimateBlockUpdateComponent"}; public CartTileSpec() { super(tag, bitmapName, speed, size, components, framesOfAnimation); } }
import android.graphics.PointF; public class CoalTileSpec extends GameObjectSpec { private static final String tag = "Inert Tile"; private static final String bitmapName = "coal"; private static final int framesOfAnimation = 1; private static final float speed = 0f; private static final PointF size = new PointF(1f, 1f); private static final String[] components = new String [] { "InanimateBlockGraphicsComponent", "InanimateBlockUpdateComponent"}; public CoalTileSpec() { super(tag, bitmapName, speed, size, components, framesOfAnimation); } }
Review the specifications and move on.
Although this next object has quite a different use to say the brick object, notice that everything is the same apart from the graphic file and the tag. This is fine.
import android.graphics.PointF; public class CollectibleObjectSpec extends GameObjectSpec { private static final String tag = "Collectible"; private static final String bitmapName = "coin"; private static final int framesOfAnimation = 1; private static final float speed = 0f; private static final PointF size = new PointF(1f, 1f); private static final String[] components = new String [] { "InanimateBlockGraphicsComponent", "InanimateBlockUpdateComponent"}; public CollectibleObjectSpec() { super(tag, bitmapName, speed, size, components, framesOfAnimation); } }
We can detect a collision and determine that it is a collectible item from its tag and then handle what should happen.
import android.graphics.PointF; public class ConcreteTileSpec extends GameObjectSpec { private static final String tag = "Inert Tile"; private static final String bitmapName = "concrete"; private static final int framesOfAnimation = 1; private static final float speed = 0f; private static final PointF size = new PointF(1f, 1f); private static final String[] components = new String [] { "InanimateBlockGraphicsComponent", "InanimateBlockUpdateComponent"}; public ConcreteTileSpec() { super(tag, bitmapName, speed, size, components, framesOfAnimation); } }
This tile has minor differences to those we have seen so far. Firstly, it is quite large 2 metres by 4 metres. It also uses a DecorativeBlockUpdateComponent
instead of an InanimateBlockUpdateComponent
. The differences between these components in code is small but useful. As trees are just decoration in the background we will not bother to add or update a collider thus saving a few computation cycles per tree, per frame.
import android.graphics.PointF; public class DeadTreeTileSpec extends GameObjectSpec { private static final String tag = "Inert Tile"; private static final String bitmapName = "dead_tree"; private static final int framesOfAnimation = 1; private static final float speed = 0f; private static final PointF size = new PointF(2f, 4f); private static final String[] components = new String [] { "InanimateBlockGraphicsComponent", "DecorativeBlockUpdateComponent"}; public DeadTreeTileSpec() { super(tag, bitmapName, speed, size, components, framesOfAnimation); } }
Review the code and move on.
This is the first component we have seen that has more than one frame of animation. Our code will detect that an Animator
object needs to be assigned to this game object and will manage a simple flickering effect on the fire tile using the frames from the sprite sheet fire.png
.
import android.graphics.PointF; public class FireTileSpec extends GameObjectSpec { private static final String tag = "Death"; private static final String bitmapName = "fire"; private static final int framesOfAnimation = 3; private static final float speed = 0f; private static final PointF size = new PointF(1f, 1f); private static final String[] components = new String[]{ "AnimatedGraphicsComponent", "InanimateBlockUpdateComponent"}; public FireTileSpec() { super(tag, bitmapName, speed, size, components, framesOfAnimation); } }
Also notice that the AnimatedGraphicsComponent
is used instead of the InanimateBlockGraphicsComponent
.
import android.graphics.PointF; public class GrassTileSpec extends GameObjectSpec { private static final String tag = "Inert Tile"; private static final String bitmapName = "turf"; private static final int framesOfAnimation = 1; private static final float speed = 0f; private static final PointF size = new PointF(1f, 1f); private static final String[] components = new String[]{ "InanimateBlockGraphicsComponent", "InanimateBlockUpdateComponent"}; public GrassTileSpec() { super(tag, bitmapName, speed, size, components, framesOfAnimation); } }
Review the code and move on.
This next spec is for the blocks that will cause the player to instantly die and end the game. It helps the level designer prevent embarrassing bugs like falling out of the game world.
package com.gamecodeschool.c22platformer.GOSpec; import android.graphics.PointF; public class InvisibleDeathTenByTenSpec extends GameObjectSpec { private static final String tag = "Death"; private static final String bitmapName = "death_visible"; private static final int framesOfAnimation = 1; private static final float speed = 0f; private static final PointF size = new PointF(10f, 10f); private static final String[] components = new String[]{"InanimateBlockGraphicsComponent", "InanimateBlockUpdateComponent"}; public InvisibleDeathTenByTenSpec() { super(tag, bitmapName, speed, size, components, framesOfAnimation); } }
Review the code and move on.
This is the same as a tree that we discussed previously except the size and the graphic. Review the specifications and move on.
import android.graphics.PointF; public class LamppostTileSpec extends GameObjectSpec { private static final String tag = "Inert Tile"; private static final String bitmapName = "lamppost"; private static final int framesOfAnimation = 1; private static final float speed = 0f; private static final PointF size = new PointF(1f, 4f); private static final String[] components = new String[]{ "InanimateBlockGraphicsComponent", "DecorativeBlockUpdateComponent"}; public LamppostTileSpec() { super(tag, bitmapName, speed, size, components, framesOfAnimation); } }
Review the code and move on.
This game object is new. Notice the MovableBlockUpdateComponent
. We will code these to make these platforms move in the game world allowing the player to jump on them and be transported up and down.
import android.graphics.PointF; public class MoveablePlatformSpec extends GameObjectSpec { private static final String tag = "Movable Platform"; private static final String bitmapName = "platform"; private static final int framesOfAnimation = 1; private static final float speed = 3f; private static final PointF size = new PointF(2f, 1f); private static final String[] components = new String [] { "InanimateBlockGraphicsComponent", "MovableBlockUpdateComponent"}; public MoveablePlatformSpec() { super(tag, bitmapName, speed, size, components, framesOfAnimation); } }
Note that we can still use a regular InanimateBlockGraphicsComponent
. Just in case it is causing confusion, inanimate refers to the fact it doesn't use a sprite sheet (has no frames of animation), we can still change its location in the game world (move it) via the MoveableBlockUpdateComponent
.
The objective game object is an important game object because it triggers the player reaching the end of the level. However, as with the collectible object all the significant functionality can be handled by the PhysicsEngine
via its unique tag and no new components or features are needed for this game object's specification.
import android.graphics.PointF; public class ObjectiveTileSpec extends GameObjectSpec { private static final String tag = "Objective Tile"; private static final String bitmapName = "objective"; private static final int framesOfAnimation = 1; private static final float speed = 0f; private static final PointF size = new PointF(3f, 3f); private static final String[] components = new String[]{ "InanimateBlockGraphicsComponent", "InanimateBlockUpdateComponent"}; public ObjectiveTileSpec() { super(tag, bitmapName, speed, size, components, framesOfAnimation); } }
This is perhaps unsurprisingly our most advanced game object. It has an AnimatedGraphicsComponent
just as the fire tile does so that it sprite sheet graphic is used to animate its appearance. It also has a specific PlayerUpdateComponent
that will be much more advanced than all the other …Update…
components. It will handle things like moving, jumping and falling. The PlayerInputComponent
will communicate with the GameEngine
class to receive and translate the players screen touches. The communication method will use the same Observer pattern that we learnt for the previous project.
import android.graphics.PointF; public class PlayerSpec extends GameObjectSpec { // This is all the unique specifications for a player private static final String tag = "Player"; private static final String bitmapName = "player"; private static final int framesOfAnimation = 5; private static final float speed = 10f; private static final PointF size = new PointF(1f, 2f); private static final String[] components = new String [] { "PlayerInputComponent", "AnimatedGraphicsComponent", "PlayerUpdateComponent"}; public PlayerSpec() { super(tag, bitmapName, speed, size, components, framesOfAnimation); } }
Review the code and move on.
Another simple walkable object.
import android.graphics.PointF; public class ScorchedTileSpec extends GameObjectSpec { private static final String tag = "Inert Tile"; private static final String bitmapName = "scorched"; private static final int framesOfAnimation = 1; private static final float speed = 0f; private static final PointF size = new PointF(1f, 1f); private static final String[] components = new String [] { "InanimateBlockGraphicsComponent", "InanimateBlockUpdateComponent"}; public ScorchedTileSpec() { super(tag, bitmapName, speed, size, components, framesOfAnimation); } }
Review the specifications and move on.
Another simple walkable object.
import android.graphics.PointF; public class SnowTileSpec extends GameObjectSpec { private static final String tag = "Inert Tile"; private static final String bitmapName = "snow"; private static final int framesOfAnimation = 1; private static final float speed = 0f; private static final PointF size = new PointF(1f, 1f); private static final String[] components = new String [] { "InanimateBlockGraphicsComponent", "InanimateBlockUpdateComponent"}; public SnowTileSpec() { super(tag, bitmapName, speed, size, components, framesOfAnimation); } }
Review the specifications and move on.
Just like the dead tree but lusher and covered in snow.
import android.graphics.PointF; public class SnowyTreeTileSpec extends GameObjectSpec { private static final String tag = "Inert Tile"; private static final String bitmapName = "snowy_tree"; private static final int framesOfAnimation = 1; private static final float speed = 0f; private static final PointF size = new PointF(3f, 6f); private static final String[] components = new String [] { "InanimateBlockGraphicsComponent", "DecorativeBlockUpdateComponent"}; public SnowyTreeTileSpec() { super(tag, bitmapName, speed, size, components, framesOfAnimation); } }
Review the specifications and move on.
This is another purely decorative game object.
import android.graphics.PointF; public class StalactiteTileSpec extends GameObjectSpec { private static final String tag = "Inert Tile"; private static final String bitmapName = "stalactite"; private static final int framesOfAnimation = 1; private static final float speed = 0f; private static final PointF size = new PointF(2f, 4f); private static final String[] components = new String [] { "InanimateBlockGraphicsComponent", "DecorativeBlockUpdateComponent"}; public StalactiteTileSpec() { super(tag, bitmapName, speed, size, components, framesOfAnimation); } }
Review the specifications and move on.
This is another purely decorative object.
import android.graphics.PointF; public class StalagmiteTileSpec extends GameObjectSpec { private static final String tag = "Inert Tile"; private static final String bitmapName = "stalagmite"; private static final int framesOfAnimation = 1; private static final float speed = 0f; private static final PointF size = new PointF(2f, 4f); private static final String[] components = new String [] { "InanimateBlockGraphicsComponent", "DecorativeBlockUpdateComponent"}; public StalagmiteTileSpec() { super(tag, bitmapName, speed, size, components, framesOfAnimation); } }
A pile of stones the player can jump on.
import android.graphics.PointF; public class StonePileTileSpec extends GameObjectSpec { private static final String tag = "Inert Tile"; private static final String bitmapName = "stone_pile"; private static final int framesOfAnimation = 1; private static final float speed = 0f; private static final PointF size = new PointF(2f, 1f); private static final String[] components = new String [] { "InanimateBlockGraphicsComponent", "InanimateBlockUpdateComponent"}; public StonePileTileSpec() { super(tag, bitmapName, speed, size, components, framesOfAnimation); } }
Review the specifications and move on.
Looking at the components you will notice the three main types each has. We will therefore need a graphics, update and input related interface. We will code them now. Throughout the rest of this project we will then code the classes that implement these interfaces and match the component classes we just saw in the GameObjectSpec
related classes.
18.224.109.21