Saturday, March 26, 2011

D&D BattleMap Using the UDK - More Dungeon Less UT

Now that we have an idea of how to setup a top-down camera and a grid overlay, it's time to clean things up and drop the Unreal Tournament functionality.  We don't need an avatar running around while we're displaying a map and we want to be able to move independently of CSG and meshes in the level.

Make a backup copy of the files we've created so far.  You'll have to save the copy outside the /Development/Src directory, otherwise the FrontEnd will try to compile them as well.

I'll outline the changes below if you want to follow along.  Otherwise, links to archives of both versions are below.

BattleMapMain.uc

All we're doing here is changing the object we're extending from, to the base GameInfo class.  We'll no longer be playing an Unreal Tournament match when we start a level:
class BattleMapMain extends GameInfo;

...
BattleMapPawn.uc

Changing the base class again, as well as dropping the first person mesh fix (since there won't be any) and supplying an empty pawn.  We also set a few properties to enable the pawn to move freely around the level.  Essentially, we put the pawn in "flying" mode instead of "walking" mode.  That allows us to move vertically as well as horizontally, and effectively zooms and pans the level.

Replace the entire BattleMapPawn.uc code with the following:

class BattleMapPawn extends Pawn;
DefaultProperties
{
 //this is just an empty skeletal mesh to prevent log errors
 Begin Object Class=SkeletalMeshComponent Name=WPawnSkeletalMeshComponent
 End Object
 Mesh=WPawnSkeletalMeshComponent
 Components.Add(WPawnSkeletalMeshComponent)

 bCollideWorld=false
 bCollideActors=false
 WalkingPhysics=PHYS_Flying
 LandMovementState=PlayerFlying
 AirSpeed=+01200.000000
}
BattleMapHUD.uc

All we're doing here is removing the laser sight code.  Delete the following three lines:
// laser sight from pawn to reticle
bmPlayerController.Pawn.GetActorEyesViewPoint(pawnEyeLocation, pawnEyeRotator);
Draw3DLine(pawnEyeLocation, mouseOrigin, RedColor);
BattleMapPlayerController.uc

A lot of changes here.  First, change the base object:
class BattleMapPlayerController extends PlayerController
 config(Game);
Delete the following blocks.  These manipulated the behavior of the UT pawn:

// This basically locks the camera to the pawn's location, and adds CameraZOffset to the Z of the camera.
simulated event GetPlayerViewPoint( out vector out_Location, out Rotator out_Rotation )
{
...
}

// Force WASD to be NWSE, so use world rotation instead of pawn rotation
state PlayerWalking
{
...
}

// This makes our weapons aim to the pawn's rotation, not the controller's rotation
function Rotator GetAdjustedAimFor(Weapon W, vector StartFireLoc)
{
...
}

// Turn the pawn toward the cursor
function UpdateRotation( float DeltaTime )
{
...
}
Add the following block.  This handles the "flying" mode that we enabled in BattleMapPawn.uc:

// Force WASD to be NWSE, so use world rotation instead of pawn rotation
state PlayerFlying
{
 function PlayerMove(float DeltaTime)
 {
  local vector X,Y,Z;

  //GetAxes(Rotation,X,Y,Z);
  GetAxes(WorldInfo.Rotation,X,Y,Z);

  Pawn.Acceleration = PlayerInput.aForward*X + PlayerInput.aStrafe*Y + PlayerInput.aUp*vect(0,0,1);;
  Pawn.Acceleration = Pawn.AccelRate * Normal(Pawn.Acceleration);

  if ( bCheatFlying && (Pawn.Acceleration == vect(0,0,0)) )
   Pawn.Velocity = vect(0,0,0);
  // Update rotation.
  UpdateRotation( DeltaTime );

  if ( Role < ROLE_Authority ) // then save this move and replicate it
   ReplicateMove(DeltaTime, Pawn.Acceleration, DCLICK_None, rot(0,0,0));
  else
   ProcessMove(DeltaTime, Pawn.Acceleration, DCLICK_None, rot(0,0,0));
 }
}
Finally, replace the DefaultProperties section.  We dropped our CameraZOffice parameter and added a reference to a new camera object we're going to create:
DefaultProperties
{
 //this makes us stop immediately instead of decelerating
 bCheatFlying=true

 InputClass=class'BattleMap.BattleMapPlayerInput'
 CameraClass=class'BattleMap.BattleMapCamera'
}
BattleMapCamera.uc

This is a new class to handle the camera.  The base PlayerController object doesn't include the same functionality as UTPlayerController, so we're moving our top-down camera code here.

All we're doing is moving the camera position and POV after the real UpdateViewTarget "does its thing".  We also want to force ApplyCameraModifiers() which handles things like camera shake.  Because the camera will be so far from the source, the engine may try to skip that processing.

We wrap our changes in a check to see if we're in CinematicMode.  We don't want to change the camera if we are.  This allows us to do things like fly-throughs of a big room before a boss fight:

class BattleMapCamera extends Camera;
var int CameraZOffset;
// let it do it's fancy thing, then just set our own LOC and FOV
function UpdateViewTarget(out TViewTarget OutVT, float DeltaTime)
{
 local vector            Loc, Pos;
 local rotator           Rot;

 super.UpdateViewTarget(OutVT, DeltaTime);
 if (!PCOwner.bCinematicMode)
 {
  Rot = rotator(vect(0,0,-1));
  Loc = PCOwner.Pawn.Location;
  Loc.Z = Loc.Z + CameraZOffset;
  Pos = Loc - Vector(Rot);
  OutVT.POV.Location = Pos;
  OutVT.POV.Rotation = Rot;
  OutVT.POV.FOV = DefaultFOV;
 }

 ApplyCameraModifiers(DeltaTime, OutVT.POV);
}

DefaultProperties
{
 CameraZOffset=6000
 DefaultFOV=35
}
BattleMapPlayerInput.uc

Lastly, we just need to change the base object of our PlayerInput class:
class BattleMapPlayerInput extends PlayerInput within BattleMapPlayerController;

...
Now when you launch the game, the Necropolis level should start behaving more like a D&D dungeon map.  Use the WASD keys to pan the map and Space or C to zoom in and out.  Hit G to turn on the grid and resize it with the mouse wheel.

(Note: If you zoom in using the C key and the camera suddenly shifts and "sticks" for a few seconds, what's happening is that your pawn is dying.  You've hit the Kill-Z for the level, just as if your avatar had run off a cliff.  The camera should reset in a few seconds.  In our own levels, we'll set Kill-Z low enough to compensate for the camera height.)

The original top-down UT mode source files
The new BattleMap mode source files

6 comments:

  1. Hey people, i really really wanted to thank you for this, its an amazing tool for us D&D players who likes the fancy stuff :D i managed to get all working and i intend on starting making some levels this weekend.

    I do have a small problem though... the grid is a bit lower on the screen (its like it starts 1" lower on the screen), any ideas why that could happen? i made the flash file 1920x1080 (monitors resolution) and put the same size on the systemsettings

    ReplyDelete
  2. Make sure that your Grid symbol in Flash is lined up with the upper left corner of your scene. We should probably add some panning keys later to move it around, but for now zooming in and out with the mouse wheel simply resizes the symbol to the right and down.

    ReplyDelete
  3. it was lined up, anyway i just moved it a bit outside the scene upwards and to the left and now it covers the entire map, so no big of a deal luckily :), Thanks again for the quick response and the new post on interactivity, im really looking forward to make some things this weekend (sadly college goes first, so no idea if ill have the time :P) keep up the good work, ill pass this around and make sure u get some donations for your hard work guys

    ReplyDelete
  4. This comment has been removed by the author.

    ReplyDelete
  5. I was having some issues where mouse look would interfere with moving around the map with the WASD keys. I found that my Pawn would always seem to start rotated so it was facing W (<-) instead of N (^) to start the game so when I used the WASD keys they would move in directions not intended. W would move me west, A would move me South, S would move me East, and D would move me North.

    To fix this I added a bit to the BattleMapPawn.uc Class.

    class BattleMapPawn extends Pawn;
    var rotator InitialRot;

    // function to force initial rotation of Pawn to face the top of screen
    // on creation of the Pawn
    function PreBeginPlay()
    {
      InitialRot.Pitch = 0;
      InitialRot.Yaw = 0;
      InitialRot.Roll = 0;
      SetRotation (InitialRot);
    }

    DefaultProperties
    {
     //this is just an empty skeletal mesh to prevent log errors
     Begin Object Class=SkeletalMeshComponent Name=WPawnSkeletalMeshComponent
     End Object
     Mesh=WPawnSkeletalMeshComponent
     Components.Add(WPawnSkeletalMeshComponent)

     bCollideWorld=false
     bCollideActors=false
     WalkingPhysics=PHYS_Flying
     LandMovementState=PlayerFlying
     AirSpeed=+01200.000000

    ReplyDelete
  6. Once I got that fixed, if my mouse was moved at all my WS would pitch up and down weird so I would zoom in and out from the map as well as the AD keys moving weird. I finally got that figured out, and added a function to my BattleMapPlayerController.uc class.

    class BattleMapPlayerController extends PlayerController
      config(Game);

    var int CameraZOffset;
    var Vector2D MousePosition;
    var Vector MouseOrigin;
    var name ObjectUnderMouse;

    simulated function SetMouseOrigin(Vector NewMouseOrigin,name NewObjectUnderMouse)
    {
      // Avoid spamming if the cursor isn't moving
      if (MouseOrigin != NewMouseOrigin)
      {
        MouseOrigin = NewMouseOrigin;
        ObjectUnderMouse = NewObjectUnderMouse;
        ServerMouseOrigin(NewMouseOrigin, NewObjectUnderMouse);
      }
    }

    reliable server function ServerMouseOrigin(Vector NewMouseOrigin, name NewObjectUnderMouse)
    {
      MouseOrigin = NewMouseOrigin;
      ObjectUnderMouse = NewObjectUnderMouse;
    }

    // Function turns off MouseLook so movement of mouse doesn't effect movement keys
    function UpdateRotation( float DeltaTime )
    {
      local Rotator DeltaRot, newRotation, ViewRotation;

      ViewRotation = Rotation;
      if (Pawn != none)
      {
        Pawn.SetDesiredRotation(ViewRotation);
      }

    // Normally UpdateRotation function from PlayerController would use the commented out
    // section to use the mouse to look up and down and left and right.
      //DeltaRot.Yaw = PlayerInput.aTurn;
      //DeltaRot.Pitch = PlayerInput.aLookUp;
    // Changed this keep player on a single plane of movement. Will not move above and
    // below plane when mouse is moved and keyboard movement keys are pressed.
      DeltaRot.Yaw = PlayerInput.aBaseX;

      ProcessViewRotation( DeltaTime, ViewRotation, DeltaRot );
      SetRotation(ViewRotation);

      NewRotation = ViewRotation;
      NewRotation.Roll = Rotation.Roll;
      
      Pawn.FaceRotation(NewRotation, deltatime);
    }

    // Force WASD to be NWSE, so use world rotation instead of pawn rotation
    state PlayerFlying
    {
      function PlayerMovie(float DeltaTime)
      {
        local Vector X,Y,Z;
        //GetAxes (Rotation,X,Y,Z);
        GetAxes (WorldInfo.Rotation,X,Y,Z);
        Pawn.Acceleration = PlayerInput.aForward * X + PlayerInput.aStrafe * Y + PlayerInput.aUp * vect(0,0,1);;
        Pawn.Acceleration = Pawn.AccelRate * Normal(Pawn.Acceleration);
        if (bCheatFlying && (Pawn.Acceleration == vect(0,0,0)) )
          Pawn.Velocity = vect(0,0,0);
        //Update Rotation.
        UpdateRotation (DeltaTime);
        if (Role < ROLE_Authority) // then save this move and replication it
          ReplicateMove(DeltaTime, Pawn.Acceleration, DCLICK_None, rot(0,0,0));
        else
          ProcessMove (DeltaTime, Pawn.Acceleration, DCLICK_None, rot(0,0,0));
      }
    }

    // Make sure our custom HUD is used
    reliable client function ClientSetHUD(class newHUDType)
    {
      if ( myHUD != None )
      {
        myHUD.Destroy();
      }

      //myHUD = (newHUDType != None) ? Spawn(newHUDType, self) : None;
      myHUD = (newHUDType != None) ? Spawn(class'BattleMap.BattleMapHUD', self) : None;
    }

    DefaultProperties
    {
      //this makes us stop immediately instead of decelerating
      bCheatFlying = true
      
      InputClass = class'BattleMap.BattleMapPlayerInput'
      CameraClass = class'BattleMap.BattleMapCamera'
    }

    ReplyDelete