Happy belated Thanksgiving

November 29, 2009

Happy belated Thanksgiving to everyone in the US. I’ve been spending more time eating than working on RpgToolkit, so I don’t have a lot of new code to share right now. I can tell you what I am working on though: UI elements for the HUD. Mostly I am working on a ‘Combat Log’ window right now, which is a scrollable log of all the various combat events. This gives me a place to watch combat unfold as I begin to implement. It also gives me a head start on the rest of the Window based UI. The combat log will be my test bed for implementing moving, resizing and scrolling, something we’ll need later on.

Advertisements

An interface starts to take shape

November 23, 2009

Slowly but surely I’m crafting an interface that I like. Through reading a tutorial from Epic on mouse cursors, and playing around quite a bit, I’ve gotten my PlayerController to start in a mouse driven interface, and go to mouse look when the right button is held. This is a more familiar RPG interface than standard first person (though I plan to offer both at some point).

Basically, our PlayerController now has a new state: MouseInterface. We have made this the default state by overriding EnterStartState and setting our state to MouseInterface. We also override the PlayerWalking state in our controller. This state is the default land movement state for any controller that has been possessed by a Pawn (which ours has, as we have an RTPlayerPawn class that is instantiated). In both cases we have overriden the PlayerMove function which controls movement input, including the mouse.

In our MouseInterface state, we actually ignore all regular movement, and instead calculate a set of mouse coordinates. These coordinates are then used by our new custom HUD class, which draws a mouse cursor when we are in the MouseInterface state. You’ll find our new PlayerController and HUD classes below:

RTPlayerController:

class RTPlayerController extends PlayerController;


var Vector MousePos;

var float HudSizeX;
var float HudSizeY;

var transient Actor SelectedActor;
var transient Camera Camera;

function EnterStartState() {
	GotoState('MouseInterface');
}

exec function StartSelect() {

}

exec function EndSelect() {
}

exec function StartInteract() {
	// Wait for the player to actually hold the button
	if(!IsTimerActive('StartMouseLook')) {
		SetTimer(0.2f, false, 'StartMouseLook');
	}
	else {
		ClearTimer('StartMouseLook');
	}
}

exec function EndInteract() {
	if(IsTimerActive('StartMouseLook')) {
		ClearTimer('StartMouseLook');
	}
}

exec function StartMouseLook() {
	GotoState('PlayerWalking');
}

state MouseInterface {
	simulated function PlayerMove(float deltaTime) {
		local vector mouseV, screenV;

		mouseV.X = deltaTime * (PlayerInput.aMouseX * 1.1); // / (InputClass.default.MouseSensitivity * DesiredFOV * 0.001);
		mouseV.Y = deltaTime * (PlayerInput.aMouseY * -1.1); // / (InputClass.default.MouseSensitivity * DesiredFOV * -0.001);

		MousePos += mouseV;

		if(HudSizeX > 0 && HudSizeY > 0) {
			screenV.X = MousePos.X + HudSizeX * 0.5;
			screenV.Y = MousePos.Y + HudSizeY * 0.5;
		}
	}
}

// This is our mouse look state
state PlayerWalking {
	simulated function PlayerMove(float deltaTime) {
		super.PlayerMove(deltaTime);
	}

	exec function EndInteract() {
		GotoState('MouseInterface');
	}

	exec function StartSelect() {
	}

	exec function EndSelect() {
	}
}

defaultproperties
{
	SelectedActor = none;
}

RTGameHud:

class RTGameHud extends HUD;

var Texture MouseCursorTexture;

simulated event PostRender() {
	super.PostRender();

	RTPlayerController(PlayerOwner).HudSizeX = Canvas.SizeX;
	RTPlayerController(PlayerOwner).HudSizeY = Canvas.SizeY;

	if(PlayerOwner.IsInState('MouseInterface')) {
		DrawMouseCursor();
	}
}

simulated function DrawMouseCursor() {
   local float XPos, YPos;

   Canvas.SetDrawColor(255, 255, 255);

   // find position of cursor, and clamp it to screen
   XPos = RTPlayerController(PlayerOwner).MousePos.X + Canvas.SizeX / 2.0;
   if (XPos < 0) {
      RTPlayerController(PlayerOwner).MousePos.X -= XPos;
      XPos = 0;
   }
   else if (XPos >= Canvas.SizeX) {
      RTPlayerController(PlayerOwner).MousePos.X -= (XPos - Canvas.SizeX);
      XPos = Canvas.SizeX - 1;
   }
   YPos = RTPlayerController(PlayerOwner).MousePos.Y + Canvas.SizeY / 2.0;
   if (YPos < 0) {
      RTPlayerController(PlayerOwner).MousePos.Y -= YPos;
      YPos = 0;
   }
   else if (YPos >= Canvas.SizeY) {
      RTPlayerController(PlayerOwner).MousePos.Y -= (YPos - Canvas.SizeY);
      YPos = Canvas.SizeY - 1;
   }

   // render mouse cursor
   Canvas.SetPos(XPos, YPos);
   Canvas.DrawTexture(MouseCursorTexture, 1.0f);

   return;
}


DefaultProperties
{
	MouseCursorTexture=Texture2D'EngineResources.Cursors.Arrow'
}

One thing you will notice in the PlayerController is that we use a timer to invoke the mouse look. This is because right click is also our basic interact input binding. As such, we want to make sure the user is actually holding the right mouse button before we enter mouse look. If the user does not hold the button for at least 0.2 seconds, we assume this is just a standard right click.

There are some tweaks to be made still, as the mouse movement is somewhat clunky. I need to play with the mouse sensitivity values, and possibly use some kind of linear interpolation to have the mouse accelerate and decelerate (something a lot of games do to make the mouse feel smooth). Right now I am using a constant mouse sensitivity, which still doesn’t feel right. The original algorithm in comments near my constants was from the Epic tutorial. This created a completely unusable mouse and I’ve scrapped their code on that for now.

The next step is to figure out picking. To do that, I need to translate my screen space mouse coordinates to world space so I can create a trace ray.

One other note: Sometime in the near future, I plan to open up a GoogleCode project for RpgToolkit, because the classes are going to start getting too big to paste in a blog. Instead I’ll keep my blog posts to snippets, and offer the full source in a Subversion repository.


Ghetto debugging at it’s finest

November 20, 2009

Well after two days of bleary eyed `log debugging (because nFringe debugging doesn’t work for the UDK yet, you see), I finally figured out why my custom GetDefaultPlayerClass function was not being called in my custom GameInfo. Basically, the stock Engine.GameInfo class has a variable which is called bDelayedStart, which it defaults to true. I would imagine this has to do with some sort of pre-game lobby functionality, which for my current level of implementation is completely worthless to me.

Simply setting that property to false in my DefaultProperties block fixed the issue and allowed my GameInfo to complete it’s full login process, including creating the player Pawn using the class I return from GetDefaultPlayerClass.

Here’s my new (still not terribly exciting) game info class:

class RTGame extends GameInfo;

function class<Pawn> GetDefaultPlayerClass(Controller c) {
	return class'RpgToolkit.RTPlayerPawn';
}

defaultproperties
{
	bDelayedStart = false
	PlayerControllerClass=class'RpgToolkit.RTPlayerController';
	PlayerReplicationInfoClass=class'RpgToolkit.RTPlayerReplicationInfo';
}

Basically our game still does nothing except spawn a PlayerController and create the appropriate Pawn class. It’s not much, but it’s a step forward.

One other nugget of knowledge I’d like to share before I wrap this post up, is the ability to override engine class functions for the purpose of logging. To come to the conclusion that I did with my last issue, I wrote overrides for many of GameInfo’s functions that simply sent out a log message with some variable values, then called the super implementation. This allowed me to trace the entire player creation process and slowly whittle down what was causing it. It’s not breakpoint debugging, but it gets you by until Pixel Mine does an update for the non-commercial version of nFringe to support the UDK.

For example, here is the PostLogin override I used to determine what was causing my last issue:

event PostLogin( PlayerController NewPlayer ) {
	`log("PostLogin: bDelayedStart = "$bDelayedStart$", bWaitingToStartMatch = "$bWaitingToStartMatch);
	super.PostLogin(NewPlayer);
}

How your players can touch you (or player input)…

November 19, 2009

Handling user input in a customized way is usually one of the first things someone wants to do when attempting to create a UDK game from scratch. In the UDK, this involves working with your binding INI file and implementing a PlayerController class.

First, lets take a look at a binding file. In this case, I will show you a snippet of my customized UTInput.ini.

Bindings=(Name="GBA_StrafeRight",Command="Axis aStrafe Speed=+1.0")
Bindings=(Name="GBA_StrafeLeft",Command="Axis aStrafe Speed=-1.0")
Bindings=(Name="GBA_Backward",Command="Axis aBaseY Speed=-1.0")
Bindings=(Name="GBA_MoveForward",Command="Axis aBaseY Speed=1.0")
...
Bindings=(Name="W",Command="GBA_MoveForward")
Bindings=(Name="S",Command="GBA_Backward")
Bindings=(Name="A",Command="GBA_StrafeLeft")
Bindings=(Name="D",Command="GBA_StrafeRight")
Bindings=(Name="E",Command="GBA_Use")
Bindings=(Name="LeftMouseButton",Command="Select")
Bindings=(Name="RightMouseButton",Command="Interact")

You’ll notice a few things. First, bindings are defined with a name and a command. The name can be the name of an actual input trigger (a keyboard key, a mouse click, an XBox button, etc), or it can be an arbitrary name, creating a binding meant to be used by other bindings. In the snippet I gave above, GBA_MoveForward is one such binding. These bindings are useful when you are developing for consoles and PC at the same time, something we mostly won’t do with the UDK (for now).

Bindings also require a command. That command can be a reference to another binding, such as how GBA_MoveForward is the command for the ‘W’ key, or it can be a function in your PlayerController class. This is how the PlayerController and user input are linked together.

In my custom UTInput.ini, you’ll notice I have bound the LeftMouseButton trigger to the Select command, and the RightMouseButton trigger to the Interact command. In the PlayerController example below, you will notice I have two functions: one named Select and one named Interact. These are the functions that the Unreal engine will invoke in response to my bindings.

class RpgToolkitPlayerController extends PlayerController;

var transient Actor SelectedActor;
var transient Camera Camera;

exec function Select() {
	`log("Select called");
}

exec function Interact() {
	`log("Interact called");
}

defaultproperties
{
	SelectedActor = none;
}

We mark the functions exec because bindings use the console to invoke their functionality. The console can only invoke functions which are marked with the exec modifier. A nice side effect of this is that we can also call the functions from the console.

There is one other feature that command binding offers which can be very useful: OnRelease triggers. Instead of a calling a single function when the control trigger is released, you can instruct Unreal to call one function when the button is pressed, and another function when the button is released. Setting up this kind of binding in your input configuration is shown in the following example taken from the default UDK configuration:

Bindings=(Name="GBA_Fire",Command="StartFire | OnRelease StopFire")
Bindings=(Name="LeftMouseButton",Command="GBA_Fire")

In this example, Unreal will call the StartFire function when the left mouse button is pressed. It will then call the StopFire function when the left mouse button is released.

One other thing to note about binding user input to PlayerControllers this way: It’s state safe. You can override your input functions in your state blocks to create different input functionality based on your game state. This comes in very hand for handling things like pause menus or inventory screens.


And off we go…

November 18, 2009

Welcome to GnomeTank’s UDK Adventure. This is a “stream of consciousness” blog where I will post various tricks, tips, hacks and shenanigans related to me implementing an RPG game toolkit on top of the Unreal Development Kit. I will try to make daily posts covering my progress, things I’ve learned, ‘gotchas’ to avoid, and more.

We’ll start with a ‘gotcha’ I overcame last night. If you plan to implement a custom Pawn class (in my case, RpgToolkitPawn), you need to make sure you add the Mesh component to the Components in your DefaultProperties block, otherwise the pawn will have no visible representation.

For example

class RpgToolkitPawn extends Pawn
    placeable;

DefaultProperties
{
    begin object class=SkeletalMeshComponent name=Mesh0
        ... // setup your Skeletal mesh here
    end object
    Mesh = Mesh0 // Set the Engine.Pawn.Mesh reference to our Mesh0 component
    Components.Add(Mesh0)  /* THIS IS THE IMPORTANT PART. Without this, the pawn will not render your skeletal mesh */
}

As you can see, adding the ‘Components.Add(Mesh0)’ was extremely important to the Pawn being a visible entity in the game world.