Delayed Events

One more thing you need to implement is the capability to easily create delayed events. An example of the type of delayed events you might want to create is in Screenshot.

Screenshot When the player touches a switch, several delayed events are set up.

Java graphics 14fig03.gif


In this figure, a few delayed events are set up when the player touches the switch: The door is opened, and seconds later, it closes again. Your first crack at creating a system to handle delayed events might be to simply use the Timer utility class. The Timer class can easily execute tasks after a specific delay. However, the Timer class won't work in a typical game, for a couple reasons. First, the Timer class keeps track of tasks by system time, not game time. The system clock keeps on ticking even if the game is paused, so events could be fired up at the wrong time when the game is paused. Secondly, the Timer class executes code in a separate thread, which could lead to synchronization problems in your game. So, you need to create an alternative system to do two things: manage the delayed tasks in the main thread, and keep track of them using game time, not system time. Start with the basic GameTask class shown here in Listing 14.11. This class takes a Runnable and a delay as a parameter, with the idea that the Runnable is executed after the specified delay.

Listing 14.11 GameTask.java
package com.brackeen.javagamebook.scripting;
/**
 A game task that can be scheduled for one-time execution.
*/
public class GameTask implements Runnable {
 private long remainingTime;
 private Runnable runnable;
 private boolean done;
 /**
 Creates a new GameTask that will execute the specified
 Runnable after a delay.
 */
 public GameTask(long delay, Runnable runnable) {
 this.remainingTime = delay;
 this.runnable = runnable;
 }
 /**
 Cancels this task.
 */
 public void cancel() {
 done = true;
 }
 /**
 Runs this task.
 */
 public void run() {
 if (runnable != null) {
 runnable.run();
 }
 }
 /**
 Checks to see if this GameTask is ready to execute; if
 so, it is executed. Returns true if the task is done
 (either it executed or it previously canceled).
 */
 public boolean check(long elapsedTime) {
 if (!done) {
 remainingTime-=elapsedTime;
 if (remainingTime <= 0) {
 done = true;
 try {
 run();
 }
 catch (Exception ex) {
 ex.printStackTrace();
 }
 }
 }
 return done;
 }
}


This class is designed so that its check() method is called on every frame. The remaining time the task has left until execution is kept track of, and this value is decremented in the check() method. When it reaches 0, the task is executed. Next, you create the GameTaskManager, in Listing 14.12, which keeps track of all the pending game events.

Listing 14.12 GameTaskManager.java
package com.brackeen.javagamebook.scripting;
import java.util.*;
/**
 Manages a queue of GameTask objects.
*/
public class GameTaskManager {
 private List tasks;
 /**
 Creates a new GameTaskManager with an empty task queue.
 */
 public GameTaskManager() {
 tasks = new ArrayList();
 }
 /**
 Adds a task to the queue that executed the specified
 runnable after a delay.
 */
 public void addTask(long delay, Runnable runnable) {
 addTask(new GameTask(delay, runnable));
 }
 /**
 Adds a task to the queue.
 */
 public void addTask(GameTask task) {
 tasks.add(task);
 }
 /**
 Clears the task queue.
 */
 public void clear() {
 tasks.clear();
 }
 /**
 Updates this manager, executing any ready tasks.
 */
 public void update(long elapsedTime) {
 List removeList = null;
 int size = tasks.size();
 // note that executing a task can potentially add more
 // tasks onto the queue.
 for (int i=0; i<size; i++) {
 GameTask task = (GameTask)tasks.get(i);
 if (task.check(elapsedTime)) {
 // add object to list of objects to remove later
 if (removeList == null) {
 removeList = new ArrayList();
 }
 removeList.add(task);
 }
 }
 // clear tasks that executed
 if (removeList != null) {
 tasks.removeAll(removeList);
 }
 }
}


The GameTaskManager has an update() method just like GameObject and many other classes in this tutorial. It calls the check() method of every GameTask. All you have to do is make sure the update() method is called every frame and you have implemented delayed tasks. In this code, we consider the fact that executing a game task can potentially add more tasks onto the queue. The code is careful about removing executed game tasks in the list, removing tasks from the list only after it has exited the loop.

Creating Delayed Events in BeanShell

BeanShell has the capability to create arbitrary interfaces, so creating delayed events is easy in your scripts. You can do something like this:

gameTaskManager.addTask(delay, new Runnable() {
 run() {
 doSomething();
 }
});


Of course, this conflicts with the goal of not requiring scripts to have classes or interfaces. We make this a bit easier with the delay() function in main.bsh, shown in Listing 14.13.

Listing 14.13 Delayed Tasks in main.bsh
/**
 Convenience method for executing a statement after a specified
 delay using the game task manager.
*/
delay(long delay, String statement) {
 gameTaskManager.addTask(delay, new Runnable() {
 run() {
 this.interpreter.eval(statement);
 }
 });
}


This function executes a statement after the specified delay. For example, if you wanted to call the closeDoor() method after six seconds, you would write this:

delay(6000, "closeDoor()");


Notice that the function is in quotes. This is because the statement is a string, which is, in turn, interpreted by BeanShell. Okay, your delayed event system sounds great. Now let's try it out.



   
Comments