Scenario Drivers

I've come up to a reasonable solution to make the development of scenarios in JevaEngine significantly simpler and comfortable. First, we must define what a scenario is.

A scenario, in abstract terms, describes a series of events (as well as a sequence in which they must take place) and how the world reacts to the occurrence of these events. Scenarios can be incredibly simple or very complex. Typically, they are constructed by associating concrete behavior to particular entities. Ie, the logic of the scenario is not centralized but rather scattered across various entities.

Consider the following example scenario where in the player must kill a goblin, acquire a key from their corpse and then open a door. In this example, the behavior might be distributed as such:

You might construct a special item called "Key to Door Y." The key in-itself has no behavior and is merely a means for the critical components in the scenario (the goblin and the door) to communicate. The key is uninteresting to the player once he passes Door Y and outside of this function it serves no purpose.

The goblin may be a typical NPC. It could be one you spawn like all others, but it would require at least some specific configuration for its act in this scenario. It might be a configuration you call 'questGoblinDoorY' with the behavior adding the special key to its inventory.

The door would need to have a tailored behavior to open only under the circumstance that the player has the required key.

As you can imagine, the development of such a simple scenario ends up become very tedious. Now imagine multiple scenarios occurring in the same scene. That makes the development process significantly worse and stressful.

As a means to centralize the configuration of scenarios, I have decided to develop and use ScenarioDriver entities. They drive a scenario described in freemind. Here is a sample scenario I have developed thus far in freemind.

Using XSLT, I intend to transform this into a format that can be parsed by the Scenario Driver entity.

The victim's hotel room...

May be worth investigating the victim's hotel room.

Design your dialogue in JevaEngine using freemind.

I have written a custom XSLT (along with several changes to the dialogue system in the RPG base) that allows you to design your character dialogue in an mind-mapping utility called FreeMind. Here is quick demonstration (I'm short on time, and a picture is worth a thousand words...)

Note that the dialogue you are about to see demonstrated is meant to project a real murder case, and is thus graphic in nature.

A screenshot of Demo3

Demo 3 is getting really close to being completed. There are a few issues/bugs to resolve.

Demo3 uses PARPG assets to construct an "investigation" quest. Unlike Demos 0-2 which are more action rpg orientated using the FLARE RPG assets.

Imported PARPG assets for demo, improved rendering system a bit

As you may know, ISceneBuffer objects are responsible for handling rendering in JevaEngine. Today I've found some time to add a new ISceneBuffer object to JevaEngine's core. There are now two ISceneBuffers built into JevaEngine (it is very easy to use your own.)

- PaintersIsometricSceneBuffer which uses the painter's algorithm for rendering isometric scenes. This method is very quick but frequently fails if your scenes are not composed of strictly fixed sized blocks. It sorts based on the location of the entity - but ignores the containing bounds of that entity. Thus this makes it impossible to depth sort walls or gates that span across multiple tiles properly without breaking them into individually size tiles.

- TopologicalIsometricSceneBuffer is much more proper in terms of depth sorting scene components. It works with variable sized components and allows entities to be sorted properly on the Z axis. However, this sorting algorithm is much more expensive and has a complexity of O(n^2) That said, I have managed to get very large scene to be sorted in a manner of 1-2ms - which is perfectly acceptable.

Here is a test for the TopologicalIsometricSceneBuffer. You'll see the scene components and their respective AABBs.

There are three components visible in this image, the gas tank, the wind mill and the ground (which is a single component.)

In previous screen-shots, the ground was composed of many different components - each per tile. With this new depth sorting algorithm, the ground can be a single sprite (generated via tiled or rendered out of blender etc...) You can imagine that generating the meta-data on a per-tile basis would be a lot of work (especially if you have a lot of tiles) so it is much more optimal to import them into tiled, construct a tile-able surface texture 20x20 tiles in dimensions use that instead.

Demo 2 - Quest Demo

Demo 2 demonstrates how you could use JevaEngine to implement various quest using a combination of quest-controlled-NPCs, and area triggers to initiate character dialogue. It also demonstrates how to use the dialogue system implemented by the RPG Base.

Below is a layout of our world in the world editor:

In the bottom left corner, you'll see a named entity placement "player." If we look at its import declaration (which can be edited via the GUI in the world editor - but I will list the JSON for convenience.)

{
    "location" : {
      "z" : 1.0099999904632568,
      "y" : 44.0,
      "x" : 19.0
    },
    "direction" : 8,
    "name" : "player",
    "config" : "artifact/entity/zombie/zombie.jec",
    "auxConfig" : {
      "behavior" : "artifact/behavior/character/puppet.js"
    },
    "type" : "character"
  }

You'll notice a few key components to the declaration. Location, name, direction and type are fairly obvious. The RbgBase presently has two types of entities that can be instantiated: "character" and "areaTrigger."

A few sample scripts!

Hey! I've basically got JevaEngine's demos to a near complete state. I thought I would share some of the scripts I have driving the behavior used in the demo.

The first script is called the "puppet" behavior. When used by an RpgCharacter it remains idle until given abstract commands. For example "attack x" or "move to y." Essentially making it a puppet. It is useful in quest scripts where you are controlling special quest characters etc (i.e, to attack the player when they enter a trigger, or speak to the player when they come near etc...) Additionally, it is also used as the player character behavior (since the player's character is a puppet to the player.)

Note that these scripts I am posting are a little hacky/messy. They are more of a means to an end design rather than thoroughly thought through implementations. They are not critical in any sense to the engine or rbgbase. They serve specifically as samples & behaviors for driving the demo. They may contain bugs etc.

var g_attackTarget = null;

var g_attackRange = 0;
var g_attackSound = null;
var g_attackDamage = 0;
var g_attackPeriod = 0;

var g_dieSound = null;

var g_controlQueue = new Queue();

me.onEnter.add(function() {

	//Upon entering a world, initialize with configuration parameters.
	var config = me.getConfiguration();

	if (config.childExists("attackDamage"))
		g_attackDamage = config.getChild("attackDamage").getValueInt();

	if (config.childExists("attackPeriod"))
		g_attackPeriod = config.getChild("attackPeriod").getValueInt();

	if (config.childExists("attackRange"))
		g_attackRange = config.getChild("attackRange").getValueDouble();

	if (config.childExists("attackSound"))
		g_attackSound = config.getChild("attackSound").getValueString();

	if (config.childExists("dieSound"))
		g_dieSound = config.getChild("dieSound").getValueString();

	constructTasks();
});

function constructTasks() {
	// Double check to assure we are constructing over a clean slate.
	me.cancelTasks();

	//If we're dead, there are no tasks that we can do...
	if (me.getHealth() <= 0)
		return;

	var controller = g_controlQueue.poll();

	if (controller != null) {
		controller.doTasks();
	} else if (g_attackTarget === null || g_attackTarget.getHealth() <= 0) {
		g_attackTarget = null;
		me.idle(500);
	} else {
		var deltaFromTarget = me.getLocation().difference(g_attackTarget.getLocation());
		var distance = deltaFromTarget.getLength();
		
		if (distance <= g_attackRange) {
			me.attack(g_attackTarget);
			me.idle(g_attackPeriod);
		} else
		{
			var targetLocation = g_attackTarget.getLocation();
			me.moveTo(targetLocation.x, targetLocation.y, g_attackRange * 0.95);
			me.invokeTimeout(2000, constructTasks); //reconstruct tasks as it is likely that our target will move.
		}
	}

	me.invoke(constructTasks);
}

me.doAttack.assign(function(attackee) {
	var distance = me.getLocation().difference(attackee.getLocation()).getLength();
	
	if (distance >= g_attackRange)
		return false;

	attackee.invokeInterface("damage", g_attackDamage);

	if (g_attackSound !== null)
		me.playAudio(g_attackSound);

	return true;
});

me.onLookFound.add(function(target) {
	if (me.isConflictingAllegiance(target) && g_attackTarget === null) {
		g_attackTarget = target;
		me.cancelTasks();
		constructTasks();
	}
});

me.onHealthChanged.add(function(delta) {
	if (me.getHealth() === 0) {
		if (g_dieSound !== null)
			me.playAudio(g_dieSound);
	}
});

me.mapInterface("target", function(target) {
	g_attackTarget = target;
});

me.mapInterface("damage", function(delta) {
	me.setHealth(me.getHealth() - delta);
});

me.mapInterface("queryControl", function(handler) {
	g_controlQueue.add(new CharacterController(handler));
});

function CharacterController(handler) {
	this.handler = handler;
}

CharacterController.prototype.doTasks = function() {
	this.handler(this);
	me.invoke(constructTasks);
};

CharacterController.prototype.moveTo = function(x, y) {
	me.moveTo(x, y);
};

CharacterController.prototype.speakTo = function(subject, dialogue) {
	me.speakTo(subject, dialogue);
};

CharacterController.prototype.setFlag = function(flag, value) {
	me.setFlag(flag, value);
};

CharacterController.prototype.idle = function(length) {
	me.idle(length);
};

Changes to JevaEngine's Scripting System

As you know, there have been some very large refactors in JevaEngine. These have allowed much more flexibility in JevaEngine - specifically the ability to use various different script engines. In this abstracting, I've also had to reorganizing the interface to these scripts. Here are some notable changes:

In the previous implementation of JevaEngine's scripting engine, you would have defined your methods in the global scope (of the script.) These methods would then be identified as handlers/delegates by their container (ie an entity) using their name/signature. There were a couple notable issues with this methods:

1. It littered the script's namespace. One could define a method without the intention of it being a handler, but by virtue of having the proper name/signature, the script's container would recognizes it as an event handler or delegate. For example, consider the onAttacked handler. Previously, it would be defined like so in the behavior script:

function attacked(attacker)
{
... Some event handling code
}

It is difficult to determine the purpose of this method. It may be used internally by the script, or it may be an event handler. One may define a method such as this with the intention of having it be used internally, but it would be acknowledged by the script's container (an entity for example) as a handler/delegate.

2. It made developing/debugging more difficult and the errors in scripts more difficult to identify. Failing to register a handler or delegate would be met with no immediate execution errors. One would simply observe absent behavior. For example, had the 'attacked' method above been named 'atttacked' instead (note the typo) the code would execute without any errors or warnings, yet one would observe the attack response behavior to be absent had they the luxury of testing the script in such an environment so readily.

The new method for registering handlers and delegates is much more efficient and resolves both of these issues. One would now do the following to register an 'attacked' handler.

me.onAttacked.add(function(attacker) {
..Handle attacked event
});

Also note that this allows for various different functions to be assigned to (or 'listen' to) events such as being attacked. Likewise, to register a handler:

me.doAttack.assign(function(attackee) {
..Do attack
});

You may have also noticed that in recent commits the feature to access a scripts defined methods/variables via the getScript method exposed on an entity's bridge was added. This has since been removed. The Entity Bridge being publicly accessible violated the privacy/control held by the respective behavior script and allows one to create unpredictable changes to the behavior's state/environment without warning. In the upcoming commits, the getScript method has been removed.

However, one will still likely need to obtain some interface/control over an entity externally (such as controlling a player for a quest script.) All entities now have a method called mapInterface (accessible only to the behavior script) and invokeInterface. This allows interface methods to be invoked and mapped, allowing one to control the entity via an interface presented by the entity's behavior script.

JevaEngine Update - July 24th, 2014

JevaEngine has been dormant for the last couple of months. The repository has also seemed pretty inactive.

Here is a sample of the current demo modules of JevaEngine and the world editor. They look the same (and admittedly could use some polishing) but they come after some major refactors on the engine. More details below the video.

I have been refactoring JevaEngine over the last month or so. These refactors come with some very important architectural changes that pushes JevaEngine towards a more flexible and powerful engine. I will be detailing the changes within the next few weeks once the refactored engine has been pushed to the repository. Below I will list some of the key changes to JevaEngine.

- All use of service locator pattern has been refactored out of the engine. The service locator pattern was used in JevaEngine to acquire an interface to the concrete implementation of Game, and to acquire an interface to the globally accessible asset factory (opening raw InputStreams to the game's filesystem.) This provided a convenient way for one to construct entities, open configuration files from anywhere inside of JevaEngine. However it also carried some issues, mainly allowing for global access to these resources (forcing factories to be thread safe) and hiding code smells that would usually become obvious.

- JevaEngine has now switched to an entirely Dependency Injection based model of passing around dependencies, using Guice to bootstrap the engine with the appropriate dependencies. This has allowed for more flexibility inside of JevaEngine. Including the ability to bootstrap JevaEngine with custom world, sprite, script, entity, physics and asset factories - allowing one to easily integrate custom file formats and scripting/physics engine seamlessly into JevaEngine.

- World projection has been entirely decoupled from the engine. Meaning one can implement various render perspectives into JevaEngine seamlessly. JevaEngine is no longer focused specifically towards Isometric Games, it can also focus towards platformers and top-down games. While no such implementations have been tested, it is theoretically possible to easily integrate these perspectives into JevaEngine.

- The world builder has been entirely reconstructed. The previous implementation was falling apart - mainly due to complications introduced by using the Swing UI subsystem so heavily. JevaEngine's new world builder is entirely based around the JevaEngine UI subsystem. This allows for a much more functional and cleaner implementation.

- JevaEngine now delegates Window construction to an injected Window Factory. The default Window Factory parses an external configuration file that defines the layout of the window and its style. The layout defines controls, their location and their respective properties (such as text, or width/height etc...) The constructed window is than injected with a behavior. This makes the task such as localization much more achievable. It also reduces the noise in UI relevant code by allowing the programmer to focus specifically on the behavior of the window rather than its layout.

- JevaEngine is now fully integrated with a physics engine (the default implementation is pure java Box2D, but it is very easy to bootstrap JevaEngine with a different physics engine.) Following this integration, JevaEngine now comes with some steering behaviors.

- These refactors have had a significant impact on the engine, and will also require some significant refactors to be applied to the client\server implementations. For the next few weeks, I will be focusing specifically on the rpgbase and the single player demos. The multiplayer implementation is being placed on the back burner for a few more weeks.

- JevaEngine now makes the appropriate use of checked and unchecked exceptions. This has made JevaEngine respond much more appropriately to errors. Developing with JevaEngine has become significantly easier. JevaEngine uses SLF4J to report errors (such as those that exist in entity scripts.) In the previous revisions of JevaEngine, when an error was encountered (such as one in an Entity's behavior script) an unchecked exception was thrown and eventually crashed the entire engine with an appropriate stack-trace. This is unacceptable and makes the job of a casual user of JevaEngine significantly more difficult. In the latest revision (in a scenario such as this) JevaEngine now logs the error in the script to the appropriate logger, and reverts to default behavior (such as that for an entity absent of a script.)

I now work full time (unfortunately) and have to work very hard to progress anywhere with JevaEngine. That said, I am very satisfied with the results of my work over the past few weeks.

Subscribe to JevaEngine RSS