Aegidius
 Plüss   Aplulogo
     
 www.aplu.ch      Print Text 
© 2021, V10.4
 
  XboxController
 
 


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

GameDemo