I also made an RPG like this previously, but I sort of went about the actions in a different way. I had a collection of AI scripts that were just strings of actual Java code, so in the editor when I created a scripted NPC their full task (like walkHere->turnThere->AttackThere or something) would actually in the end just be a longer string. In the editor you would just combine actions from a list, but once you saved the level it would combine the strings of code into one long string. Then it would use Javac to compile that single class of code, and then finally I would just run that each timestep.
It worked totally fine and allowed absolutely any sort of control I wanted with very little fuss. I didn’t even have to use multiple threads.
I don’t have my actual source code with me, but here are some quick examples written from scratch:
What the script for a move action might look like.
//This is an example java file that contains the moveTo script, which will attempt to go somewhere.
currentActionName = "moveTo";
//First the code I want executed for the action.
Vector2 target = (Vector2) getScriptVariable(0);
Vector2 change = Vector2.subtract(target,owner.getPosition());
change.normalize();
owner.move(Vector2.scale(change,owner.getSpeed());
//Then I also need to notify the main script to go on when I'm finished.
if (owner.getPosition().equals(target))
finishAction();
What the script for a talk action might look like.
//This is another example java file that contains the talk script, which will speak a message.
currentActionName = "talk";
//First the code I want executed for the action.
String message = (String) getScriptVariable(0);
owner.addDialogMesage(message);
//Then I also need to notify the main script to go on when I'm finished.
finishAction();
What the final generated task script might look like:
//This script was created by the editor. It consists of:
//moveTo
//talk
//Loop:
// moveTo
//talk
public class EntityTaskChicken1 implements Task
{
private HashMap<String,Object> scriptVariables = new HashMap<String,Object>();
private Entity owner;
private int currentActionID = 0;
private String currentActionName = "";
private boolean moveOn = false;
public EntityTaskChicken1()
{
//Get the owner from the level and give them our task.
owner = Level.getEntityWithTag("Chicken1");
owner.addTask(this);
//Create the script variables.
scriptVariables.put("moveTo" + 0 + "" + 0, new Vector2(100,35));
scriptVariables.put("talk" + 1 + "" + 0, "Hey there, watch me run in circles!");
for (int i = 2; i < 102; i++)
scriptVariables.put("moveTo" + i + "" + 0, new RandomVector2(owner.getPosition(),10));
scriptVariables.put("talk" + 102 + "" + 0, "Whew, that was fun!");
}
//Execute the task and the current action. Returns true if the task has finished.
//Implemented from the task interface.
public boolean update()
{
if (currentActionID == 0)
moveTo();
else if (currentActionID == 1)
talk();
else if (currentActionID >= 2 && currentActionID < 102)
moveTo();
else if (currentActionID == 102)
talk();
if (moveOn)
{
currentActionID++;
moveOn = false;
}
return (currentActionID > 102);
}
//Finish the current action.
//Implemented from the task interface.
private void finishAction()
{
moveOn = true;
}
//Returns a script variable for the current action.
//Implemented from the task interface.
private Object getScriptVariable(int number)
{
return scriptVariables.get(currentActionName + currentActionID + number);
}
//The moveTo action.
private void moveTo()
{
//This is an example java file that contains the moveTo script, which will attempt to go somewhere.
currentActionName = "moveTo";
//First the code I want executed for the action.
Vector2 target = (Vector2) getScriptVariable(0);
Vector2 change = Vector2.subtract(target,owner.getPosition());
change.normalize();
owner.move(Vector2.scale(change,owner.getSpeed());
//Then I also need to notify the main script to go on when I'm finished.
if (owner.getPosition().equals(target))
finishAction();
}
//The talk action.
private void talk()
{
//This is another example java file that contains the talk script, which will speak a message.
currentActionName = "talk";
//First the code I want executed for the action.
String message = (String) getScriptVariable(0);
owner.addDialogMesage(message);
//Then I also need to notify the main script to go on when I'm finished.
finishAction();
}
}
The code that generates the task script.
public String generateScript(String ownerID, ArrayList<Action> actions)
{
//Create a string buffer to hold the resulting string.
StringBuffer script = new StringBuffer();
//Comments at the top to show what this script does.
script.append("//This script was created by the editor. It consists of:\n");
for (Action action : actions)
{
if (action.loops())
script.append("//Loop:\n//\t");
else
script.append("//");
script.append(action.getType() + "\n");
}
//Class definition.
script.append("\n");
script.append("public class EntityTask" + ownerID + " implements Task\n");
script.append("{\n");
//Variable declarations.
script.append("\tprivate HashMap<String,Object> scriptVariables = new HashMap<String,Object>();\n");
script.append("\tprivate Entity owner;\n");
script.append("\tprivate int currentActionID = 0;\n");
script.append("\tprivate String currentActionName = "";\n");
script.append("\tprivate boolean moveOn = false;\n");
script.append("\t\n\n");
//Constructor.
script.append("\tpublic EntityTask" + ownerID + "()\n");
script.append("\t{\n");
script.append("\t\t//Get the owner from the level and give them our task.\n");
script.append("\t\towner = Level.getEntityWithTag(" + ownerID + ");\n");
script.append("\t\towner.addTask(this);\n");
script.append("\t\t\n");
//Put script variables in.
script.append("\t\t//Create the script variables.\n");
int actionID = 0;
for (Action action : actions)
{
if (action.loops())
{
script.append("\t\tfor (int i = " + actionID + "; i < " (actionID+action.getLoopCount()) + "; i++)\n");
script.append("\t\t\tscriptVariables.put(\"" + action.getType() + "\" + i + \"\" + 0, " + action.getObjectString() + "\n");
actionID += action.getLoopCount();
}
else
script.append("\t\t\tscriptVariables.put(\"" + action.getType() + "\" + " + actionID + " + \"\" + 0, " + action.getObjectString() + "\n");
actionID++;
}
script.append("\t\t}\n\n");
//Update method.
script.append("\t//Execute the task and the current action. Returns true if the task has finished.\n");
script.append("\t//Implemented from the task interface.\n");
script.append("\tpublic boolean update()\n");
script.append("\t{\n");
actionID = 0;
for (Action action : actions)
{
if (actionID == 0)
script.append("\t\tif");
else
script.append("\t\telse if");
if (action.loops())
{
script.append(" (currentActionID >= " + actionID + " && currentActionID < " + (actionID+action.getLoopCount()) + ")\n");
actionID += action.getLoopCount();
}
else
script.append(" (currentActionID == " + actionID + ")\n");
script.append("\t\t\t" + action.getType() + "();\n");
actionID++;
}
script.append("\n");
script.append("\t\tif (moveOn)\n");
script.append("\t\t{\n");
script.append("\t\t\tcurrentActionID++;\n");
script.append("\t\t\tmoveOn = false;\n");
script.append("\t\t}\n");
script.append("\t\treturn (currentActionID > " + (actionID-1) + ");\n");
script.append("\t}\n");
//FinishAction method.
script.append("\t//Finish the current action.\n");
script.append("\t//Implemented from the task interface.\n");
script.append("\tprivate void finishAction()\n");
script.append("\t{\n");
script.append("\t\tmoveOn = true;\n");
script.append("\t}\n");
//GetScriptVariable method.
script.append("\t//Returns a script variable for the current action.\n");
script.append("\t//Implemented from the task interface.\n");
script.append("\tprivate Object getScriptVariable(int number)\n");
script.append("\t{\n");
script.append("\t\treturn scriptVariables.get(currentActionName + currentActionID + number);\n");
script.append("\t}\n\n");
//Methods for each of the actions.
for (Action action : actions)
{
script.append("\tprivate void " + action.getType() + "()\n");
script.append("\t{\n");
script.append(action.getScriptString(2));
script.append("\t}\n\n");
}
script.append("}");
return script.toString();
}
Wow, I really didn’t expect to type so much. Oh well, you get the idea.