Ng -- Programming With Plug-ins

 

For questions about this document or this project contact Thomas Seufert at seufet@users.sourceforge.net.

 

Overview.. 1

The Concept 1

Plug-ins. 1

Writing a Plug-in. 2

Defining a Task. 2

Implement the Plug-in. 3

Configuring the Plug-in Using the Variant Manager 3

Using the Plug-in In Your Game. 4

 

 

Overview

 

The Concept

 

One of the principal mechanisms by which the Ng Engine assists developers in writing loosely coupled games is its Plug-in architecture.  The concept, which will be familiar to many developers, involves defining certain tasks within an application that will be delegated to a component that conforms to a certain contract.  Among the stipulations in this contract is that the code using the Plug-in must refer to the component that will be plugged in only through the established contract.  This allows any component which fulfills that contract to be “Plugged in” in place of any other component, greatly improving the flexibility of the code.

 

In Java terms, the contract will typically be one or more interfaces that Plug-ins must implement, although it could also require a particular base class. 

 

Plug-ins

 

The Ng Plug-in support classes reside in the rle.core.vm package.  The PlugIn interface must be implemented by all Plug-ins, and contains the following methods:

 

  /** Set parameters. */

  public void setParameters(String s);

 

  /** Provide access to parameters set via setParameters(). */

  public Map<String,String> paramMap();

 

  /** Set the configuring InfoView. */

  public void setConfig(InfoView view);

 

  /** Returns the configuration InfoView used to create this PlugIn.  See

   * InfoView.createPlugIn(String). */

  public InfoView config();

 

 

  /** For convenience.  This should always return the reference stored by

   * config(). */

  public GameContext getContext();

 

Note that the getContext() method retrieves the GameContext, and the config() method retrieves an instance of rle.core.vm.InfoView, a class used to provide a read-only view of an InfoNode from the Variant Manager.  This means that any Plug-in can:

 

  1. Be configured by a Variant Manager node
  2. Access the GameContext

 

These two facts are very important.  Being able to manage Plug-ins from the Variant Manager greatly reduces the effort required to define, configure, and maintain Plug-ins.  Having access to the GameContext is required for most any action that a Plug-in might take—the GameContext object provides handles to the player, dungeon, game clock, and several other important components. 

 

Writing a Plug-in

 

Defining a Task

 

The first step in writing a Plug-in is to define what your Plug-in will do, or rather, by which interface(s) will it do what it does.  We know that all Plug-ins must implement rle.core.vm.Plug-in, but almost all Plug-ins will require game code to call certain methods to perform useful behavior.  Fortunately, you need not always start from scratch – the Core defines several interfaces for Plug-ins relating to its event-based architecture.  One of the simplest of these is rle.core.event.GameClockListener, which is implemented by classes that wish to receive notification when a new turn starts, but there are many others.  See “Overview of Available Core Services” for details on the GameContext and all other Core functionality.

 

If none of these interfaces seem suitable to the task you want your Plug-in to perform, you can easily write your own from scratch, modeling it on one of the existing interfaces.

 

To assist in building up a picture of how Plug-ins work, we’ll look at an example from SimpleGame:  the DungeonBuilder Plug-in.  Here, the task is to populate a Dungeon with monsters, items, and traps(Maze building will come later).  SimpleGame does not define an interface for this task; it specifies that the rle.simple.dungeon.DungeonBuilder class must be used or subclassed.  An interface could always be defined later, but for now, the convenience of having a single base class provide functionality (such as randomly placing monsters and items) outweighs the need for total flexibility in which class can be plugged in.

 

Implement the Plug-in

 

Writing the Plug-in is easy; simply create a class which implements Plug-in and any other interfaces your task requires.  Note that to reduce the burden of implementing PlugIn, the PlugInBase class is provided, which implements all required methods.  Feel free to subclass PlugInBase with your Plug-ins.

 

Our Plug-in is implemented in rle.simple.dungeon.DungeonBuilder.  Looking at the code, we see that some aspects of its behavior, namely the number of traps, monsters, and items it places, are specified by configuration information:

 

    // Place some traps

    Map<String,String> params = paramMap();

    SimpleFormulaComputer computer = new SimpleFormulaComputer();

 

    // Place some monsters

    String monFormula = params.get("monster-count");

    int monsterCount = isEmpty(monFormula) ? 5 : computer.roll(monFormula);

    info("Placing " + monsterCount + " monsters");

    for (int i=0; i<monsterCount; i++){

      placeRandomMonster();

    }

 

Note that the “paramMap()” method is defined in PlugInBase, which parses the key/value pairs specified in the VM.  Configuring the Plug-in using the VM is the subject of the next section.

 

Configuring the Plug-in Using the Variant Manager

 

The basic task in configuring a Plug-in using the Variant Manager(VM) is to define an InfoNode which will represent it.  The VM is capable of infinite possibilities for configuration, but the easiest way is to start your project based on the SimpleGame prototype.  The prototype project has a group node root/plug-ins already configured for adding Plug-ins. 

 

Important things to note from the picture is that the VM provides a graphical interface for configuring your Plug-in.  Let’s take a look at what’s going on here:

 

¨     Provider Class – the fully qualified class name of your Plug-in

¨     Parameters – key/value pairs specifying arbitrary textual data you want to pass to your Plug-in.  In this way, the same Java class can be configured for “Plugging in” in several different ways.  In this example, the number of traps, monsters, and items are specified using a format that rle.core.data.SimpleFormulaComputer can use to “roll” a value.  The syntax norm(10,3) indicates that a normal distribution will be used, with a mean of 10 and standard deviation of 3.

¨     Help – Describe what this Plug-in does.  This is now useful mainly as a reminder, but will also help users and developers when auto-generated help files are implemented.

¨     Contract – The required interfaces/subclasses you would like to test the Plug-in for compliance to.  The format is a comma separated list of fully qualified class names.

¨     Test – Clicking this button will cause your Plug-in to be instantiated, configured, and tested against the specified interfaces.  This is an excellent way to double-check your configuration settings at design time.

 

Using the Plug-in In Your Game

 

You’re almost home!  The simplest way by far to use a Plug-in is to use its configuring InfoView from the Variant Manager to instantiate and configure it.  In SimpleGame.java, here is the code that finds and uses a Plug-in to create the Dungeon.

 

    // Locate the configuration node

    InfoView builderInfo = data().findInfo("plug-ins/dungeon-builder");

 

    // Instantiate and configure the Plug-in

    DungeonBuilder builder = (DungeonBuilder)builderInfo.createPlugIn();

 

    // Call methods

    builder.setDungeon(currentDungeon);

    builder.buildDungeon();

 

As a short-cut, a GameContext instance can also be used to instantiate a Plug-in in one step by just passing the path of the appropriate node:

 

    builder = (DungeonBuilder)context.createPlugIn("plug-ins/dungeon-builder");

 

 

Revision Date

User

Comments

6/15/2004

Thomas Seufert

Created

7/8/2004

Thomas Seufert

Update for 0.0.9

3/30/05

Thomas Seufert

Update for 0.2.0