Getting Started
Start Processing then from the menus select Sketch | Import Library and choose AI_for_2D_Games and you will see the following import statements.
import game2dai.entities.*;
import game2dai.entityshapes.ps.*;
import game2dai.maths.*;
import game2dai.*;
import game2dai.entityshapes.*;
import game2dai.fsm.*;
import game2dai.steering.*;
import game2dai.utils.*;
import game2dai.graph.*;
Since these will appear in any sketch using this library there is no need to show them again in these guides.
The following code is the starting template for most sketches using this library.
// GettingStarted_01
World world;
StopWatch sw;
public void setup() {
size(480, 320);
world = new World(width, height);
sw = new StopWatch();
// Other setup code here
sw.reset(); // should always be last line in setup
}
public void draw() {
double elapsedTime = sw.getElapsedTime();
world.update(elapsedTime);
background(200, 255, 200);
world.draw(elapsedTime);
}
Setup
All sketches need a global World and StopWatch variable and these
are declared in lines 2 and 3. The setup method starts as usual with
a call to size(...)
.
world = new World(width, height);
This is where the world object is created by passing the width and height of the window into the constructor. It is important to realise that we are not defining the size of the world because that is unlimited (not really, but close enough that we don’t have to worry about it). Instead we are specifying the part of the world that will be displayed initially. So in this code the part of the world being displayed is the region defined by the corners [0,0] and [width, height], since the initial display scale is always 1.0 then each world unit is 1 pixel so there is exact correspondence between pixel positions and world positions.
Since any part of the world can be viewed at any scale discussions about game entity positions, velocities etc. will always in world units!
In line 8 we create the StopWatch, a hires timer that will be used to control the physics calculations and the last line of setup should always reset the timer to zero before going into the draw loop.
Draw
The first 4 lines do all the work.
double elapsedTime = sw.getElapsedTime();
world.update(elapsedTime);
background(200, 255, 200);
world.draw(elapsedTime);
In the first line we get the time (in seconds) that has elapsed since we last called this method. Notice that the data type is double rather than the float that you are probably more familiar with. The next line will update all the game physics based on the elapsed time. Then we clear the display using background(...) and finally in the next line we tell the world to draw itself.
At the moment there is nothing to draw because the world is empty.
Adding an autonomous agent
An autonomous agent is a game entity where the user specifies its attributes such as velocity, heading etc. and leaves the library to update the entities position and state accordingly. There are two types of autonomous agent depending on whether we use the MovingEntity or Vehicle class to create it. The only difference between them is that a Vehicle agent can be aware of other game entities (e.g. buildings, obstacles and other moving entities) and change its velocity accordingly.
If we add a MovingEntity then our sketch the code will become
// GettingStarted_01.pde
World world;
StopWatch sw;
MovingEntity mover0;
public void setup() {
size(480, 320);
world = new World(width, height);
sw = new StopWatch();
// Create the mover
mover0 = new MovingEntity(
new Vector2D(width/2, height/2), // position
15, // collision radius
new Vector2D(15, 15), // velocity
40, // maximum speed
new Vector2D(1, 1), // heading
1, // mass
0.5, // turning rate
200 // max force
);
// What does this mover look like
ArrowPic view = new ArrowPic(this);
// Show collision and movement hints
view.showHints(Hints.HINT_COLLISION | Hints.HINT_HEADING | Hints.HINT_VELOCITY);
// Add the renderer to our MovingEntity
mover0.renderer(view);
// Constrain movement
Domain d = new Domain(60, 60, width-60, height-60);
mover0.worldDomain(d, SBF.REBOUND);
// Finally we want to add this to our game domain
world.add(mover0);
sw.reset();
}
public void draw() {
double elapsedTime = sw.getElapsedTime();
world.update(elapsedTime);
background(200, 255, 200);
// Make the movers constraint area visible
Domain d = mover0.worldDomain();
fill(255, 200, 200);
noStroke();
rect((float)d.lowX, (float)d.lowY, (float)d.width, (float)d.height);
world.draw(elapsedTime);
}
The is to add a global variable declaration for our agent (line 4). Next thing is to create the MovingEntity in setup, this is done in lines11-20. Although it seems complicated all MovingEntity(s) and Vehicle(s) use the same parameters for its constructor so eventually you will get used to it, if not. a bit of 'copy, paste and edit' simplifies the job.
Some of the parameters should be obvious but I will talk about each one in turn. The library uses the Vector2D class to represent all locations and directions in the game domain (world) so the first parameter defines the world position where the agent is to start from. The next is obviously the collision radius and is used by the physics engine. The collision radius does not have to match the size of the agents visual representation but I recommend that it should be close otherwise you might get some weird interactions between game entities.
The third parameter is the velocity vector so defines the current speed and direction that the agent travels. The fourth is the maximum speed of the agent and this is enforced in the world update.
The next one is interesting as the heading vector defines the direction that the agent is facing and can be different from the direction it is travelling (velocity vector). During the update the heading will gradually be aligned with the velocity vector, the turning rate (7th parameter on line 18), measured in radians per second, controls how fast this occurs.
The physics engine calculations are based on Newton’s Laws of Motion so we need to know the mass of the entity.
The last one is the maximum force that can be applied to the entity, that combined with the maximum speed ensure we get reasonable agent movement.
We still haven’t specified what the agent will look like so in this line a renderer is created
ArrowPic view = new ArrowPic(this);
and then added to the entity in this line.
mover0.renderer(view);
Sometimes it is useful to have a visual guide to show the velocity and heading vectors, and the collision circle. The library calls these hints and line 24 shows how we make them visible.
view.showHints(Hints.HINT_COLLISION | Hints.HINT_HEADING | Hints.HINT_VELOCITY);
Sometimes we want to constrain the movement to a rectangular area of the world (called a domain) and decide what action should happen when the agent reaches the domain boundary. In this example I have created a domain in the centre of the display and instructed the agent to rebound off the domain’s boundary.
Domain d = new Domain(60, 60, width-60, height-60);
mover0.worldDomain(d, SBF.REBOUND);
In line 31 we finally add the entity to our world so the world will update the agents position and draw the agent in the display.
world.add(mover0);
Notice that the draw method is unchanged except for lines 39-44 which just to show the domain constraining the agents movement
The completed sketch
Notice the hints, a semi-transparent yellow disc for the collision circle, a purple line for the velocity and blue line for the heading. Try increasing the turning rate speed (line 18) to get a more realistic bounce.
Also try changing the domain boundary behaviour in line 29 so that the agent wraps around to the other side instead of bouncing.
mover0.worldDomain(d, SBF.WRAP);
You can see this in action in the Marketplace Patrol sketch.
As well as the arrow renderer the library provides a couple more you might use. Replace line 22 with one of these.
CirclePic view = new CirclePic(this);
PersonPic view = new PersonPic(this);
You will discover how to create your own renderers in a later guide.