Documentation
From Jig
This page links to documentation on specific JIG topics. Check out the Getting Started page if you haven't already. If your looking for a general example of a whole game, check out the Game Demos or Tutorials.
Contents |
Graphics
Spritesheets
The Java Instructional Gaming Engine allows you to add images quite simply by using what is called a spritesheet. The spritesheet is a simple XML file that explains which parts of an image file contain each objects image.
Your main game class should contain a constant with the path to the spritesheet.
static String RSC_PATH = "jig/demos/superspacefrenzy/resources/"; static String SPRITE_SHEET = RSC_PATH + "spacefrenzy.png";
Ensure the path above matches where you have placed the resources.
It is important to note that if you are using NetBeans you must clean and rebuild your project every time you change any graphics files in the project. If you are using Eclipse, you must click on your project and press f5 to refresh the project.
Next we need to load the resources in our main game constructor.
... ResourceFactory.getFactory().loadResources(RSC_PATH, "resources.xml"); ...
If you take a look at the resources.xml file it looks like this.
<resources> <spritesheet> <imagesrc>spacefrenzy.png</imagesrc> <framesrc>spacefrenzy.xml</framesrc> </spritesheet> <audioclip> <src>2_5sec-explosion.wav</src> </audioclip> <audioclip> <src>3sec-explosion.wav</src> </audioclip> <audioclip> <src>10sec-thrust.wav</src> </audioclip> <audioclip> <src>1sec-missile.wav</src> </audioclip> </resources>
It contains the location of all the resources for the game. As you can see, a spritesheet has an image and a XML file that explains what each of the parts of the image represent. Lets take a look at the xml file.
<spriteset> <author>Scott Wallace</author> <name>SpaceFrenzy</name> <type>image/png</type> <width>128</width> <height>192</height> <frameset> <name>asteroid-3</name> <top>0</top> <left>0</left> <width>128</width> <height>128</height> </frameset> <frameset> <name>asteroid-2</name> <top>128</top> <left>0</left> <width>64</width> <height>64</height> </frameset> ...
As you can see it is a set of framesets. Each frameset represents one object, such as an asteroid, or the players image. You simply specify the name, offset from the top of the image, offset from the side of the image, and width and height. For animations you may also specify how many rows and columns are in the image. Then you can alternate through the frames to animate the object.
Now that the spritesheet is loaded, we simply need to refer to the name of the object when loading it.
In the Ship constructor lets use this super constructor.
Ship() {
super(SuperSpaceFrenzy.SPRITE_SHEET + "#big-ship");
...
Programmer Generated Graphics
If you want to create graphics progromatically, you can use the PaintableImage class. It allows you to create an image, draw lines, text, and shapes on it, and put it into the resource factory for later use.
PaintableImage p = new PaintableImage(w, h, nframes, null);
for (int i = 0; i < nframes; i++) {
p.setWorkingFrame(i);
switch (i) {
case 0:
p.drawRectangle(1, 1, w-2, h-2, Color.red);
break;
default:
p.drawOval(1, 1, w-2, h-2, Color.red);
}
}
p.getFrames(rscName);
Default Graphics
The resource factory also provides a way to generate some simple shape images through the getStandardFrameResource method. Here we use it to generate a simple black background.
//add a black background ImageBackgroundLayer backgroundLayer = new ImageBackgroundLayer( ResourceFactory.getFactory().getStandardFrameResource(SCREEN_WIDTH, SCREEN_HEIGHT, 'r', Color.black).get(1), SCREEN_WIDTH, SCREEN_HEIGHT, ImageBackgroundLayer.TILE_IMAGE); gameObjectLayers.add(backgroundLayer);
Text
System Fonts
System fonts are fonts rendered using the systems version of the font.
public Game() {
font = ResourceFactory.getFactory().getSystemFont(new Font("Sans Serif", Font.BOLD, 14));
}
public void render(RenderingContext rc) {
String text = String.format("Turrets Remaining: %s/%s", turrets,
NUM_TURRETS);
AffineTransform at = AffineTransform.getTranslateInstance(SCREEN_WIDTH
- TEXT_OFFSET, TankGame.SCREEN_HEIGHT - HUD_OFFSET);
font.render(text, rc, at);
}
Bitmap Fonts
Bitmap fonts are fonts where each letter is an actual image. These fonts can be created from system fonts, or they can be generated from a correctly formatted bitmap image file.
public Game() {
statusText = ResourceFactory.getFactory().getBitmapFont(new Font("Sans Serif", Font.BOLD, 18),
Color.white, new Color(0,0,0,0) );
}
public void render(RenderingContext rc) {
scoreDisplayFont.render(new Integer(score).toString(), rc,
AffineTransform.getTranslateInstance(SCORE_DISPLAY_POSITION
.getX(), SCORE_DISPLAY_POSITION.getY()));
}
Word Wrapping
Word wrapping is not currently built into the JIG library. But you can use the following function or edit it to suit your needs.
/**
* Renders a font at the given position with word wrapping enabled.
*
* @param rc the current rendering context
* @param font the bitmap font to use for rendering
* @param string the message to be rendered
* @param maxPixelWidth the max width a line can be in pixels
* @param x the x position of the first line of text in pixels
* @param y the y position of the first line of text in pixels
*/
public void renderFontWordWrap(RenderingContext rc, BitmapFont font, String string, int maxPixelWidth, int x, int y)
{
int lineHeight = (int)(font.getHeight() * 1.10);
int currentLine = 0;
int currentWidth = 0;
for(String line : string.split("\n"))
{
for(String word : line.split(" "))
{
int wordWidth = 0;
for(int i=0,n= word.length(); i < n; i++)
{
wordWidth += font.getCharWidth(word.charAt(i));
}
wordWidth += font.getCharWidth(' ');
if(currentWidth + wordWidth > maxPixelWidth)
{
currentWidth = 0;
currentLine++;
}
font.render(word + " ", rc, AffineTransform.getTranslateInstance(x + currentWidth, y + currentLine * lineHeight));
currentWidth += wordWidth;
}
currentWidth = 0;
currentLine++;
}
}
Collision Detection
Spheres
The JIG engine comes with a collision detection class for basic spheres. This is typically enough for most simple games, as modeling the objects as circles is generally 'close enough'.
public Game() {
AbstractBodyLayer<VanillaSphere> shipLayer = new AbstractBodyLayer.NoUpdate<VanillaSphere>();
gameObjectLayers.add(shipLayer);
physics.manageViewableSet(shipLayer);
AbstractBodyLayer<VanillaSphere> asteroidLayer = new AbstractBodyLayer.NoUpdate<Asteroid>();
gameObjectLayers.add(asteroidLayer);
physics.manageViewableSet(asteroidLayer);
CollisionHandler d = new VanillaSphereCollisionHandler<VanillaSphere, Asteroid>(
shipLayer, asteroidLayer) {
@Override
public void collide(final VanillaSphere a, final Asteroid b) {
if (--lives == 0) {
a.setActivation(false);
}
// trigger the Asteroid event of 'breaking apart'
b.breakApart(layer2Additions);
score += 10;
}
};
physics.registerCollisionHandler(d);
}
Rectangles
If you are thinking about doing a rectangle collision handler, carefully consider two things. One, will a sphere collision handler work just as well? If not, will the rectangles be able to rotate? If the rectangles are not able to rotate, the implementation of the collision handler will be pretty simple.
Convex Polygons
To implement a collision handler that handles any two convex polygons you should complete the Separating Axis Theorem project.
Sound
Wave Files
You can play wave files in JIG without needing any other libraries. First load your in the game class like this.
private static AudioClip thrustSound = ResourceFactory.getFactory() .getAudioClip(SuperSpaceFrenzy.RSC_PATH + "10sec-thrust.wav"); private ClipPlayback thrustPlay;
The ClipPlayback object will keep track of the current state of the sound.
Now in the ships thrust method, lets play the sound when the player is moving.
void thrust(final boolean yes) {
if(yes && isActive()) {
thrust = SuperSpaceFrenzy.SHIP_THRUST;
if (thrustPlay == null) {
thrustPlay = thrustSound.loop(-1, .5);
} else if (thrustPlay.getState() == AudioState.PAUSED) {
thrustPlay.resume();
}
} else {
thrust = 0;
if (thrustPlay != null)
thrustPlay.pause();
}
}
Notice how we are looping this sound and pausing and resuming. This is important to do for this sound since it is 10 seconds long. With short sounds, you can just play them once and not worry about keeping track of them.
Music Files
We can play music files as long as we link to the correct library. For more information on including these libraries please look at the Adding Sound Libraries page.
To actually play the music file, add this to your main game class.
private AudioStream music;
Game(boolean fullscreen) {
...
music = new AudioStream(RSC_PATH + "SherpaIsRelaxing.ogg");
music.play(.3);
...
Here we load the music file, and then play it at a sound level of 0.3
Time
Built in Timers
You can use the alarm class to easily trigger events based on time. You simply create an alarm with a duration, and test if it is expired. Once the alarm expires, simply call the reset method to reset it. Not only does the built in Alarm class make the coding simpler, it works when you pause the game with the GameClock class.
public Gun() {
fireAlarm = clock.setAlarm(FIRE_DELAY * GameClock.NANOS_PER_MS);
public void update(long deltaMs) {
if(fireAlarm.expired()) {
fireAlarm.reset();
fire();
}
}
Manual Timer
You can keep track of the game time manually as well, although it is more work. First we make a variable in the game class to keep track of the total time.
/** The current time elapsed in milliseconds */
private long currentTime = 0;
public long getCurrentTime() {
return currentTime;
}
Next on every update we increment this.
public void update(final long deltaMs) {
super.update(deltaMs);
currentTime += deltaMs;
...
Now we can keep track of the last time something happened and how many milliseconds have passed since that action.
/** The min delay in milliseconds between shots. */
static final long DELAY_BETWEEN_SHOTS = 500;
private long lastFireTime = 0;
public boolean canFire()
{
return (Game.getInstance().getCurrentTime() - lastFireTime > DELAY_BETWEEN_SHOTS);
}
Mouse
Obtain Mouse Position
If you are using a class that subclasses AbstractSimpleGame, then you can access the mouse location very simply.
Point loc = mouse.getLocation();
Detect Mouse Click
You can easily detect if a mouse button is pressed down using the following code.
if(mouse.isLeftButtonPressed()) {
tank.fire();
}
Note if you want to only do a action once per mouse click you will need to keep track of the previous state of the mouse button in a variable.
Keyboard
Detect Key Press
If you only want to tell if the keyboard button is down you can use this code.
if(keyboard.isPressed(KeyEvent.VK_UP))
{
moveUp();
}
If you want to process keyboard events, you can use the following loop structure.
/**
* Processes the keyboard for player input.
*/
private void processKeyboard()
{
KeyInfo k2;
ArrayList<KeyInfo> keyList = new ArrayList<KeyInfo>();
while ((k2 = keyboard.get()) != null) {
keyList.add(k2);
}
for(KeyInfo k : keyList)
{
int keyCode = k.getCode();
if(!k.wasPressed())
continue;
if(keyCode == KeyEvent.VK_SPACE)
{
nextLevel();
}
if(keyCode == KeyEvent.VK_F7)
{
isSolved = true;
}
if (currentLevel != null)
currentLevel.processKeyEvent(k);
}
if(!isSolved && currentLevel.isSolved())
{
isSolved = true;
}
}
The list of keys is optional, but if you make it accessible, it allows other objects to process keys as well.
