Skip to main content

Getting started with the nenya library by threerings design inc.

Posted in

When releasing the scenepainter setup utility yesterday, I was, upon notifying people, asked the question of how to actually compile and use threering's nenya library for own projects. Simple question, not so simple answer. Digging through the API can be quite time consuming. Luckily, I remembered having written a quick "tutorial" about the subject earlier this year, I just never got around to publish it until now. So with a few months delay, here it is.

For convenience sake, all code examples, given in this quick howto (as well as the howto itself) can also be found in the ZIP file attached to the bottom of this page.


About

This document gives you a brief tour on how to utilize nenya for rendering
isometric scenes. It is assumed, that you are familiar with your JDK and
apache ant. Also you should be familiar with UNIX style notation for files
and directories.

Prerequisites

Before getting started, you'll need to build the library itself. Assuming
you downloaded the snapshot version (as oppose to the SVN repository), you
still need to manually resolve some dependencies. Get the software from the
following locations and drop the according jar files into the "lib" directory:

Afterwards invoke apache ant with the target "dist". This will
create the directory "dist" and compile the source to it.

Next thing you need is a tool for creating scenedescriptions, and some
prepackaged resourcefiles. Use the game "Yohoho! Puzzle Pirates" as a basis
for now:

Last but not least, you'll need to include all jar files in the "lib"
as well as the "dist/lib" directory in your classpath and copy the "code"
as well as the "rsrc" directory of the game to your working directory.

A glance at concepts

Before going on, a few words are required to get a rough (detail spared for
now) idea on how the nenya package works. Basically it consists of four
parts (actually more, but only four are relevant for this tutorial);

  1. Tilesets, which are the graphics most commonly used to build the static
    parts of a scene (e.g. floors, walls,...). The idea behind a tileset is
    bundling graphics together, that somehow represents the same kind of
    object, which may have different views.
  2. Components, which are more sophisticated tilesets, allowing to build
    multipart images. Those are typically used for the animated parts
    of a scene, e.g. avatars.
  3. A resource loading infrastructure, which manages the various
    resourcefiles and therefore takes care of resolving tilesets and
    components that are both stored as serialized objects on disc.
  4. A rendering infrastructure, that is used for actually displaying a scene.

The course of action is therefore, loading a scene description, resolving
the required graphics through the resourcemanager and finally displaying
everything on screen.

Using the ResourceManager

Without the resourcemanager, graphics cannot be resolved, meaning it needs
to be instantiated first, which can be done using the following code sample:

  File current = new File(System.getProperty("user.dir"));
  String root = "rsrc";
  File base = new File(current,root);
  String cfg = "config/resource/manager.properties";
  ResourceManager resmgr=null;
 
  try { 
    URL extra[] =  new URL[1];
    File codedir= (new File(current,"code"));
    extra[0]=new File(codedir,"config.jar").toURL();
    resmgr = new ResourceManager(root, new URLClassLoader(extra));
    resmgr.initBundles(base.getAbsolutePath(),cfg,null);
  }
  catch (Exception e) { e.printStackTrace(); }

For this example, it is assumed, that your current directory contains the
subdirectories "rsrc" and "code" from the sceneeditor. Note, that only the
initBundles() method expects a pathname in a system dependant notation. The
configfile is loaded via the classloader and therefore uses forward slashes
as separator character (this is also why you need the "root" variable).
Next in line is the creation of the imagemanager, which will use the
resourcemanager as a backend for loading the images it serves:

  ImageManager immgr = new ImageManager(resmgr,new AWTImageCreator(null));

With the imagemanager, the tilemanager can be enabled:

  BundledTileSetRepository btr;
  String tsname= "tilesets";
  btr = new BundledTileSetRepository(resmgr,immgr,tsname);
  MisoTileManager tmgr =  new MisoTileManager(resmgr,immgr);
  tmgr.setTileSetRepository(btr);

The important thing to note here is the "tsname" variable. which holds the
name of the resourceset, containing the tiles. This name is used in the
"manager.properties" file for looking up the actual files, belonging to the
requested set.
Last but not least, a charactermanager is required:

  CharacterManager cmgr;
  BundledComponentRepository bcr = null;
  String csname = "components";
  try {
    bcr = new BundledComponentRepository(resmgr,immgr,csname);
  }
  catch (Exception e) { e.printStackTrace(); }
  cmgr = new CharacterManager(immgr,bcr);

The rest of the demo application, you are going to write will only have to
deal with "tmgr", "cmgr" and "bcr", so keep those two instances around.

Displaying something

Start up the sceneeditor and create a small scene (a simple floor will
suffice) and save it under the name "myscene.xml". The following code will
load that description in your demo application:

  File scene = new File("myscene.xml");
  String prefix = "scene";
  SparseMisoSceneParser smsp = new SparseMisoSceneParser(prefix);
  SparseMisoSceneModel smsm = null;
  try {
    smsm = smsp.parseScene(scene.getAbsolutePath());
    if (smsm==null) throw new NullPointerException();
  }
  catch (Exception e) { e.printStackTrace(); }

The variable "prefix" is required to find the node in the XML tree, that
contains the isometric layout of the scene. The prefix is the path, that has
to be stripped in order to get to this node.
In order to display the scene, a helper class is required first:

  import com.threerings.media.FrameManager;
  import com.threerings.miso.tile.MisoTileManager;
  import com.threerings.miso.util.MisoContext;
 
  public class Context implements MisoContext {
 
    private FrameManager fmgr;
    private MisoTileManager tmgr;
 
    public Context(FrameManager fmgr, MisoTileManager tmgr) {
      this.fmgr=fmgr;
      this.tmgr=tmgr;
    }
 
    public MisoTileManager getTileManager() { return tmgr;}
    public FrameManager getFrameManager() { return fmgr; }
  
  }

Now to finally bring it up to the screen:

  ManagedJFrame frame = new ManagedJFrame("Demo!");
  FrameManager fmgr = FrameManager.newInstance(frame);
  MisoSceneMetrics metrics = MisoConfig.getSceneMetrics();
  Context context = new Context(fmgr,tmgr);
  MisoScenePanel msp = new MisoScenePanel(context,metrics);
  msp.setSceneModel(smsm);
  frame.add(msp);
  frame.setBounds(new Rectangle(0,0,800,600));
  frame.setVisible(true);
  fmgr.start();

You'll notice, that a MisoScenePanel cannot be put in a normal JFrame, as it
has a different rendering strategy, called "active rendering". Active
rendering is the process of constantly redrawing the screen, just in case
something changed, which is a sane way to do for games, where potentially
lot's of things can happen independently on the screen and you do not an
event driven redrawing.

Getting some life into it

Life comes by the means of sprites and sprites can be constructed from
components in the following way:

  CharacterDescriptor desc = CastUtil.getRandomDescriptor("female",bcr);
  CharacterSprite sprite = cmgr.getCharacter(desc);
  sprite.setLocation(10,10);
  sprite.setActionSequence("standing");
  msp.addSprite(sprite);

This will create a random (and probably totally awkward looking) female
character and place it into the scene with a standing pose.
The character can be moved around by calling the according methods of the
CharacterSprite class.