This tutorial can be applied to both PogamutUT2004 and PogamutUDK examples.
Previous tutorial has shown us how to use navigation graph for planning movement of the bot. This tutorial presents an alternative approach that can be used when you don't have navigation graph at all or when the graph doesn't provide all the necessary information.
Raycasting is technique commonly known from computer graphics
based on analytical geometry. The goal of raycasting is to compute
intersection of a ray
and the
geometry
of the world. Simple high school analytical
geometry can be used for implementation of raycasting but this isn't
topic of this tutorial. If you are interested in math behind raycasting
you can read this
site. This tutorial is concerned with using raycasting in Pogamut
and Unreal Tournament.
The basic low-level API provided by Pogamut requires these 3 stages:
Enable raycasting feature and rays visualization
If you want to start continuous computation of ray X world
intersections set AutoTrace
parameter of the
bot to true
, you can do this by
getAct().act(new Configuration().setAutoTrace(true));
method call.
For debugging it is handy to enable also DrawTraceLines
feature. This feature will visualize the rays you will set up in
UT. Thus, you can extend the previous call to
getAct().act(new
Configuration().setDrawTraceLines(true).setAutoTrace(true))
.
Initialize rays
Set the ray by AddRay
command, eg. by
getAct().act(new AddRay("LEFT45", new Vector3d(1, -1, 0),
500, true, true, true));
. Exact meaning of the parameters
is described in JavaDoc.
Handle results
Once the AddRay
was send, the UT
computes the result and returns it in
AutoTraceRay
message. You can obtain this
object by:
registering a listener that will be notified each time when a new ray is received from the environment
IWorldObjectEventListener<AutoTraceRay, WorldObjectFirstEncounteredEvent<AutoTraceRay>> rayListener = new IWorldObjectEventListener<AutoTraceRay, WorldObjectFirstEncounteredEvent<AutoTraceRay>>() { public void notify(WorldObjectFirstEncounteredEvent<AutoTraceRay> event) { //check which ray it is if (event.getObject().getId().getStringId().equals("LEFT45")) { //do something with the ray } } }; //don't forget to register listener above to the WorldView! (best in botInitialized() method) getWorldView().addObjectListener(AutoTraceRay.class, WorldObjectFirstEncounteredEvent.class, rayListener);
OR by checking presence of the object in world view
periodically, eg. in logic()
method
AutoTraceRay ray = null; ... if (ray == null) { // ray has not been initialized yet, try to obtain the instance ray = getWorldView().getAll(AutoTraceRay.class).get(UnrealId.get("LEFT45")); }
Once you obtain the
AutoTraceRay
object, this object will be
automatically updated by the platform when new results are
available.
As you can see the process of initializing rays is quite complex.
To ease this process there is Raycasting
class
that is utilized also by 03-RaycastingBot example. First, let's open the
example, you will find it under →
→
→
→ .
Raycasting class is automatically instanciated in UT2004BotModuleController
class. Through this class we will set up the rays. This has several stages:
Initialize multiple rays at once, RaycastingBot does this in
botInitialized()
method but you can do it
in logic()
or
botSpawned()
as well. Do not do it in prepareBot()
method (it won't work because the connection to the environment is not ready
at the time of execution of prepareBot()
).
// 1. remove all previous rays, each bot starts by default with three // rays, for educational purposes we will set them manually getAct().act(new RemoveRay("All")); // 2. create new rays raycasting.createRay(LEFT90, new Vector3d(0, -1, 0), rayLength, fastTrace, floorCorrection, traceActor); raycasting.createRay(LEFT45, new Vector3d(1, -1, 0), rayLength, fastTrace, floorCorrection, traceActor); raycasting.createRay(FRONT, new Vector3d(1, 0, 0), rayLength, fastTrace, floorCorrection, traceActor); raycasting.createRay(RIGHT45, new Vector3d(1, 1, 0), rayLength, fastTrace, floorCorrection, traceActor); raycasting.createRay(RIGHT90, new Vector3d(0, 1, 0), rayLength, fastTrace, floorCorrection, traceActor);
You can notice that two things has happened. First, all previously added rays were removed (this is done just to be sure that we are starting on clear ground). Then, five new rays were created.
Register the listener that will be called after all rays were initialized (the first result of computation come from UT):
// register listener called when all rays are set up in the UT engine raycasting.getAllRaysInitialized().addListener(new FlagListener<Boolean>() { public void flagChanged(Boolean changedValue) { // once all rays were initialized store the AutoTraceRay objects // that will come in response in local variables, it is just // for convenience left = raycasting.getRay(LEFT45); front = raycasting.getRay(FRONT); right = raycasting.getRay(RIGHT45); } });
This is the advantage of using Raycasting object over previously presented low level API. You don't have to manually code the mechanism that will wait for initialization of all rays.
Inform Raycasting instance that you don't intend to register any new rays (so the counting of incoming AutoTraceRay objects may start):
// 3. declare that we are not going to setup any other rays raycasting.endRayInitSequence();
Navigation bot uses a simple reactive algorithm of navigation that
can be in brief described as IF no ray is signalling THEN go
forward ELSE move in opposite direction of the signalling ray
.
The algorithm is coded in logic()
method.
Now, let's see how the bot is doing. Start the server with
DM-TrainingDay map by executing
startGamebotsDMServer.bat
. Then switch to spectate
mode by right clicking the server node in Netbeans and selecting
action. After the UT client loads,
you will see the raycasting bot navigating through the environment. The
bot will have five rays; however, only three of them are used by the
navigation algorithm. When the ray is green then the ray hasn't collided
with any wall or actor, otherwise the will turn red.