JGameGrid
 
 

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

Ex23: Dynamic Sprite Transformation

Normally the displayed sprite image of an actor is taken from a disk file in gif, jpg or png format. When rotated actors are used, the image is rotated by library code when the actor is created and the images are stored in a sprite buffer. (For an actor with multiple sprite ids, this procedure is applied to every sprite image.)

For applications where multiple sprite images are used that differ only in size and rotation angle, it is cumbersome to create all the images statically using an image editor. To simplify your work you can transform the one given sprite image dynamically by size and rotation with the Actor class getScaleImage() methods. Using the returned BufferedImage you create new actors with one or several sprite images. With this approach it is very simple to produce special visual effects.

For the demonstration we use one sprite image explosion.gif and show it like coming out of the black background. The application class creates the actor and passes it to the ScaledActor class.

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

public class Ex23 extends GameGrid
{
  public Ex23()
  {
    super(3003001false);
    setSimulationPeriod(100);
    Actor explosion = new Actor("sprites/explosion.gif");
    addActor(new ScaledActor(explosion)new Location(150150));
    show();
    doRun();
  }

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

The class ScaledActor extracts the sprite image and creates a single dynamic actor with ten scaled and rotated sprite images, each with an increasing sprite id. The act() method shows consecutive images by cycling through the sprite ids.

import java.awt.*;
import java.awt.image.BufferedImage;
import ch.aplu.jgamegrid.*;

public class ScaledActor extends Actor
{
  private static int nb = 10;  // Number of sprites

  public ScaledActor(Actor actor)
  {
    super(createImages(actor));
  }

  public void act()
  {
    showNextSprite();
  }

  private static BufferedImage[] createImages(Actor actor)
  {
    BufferedImage[] bis = new BufferedImage[nb];
    for (int i = 0; i < nb; i++)
    {
      double factor = 0.1 * (+ 1);
      bis[i] = actor.getScaledImage(factor, 10 * i);
    }
    return bis;
  }
}

Execute the program locally using WebStart.

 

jgamegridex23

 

Ex24: Overlaid Actor Dragging

For many games, mouse interactions play an important role. It is not uncommon to move actors by mouse dragging and we should learn how to implement actor movements even when several actors are superposed. Here the principles: When the mouse button is pressed, it is necessary to detect which actor images intersect with the current mouse cursor. For the dragging only the actor displayed on top is concerned. If only standard mouse callbacks are used, a lot of coding is necessary to do this simple operation. To make your life easier, JGameGrid introduced the concept of a "mouse touch": For every actor you can define a "mouse touch area" (non-transparent image pixels, rectangle/circle of any size and position) and register a MouseTouchListener that reports via its callback method when the mouse cursor intersects the area.

Normally all actors under the current mouse cursor will send a notification. If only the top actor's event is needed, you can register the MouseTouchListener with the onTopOnly flag set to true.

In the following example ten chip-like actors with four different colors (generated dynamically using Graphics2D) are first stacked in the upper left cell. It's your challenge to move them into the corresponding color box with as few mouse-drags as possible.

First comes the ChipActor class, where the sprite image is created using RadialGradientPaint to prettify the chip image. The advantage of code-generated images compared to predefined disk images is the ease to produce many different chip colors in no time.

import java.awt.*;
import java.awt.image.BufferedImage;
import ch.aplu.jgamegrid.*;
import java.awt.geom.*;

public class ChipActor extends Actor
{
  private Color color;
  
  public ChipActor(Color color)
  {
    super(createImage(color));
    this.color = color;
  }
  
  protected Color getColor()
  {
    return color;
  }

  private static BufferedImage createImage(Color color)
  {
    int d = 50;
    BufferedImage bi = new BufferedImage(d, d, Transparency.BITMASK);
    Graphics2D g2D = bi.createGraphics();

    // Set background color
    g2D.setColor(new Color(2552552550));  // Transparent
    g2D.fillRect(00, d, d);

    float[] fractions =
    {
      0.7f1f
    };
    Color[] colors =
    {
      color, Color.darkGray
    };
    Point2D center = new Point2D.Float(/ 2, d / 2);
    RadialGradientPaint paint = 
      new RadialGradientPaint(center, d / 2f, fractions, colors);
    g2D.setPaint(paint);
    g2D.fillOval(00, d, d);
    return bi;
  }
}

In the application class we first label the color boxes using TextActors. Then we create ten ChipActors colored randomly using four predefined colors.

We turn now our special attention to the callback method mouseTouched() that is registered with addMouseTouchListener() for lPress, lDrag and lRelease events. Because we are only interested in events from the top actor, we set the onTopOnly flag to true. When a lPress event is fired, we store the current cell location and the pixel coordinates of the mouse cursor relative to the image. The first is necessary to bring the dragged actor back to its origin if we move the mouse so fast that the image cannot follow. The coordinate relative to the sprite image is used to calculate the image center location while dragging. JGameGrid reports a release event, even if the mouse button is released outside the image, but in this case the parameter spot reports the coordinates (-1,-1). Therefore in the lRelease case we check for spot.x and set the actor back to its original cell (to avoid that the actor remains at a partially dragged position). actor.setOntop() is called in the lPress event to ensure that the selected actor moves on top of all other actors independent of the previous paint order.

(One special case should be mentioned: If the mouse is pressed outside an image and then dragged inside, no drag events are fired.)

Because the simulation loop is not running, we must repaint the game grid ourselves by calling refresh(). Whenever a chip is released, we check with isOver() if all the chips are in the right color box. To do this we reclaim all actors in every cell and check if they have the corresponding color.

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

public class Ex24 extends GameGrid implements GGMouseTouchListener
{
  private Point hotSpot;
  private Location startLocation;
  private final Location stackLocation = new Location(00);
  private int nbMoves = 0;

  public Ex24()
  {
    super(2280Color.red, false);
    setTitle("Color Sorter");
    setBgColor(Color.gray);
    addStatusBar(25);
    setStatusText("Drag chips to labeled box!")

    TextActor[] labels =
    {
      new TextActor("Red")new TextActor("Yellow"),
      new TextActor("Green")new TextActor("Blue")
    };

    for (int x = 0; x < 2; x++)
    {
      for (int y = 0; y < 2; y++)
      {
        int index = 2 * x + y;
        addActor(labels[index]new Location(x, y));
        labels[index].setLocationOffset(new Point(-15-30));
      }
    }

    for (int i = 0; i < 10; i++)
    {
      int colorId = (int)(4 * Math.random());
      ChipActor chip = new ChipActor(getColor(colorId));
      addActor(chip, stackLocation);
      chip.addMouseTouchListener(this,
        GGMouse.lPress | GGMouse.lDrag | GGMouse.lRelease, true);
    }
    show();
  }

  public void mouseTouched(Actor actor, GGMouse mouse, Point spot)
  {
    switch (mouse.getEvent())
    {
      case GGMouse.lPress:
        startLocation = toLocation(mouse.getX(), mouse.getY());
        actor.setOnTop();
        hotSpot = spot;
        break;
      case GGMouse.lDrag:
        Point imageCenter =
          new Point(mouse.getX() - hotSpot.x, mouse.getY() - hotSpot.y);
        actor.setPixelLocation(imageCenter);
        refresh();
        break;
      case GGMouse.lRelease:
        if (spot.x == -1)  // Cursor is outside image
          actor.setLocation(startLocation);
        else
        {
          actor.setPixelLocation(new Point(mouse.getX(), mouse.getY()));
          nbMoves++;
          if (isOver())
            setStatusText("Total #: " + nbMoves + ". Done.");
          else
            setStatusText("#: " + nbMoves);
        }
        actor.setLocationOffset(new Point(00));
        hotSpot = null;
        refresh();
        break;
    }
  }

  private Color getColor(int colorId)
  {
    switch (colorId)
    {
      case 0:  // Red
        return new Color(25500);
      case 1:  // Yellow
        return new Color(2552550);
      case 2// Green
        return new Color(01500);
      case 3:  // Blue
        return new Color(0180255);
    }
    return Color.black;
  }

  private boolean isOver()
  {
    for (int x = 0; x < 2; x++)
    {
      for (int y = 0; y < 2; y++)
      {
        int index = 2 * x + y;
        for (Actor actor : getActorsAt(new Location(x, y), ChipActor.class))
        {
          ChipActor chip = (ChipActor)actor;
          if (chip.getColor().getRGB() != getColor(index).getRGB())
            return false;
        }
      }
    }
    return true;
  }

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

Execute the program locally using WebStart.

jgamegridex24