The sandbox scenario for this tutorial be downloaded here: The steering_playground project.

Learning Goal

The goal of this tutorial is to get familiar with the scripting API of PALAIS within a pre-made movement playground. In particular, we will be covering the Vector API and actor movement. Finally, we will discuss how we realised this steering playground environment within PALAIS using just 67 lines of code.

The Scene Logic

The scene itself depicts an empty playing field. Once you run the scene two actors will be spawned: A red and a green one. The green one will indefinitely move in between two set points on the playing field. The red one will do nothing but turn towards the green actor - for now.

You will find the following code in the file movement.js within the project directory:

 1 function seek(agent, targetPosition, maxSpeed) {
 2 	return targetPosition.subtract(agent.position)
 3 	                     .normalize()
 4 	                     .multiply(maxSpeed);
 5 }
 7 function flee(agent, targetPosition, maxSpeed) {
 8 	return targetPosition.subtract(agent.position)
 9 	                     .normalize()
10 	                     .multiply(-maxSpeed);
11 }
13 function movementBehavior(agent, targetPosition, maxSpeed) {
14 	//return seek(agent, targetPosition, maxSpeed);
15 	//return flee(agent, targetPosition, maxSpeed);
16 	return new Vector3(0,0,0);
17 }

The code represents two different movement behaviors: seek and flee. The seeking behavior will cause the red agent to home in on the moving green agent. The flee behavior will cause the red agent to run away from the green agent.

The inputs to each of these behaviors is the agent being moved, the position of the green agent in targetPosition and the maximum velocity maxSpeed of the red agent. The return value is the velocity vector that, when applied to the red agent, realizes a specific movement behavior.

Uncomment line 10 or 11 and reload the project ([CTRL] + [L] / [CMD] + [L]) to watch the simulation of these behaviors.

The path of the red agent will be drawn for the first 8 seconds of the simulation to visualise the movement behavior. The resulting scene approaching the timepoint at 8 seconds after simulation start should look like the following pictures. The first picture shows the resulting path of the seek behavior and the second picture shows the path taken when employing the flee behavior.

How is it implemented?

Lets start by looking at the scene setup code in the main project file scenario.js. Firstly, we create a Drawer object that will be used to draw the red agents path. We spawn the two agents in the actual onSetup function by calling a custom routine. For the green agent we set up the indefinite movement pattern by making sure the movement target is switched to the mirrored position on the map every time the movement target is reached. We set the orientation of both actors by setting their look-at targets. The movement_target and lookat_target properties are added to PALAIS via the pathfinding plugin.

 1 var d = Scene.createDrawer("velocityDrawer");
 2 function onSetup() {
 3 	spawnAgent(new Vector3(0,0,0), "green");
 4 	spawnAgent(new Vector3(2.5,0,2.5), "red");
 6 	redAgent.setKnowledge("lookat_target", greenAgent.position);
 7 	greenAgent.setKnowledge("lookat_target", redAgent.position);
 8 	greenAgent.setKnowledge("movement_target", new Vector3(-3,0,3))
 9 	greenAgent.knowledgeRemoved.connect(function(key) {
10 		// Keep moving between the two target points indefinitely.
11 		if(key == "movement_target") {
12 			var p = greenAgent.position;
13 			greenAgent.setKnowledge("movement_target", 
14 			                        new Vector3(-p.x, p.y, -p.z))
15 		}
16 	})
17 	greenAgent.setKnowledge("movement_speed", 1)
18 }

The custom spawnAgent routine simply instantiates an agent at a specified position and performs some setup on it. Specifically, the walk animation ("my_animation") is enabled and the rotation_speed property used by the pathfinding plugin is set.

1 function spawnAgent(position, color) {
2 	var agent = Scene.instantiate(color + "Agent", 
3 	                              "soldier2" + color, 
4 	                              position);
5 	agent.setScale(0.2)
6 	agent.setKnowledge("rotation_speed", 140) // in degrees per second.
7 	agent.enableAnimation("my_animation")
8 	return agent;
9 }

The update-loop

The update function controls the time-dependent logic of the scene. The active movement behavior movementBehavior is evaluated at every time step to obtain the current velocity of the red agent. Since movementBehavior function is defined in another file (movement.js) it has to be included into the current file via require() to be accessible. An integration step using the current velocity advances the red agent's position in line 27. Additionally, the custom drawing routine drawPath() is invoked for the first 8 seconds of the simulation.

 1 require("movement.js")
 3 var accum = 0;
 4 function update(deltaTime) {
 5 	var maxSpeed = 0.75;
 6 	var before = redAgent.position;
 8 	// Apply the custom movement behavior.
 9 	var v = movementBehavior(redAgent, greenAgent.position, maxSpeed);
11 	// Clamp the agent's velocity to __maxSpeed__.
12 	if(v.length() > maxSpeed) {
13 		v.normalize().multiply(maxSpeed);
14 	}
15 	var velocity = new Vector3(v.x, v.y, v.z);
16 	velocity.multiply(deltaTime);
18 	// Draw the path fo the agent for the first 8 seconds.
19 	accum += deltaTime;
20 	if(accum < 8) {
21 		drawPath(velocity);
22 	}
24 	// Apply the position update if the agent isn't going to move out of bounds.
25 	var newPos = before.add(velocity);
26 	if(newPos.x < 8 && newPos.x > -8 &&
27 	   newPos.z < 8 && newPos.z > -8) {
28 		redAgent.position = newPos;
29 	}
31 	// Update the actor's orientations.
32 	redAgent.setKnowledge("lookat_target", greenAgent.position)
33 	greenAgent.setKnowledge("lookat_target", redAgent.position)
34 }


The custom drawPath() function simply draws an arrow from the current position of the red agent to the position the red agent would have if it were to move in direction of its movement vector. Furthermore, the start and end positions of the arrow are offset in the y-axis to prevent z-fighting with the ground plane.

1 function drawPath(velocity) {
2 	var s = redAgent.position;
3 	s.y += 0.2;
4 	var e = redAgent.position.add(velocity);
5 	e.y += 0.2;
6 	d.drawArrow(s, e, Colors.RED);
7 }