Creating a Basic Navigation Camera

From C4 Engine Wiki

Jump to: navigation, search

Previous Section: Creating an Application and World

Contents

Creating a Basic Navigation Camera

Let’s create the camera code next. We’ll call it RTSCamera, so create the RTSCamera header and source file (RTSCamera.h and RTSCamera.cpp).

<RTSCamera.h>

#ifndef RTS_CAMERA_H
#define RTS_CAMERA_H

#include "C4Cameras.h"

namespace C4
{
	class RTSCamera : public FrustumCamera
	{
		public:
			
			RTSCamera();
			~RTSCamera();

			void Preprocess(void);
			void Move(void);
	};
}

#endif

<RTSCamera.cpp>

#include "RTSCamera.h"
#include "RTS.h"

using namespace C4;

RTSCamera::RTSCamera() : FrustumCamera(2.0F, 1.0F)
{
}

RTSCamera::~RTSCamera()
{
}

void RTSCamera::Preprocess(void)
{
	FrustumCamera::Preprocess();
}

void RTSCamera::Move(void)
{
	this->SetNodePosition(Point3D(4.0f, 4.0f, 3.0f));
	this->LookAtPoint(Point3D(0.0f, 0.0f, 0.0f));
}

The Move function simply contains some test code to make sure it points at the playfield. For now, define constants for camera node position and direction, just to make sure we have the world and camera working correctly.


Now that the camera code is implemented, the hooks to use the camera can be added in the CombatWorld class.

<CombatWorld.h>

#define COMBAT_WORLD_H

#include "C4Engine.h"
#include "C4World.h"
#include "RTSCamera.h"	// <-- Add

namespace C4
{
	class CombatWorld : public World
	{
		private:
			RTSCamera navCamera;	// <-- Add
		public:
			CombatWorld(const char *name);
			~CombatWorld();
		
			WorldResult Preprocess(void);
			void Render(void);
	};
}


#endif

<CombatWorld.cpp>


WorldResult CombatWorld::Preprocess(void)
{
	WorldResult result = World::Preprocess();

	SetCamera(&navCamera);	// <-- Add
	
	return (kWorldOkay);
}


And that’s it for now. Load up C4, type “load rts” again, and you should get something like this:


Image: camworld.jpg


Congratulations on loading your game world in C4! Next step is to get some keyboard actions into the game, similar to the Hello World Redux tutorial. And then with keyboard actions, we’ll be able to pan and rotate the camera.

Implementing the Navigation Action

Create a new Action class. We’ll call it NavAction. As you’ll notice, the enumeration lists all of the constants that map to the appropriate four-character keywords listed in the engine.cfg file in the <C4>\Data\Engine\ directory (except for ‘rotl’ and ‘rotr’, which will need to be added).

<NavAction.h>

#ifndef MOVEMENT_ACTION_H
#define MOVEMENT_ACTION_H

#include "C4Engine.h"
#include "C4Input.h"

namespace C4
{
	enum
	{
		kActionForward		= 'frwd',
		kActionBackward		= 'bkwd',
		kActionRotLeft		= 'rotl',
		kActionRotRight		= 'rotr',
		kActionLeft		= 'left',
		kActionRight		= 'rght'
	};

	class NavAction : public Action
	{
		private:
			
			unsigned long	movementFlag;
			
		public:
			
			NavAction(unsigned long type, unsigned long flag);
			~NavAction();

			void Begin(void);
			void End(void);
	};
}

#endif

<NavAction.cpp>

#include "NavAction.h"
#include "C4Engine.h"
#include "RTS.h"

using namespace C4;

NavAction::NavAction(unsigned long type, unsigned long flag) : Action(type)
{
	movementFlag = flag;
}

NavAction::~NavAction()
{
}

void NavAction::Begin(void)
{
	CombatWorld *world = static_cast<CombatWorld *>(TheWorldMgr->GetWorld());
	RTSCamera *camera = world->GetNavCamera();
	camera->SetNavFlags(camera->GetNavFlags() | movementFlag);
}

void NavAction::End(void)
{
	CombatWorld *world = static_cast<CombatWorld *>(TheWorldMgr->GetWorld());
	RTSCamera *camera = world->GetNavCamera();
	camera->SetNavFlags(camera->GetNavFlags() & ~movementFlag);
}


Okay, what’s type and what’s flag? The type will be one of the items in the enumeration above that lets the engine know what key to map to this Action, and flag is the movement flag that we will define in our camera class that maps the type to the actual movement of the camera. Got it? Onward then!

You’ll notice that we also need to add a few accessor functions. The RTSCamera class now use SetNavFlags and GetNavFlags functions, and CombatWorld uses an accessor to an RTSCamera.

Add this in CombatWorld.h as a public function:

<CombatWorld.h>

RTSCamera *GetNavCamera(void)
{
	return &navCamera;
}

(You really don’t need to create this accessor since the World class already has a GetCamera function, but whatever... saves us some casting.)

Add the following private variable in RTSCamera.h:

<RTSCamera.h>

long movementFlags;

And add this in RTSCamera.h as public functions:

<RTSCamera.h>

void SetNavFlags(unsigned long flags)
{
	movementFlags = flags;
}

unsigned long GetNavFlags()
{
	return movementFlags;
}

Don’t forget to initialize movementFlags in the constructor, too.

<RTSCamera.cpp>

RTSCamera::RTSCamera() : FrustumCamera(2.0F, 1.0F), movementFlags(0)
{
}


All right, now let’s define what the list of possible values for the movementFlags variable. In RTSCamera.h, put this right outside the class definition:

<RTSCamera.h>

enum
{
	kNavMoveForward		= 1 << 0,
	kNavMoveBackward	= 1 << 1,
	kNavMoveLeft		= 1 << 2,
	kNavMoveRight		= 1 << 3,
	kNavMoveRotLeft		= 1 << 4,
	kNavMoveRotRight	= 1 << 5,
	kNavMoveZoomIn		= 1 << 6,
	kNavMoveZoomOut		= 1 << 7,
	kNavMoveMask		=	kNavMoveForward | kNavMoveBackward |
						kNavMoveLeft | kNavMoveRight |
						kNavMoveRotLeft | kNavMoveRotRight |
						kNavMoveZoomIn | kNavMoveZoomOut
};


With these, we can finally register the actions that map to the specific navigation commands. Create six NavActions as private members in the RTS class (Remember to #include “NavAction.h”):

<RTS.h>

NavAction	*moveForwardAction;
NavAction	*moveBackwardAction;
NavAction	*moveLeftAction;
NavAction	*moveRightAction;
NavAction	*rotLeftAction;
NavAction	*rotRightAction;

And then initialize and register them in the constructor:

<RTS.cpp>

moveLeftAction = new NavAction(kActionLeft, kNavMoveLeft);
moveRightAction = new NavAction(kActionRight, kNavMoveRight);
moveForwardAction = new NavAction(kActionForward, kNavMoveForward);
moveBackwardAction = new NavAction(kActionBackward, kNavMoveBackward);
rotLeftAction = new NavAction(kActionRotLeft, kNavMoveRotLeft);
rotRightAction = new NavAction(kActionRotRight, kNavMoveRotRight);

TheInputMgr->AddAction(moveLeftAction);
TheInputMgr->AddAction(moveRightAction);
TheInputMgr->AddAction(moveForwardAction);
TheInputMgr->AddAction(moveBackwardAction);
TheInputMgr->AddAction(rotLeftAction);
TheInputMgr->AddAction(rotRightAction);

And delete them in the destructor:

<RTS.cpp>

if(moveLeftAction)
	delete moveLeftAction;

if(moveRightAction)
	delete moveRightAction;

if(moveForwardAction)
	delete moveForwardAction;

if(moveBackwardAction)
	delete moveBackwardAction;

if(rotLeftAction)
	delete rotLeftAction;

if(rotRightAction)
	delete rotRightAction;

One final step to make sure the keyboard input will be handled by the game actions is to change the input management mode in the interface manager. If this is not set in your game constructor, keyboard commands will not be handled by your action.

<RTS.cpp>

TheInterfaceMgr->SetInputManagementMode(kInputManagementAutomatic);

Implementing the Navigation Logic

This finally compiles, but no movement is going to happen until we finish hooking up the camera movement flag to some actual code in the camera’s Move() function.

We’ll rip out our previous RTSCamera::Move code and put in something a little more interesting this time. But first, create some private variables and initialize them:

<RTSCamera.h>

float curDirAngle;
Point3D curFocus;
float curDistance;
float curTiltAngle;

<RTSCamera.cpp>

RTSCamera::RTSCamera() : 
	FrustumCamera(2.0F, 1.0F), 
	movementFlags(0),
	curDirAngle(0.25f * K::pi),
	curFocus(0.0f, 0.0f, 0.0f),
	curDistance(10.0f),
	curTiltAngle(0.25f * K::pi)
{
}


Then add the math to make it happen:

<RTSCamera.cpp>

void RTSCamera::Move(void)
{
	float dt = TheTimeMgr->GetFloatDeltaTime() * 0.001f;

	float deltaFwd = 0.0f;
	float deltaRight = 0.0f;
	float deltaRot = 0.0f;

	// Position
	if((movementFlags & kNavMoveLeft) != 0)
	{
		deltaRight -= 1.0f * dt;
	}
	if((movementFlags & kNavMoveRight) != 0)
	{
		deltaRight += 1.0f * dt;
	}
	if((movementFlags & kNavMoveForward) != 0)
	{
		deltaFwd += 1.0f * dt;
	}
	if((movementFlags & kNavMoveBackward) != 0)
	{
		deltaFwd -= 1.0f * dt;
	}

	// Rotation
	if((movementFlags & kNavMoveRotLeft) != 0)
	{
		deltaRot -= 1.0f * dt;
	}
	if((movementFlags & kNavMoveRotRight) != 0)
	{
		deltaRot += 1.0f * dt;
	}

	// Update rotation
	curDirAngle += deltaRot; 
	
	// Update Focus Point
	Vector3D dirVector = Vector3D(deltaRight, deltaFwd, 0.0f);
	
	Matrix3D dirMatrix;
	dirMatrix.SetIdentity();
	dirMatrix.SetRotationAboutZ(curDirAngle);
	dirVector = dirVector * dirMatrix;
	
	curFocus += dirVector;
	
	// Update Camera position
	Point3D camPosition;

	camPosition.Set(0.0f, -curDistance, 0.0f);

	Matrix3D tiltMatrix;
	tiltMatrix.SetIdentity();
	tiltMatrix.SetRotationAboutX(curTiltAngle);

	Transform4D tiltTransform;
	tiltTransform.SetIdentity();
	tiltTransform.SetMatrix3D(tiltMatrix);
	camPosition = camPosition * tiltTransform;

	Matrix3D orbitMatrix;
	orbitMatrix.SetIdentity();
	orbitMatrix.SetRotationAboutZ(curDirAngle);

	Transform4D orbitTransform;
	orbitTransform.SetIdentity();
	orbitTransform.SetMatrix3D(orbitMatrix);
	camPosition = camPosition * orbitTransform;
	camPosition += curFocus;

	this->SetNodePosition(camPosition);
	this->LookAtPoint(curFocus);
}

The first part of this is simply grabbing the application’s elapsed time and using it to calculate the delta for the actual movement from the keyboard input. The second part is the math behind rotating and positioning the camera.

dirVector holds the forward/backward and left/right incremental movement, and then this is multiplied by the dirMatrix to account for the rotation. This final change is then added to curFocus.

After we have the focus point locked down, the camera is positioned around it.

First, camPosition is offset by the given curDistance on the Y axis.

Then, a tiltTransform is created from the tiltMatrix that contains the tilt rotation angle. This transform is then multiplied into the camPosition to tilt it up.

Lastly, an orbitTransform is created from the orbitMatrix that contains the orbit rotation angle, and now this transform is multiplied into the camPosition to rotate it around the focus point.

After the rotations are completed, simply add the curFocus as the camera offset.

Call SetNodePosition and LookAtPoint as before, but now we use camPosition as the node’s position and the curFocus as the look at point.

Keep in mind, there are several ways of actually doing this math, but it was done this way for the purposes of instruction.

You can finally compile this and run it. The WSAD keys are hooked up to the forward/backward/left/right movement, and you’ll have to add ‘rotl’ and ‘rotr’ to the input.cfg to get rotation to work. I have mine hooked up to the Q and E keys.

Clearing the Background

You may also notice that when you pan around the world, it looks something like this:

Image:clearcolor.jpg

That’s because we never set a clear color. To do this, go into the World Editor, switch one of the viewports to the Scene Graph, find the Infinite Zone, Get Info, switch to Properties and Assign the ClearColor property. Save the world and this issue will be fixed now that the background will get cleared before any rendering is done.

Great! Now we have a working view, and our first movement commands.

Implementing the Mouse Cursor and AutoScroll

This tutorial will implement the mouse cursor in a way that may not be the most ideal way to implement in C4. An OutlineElement is used for the cursor, instead of creating a full-blown interface and interface task handler. There are other, more correct ways to do the mouse cursor, but this will suit our purposes. The cursor is declared in CombatWorld, and since we’re using user interface elements, #include “C4Interface.h” needs to be added.

<CombatWorld.h>

private:
	OutlineElement		*mouseCursor;
	Point2D			mousePosition;

public:
	void ChangeCursorPosition(const float deltaX, const float deltaY);
	Point2D GetCursorPosition();

In the constructor, initialize the cursor element, its position, and add it to the Interface Manager:

<CombatWorld.cpp>

float halfDisplayWidth = 0.5f * TheDisplayMgr->GetDisplayWidth();
float halfDisplayHeight = 0.5f * TheDisplayMgr->GetDisplayHeight();

mousePosition = Point2D(halfDisplayWidth, halfDisplayHeight);

mouseCursor = new OutlineElement(10.0f, 10.0f);
mouseCursor->SetOutlineColor(C4::ColorRGB(1.0f, 1.0f, 1.0f));
mouseCursor->SetElementPosition(Point3D(mousePosition.x, mousePosition.y, 0.0f));
TheInterfaceMgr->AddElement(mouseCursor);

And cleanup code in the destructor:

<CombatWorld.cpp>

if(mouseCursor)
{
	delete mouseCursor;
}

Now, the two functions:

<CombatWorld.cpp>

void CombatWorld::ChangeCursorPosition(const float deltaX, const float deltaY)
{
	mousePosition.x += deltaX;
	mousePosition.y += deltaY;

	float displayWidth = TheDisplayMgr->GetDisplayWidth();
	float displayHeight = TheDisplayMgr->GetDisplayHeight();

	if(mousePosition.x < 0.0f)
		mousePosition.x = 0.0f;

	if(mousePosition.y < 0.0f)
		mousePosition.y = 0.0f;
	
	if(mousePosition.x >= displayWidth)
		mousePosition.x = displayWidth - 1;

	if(mousePosition.y >= displayHeight)
		mousePosition.y = displayHeight - 1;
	
	if(mouseCursor)
	{
		float posX = mousePosition.x - 0.5 * mouseCursor->GetElementWidth();
		float posY = mousePosition.y - 0.5 * mouseCursor->GetElementHeight();

		mouseCursor->SetElementPosition(Point3D(posX, posY, 0.0f));
		mouseCursor->Invalidate();
	}
}

Point2D CombatWorld::GetCursorPosition()
{
	return mousePosition;
}

ChangeCursorPosition() accepts a change in position, and also takes responsibility in clamping the mouse position to the extents of the application’s display dimensions.

Update the cursor position in RTSCamera’s Move function by adding this code before the keyboard input checks:

<RTSCamera.cpp>


float sensitivity = 300.0f;

float deltaX = -sensitivity * TheInputMgr->GetMouseDeltaX();
float deltaY = -sensitivity * TheInputMgr->GetMouseDeltaY();

Point2D cursorPos = Point2D(0.0f, 0.0f);

CombatWorld *world = static_cast<CombatWorld*>(TheWorldMgr->GetWorld());
if(world)
{
	world->ChangeCursorPosition(deltaX, deltaY);
	cursorPos = world->GetCursorPosition();
}

Sensitivity is just a constant so that the camera movement is noticeable. (This value can be incredibly high for mouse input, but for input devices such as digitizing tablets, the sensitivity level can be brought down significantly lower.) Although, I am still unclear as to why the delta is returned as the negative of what I would expect, it’s handled by flipping the sign. Running this code will result in a moving mouse cursor.

Notice that the cursorPos variable is set in the previous listing. Add the following code right after setting cursorPos:


<RTSCamera.cpp>

float autoScrollThreshold = 0.1f;

float displayWidth = TheDisplayMgr->GetDisplayWidth();
float displayHeight = TheDisplayMgr->GetDisplayHeight();

if(cursorPos.x < autoScrollThreshold * displayWidth)
	deltaRight -= 1.0f * dt;

if(cursorPos.x >= (1.0f - autoScrollThreshold) * displayWidth)
	deltaRight += 1.0f * dt;

if(cursorPos.y < autoScrollThreshold * displayHeight)
	deltaFwd += 1.0f * dt;

if(cursorPos.y >= (1.0f - autoScrollThreshold) * displayHeight)
	deltaFwd -= 1.0f * dt;

This is the autoscroll code. autoScrollThreshold is the percentage of the edge of the screen that qualifies as the region that is auto-scrollable. The variables deltaRight and deltaFwd are the same variables used earlier for the keyboard input code.

That should be it for the autoscroll. Pretty simple. Although it’s not the most ideal place to put the cursor update code, it works and gets the idea across.

Implementing Scroll Zoom

Add the CameraAction constant to the camera actions enumeration.

<NavAction.h>

kActionZoomWheel			= 'next',


Add an overide for the Update Method of the Action.

<NavAction.h>

class NavAction :
	public Action
{
	        //...
	public:
		//...
		void Update(float value);
};

Add the code for the update method.

It would be smart to take the 0.01f and set the value in some variable so the zoom sensitivity could be adjusted.

<NavAction.cpp>

void NavAction::Update(float value)
{
	ActionType type = GetActionType();
	if (type != kActionZoomWheel) 
		return;

	RTSWorld *world = static_cast<RTSWorld *>(TheWorldMgr->GetWorld());
	RTSCamera *camera = world->GetNavCamera();
	camera->SetCurrentDistance(camera->GetCurrentDistance() +  (0.01f * -value));
}


Now add kNavMoveZoomWheel to the camera movement mask.

<RTSCamera.h>

enum
{
	kNavMoveForward		= 1 << 0,
	kNavMoveBackward	= 1 << 1,
	kNavMoveLeft		= 1 << 2,
	kNavMoveRight		= 1 << 3,
	kNavMoveRotLeft		= 1 << 4,
	kNavMoveRotRight	= 1 << 5,
	kNavMoveZoomIn		= 1 << 6,
	kNavMoveZoomOut		= 1 << 7,
        kNavMoveZoomWheel	= 1 << 9,
	kNavMoveMask		=	kNavMoveForward | 
                                        kNavMoveBackward |
					kNavMoveLeft | 
                                        kNavMoveRight |
					kNavMoveRotLeft | 
                                        kNavMoveRotRight |
					kNavMoveZoomIn | 
                                        kNavMoveZoomOut |
                                        kNaveMoveZoomWheel

};

Now add zoomWheelAction to the RTS game class.

<RTS.h>

private:
		//...
		NavAction	*zoomWheelAction;

Now initialize the action and register it with the interface.

<RTS.cpp>

        zoomWheelAction = new NavAction(kActionZoomWheel, kNavMoveZoomWheel);

	TheInputMgr->AddAction(zoomWheelAction);        

Now add the clean up for the action in the destructor.

<RTS.cpp>

     if (zoomWheelAction)
          delete zoomWheelAction;

Implementing Zoom With the Keyboard and Mouse

We'll add two types of Zoom to our RTS demo. The first Zoom will use two keys which can be Page up/Down, or brackets. This example uses Z and C, since they are in our WASD schema. The second zoom will use the Ctrl key as a Mouse Shift, and the Mouse-Y will zoom in and out while we hold down the key.


First, we need to add some new binds to our engine.cfg to handle the extra keys.

<input.cfg>

$device = "Mouse";
bind "Wheel" %next;
bind "Button 0" %fire;
bind "Button 1" %trig;

$device = "Keyboard";
bind "W" %frwd;
bind "A" %left;
bind "S" %bkwd;
bind "D" %rght;
bind "Space" %jump;
bind "Q" %rotl;
bind "E" %rotr;
bind "C" %zout;
bind "Z" %zmin;
bind "Ctrl" %zoom;

undef $device;

We'll also need to add our Action enums to NavAction.h:

<navAction.h>

	enum
	{
		kActionForward		= 'frwd',
		kActionBackward		= 'bkwd',
		kActionRotLeft		= 'rotl',
		kActionRotRight		= 'rotr',
		kActionLeft		= 'left',
		kActionRight		= 'rght',
		kActionZoom		= 'zoom',
		kActionZoomIn		= 'zmin',
		kActionZoomOut		= 'zout',
		kActionZoomWheel	= 'next'
	};

The current enum in RTSCamera has two zoom functions already, but we will need a third for our shift key:

<RTSCamera.h>

enum
	{
		kNavMoveForward				= 1 << 0,
		kNavMoveBackward			= 1 << 1,
		kNavMoveLeft				= 1 << 2,
		kNavMoveRight				= 1 << 3,
		kNavMoveRotLeft				= 1 << 4,
		kNavMoveRotRight			= 1 << 5,
		kNavMoveZoomIn				= 1 << 6,
		kNavMoveZoomOut				= 1 << 7,
		kNavMoveZoom				= 1 << 8,
		kNavMoveZoomWheel			= 1 << 9,
		kNavMoveMask			= kNavMoveForward | 
						  kNavMoveBackward |
						  kNavMoveLeft | 
						  kNavMoveRight |
						  kNavMoveRotLeft | 
						  kNavMoveRotRight |
						  kNavMoveZoomIn | 
						  kNavMoveZoomOut |
						  kNavMoveZoom |
						  kNavMoveZoomWheel
	};

Now that we have the extra enums there we need to hook them together. First, add the actions to RTS in the private section:

<rts.h>

    private:
		NavAction	*moveForwardAction;
		NavAction	*moveBackwardAction;
		NavAction	*moveLeftAction;
		NavAction	*moveRightAction;
		NavAction	*rotLeftAction;
		NavAction	*rotRightAction;
		NavAction	*zoomInAction;
		NavAction	*zoomOutAction;
		NavAction	*zoomAction;
		NavAction	*zoomWheelAction;

And update the constructor and destructor as well so they now look like this:

<rts.cpp>

RTS::RTS(void) : Singleton<RTS>(TheGame)    
{ 
	TheWorldMgr->SetWorldConstructor(&ConstructWorld);
	moveLeftAction = new NavAction(kActionLeft, kNavMoveLeft);
	moveRightAction = new NavAction(kActionRight, kNavMoveRight);
	moveForwardAction = new NavAction(kActionForward, kNavMoveForward);
	moveBackwardAction = new NavAction(kActionBackward, kNavMoveBackward);
	rotLeftAction = new NavAction(kActionRotLeft, kNavMoveRotLeft);
	rotRightAction = new NavAction(kActionRotRight, kNavMoveRotRight);
	zoomInAction = new NavAction(kActionZoomIn, kNavMoveZoomIn);
	zoomOutAction = new NavAction(kActionZoomOut, kNavMoveZoomOut);
	zoomAction = new NavAction(kActionZoom, kNavMoveZoom);
	zoomWheelAction = new NavAction(kActionZoomWheel, kNavMoveZoomWheel);
	
	TheInputMgr->AddAction(moveLeftAction);
	TheInputMgr->AddAction(moveRightAction);
	TheInputMgr->AddAction(moveForwardAction);
	TheInputMgr->AddAction(moveBackwardAction);
	TheInputMgr->AddAction(rotLeftAction);
	TheInputMgr->AddAction(rotRightAction);
	TheInputMgr->AddAction(zoomInAction);
	TheInputMgr->AddAction(zoomOutAction);
	TheInputMgr->AddAction(zoomAction);
	TheInputMgr->AddAction(zoomWheelAction);

	TheInterfaceMgr->SetInputManagementMode(kInputManagementAutomatic);
}

RTS::~RTS(void) 
{
	TheWorldMgr->UnloadWorld();
	TheWorldMgr->SetWorldConstructor(nullptr);

	if(moveLeftAction)
		delete moveLeftAction;

	if(moveRightAction)
		delete moveRightAction;

	if(moveForwardAction)
		delete moveForwardAction;

	if(moveBackwardAction)
		delete moveBackwardAction;
	
	if(rotLeftAction)
		delete rotLeftAction;
	
	if(rotRightAction)
		delete rotRightAction;

	if(zoomInAction)
		delete zoomInAction;

	if(zoomOutAction)
		delete zoomOutAction;

	if(zoomAction)
		delete zoomAction;

        if(zoomWheelAction)
		delete zoomWheelAction;
}

We're almost there! We'll add a min and max distance float to our camera object. (You may just wish to make these constants) RTSCamera now looks like this:

<RTSCamera.h>

		private:
			float curDirAngle;
			Point3D curFocus;
			float curDistance;
			float curTiltAngle;
			long movementFlags;
			float minDistance;
			float maxDistance;

<RTSCamera.cpp>

RTSCamera::RTSCamera() : 
	FrustumCamera(2.0F, 1.0F), 
	movementFlags(0),
	curDirAngle(0.25f * K::pi),
	curFocus(0.0f, 0.0f, 0.0f),
	curDistance(10.0f),
	curTiltAngle(0.25f * K::pi),
	minDistance(1.0f),
	maxDistance(20.0f)
{
}

The meat of the Zoom functions is in RTSCamera::move. First, if we're holding down the control key to zoom, we don't want the cursor flying all over during that process, so the mouse cursor code is now encapsulated in an if, to check for that. To zoom in and out, all we need to do is change the curDistance before it is sent to the camera position. We also need to check and make sure that the zoom is within our boundaries. The finished RTSCamera::Move is below:

<RTSCamera.cpp>

void RTSCamera::Move(void)
{
	float dt = TheTimeMgr->GetFloatDeltaTime() * 0.001f;

	float deltaFwd = 0.0f;
	float deltaRight = 0.0f;
	float deltaRot = 0.0f;

	//Either Zoom if the Zoom button is down, else move the cursor
	if((movementFlags & kNavMoveZoom) != 0)
	{
		curDistance -= 1.0f * TheInputMgr->GetMouseDeltaY();
	}
	else
	{
		float sensitivity = 300.0f;
		
		float deltaX = -sensitivity * TheInputMgr->GetMouseDeltaX();
		float deltaY = -sensitivity * TheInputMgr->GetMouseDeltaY();
	
		Point2D cursorPos = Point2D(0.0f, 0.0f);
	
		CombatWorld *world = static_cast<CombatWorld*>(TheWorldMgr->GetWorld());
		if(world)
		{
			world->ChangeCursorPosition(deltaX, deltaY);
			cursorPos = world->GetCursorPosition();
		}
	
		//Auto Scroll with Mouse
		float autoScrollThreshold = 0.1f;
	
		float displayWidth = TheDisplayMgr->GetDisplayWidth();
		float displayHeight = TheDisplayMgr->GetDisplayHeight();
	
		if(cursorPos.x < autoScrollThreshold * displayWidth)
			deltaRight -= 1.0f * dt;

		if(cursorPos.x >= (1.0f - autoScrollThreshold) * displayWidth)
			deltaRight += 1.0f * dt;
	
		if(cursorPos.y < autoScrollThreshold * displayHeight)
			deltaFwd += 1.0f * dt;
	
		if(cursorPos.y >= (1.0f - autoScrollThreshold) * displayHeight)
			deltaFwd -= 1.0f * dt;
	}

	// Position
	if((movementFlags & kNavMoveLeft) != 0)
	{
		deltaRight -= 1.0f * dt;
	}
	if((movementFlags & kNavMoveRight) != 0)
	{
		deltaRight += 1.0f * dt;
	}
	if((movementFlags & kNavMoveForward) != 0)
	{
		deltaFwd += 1.0f * dt;
	}
	if((movementFlags & kNavMoveBackward) != 0)
	{
		deltaFwd -= 1.0f * dt;
	}

	// Rotation
	if((movementFlags & kNavMoveRotLeft) != 0)
	{
		deltaRot -= 1.0f * dt;
	}
	if((movementFlags & kNavMoveRotRight) != 0)
	{
		deltaRot += 1.0f * dt;
	}

	// Update rotation
	curDirAngle += deltaRot; 
	
	// Update Focus Point
	Vector3D dirVector = Vector3D(deltaRight, deltaFwd, 0.0f);
	
	Matrix3D dirMatrix;
	dirMatrix.SetIdentity();
	dirMatrix.SetRotationAboutZ(curDirAngle);
	dirVector = dirVector * dirMatrix;
	
	curFocus += dirVector;
	
	//Check for zoom
	if((movementFlags & kNavMoveZoomIn) != 0)
		curDistance -= 1.0f * dt;

	if((movementFlags & kNavMoveZoomOut) != 0)
		curDistance += 1.0f * dt;

	// Fix Zoom Boundaries
	if (curDistance < minDistance)
		curDistance = minDistance;

	if (curDistance > maxDistance)
		curDistance = maxDistance;


	// Update Camera position
	Point3D camPosition;

	camPosition.Set(0.0f, -curDistance, 0.0f);

	Matrix3D tiltMatrix;
	tiltMatrix.SetIdentity();
	tiltMatrix.SetRotationAboutX(curTiltAngle);

	Transform4D tiltTransform;
	tiltTransform.SetIdentity();
	tiltTransform.SetMatrix3D(tiltMatrix);
	camPosition = camPosition * tiltTransform;

	Matrix3D orbitMatrix;
	orbitMatrix.SetIdentity();
	orbitMatrix.SetRotationAboutZ(curDirAngle);

	Transform4D orbitTransform;
	orbitTransform.SetIdentity();
	orbitTransform.SetMatrix3D(orbitMatrix);
	camPosition = camPosition * orbitTransform;
	camPosition += curFocus;

	this->SetNodePosition(camPosition);
	this->LookAtPoint(curFocus);
}

That is it... or is it? While we're shifting with the Ctrl key, the Mouse-X isn't doing anything. We can put it to good use by using it as a rotate while we're in zoom mode with one line of code. Change the Zoom section like this:

<RTSCamera::Move>

	if((movementFlags & kNavMoveZoom) != 0)
	{
		curDistance -= 1.0f * TheInputMgr->GetMouseDeltaY();
		deltaRot -= 0.5f * TheInputMgr->GetMouseDeltaX();
	}

Next Step

Now that we've got a camera in the world, we can move on to putting in an entity in the world in Creating a Unit Entity.

Personal tools