Flying Ship Tutorial
From Victory Engine
In this tutorial we'll build a small game in which the player can freely fly a spaceship around the screen. In this tutorial you'll learn:
- Setting up the basics of a Victory Engine game.
- Loading resources (images).
- Creating entities.
- Getting user input.
- Moving physical objects.
Contents |
The Beginning
Let's begin with this very basic code:
package org.victoryengine.tutorials.flyingship;
import org.victoryengine.core.GameLoop;
import org.victoryengine.graphics2d.AbstractDisplayListener;
import org.victoryengine.graphics2d.Display2D;
import org.victoryengine.graphics2d.j2d.JDisplay;
/**
* Simple game in which the player controls a spaceship.
* @author Bart van Heukelom
*/
public class FlyingShipGame extends AbstractDisplayListener {
private Display2D display;
public static void main(String[] args) {
new FlyingShipGame().start();
}
/**
* Set the game up.
*/
public FlyingShipGame() {
final JDisplay disp = new JDisplay();
disp.addListener(this);
disp.setWindowTitle("Flying Ship");
display = disp;
}
/**
* Start the game.
*/
public void start() {
display.start();
GameLoop.get().start();
}
/**
* Exit the game when the window is closed.
*/
public void windowClosed() {
System.exit(0);
}
}
This is our main game class. This is where the game begins (as you can see by the presence of the main() method). In the constructor of this class, we begin by setting up a Java2D-based display, which is what we're going to use in this project. Note that the display component is entirely seperated from the rest, which means you'll be able to use other systems if you want (for example something based on OpenGL, even 3D is possible).
So anyway, in the next line we add this object as a listener to the display, which means we get notified if something important (an event) happens in the display. You can assign objects as listeners if they implement the interface DisplayListener, which in this case we do by extending AbstractDisplayListener. Now, the particular event we're interested in is the windowClosed() event, which you can see at the bottom of the class. This event occurs when the window is closed by the user. Contrary to what one may think, the game is not automatically exited if this happens. Here we make sure it is by calling System.exit(0). Not the nicest sollution, but it'll do in this simple example.
Back to the constructor! After setting the display listener, we give the window a nice title, and save the display in a class field for later reference.
When the constructor finishes, the method start() is called in main(). In this method we start the display, which means the window will show and a drawing loop will be started (in a seperate thread) which we can later insert stuff in to display it on screen. After that we start the game loop. Similar to the display, we will add game logic to this loop later on in this tutorial.
Try running the game now. You'll see a 800x600 white window, which you can close to exit the game.
Loading Resources
The next step will be to load the resources we're going to need. Resource is a generic term for an external file which needs to be loaded. In this case, we'll load images and sprites.
The Victory Engine includes a flexible resource framework. The core is the Resource Manager. This is a class (implementing the interface ResourceManager) which, well, manages the resources.
Defining Resources in XML
In this case we're going to use the ListingResourceManager. This manager allows you to define resources in an XML and will load them when needed.
Begin by creating a directory "res" in the classpath (the root of your code). In this directory place these images and create a new XML file called resources.xml. Resulting in the following directory structure:
Project
org
victoryengine
tutorials
flyingship
FlyingShipGame.java
res
images
sprites
ship.png
ship_fire.png
background.png
resources.xml
Now edit "resources.xml" to contain:
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<modifier var="location" expression="{FILE_DIR}{res.location}" />
<package name="images" modify="location">
<package name="sprites" modify="location">
<image name="ship" location="ship.png" />
<image name="ship_fire" location="ship_fire.png" />
</package>
<image name="background" location="background.png" />
</package>
<package name="sprites">
<modifier var="image" expression="images.{RES_NAME}" />
<sprite name="ship" originx="50%" originy="50%" />
<sprite name="ship_fire" originx="50%" originy="50%" />
</package>
</resources>
Let's break it down. resources is the root element here. It defines this file as a resource list.
The first element is a modifier element. We'll come back to that later.
Within the root element we see package elements. All resources within a package will have "<package name>." prepended to their name. For example, a resource named "ship" in a package "images" will get the name "images.ship". You can also nest packages or place resources directly inside the root, outside of any package.
The first package is called images. This is where we define the images we're going to load. There are three images: a spaceship, a spaceship with it's engines firing and a background (the first two are both in a nested package "sprites"). For each we define a name and a location (the filename). The location should be an absolute path, something like "C:\Documents\Game\res\ship.png".
Of course, it's impractical to write that for every image, and it makes the game absolutely unportable. This is where modifiers come in.
Modifiers
Modifiers are defined by:
<modifier var="a" expression="b" [target="c"] />
A modifier takes a certain property from a resource (like "location") and changes it's value to whatever is defined in expression. This expression can contain references to other properties in the resource and some global variables. For example, the expression "foo/{res.location}" applied to the property "location" will simply prepend "foo/" to all resources' "location" property.
The optional target attribute can be used to apply the modifier only to resources of a certain type ("sprite" or "image"). You'll rarely use this, because it's good design to have packages contain resources of one type only.
Modifiers are processed backwards. The most recently defined ones get executed first, then the older ones. Also, modifiers defined inside packages only apply inside that package.
In the case of the images, there are two modifiers that will be applied. The first (but last to be applied) is the modifier defined as the first element. This will prepend {FILE.DIR} to the location. {FILE.DIR} is a reference to the directory in which the XML file is contained. If the XML file is "C:\Documents\Game\res\resources.xml", this modifier will set all "location" properties to "C:\Documents\Game\res\{res.location}", where "{res.location}" is the value of the "location" field before being modified.
The next (first to be applied) modifier is defined by giving the package an attribute "modify" with the value "location". What this does is equivalent to:
<modifier var="location" expression="<package name>/{res.location}" />
I think by now you should be able to figure out what that does.
Sprites
The next packages is sprites. Sprites could be described as "images extended". Where images are just plain images, sprites are actually useful for representing objects in a game. This is because they define an origin. The origin of a sprite is a point in it's image around which it will pivot if it's rotated, and center on when it's given a location.
We define two sprites for the ship, and set their origins in the middle by setting them to "50%". You can use any percentage, or absolute pixels; they may be positive or negative and lie outside or inside of the actual image area.
Note that we do not assign specific images to the sprites. The modifier inside the sprites package automatically does that for us. It uses the variable "RES_NAME" instead of the expected "res.name" because "name" is a hardcoded property that every resource type has.
Loading Resource information in the game
So we've defined some resources. Now we need to get those definitions into the game. Start by adding the required imports:
import org.victoryengine.graphics2d.SpriteLoader; import org.victoryengine.res.listing.ListingResourceManager; import org.victoryengine.res.listing.XmlResourceListImporter; import org.victoryengine.graphics2d.Image;
Then insert this code in the bottom of the constructor:
// set up the resource manager
final ListingResourceManager resourceManager = new ListingResourceManager();
resourceManager.addLoader("image", display.getImageIO());
resourceManager.addLoader("sprite", new SpriteLoader(resourceManager));
ResourceManager.setResourceManager(resourceManager);
// load resource definitions
resourceManager.importInfo(new XmlResourceListImporter(
ClassLoader.getSystemResource("res/resources.xml")));
disp.setBackgroundImage((Image) resourceManager.getResource("images.background"));
First we create a new Listing Resource Manager. At this point it's useless, it does not know any resource types. We need to tell it what types of resources we are going to use and give it some objects it can call on to load them. These objects implement the interface ResourceLoader.
First we need an image loader. Because we're going to use Java2D, we need to use Java2D images (you know, BufferedImage et al) and our display's ImageIO object loads exactly that (it's a wrapper around the Java2D ImageIO which also implements ResourceLoader).
Then we define the loader for sprites. The SpriteLoader we use is a part of the graphics2d package, because it uses the Victory Engine's 2D Graphics System images for the sprites. We give it the resource manager as a parameter so it will have a place to find the image that the sprites use.
As a convenience for later access, we store the resource manager in a static field of the ResourceManager abstract class.
Now finally on to loading the XML file. For this we create a XmlResourceListImporter pointing to the file "res/resources.xml" in our classpath. We tell the resource manager to run this importer.
After loading everything we put the background image in place, to see everything works. Run it to see it does, and that's the end of this chapter.
Creating a ship
Let's create our spaceship.
package org.victoryengine.tutorials.flyingship;
import org.victoryengine.core.EntityList;
import org.victoryengine.extra.PhysicalEntity;
import org.victoryengine.graphics2d.Drawable;
import org.victoryengine.graphics2d.GC;
import org.victoryengine.graphics2d.Sprite;
import org.victoryengine.res.ResourceManager;
public class Ship extends PhysicalEntity implements Drawable {
private FlyingShipGame game;
private Sprite sprite;
public Ship(FlyingShipGame pGame) {
game = pGame;
setPosition(game.getDisplay().getCenter());
sprite = (Sprite) ResourceManager.get().getResource("sprites.ship");
EntityList.add(this);
game.getDisplay().addDrawable(this);
}
public void draw(GC context) {
sprite.draw(context, getPosition());
}
public double getDepth() {
return 0;
}
}
To make this work, add this method to FlyingShipGame:
public Display2D getDisplay() {
return display;
}
and insert
new Ship(this);
at the end of it's constructor.
The Ship class is quite simple. It extends PhysicalEntity which is a simple class for physical objects (objects which have a location, speed, etc.). We put the ship in the middle of the screen and add it to the entity list. This will make the ship known to the game loop, so we can execute some code every step (which we'll do later).
We also add the ship as a Drawable to the display, so we can draw it's sprite, which we get from the resource manager.
In the ship's draw method we draw it's sprite on the screen.
Getting player input
Before we can move the ship, we need a way to get player input. The generic interface you can use for this in the Victory Engine is the InputManager. In this case we'll use the AWTInputManager because it works with Java2D.
Declare a field and method in FlyingShipGame:
private InputManager input;
public InputManager getInput() {
return input;
}
In the constructor, insert this code before "new Ship(this);":
input = new AWTInputManager(disp.getCanvas()); GameLoop.get().insertHook((AWTInputManager) input);
This creates our input manager. Because it's AWT based, it needs an AWT container, which JDisplay.getCanvas() provides. We also insert it as a hook to the game loop, so that it will check for new input before every step.
Finally, in the start method, add after "display.start()":
input.init();
This prepares the inputmanager for work. We are now ready to get some input.
Moving the Ship
In this step we're going to do two things.
- We're going to make the ship move forward when pressing the up arrow keyboard button.
- We're going to make the ship rotate when pressing left or right.
- We're going to change the sprite when moving forward.
You have probably noticed that previously, the ship always pointed to the right (because a direction of 0 means to the right in PhysicalEntity and other parts of the entity). It would be nicer to have it point up, and even better to let the player rotate it. For this purpose, PhysicalEntity has a rotation property. Let's point the ship up by default. In the constructor, add after "setPosition()":
setRotation(-Math.PI/2);
To point the ship up we need to rotate it 90 degrees counter-clockwise (in other words to the left) from the normal (rotation zero). The engine uses radians, and 0.5PI radians is 90 degrees. A negative amount means counter-clockwise.
We need to adapt the draw method to use the rotation property. Replace it's contents with
sprite.drawTransformed(context, getPosition(), getRotation(), Vector2.ONE, 1);
drawTransformed takes 5 parameters:
- The context to draw one
- The sprite to draw
- The amount of rotation to use
- The scaling (we set it to Vector2.ONE to apply no scaling).
- The transparency (we set it to 1 to not use it).
At the moment we have one variable for the sprite. We're going to make this three. Two to hold the different two, and one to know which one is currently displayed.
First, let's declare the two new fields:
private Sprite spriteNormal;
private Sprite spriteFire;
In the constructor of Ship, replace "sprite = ..." with:
spriteNormal = (Sprite) ResourceManager.get().getResource("sprites.ship");
spriteFire = (Sprite) ResourceManager.get().getResource("sprites.ship_fire");
sprite = spriteNormal;
Now, on to the actual movement. Create a new method called step which will, as the name suggests, be called every step in the game loop:
public void step() {
final InputManager in = game.getInput();
if (in.getKeyDown(InputManager.VK_UP)) {
setForce("thrust", Vector2.fromLengthDirection(THRUST_FORCE, getRotation()));
sprite = spriteFire;
} else {
removeForce("thrust");
sprite = spriteNormal;
}
if (in.getKeyDown(InputManager.VK_LEFT)) {
setRotation(getRotation() - ROTATION_SPEED);
}
if (in.getKeyDown(InputManager.VK_RIGHT)) {
setRotation(getRotation() + ROTATION_SPEED);
}
move();
}
You'll notice two constants there, which you need to define in the class
private static final double ROTATION_SPEED = Math.PI / 20;
private static final double THRUST_FORCE = 10;
First, we get the input manager from the game. We use to check whether the up arrow key (VK_UP) is pressed down.
If it is, we apply a force to our ship, in the direction it's pointing in. Forces are a part of PhysicalEntity and allow natural movement of objects. As in real life, when a force is applied to an object it will move in that direction with a gradually increasing speed. This is what we want. We also set the sprite to be the firing one.
If the key is no longer pressed, we remove the force, so the ship's speed will not increase further. We also reset the sprite to the normal one. Note that the ship will also not slow down, it will keep moving in the same direction at the same speed. This is normal in space. The player can "brake" by turning the ship around and pressing up.
Turning, which we do in the next block, by changing the rotation depending on the player pressing the left or right arrow keys. This is fairly straightforward.
Finally we call the PhysicalEntity method move, so that all this setting forces and rotations and things, actually is of any use.
Try to run the game now and fly around for a bit. See you in half an hour or so...
Details
Now that you're finished flying, you have probably encountered the problem a few times of flying off screen and not being able to find the way back. To solve this, let's allow the player to press "R" to be reset to the middle of the screen, with no speed. Insert in step, before "move()":
if (in.getKeyDown(InputManager.VK_R)) {
setPosition(game.getDisplay().getCenter());
setRotation(-Math.PI/2);
setSpeed(Vector2.ZERO);
}
You might also have noticed that the ship appear jaggy when rotated. To solve this we need to enable interpolation in Java2D. To do this, simply add this method in FlyingShipGame:
public void frameStart(GC context) {
context.setInterpolation(Interpolation.Bilinear);
context.setAntiAlias(true);
}
frameStart is a part of DisplayListener (which FlyingShipGame implements, remember?) and is called at the beginning of every frame. It can thus be used to set some properties on the graphics object that will apply for every rendering operation after it. We set interpolation to Bilinear, and enable anti-aliasing while we're at it.
Download
Well, I guess we're finished now. Happy flying! Download the complete result of this tutorial (code and images) here.
