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

The source code of all examples is included in the JGameGrid distribution.

Ex05: Changing Behavior by Class Derivation and Overriding

One of the miraculous features of OOP is the possibility to modify the behavior of a class without modifying the class itself, simply by creating a subclass that overrides some of the methods of the base class. If needed, the overriding method can still use the service of the base class method by calling it with the prefix super. Without class derivation you would create a class with a different name and copy most of the code from one class to the new one. It is well-known that this code duplication is one of the worst programmer's sin, because it is almost sure that if the code is modified somewhere the code update will be forgotten in the duplicated part.

Class derivation in a didactical framework may also serve to simplify, extend or modify the behavior of a class by creating a subclass and adding it to the class library. The code is then completely hidden and the new feature can be used as if it were part of the library. As an example, we want to create an Actor-like class that is aware of the playground border. Calling move() will no longer move the actor outside the visible part of the playground but let him "mirror" at the playground border like a bouncing ball.

As stated before, we derive our new class CapturedActor from Actor and override move(int distance). (To keep the example simple, we will not override the parameterless method move().) In the derived class special care must be taken to provide base class initialization using super(). Actor has several overloaded constructors and normally each them should be provided to the derived class user. Here we only implement two of them: the most general that takes a boolean flag (true for rotatable sprite images), the path to the image file and the number of sprites for this actor and the most simple that takes only the path of the image file.

The new move() method checks if the location is outside the visible playground using pixel coordinates and performs the appropriate change of the moving direction and a horizontal mirroring to let the actor "look" in the moving direction. Then the overriden method is called. Do not forget the keyword super otherwise you will get an ugly recursion and the program crashes. To access GameGrid's methods from the Actor class we must use the public gameGrid reference.

(We could also use the corresponding GameGrid methods and request the GameGrid reference by getGameGrid(), but we want to use CaputerActor in examples, where no GameGrid window is present.)

import ch.aplu.jgamegrid.*;

public
 class CapturedActor extends Actor
{
  
public CapturedActor(boolean isRotatable, String imagePath, 
                       
int nbSprites)
  
{
    
super(isRotatable, imagePath, nbSprites);
  
}

  public CapturedActor(String imagePath)
  
{
    
super(false, imagePath, 1);
  
}

  
public void move(int dist)
  
{
    java.awt.
Point pt = 
      
gameGrid.toPoint(getNextMoveLocation());
      
    
double dir = getDirection();
    
if (pt.x < 0 || pt.x > gameGrid.getPgWidth())
    
{
      
setDirection(180 - dir);
      
setHorzMirror(!isHorzMirror());
    
}

    
if (pt.y < 0 || pt.y > gameGrid.getPgHeight())
      
setDirection(360 - dir);

    
super.move(dist);
  
}
}

In order to test our new class we write a simple application that moves the actor like a billiard ball. We paint a gray background by using the method clear() of the class GGBackground. If you forget the parameter 10 when calling move(), there is no syntax error, but the program does not behave as wanted. Why?

import ch.aplu.jgamegrid.*;

public
 class Ex05 extends GameGrid
{
  
private CapturedActor head =
    
new CapturedActor(false"sprites/head_0.gif", 1);

  
public Ex05()
  
{
    
super(800, 600, 1);
    
setSimulationPeriod(40);
    
setBgColor(new java.awt.Color(100, 100, 100));
    
addActor(head, new Location(400, 200), 66);
    
show();
  
}

  
public void act()
  
{
    head.
move(10);
  
}

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

Execute the program locally using WebStart.

The program uses a sprite from the code distribution of the book "Brackeen, Developing Games in Java". The book is highly recommended for developing high performance commercial games running in a full-screen window.

 

Ex05a: Change and Animate the Appearance of an Actor

We will profit from our previous work an use the new class CaptureActor in the following example where animate a sprite image. The technique need for many games to give the illusion of man walking, birld flying, animal eating, etc. You may also want to change the visual appearance of a figure in a typical game scenario, like getting red when angry, changing clothes when meating people, getting fat while eating, etc.

Changing the visual appearance of an actor could be established by creating a new actor, but this is not appropriate for obvious reasons: attributes like position, direction must be explicitely synchronized for all actors, waste of memory space, complicated coding. A much better solution is to change the sprite image at runtime. For performance reasons and for simpler coding JGameGrid implements many-sprite actors as follows: When creating an Actor instance, an arbitrary number of sprite images can be attributed to each actor. They are referenced by an integer sprite identifier running up from 0. At any time, only one (or none) sprite image is visible, selected by using setVisible(int spriteId, boolean isVisible). If we want to cycle through all available sprite images it is simpler to use showNextSprite() like in the following example. The constructor takes the total number of sprites as last parameter. The filename of each image is hard-coded using the given partial path and _0, _1. _2... appended. For this examples head_0.gif, head_1.gif, head_2.gif must reside in the subdirectory sprites. In many cases the call of move() and die cycling of the images should not be completely synchronized ir order to obtain a pretty animation flow. In this example we want 5 move() calls for one sprite change. Since both actions must be undertaken in the act() method, we must skip the images change accordingly by counting how many times act() was called. You can do this easily by hand, but there is a convenient method for this: getNbCycles() returns the total number of act() calls. If we take this number modulo 5 we get the necessary condition for cycling the sprites.

public class Player extends CapturedActor
{
  
public Player()
  
{
    
super(false"sprites/head.gif", 3);
  
}
  
  
public void act()
  
{
    
if (getNbCycles() % 5 == 0)
      
showNextSprite();
    
super.move(10);
  
}
}

 

For our pleasure we create 3 players and let them start at slightly different locations in different direction.

import ch.aplu.jgamegrid.*;

public class Ex05a extends GameGrid
{
  public Ex05a()
  {
    super(8006001false);
    setSimulationPeriod(40);
    setBgColor(new java.awt.Color(100, 100, 100));
    for (int i = 0; i < 3; i++)
    {
      Player player = new Player();
      Location startLocation = new Location(200 + 100 * i, 125);
      int startDirection = 15 * i;
      addActor(player, startLocation, startDirection);
    }
    show();
    doRun();
  }

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

 

Here you only see a snapshot, for more fun execute the program locally using WebStart.

GG4

[Sprite images from "Brackeen, Developing Games in Java"]

 

Ex05b: The Full Panoply of Actor's Animation: move, modify, rotate

Now you know how to move and modify the appearance of an actor, it' s time to add automatic image rotation when the actor changes his moving direction. There is nothing new to learn, just create the actor with the isRotatable flag set to true and JGameGrid displays the actor image adapted to the current direction. The only thing to take into account is to paint the image looking to the right (east direction). In the demonstration we prepare the movement of ghosts for the PacMan game. These ugly creatures move around by opening and closing their mouth to show that they are eager to catch the PacMan. We first declare the Ghost class by applying what we learned before (rotation and not mirroring should be used in this case):

import ch.aplu.jgamegrid.*;

public class Ghost extends Actor
{
  public Ghost()
  {
    super(true"sprites/ghost.gif"4);
  }

  public void act()
  {
    if (getNbCycles() % 5 == 0)
      showNextSprite();

    java.awt.Point pt =
      getGameGrid().toPoint(getNextMoveLocation());
    double dir = getDirection();

    if (pt.x < 0 || pt.x > getGameGrid().getPgWidth())
      setDirection(180 - dir);

    if (pt.y < 0 || pt.y > getGameGrid().getPgHeight())
      setDirection(360 - dir);

    move();
  }
}

 

The application creates 8 ghosts and prepares them for a crazy life after the button Run is pressed:

import ch.aplu.jgamegrid.*;

public class Ex05b extends GameGrid
{
  public Ex05b()
  {
    super(800, 600, 1, nulltrue);
    setSimulationPeriod(20);
    setBgColor(new java.awt.Color(100, 100, 100));
    Ghost[] ghost = new Ghost[7];
    for (int i = 0; i < 7; i++)
    {
      ghost[i] = new Ghost();
      Location startLocation = new Location(310 + 30 * i, 100);
      int startDirection = 66 + 66 * i;
      addActor(ghost[i], startLocation, startDirection);
    }
    show();
  }

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

Execute the program locally using WebStart.

For performance reasons the rotation of the sprite images is done by the Actor constructor for 64 angle increments out of 360 degrees. Then the rotated images are stored in memory and recalled as needed when the animation runs. That is the reason why the sprite image file and the number of sprites must be selected at creating time.