Game apps for smartphones and tablets

Research project PHBern  
HomeStart online editorAndroid TurtlegraphicsPrintJava-Online

Collisions in pixels

In theorie, two actors collide if two pixels of their sprite are overlaying. That makes it necessary to check every non-transparent pixel of each sprite for this condition. Even if the areas are small, say 50 x 50 pixels wide, the check would involve 2500 x 2500 comparisons, too much to be done in real-time, where we should react within milliseconds. To overcome this problem we attach a simple shape as collision area to the sprite image and handle the collision between these shapes. In JDroidLib we use rectangles, circles, lines and points that can be oriented arbitrarily. As default, the collision area is the bounding rectangle.

Example 1: A ball is in a pixel-gamegrid and reflects at the borders. If the ball hits the vertically moving stick, a sound is played and it is likewise reflected. This example can be extended into the well known game Pong.

In the application class, the interface GGActorCollisionListener is implemented. This interface is registered by calling addActorCollisionListener(this), which makes every collision event call the method collide.

The stick is told by addCollisionActor(ball) that it is supposed to react to collisions with ball. For ball it is most sensible to chose a circle as collision area.

This example makes it very easy to observe how well collisions work in JDroidLib. Even if the two actors only touch slightly, a collision is registered.

Beispiel im Online-Editor bearbeiten

App installieren auf Smartphone oder Tablet

QR-Code

Sources downloaden (AndroidEx20.zip). Auf Tablet mit Longpress.

 

// AndroidEx20.java

package app.ex20;

import ch.aplu.android.*;
import android.graphics.Point;

public class AndroidEx20 extends GameGrid implements GGActorCollisionListener
{
  public AndroidEx20()
  {
    super(400, 400, 1);
  }
  
  public void main()
  {
    setSimulationPeriod(10);
    getBg().clear(DKGRAY);
    Ball ball = new Ball();
    addActor(ball, new Location(300, 30));
    Stick stick = new Stick();
    addActor(stick, new Location(200, 200));
    stick.setDirection(90);
    stick.addCollisionActor(ball);
    stick.addActorCollisionListener(this);
    doRun();
  }

  public int collide(Actor actor1, Actor actor2)
  {
    actor2.setDirection(actor2.getDirection() + 180);
    playTone(1200, 20);
    return 10;
  }
}

// --------------class Stick ---------
class Stick extends Actor
{
  public Stick()
  {
    super("stick");
  }

  public void act()
  {  
    move();
    if (getY() < 40 || getY() > 360)
      turn(180);
  }
}

// ---------class Ball ---------------
class Ball extends Actor
{
  private final int radius = 20;

  public Ball()
  {
    super("ball");
    setCollisionCircle(new Point(0, 0), radius + 1);
  }

  public void act()
  {
    Location loc = getLocation();
    double dir = getDirection();

    if (loc.x < radius)
    {
      dir = 180 - dir;
      setLocation(new Location(radius, loc.y));
    }
    if (loc.x > 400 - radius)
    {
      dir = 180 - dir;
      setLocation(new Location(400 -radius, loc.y));
    }
    if (loc.y < radius)
    {
      dir = 360 - dir;
      setLocation(new Location(loc.x, radius));
    }
    if (loc.y > 400 -radius)
    {
      dir = 360 - dir;
      setLocation(new Location(loc.x, 400 -radius));
    }
    setDirection(dir);
    move();
  }
}

Explanations to the program code:
setSimulationPeriod(10) Is shortening the simulation period, such that act() is called more often, ending up in the actors moving faster.
collide(Actor actor1, Actor actor2)

Callback method of the CollisionListener. Declares the behavior of the colliding actors after collision. (actor1 is Stick, actor2 is Ball)

setCollisionCircle(new Point(0, 0), radius) The collision area is set to a circle with center in (0,0) and radius 21 pixels
return 10 The return value sets the minimal amount of simulation cycles that it takes, until the actors can collide again.

 

Example 2: Five colored spheres are moving freely on the playground, only reflected by the boarders. When a sphere touches another, a collision is registered: Both spheres reflect physically correct and play a tune.

Beispiel im Online-Editor bearbeiten

App installieren auf Smartphone oder Tablet

QR-Code

Sources downloaden (AndroidEx21.zip). Auf Tablet mit Longpress.

 

 

// AndroidEx21.java

package app.ex21;

import ch.aplu.android.*;
import android.graphics.Point;

public class AndroidEx21 extends GameGrid implements GGActorCollisionListener
{
  protected static int nbBalls = 5;
  
  public AndroidEx21()
  {
    super(400, 400, 1);
  }
  
  public void main()
  {
    setSimulationPeriod(10);
    getBg().clear(DKGRAY);
    Ball[] balls = new Ball[nbBalls];
    for (int = 0; i < nbBalls; i++)
    {
      balls[i] = new Ball();
      balls[i].show(i);
      addActor(balls[i], new Location(100 + 50 * i, 100 + 50 * i), Math.random()*360);
      balls[i].addActorCollisionListener(this);
    }

    for (int = 0; i < nbBalls; i++)
    {
      for (int = + 1; k < nbBalls; k++)
        balls[i].addCollisionActor(balls[k]);
    }
    doRun();
  }

  public int collide(Actor actor1, Actor actor2)
  {
    actor1.setDirection(actor1.getDirection() + 180);
    actor2.setDirection(actor2.getDirection() + 180);
    playTone(1200, 20);
    return 10;
  }
}

// ------- class Ball ------------------
class Ball extends Actor
{
  private final int radius = 20;
  
  public Ball()
  {
    super("peg"5);
   setCollisionCircle(new Point(0, 0), radius + 1);
  }

  public void act()
  {
    Location loc = getLocation();
    double dir = getDirection();

    if (loc.x < radius)
    {
      dir = 180 - dir;
      setLocation(new Location(20, loc.y));
    }
    if (loc.x > getNbHorzCells() - radius)
    {
      dir = 180 - dir;
      setLocation(new Location(getNbHorzCells()-radius, loc.y));
    }
    if (loc.y < radius)
    {
      dir = 360 - dir;
      setLocation(new Location(loc.x, radius));
    }
    if (loc.y > getNbVertCells()-radius)
    {
      dir = 360 - dir;
      setLocation(new Location(loc.x, getNbVertCells()-radius));
    }
    setDirection(dir);
    move();
  }
}

Erklärungen zum Programmcode:
setCollisionCircle(new Point(0, 0), 21) The collision area is set to a circle with center in (0,0) and radius 21 pixels
balls[i].addActorCollisionListener(this) The CollisionListener is added to every sphere
balls[i].addCollisionActor(balls[k]); For every sphere, collisions with every other sphere are registered

 

 

Example 3: A dart can be moved by touch. The tip is a (CollisionSpot) which can be used to burst balloons

15 Balloons are created in randomly chosen locations. The tip of the dart is set as collision point with dart.setCollisionSpot(new Point(45, 0)). To define a collision spot, a sprite coordinate system is used with the middle of the sprite as origin (0, 0). The sprite is 90 x 17 pixels, so the tip ends up being at the position (45, 0). To avoid obscuring the dart with one's own finger, the touch position can be modified to the back end. For compatability with different screen sizes, windowZoom() is used. This scales the images and touch positions automatically.

Beispiel im Online-Editor bearbeiten

App installieren auf Smartphone oder Tablet

QR-Code

Sources downloaden (AndroidEx22.zip). Auf Tablet mit Longpress.

 

 

// AndroidEx22.java

package app.ex22;

import ch.aplu.android.*;
import android.graphics.Color;
import android.graphics.Point;

public class AndroidEx22 extends GameGrid implements GGActorCollisionListener
{
  public AndroidEx22()
  {
    super("town"windowZoom(500));
    setScreenOrientation(LANDSCAPE);
  }

  public void main()
  {
    setSimulationPeriod(50);
    Dart dart = new Dart();
    addActor(dart, new Location(50, 300));
    addTouchListener(dart, GGTouch.drag);
    dart.setCollisionSpot(new Point(45, 0));
    
    for (int = 0; i < 15; i++)
    {
      Actor balloon = new Actor("balloon");
      Location loc = new Location((int)(420*Math.random() + 40 ), 
                                  (int)(420*Math.random() + 40));       
      addActor(balloon, loc.toReal());
      dart.addCollisionActor(balloon);
      dart.addActorCollisionListener(this);
    }
    doRun();    
  }
   
  public int collide(Actor actor1, Actor actor2)
  {
    actor2.removeSelf();
    playTone(1200, 20);
    return 0;
  }  
}

// ------class Dart ------------------
class Dart extends Actor implements GGTouchListener
{
  public Dart()
  {
    super("dart");  
  }

  public boolean touchEvent(GGTouch touch)
  {
    Location location =
      gameGrid.toLocationInGrid(touch.getX() + 90, touch.getY());
    setLocation(location);
    return true;
  }
}

Erklärungen zum Programmcode:
super("town", windowZoom(500)); The window size is automatically adapted to screen size. 500 pixels is the original size of the background image.
setScreenOrientation(LANDSCAPE) Fixes the orientation of the screen to landscape mode. Turning the device will not change the orientation.
dart.setCollisionSpot(new Point(45, 0)) Sets the collision point to the tip of the dart
addActor(balloon, loc.toReal()) Calculates the coordinates of the touch event to the coordinates according to zoom.