First example
Purpose:
In a typical gaming application, an icon (e.g. a spaceship) is moved over
a background image (e.g. stars, other space vehicles) using the left or
right thumb control.. When a certain digital button is pressed, an event
happens (e.g. weapon fired). Because we don't like so much 'Shoot 'em
Up' games, the icon to move here is a pyranha fish in an underwater background
scene. When the B button is pressed, the fish breathes out bubbles that
mount to the surface (rarely seen in nature).
This animated
graphics application is not straightforward to code when using the Java Standard Library (mostly Swing) with no third party graphics library addon. The code is much simplified
by the class GPanel in the utility package ch.aplu.util
(download jar,
doc)
because GPanel handles the double buffering needed to restore the background
scene whenever the icon changes its position. Moreover the XboxController
package frees us to poll the controller and lets us consider the movement
of the thumb control and the action of the B button as events (in my opinion
a very natural point of view).
// GameDemo.java
// Example how to use the Xbox360 controller with animated graphics
// scene.gif and piranha.gif must reside in class directory
import ch.aplu.util.*;
import ch.aplu.xboxcontroller.*;
import java.awt.Color;
public class GameDemo implements ExitListener
{
private GPanel p =
new GPanel("Connecting...", -100, 100, -100, 100);
private double mag = 0;
private double dir = 0;
private JRunner jRunner = new JRunner(this);
private String connectInfo = "GameDemo V1.0 (www.aplu.ch) " +
"-- Move: LeftThumb; Breeze: Press B";
public GameDemo()
{
XboxController xc = new XboxController();
if (!xc.isConnected())
p.title("Xbox controller not connected");
else
p.title(connectInfo);
xc.setLeftThumbDeadZone(0.2);
p.addExitListener(this);
p.resizable(false);
p.image("scene.gif", -100, -100); // Draw background scene
p.storeGraphics();
p.enableRepaint(false);
p.color(Color.white);
showFish();
// Register callbacks
xc.addXboxControllerListener(new XboxControllerAdapter()
{
public void leftThumbMagnitude(double magnitude)
{
mag = magnitude;
showFish();
}
public void leftThumbDirection(double direction)
{
dir = direction / 180.0 * Math.PI;
showFish();
}
public void buttonB(boolean pressed)
{
if (pressed)
jRunner.run("breeze"); // Run breeze() in a new thread
}
public void isConnected(boolean connected)
{
if (connected)
p.title(connectInfo);
else
p.title("Connection lost");
}
});
// Main thread will sleep until termination
Monitor.putSleep();
xc.release();
System.exit(0);
}
private
synchronized void showFish()
{
p.recallGraphics(); // Restore scene
double x = 100 * mag * Math.sin(dir);
double y = 100 * mag * Math.cos(dir);
p.image("piranha.gif", x - 45, y - 15);
p.repaint();
}
private void breeze()
{
double xcenter = 100 * mag * Math.sin(dir);
double ycenter = 100 * mag * Math.cos(dir);
HiResAlarmTimer timer = new HiResAlarmTimer(50000);
for (int r = 0; r < 200; r += 5)
{
timer.start();
drawBubbles(xcenter, ycenter, r);
while (timer.isRunning())
Thread.yield();
showFish();
}
}
private void drawBubbles(double xcenter, double ycenter,
double radius)
{
double phi;
double x, y;
for (int angle = 70; angle <= 110; angle += 2)
{
phi = angle / 180.0 * Math.PI;
x = xcenter + radius * Math.cos(phi);
y = ycenter + radius * Math.sin(phi);
if (x > -100 && x < 100 && y > -100 && y < 100)
{
p.move(x, y);
p.fillCircle(1);
}
}
p.repaint();
}
public void notifyExit()
{
Monitor.wakeUp();
}
public static void main(String[] args)
{
new GameDemo();
}
}
Discussion:
By setting enableRepaint(false) we disable the automatic
repaint of the graphics offscreen buffer that holds the graphics elements
of each GPanel's graphics draw operation. We must render the offscreen
buffer on the screen window by calling repaint() manually like
at the end of showFish() and drawBubbles().
There is another
offscreen buffer (store buffer) where the background image is stored.
storeGraphics() copies the current screen image to the store buffer,
recallGraphics() moves the content of the store buffer back to
the graphics offscreen buffer (by erasing its current content). The typical
use is seen in showFish(): first the background scene is restored,
then the current icon drawn at the new position, finally the graphics
offscreen buffer is rendered on the screen window.
Keep in mind
that callback methods must return quickly so that other events are not
blocked. As usual this is achieved by delegating lengthy work to a special
"worker thread". Despite the handling of threads in Java is
not complicated, we use the JRunner utility class (a very simple
version of the Java SwingWorker class) that makes the creation of a worker
thread a snap: When calling its run-method, the (parameterless) method
passed as string parameter is executed in a newly created thread. Each
time the B button is hit, a new thread is created that moves the bubbles
positioned on an arc in upward direction step by step in a timed loop.
To avoid any interferences between the threads, showfish() is synchronized.
After initialization,
the application thread is put in a wait state by calling Monitor.putSleep().
All work is done by the event callback methods. When the title bar's exit
button is hit, notifyExit() is called. Monitor.wakeUp()
resumes the application thread and the cleanup code is executed.
Execute GameDemo using WebStart.
If the execution fails,
check the system requirements (see above)
Fig.: The xbox-controlled pyranha fish
|