GameGrid: Game programming with Java

Research project PHBern  
HomePrintJava-Online

Exact collision identification

Theoretically two actors collide when at least two pixel overlap each other. Since actors are always inside a rectangle area, all non-transparent pixel of both pictures would have to be check for possible overlappings. This process is even for small images enormous. For example two pictures of the size 50x50 pixel would need 2500x2500 overlap checks. To shrink the process, geometric shapes are defined as collision areas. In JGameGrid we use rectangles, circles, lines or points (spots) which can have any position or orientation compared to its actor. By default the collision area is set as a bounding rectangle.

Example 1: Two actors move on a circular path and reflect on the grid's border. When they collide with each other, a sound is played and their direction is changed by 180°.

The application class implements GGActorCollisionListener, which is registerd by addActorCollisionListener(this). This allows to call the callbackmethode collide() after each collision event.

With addCollisionActor(stick2) stick1 knows that it only has to react to collisions with stick2.

This example allows us to clearly observe the exactness of the collision algorithm of JGameGrid. Even when the two actors only touch at a corner pixel, the collsion is registered.

Run this example

Edit this example in the Online-Editor

 

// JGameEx26.java

import ch.aplu.jgamegrid.*;

public class JGameEx26 extends GameGrid implements GGActorCollisionListener
{
  public JGameEx26()
  {
    super(600, 600, 1, false);
    setSimulationPeriod(10);
    Stick stick1 = new Stick();
    addActor(stick1, new Location(200, 200), 30);
    Stick stick2 = new Stick();
    addActor(stick2, new Location(400, 400), 30);
    stick2.show(1);
    stick1.addCollisionActor(stick2);
    stick1.addActorCollisionListener(this);
    playSound(GGSound.DUMMY);
    show();
    doRun();
  }

  public int collide(Actor actor1, Actor actor2)
  {
    actor1.setDirection(actor1.getDirection() + 180);
    actor2.setDirection(actor2.getDirection() + 180);
    playSound(GGSound.PING);
    return 10;
  }

  public static void main(String[] args)
  {
    new JGameEx26();
  }
}

//
class Stick extends Actor
{
  private final double step = 1;

  public Stick()
  {
    super(true"sprites/stick.gif"2 );  // Rotatable
  }

  public void act()
  {
    Location loc = getLocation();
    double dir = (getDirection() + step% 360;

    if (loc.x < 50)
    {
      dir = 180 - dir;
      setLocation(new Location(55, loc.y));
    }
    if (loc.x > 550)
    {
      dir = 180 - dir;
      setLocation(new Location(545, loc.y));
    }
    if (loc.y < 50)
    {
      dir = 360 - dir;
      setLocation(new Location(loc.x, 55));
    }
    if (loc.y > 550)
    {
      dir = 360 - dir;
      setLocation(new Location(loc.x, 545));
    }
    setDirection(dir);
    move();
  }
}

Explaining the program code:
setSimulationPeriod(10) decreases the simulation periode to let the sticks move faster
stick2.show(1) both sticks are instances of the same class Stick, where stick1 shows the Sprite stick_0.gif (red) and stick2 shows stick_1.gif (yellow). By default the Sprite with the id 0 is shown
collide(Actor actor1, Actor actor2) callbackmethode of the CollisionListener. Defines the actions of the two actors after the collision
return 10 the return value of the method collide() sets the minimal simulation periode until it is reset to its default value

 

 

Example 2: The fish needs to avoid the swimming jellyfish. If it hits a jellyfish it is reset to its starting position on the left side of the grid. The goal of the game is to reach the right side.

 

 

Run this example

Edit this example in the Online-Editor

 

// JGameEx27.java

import ch.aplu.jgamegrid.*;
import java.awt.*;
import java.awt.event.KeyEvent;


public class JGameEx27 extends GameGrid implements GGActorCollisionListener
{
  private final int countMax = 10;
  private int count = countMax;
  private Clownfish1 nemo = new Clownfish1();
  private final Location startLocation = new Location(50, 300);

  public JGameEx27()
  {
    super(600, 600, 1, nullfalse);
    setSimulationPeriod(100);
    addActor(nemo, startLocation);
    addKeyListener(nemo);
    nemo.addActorCollisionListener(this);
    nemo.setCollisionCircle(new Point(0, 0), 40);
    for (int = 0; i < 10; i++)
      createJellyfish();
    show();
    doRun();
  }

  public int collide(Actor actor1, Actor actor2)
  {
    nemo.setLocation(startLocation);
    return 0;
  }

  private void createJellyfish()
  {
    int = 500 + (int)(400 * Math.random());
    int = (int)(600 * Math.random());
    Jellyfish jelly  = new Jellyfish();
    addActor(jelly, new Location(x, y), -180);
    nemo.addCollisionActor(jelly);
  }

  public void act()
  {
    count--;
    if (count == 0)
    {
       createJellyfish();
       count = countMax;
    }
  }

  public static void main(String[] args)
  {
    new JGameEx27();
  }
}

// ---------------------   class Clowfish1 --------------------------------
class Clownfish1 extends Actor implements GGKeyListener
{
  private int id = 0;

  public Clownfish1()
  {
    super("sprites/nemo.gif"2);
  }

  public boolean keyPressed(KeyEvent evt)
  {
    switch (evt.getKeyCode())
    {
      case KeyEvent.VK_UP:
        setLocation(new Location(getLocation().x, getLocation().y - 5));
        break;
      case KeyEvent.VK_DOWN:
        setLocation(new Location(getLocation().x, getLocation().y + 5));
        break;
      case KeyEvent.VK_LEFT:
        setLocation(new Location(getLocation().x - 5, getLocation().y));
        break;
      case KeyEvent.VK_RIGHT:
        setLocation(new Location(getLocation().x + 5, getLocation().y));
        break;
    }
    return false;  // Don't consume
  }

  public void act()
  {
    if (getLocation().x > 500)
      showNextSprite();
  }

  public boolean keyReleased(KeyEvent evt)
  {
    return false;
  }
}

// ------------------------ class Jellyfish -----------------------------------

class Jellyfish extends Actor
{
  public Jellyfish()
  {
    super("sprites/jellyfish.gif");
  }

  public void act()
  {
    move();
    if (getLocation().x < - 10)
      removeSelf();
  }
}

Explaining the program code:
nemo.setCollisionCircle(new Point(0, 0), 40) the collision area is defined as a circle with its center at 0, 0 and a radius of 40
nemo.addCollisionActor(jelly) each new jellyfish is registered as a CollisonActor

count = countMax
count--

the jellyfish appear at the right side of the grid and move to the left side. To be sure that there are not to many jellyfish inside the grid, they are counted
nemo.setLocation(startLocation) the fish is reset back to its starting position after each collision. If it reaches the right side of the grid, it changes its appearance

 

Example 3: a mouse controlled arrow can burst balloons with its point (CollisionSpot).

10 balloons are set at random locations. The arrow's point is defined as a collisionspot with dart.setCollisionSpot(new Point(30, 0)). To assign the collisionspot, a coordinate system with its source in the center of the Sprite (0, 0) is used. The Sprite is 60x17 pixel. This defines 30, 0 as the collisionspot of the arrow.

The arrow rotates in the direction of the mouse movement. This is defined in the class Dart with the following procedure: observing the mouse cursor continously, its position is known all the time. Each time the old and the new position are 5 pixel appart, the connecting line of both positions is recalculated. Its direction, which is defined with the method actan2(), is the moving direction of the mouse.

 

Run this example

Edit this example in the Online-Editor

 

// JGameEx28.java

import ch.aplu.jgamegrid.*;
import ch.aplu.util.*;
import java.awt.*;

public class JGameEx28 extends GameGrid implements GGActorCollisionListener
{
  public JGameEx28()
  {
    super(6006001null"sprites/town.jpg"false);
    setTitle("Move dart with mouse to pick the balloon");
    setSimulationPeriod(50);
    playSound(this, GGSound.DUMMY);
    Dart dart = new Dart();
    addActor(dart, new Location(100300));
    addMouseListener(dart, GGMouse.lDrag);
    dart.setCollisionSpot(new Point(300))// Endpoint of dart needle
    
    for (int i = 0; i < 10; i++)
    {
      Actor balloon = new Actor("sprites/balloon.gif");
      addActor(balloon, new Location((int)(500*Math.random() + 50 )
                                     (int)(500*Math.random() + 50)));
      dart.addCollisionActor(balloon);
      dart.addActorCollisionListener(this);
    }
    show();
    doRun();
  }

  public int collide(Actor actor1, Actor actor2)
  {
    actor2.removeSelf();
    playSound(this, GGSound.PING);  
    return 0;
  }

  public static void main(String[] args)
  {
    new JGameEx28();
  }
}

// --------------------- class Dart -----------------------
class Dart extends Actor implements GGMouseListener
{
  private Location oldLocation = new Location();

  public Dart()
  {
    super(true"sprites/dart.gif");  // Rotatable
  }

  public boolean mouseEvent(GGMouse mouse)
  {
    Location location =
      gameGrid.toLocationInGrid(mouse.getX(), mouse.getY());
    setLocation(location);
    double dx = location.x - oldLocation.x;
    double dy = location.y - oldLocation.y;
    if (dx * dx + dy * dy < 25)
      return true;
    double phi = Math.atan2(dy, dx);
    setDirection(Math.toDegrees(phi));
    oldLocation.x = location.x;
    oldLocation.y = location.y;
    return true;
  }
}

Explaining the program code:
dart.setCollisionSpot(new Point(30, 0)) sets the collisionspot at the point of the arrow, relativ to the sprite's center
dart.addCollisionActor(balloon) each new balloon is registered as a CollisonActor

actor2.removeSelf()

removes the balloon from the grid when it collided with the arrow's point
return 0 the amount of simulation cycles is returned with the method collide() until the collision detection is reactivated. In this example the detection can restart immediately
double dx = location.x - oldLocation.x
double dy = location.y - oldLocation.y
calculates the difference between the old and new coordinate
if (dx * dx + dy * dy < 25)
  return true
waits until the difference is at least 5 pixel