An interface starts to take shape

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.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: