Tutorial body

Note: 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

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.

Configuring rays - low level API

The basic low-level API provided by Pogamut requires these 3 stages:

  1. 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)).

  2. 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.

  3. 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.

Configuring rays - Raycasting facade

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 FileNew Project ...SamplesPogamut03-RaycastingBot.

Raycasting class is automatically instanciated in UT2004BotModuleController class. Through this class we will set up the rays. This has several stages:

  1. 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.

  2. 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.

  3. 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();
    

Navigating using raycasting

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.

Observing raycasting bot in UT

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 Spectate 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.