Dear Community,
Hi there!
This is a rather large tutorial where I've gathered several core concepts as well as a lot of specific code for:
1. 3rd person cameras
2. Mouse cursor and clicking on 3D world objects with mouse
3. How to connect your custom classes to each other to make your own game mechanics
~~~
Video Example
Here's a video of my unrealscripted game engine, where I demonstrate my implementation of multi-object selection (drag selection) to build a massive destroyable structure.
In this tutorial I am giving you all the core code that you need to:
1. implement a 3rd person camera that follows your custom pawn
2. implement a toggle-able free-roaming camera.
This custom camera rotates with the mouse, just like the regular game camera, and is moved with WSAD keys, but it moves freely around the game world.
3. implement a mouse cursor, free-roaming or centered with camera
4. click on Actors/Objects in the 3D game world with mouse cursor
5. drag selection of Actors/Objects
6. draw colored thick 3D boxes around selected actors
~~~
How It All Comes Together
There are other mouse cursor tutorials out there, but the main focus of this tutorial is showing you how all the classes and their code come together to create the actual game mechanic you want.
Enabling Custom Cross-class Communication
One of the essential concepts I utlized to make my game engine was the idea of direct cross-class communication of my custom classes.
Enabling your various classes to directly communicate with each other revolves around PostBeginPlay and your customGameInfo class.
I cover this process in-depth in this tutorial because it is fundamental to really creating your own kind of game.
~~~
The Player Camera
The PlayerController class is the main game camera you are used to.
rotates main player camera
moves camera
~~~
Step 1: How to Capture Left Mouse Clicking and Key Presses
Here's the code for getting the info about left mouse clicks so you can call custom functions when the player presses left mouse.
Go to your udk install directory and go to UDKGame/config/DefaultInput.ini
Put this in the section of ini that is commented with “BINDINGS USED TO ORGANIZE ALL GAME BINDABLE ACTIONS IN ONE PLACE FOR SYSTEMS SUCH AS UI
; GBA - GAME BINDABLE ACTION”
Put the below code in the section of ini called “Game Keyboard/Mouse Bindings” toward the bottom
Use similar code for W,A,S,D keys and the F10 key to toggle custom camera
This goes in the ame section as GBA_LeftMouseButton
your function names in your PlayerController Class must match the names listed above, or change the names listed above
This code goes in same place as the code for Name=”LeftMousebutton”
~~~
Player Controller Class
So now we have the ability to act on the user key presses for W,A,S,D, and the left mouse, in addition to how their default behaviors for the player camera.
If you are not using the custom camera in this tutorial you don't need WASD, just left mouse.
Note the “exec” in front of the functions above, this means that the function will be looked up by your DefaultInput.ini file and if a matching function name is found in the .ini it gets executed / run.
~~~
Step 2: How to Capture Mouse Movements
I've read sooo many tutorials how how to capture mouse movement.
Here's the utterly simple method that I personally use and figured out on my own:
In your PlayerController Class:
Note that this player controller class needs access to the instance of your HUD class, so make sure to add at the top of the playercontroller class:
and you will need to ensure that this variable gets referenced properly via the GameInfo class
Why are we settting HUD variables from within PlayerTick?
PlayerTick is a function that incessantly runs as the game is running, and it is the main interface between the player and the main game camera and controls and the player pawn.
So PlayerTick is the ideal place to capture important player input and pass it to the rest of your classes.
This is the simple setup that I use, anyway.
However my method does require setting up the relationship between the HUD class and the PlayerController class so that they can communicate with each other during game time.
~~~
How to Connect the HUD and the PlayerController Class
Both your custom HUD and your PlayerController class have access to a variable called WorldInfo, which is a GameInfo variable.
Your custom GameInfo class can store variables to your custom classes.
Then you can have each class tell the GameInfo class about itself, when it is first created during game time, using postbeginplay().

This is important because your classes need to know about the INSTANCES of each other, not the pre-game-time class code.
So when your HUD actually gets instanced/created during game time, it can tell your GameInfo about itself, and then the PlayerController can access that custom variable info from your custom GameInfo class.
This is how you can enable the actual in-game instances of your classes to talk to each other during game-time.
So now here's the small but critically important custom GameInfo code you need to do this:
~~~
Custom GameInfo Class
~~~
Custom HUD Class
When your custom HUD first comes into existance during game time / is instanced, it needs to also call PostBeginPlay() so that your PlayerController class can get connected to your HUD intance via the GameInfo class instance (WorldInfo)
~~~
Why is PlayerController PostBegin Play Setting a Timer?
In PlayerController class
I found that the HUD PostBeginPlay was running after / slower than the PlayerController postbeginplay, so the Playercontroller was asking about the HUD instance from WorldInfo before it was created. So I used a timer to give the HUD time to come into existence and set its WorldInfo var properly.
If you have any trouble getting your HUD and playercontroller connected you could put the HUD worldinfo request:
into a timer as well.
~~~
DrawHUD Function
This is the main function inside your HUD class and it gets called every moment of game time.
The Canvas
The canvas is the space into which you can draw text and pictures and lines, but the Canvas variable is only valid inside of DrawHUD, as DrawHUD is constantly destroying and re-making the Canvas every moment of game time.
So outside of DrawHUD the status of Canvas is indeterminate.
This means we cannot call Canvas functions from other classes, so we should get data from other classes while we are in the DrawHUD function.
This is why the HUD class needs to have access to the PlayerController instance, so that DrawHUD can gather data easily from other classes.
Note that the cursorTile is set to a Texture2D resource that you should already have, as part of the UDK resources.
Note that we are SETTING a variable that is located in the playercontroller class, from within the HUD, this complete communication between playercontroller and HUD, they are getting and setting each others variables.
Again I do this to enable DrawHUD to have all the info it needs, and to enable the cursor drawn in DrawHUD to tell the playercontroller what the player is doing with the cursor.
in drawHUD()
Also note that DrawHud is calling/running functions that are within the controller class from itself, as those functions perform actions that are important for the drawing of the selection boxes.
Drag Selection
Notice that drag-selection is automatically implemented here, because DrawHUD is called every moment of game time, and as long as the player is holding down the left mouse button, the bool in the Controller class will be true, and so the DrawHUD will be continously calling the addSelectionActor function in the controller class, making the dynamic array in controller class fill up with Actors that the cursor travels over.
Also note that in the playercontroller class, the addSelectionActor will NOT add duplicates. This is VERY important, as otherwise the selectedActorArray would become huuuuuuge as the DrawHUD incessantly calls addSelectedActor every moment of time.
~~~
Draw Colored Thick 3D Boxes
Add this code to your HUD class to draw thick 3D boxes, DrawHUD calls this already. I wrote all this from scratch
~~~
What We Have So Far
So now we have a custom HUD class that is drawing a cursor to the screen, and when player is hodling down LMB, whether for a moment or for drag select, the HUD calls playercontroller to add any actors it finds to the selectedActors array.
Then the drawBoxesAroundSelectedActors function draws boxes around any actors that are in the selectedActors array.
Note that the lines do not persist, since the canvas is destroyed each moment of time, so that is why drawBoxesAroundSelectedActors must be constantly called inside DrawHUD.
~~~
The Custom Camera Classes
Both of the camera classes are largely based on pioneering work by Mougli, via the tutorials on his site. Thank you Mougli ♥
~~~
Camera Class
In the PlayerController Class note there is a CameraClass specified
Here is that class:
~~~
CustomCameraBase Class
~~~
Your Custom Pawn Class
Add this code to your player pawn class:
The camera classes rely on you setting this custom pawn reference in your WorldInfo/GameInfo.
So in other words, you really must do this for the code I'm providing you with to work, otherwise when you go in game your camera will be confused.
~~~
Conclusion
Using what I've shown you here you can now implement any or all of the following
1. a 3rd person camera
2. a custom free-roaming camera
3. free roaming or centered selection cursor
4. multiselection of actors
5. drawing of 3D boxes around actors
You don't have to utilize all of these features, but now you see how you can!
~~~
Cross Class Communication Review
Reviewing this code you see how you can create all sorts of complex inter-class interactions to really make your game ideas come to life!
♥
Rama
Hi there!
This is a rather large tutorial where I've gathered several core concepts as well as a lot of specific code for:
1. 3rd person cameras
2. Mouse cursor and clicking on 3D world objects with mouse
3. How to connect your custom classes to each other to make your own game mechanics
~~~
Video Example
Here's a video of my unrealscripted game engine, where I demonstrate my implementation of multi-object selection (drag selection) to build a massive destroyable structure.
In this tutorial I am giving you all the core code that you need to:
1. implement a 3rd person camera that follows your custom pawn
2. implement a toggle-able free-roaming camera.
This custom camera rotates with the mouse, just like the regular game camera, and is moved with WSAD keys, but it moves freely around the game world.
3. implement a mouse cursor, free-roaming or centered with camera
4. click on Actors/Objects in the 3D game world with mouse cursor
5. drag selection of Actors/Objects
6. draw colored thick 3D boxes around selected actors
~~~
How It All Comes Together
There are other mouse cursor tutorials out there, but the main focus of this tutorial is showing you how all the classes and their code come together to create the actual game mechanic you want.
Enabling Custom Cross-class Communication
One of the essential concepts I utlized to make my game engine was the idea of direct cross-class communication of my custom classes.
Enabling your various classes to directly communicate with each other revolves around PostBeginPlay and your customGameInfo class.
I cover this process in-depth in this tutorial because it is fundamental to really creating your own kind of game.
~~~
The Player Camera
The PlayerController class is the main game camera you are used to.
Code:
PlayerController.SetRotation
Code:
PlayerController.SetLocation
~~~
Step 1: How to Capture Left Mouse Clicking and Key Presses
Here's the code for getting the info about left mouse clicks so you can call custom functions when the player presses left mouse.
Go to your udk install directory and go to UDKGame/config/DefaultInput.ini
Put this in the section of ini that is commented with “BINDINGS USED TO ORGANIZE ALL GAME BINDABLE ACTIONS IN ONE PLACE FOR SYSTEMS SUCH AS UI
; GBA - GAME BINDABLE ACTION”
Code:
.Bindings=(Name="GBA_LeftMouseButton",Command="LMBDown | OnRelease LMBUp")
Code:
.Bindings=(Name="LeftMouseButton",Command="GBA_LeftMouseButton")
This goes in the ame section as GBA_LeftMouseButton
Code:
.Bindings=(Name="GBA_WKey",Command="KeyDownW | OnRelease keyupW") .Bindings=(Name="GBA_SKey",Command="KeyDownS | OnRelease keyupS") .Bindings=(Name="GBA_DKey",Command="keyDownD | OnRelease keyupD") .Bindings=(Name="GBA_AKey",Command="keyDownA | OnRelease KeyupA") .Bindings=(Name="GBA_F10Key",Command="keydownF10”)
Code:
Command=”yourkeydownFunc | OnRelease keyupYourFuncName”)
Code:
.Bindings=(Name="W",Command="GBA_WKey") .Bindings=(Name="A",Command="GBA_AKey") .Bindings=(Name="S",Command="GBA_SKey") .Bindings=(Name="D",Command="GBA_DKey") .Bindings=(Name="F10",Command="GBA_F10Key")
Player Controller Class
Code:
var CustomHUDClass customHUD; var CustomCameraBase theCustomCameraBase; var array<actor> selectedActorsArray; var actor selectionActor; var bool multiSelectActive; var bool keyisDownLMB; var bool keyisDownA; var bool keyisDownD; var bool keyisDownS; var bool keyisDownW; exec Function LMBDown() { keydownLMB = true; } exec Function LMBUp() { clearSelectedActors() keydownLMB = false; } exec Function KeyDownW() { KeyisDownW = true; } exec Function keyupW() { KeyisDownW= false; } exec Function keyDownA() { keyisDownA= true; } exec Function KeyUpA() { keyisDownA= false; } exec Function KeyDownS() { KeyisDownS = true; } exec Function keyupS() { KeyisDownS= false; } exec Function keyDownD() { keyisDownD = true; } exec Function keyupD() { keyisDownD= false; } //toggle custom camera //each time f10 is pressed exec Function keydownf10() { if(theCustomCameraBase.customCameraActive) useCustomCamera(false); else useCustomCamera(true); } function SetWorldVars(){ //retrieve worldinfo vars customHUD = CustomGameInfo(WorldInfo.Game).hudWORLD; theCustomCameraBase = CustomGameInfo(WorldInfo.Game).customCameraBaseWORLD; } Simulated Event PostBeginPlay() { //very important line super.postbeginplay(); //set Self's worldinfo var CustomGameInfo(WorldInfo.Game).playerControllerWORLD = Self; SetTimer(0.1, false, 'SetWorldVars'); } function bool isSelectedInArray(Actor a) { if (selectedActorsArray.find(a) != -1) return true; return false; } function addSelectedActor() { //most recently clicked is in array already if (selectedActorsArray.find(SelectionActor) != -1 ) return; //add item selectedActorsArray.addItem(selectionActor); //only 1 selected? if (selectedActorsArray.length <= 1) { multiSelectActive = false; } else { multiSelectActive = true; } } function removeSelectedActor() { //remove item selectedActorsArray.removeItem(selectionActor); //only 1 selected? if (selectedActorsArray.length <= 1) { multiSelectActive = false; } else { multiSelectActive = true; } } function clearSelectedActors() { multiSelectActive = false; selectedActorsArray.length = 0; } function useCustomCamera(bool b){ theCustomCameraBase.setCustomCameraActive(b); } Simulated Event PlayerTick(float deltaTime) { local Rotator r; local vector v; //Mouse local float mx; local float my; //capture change in mouse movements mx = PlayerInput.aMouseX; my = PlayerInput.aMouseY; //Order Matters, must be after mx/y settings //this little line is very important //this calls the tick for the superclass your //controller is extending from super.playertick(deltatime); //pass the info to the HUD for drawing cursor //the > 0 check prevents mouse from going off screen //in upper left corner. //for lower right corner you can use //customHUD.sizeX and customHUD.sizeY if(customHUD.MousePosition.X + mx * mouseSpeed > 0) customHUD.MousePosition.X += mx * mouseSpeed; if(customHUD.MousePosition.Y + -my * mouseSpeed > 0) customHUD.MousePosition.Y += -my * mouseSpeed; //~~~~~~ //Custom Camera Related //~~~~~ if (theCustomCameraBase.customCameraActive) { // W S D A CONTROLS if (keyisdownW) { r = theCustomCameraBase.customCameraRot; v = Vector(r); //move camera forward in direction //it is facing, by the amount of cameraSpeed theCustomCameraBase.editorPos += v * cameraSpeed; } if (keyisdownS) { r = theCustomCameraBase.customCameraRot; v = Vector(r); //move camera backward from direction //it is facing, by the amount of cameraSpeed theCustomCameraBase.editorPos -= v * cameraSpeed; } if (keyisdownA) { r = theCustomCameraBase.customCameraRot; r.yaw -= 90 * DegToUnrRot; v = Vector(r); v.x *= cameraSpeed; v.y *= cameraSpeed; v.z = 0; theCustomCameraBase.customCameraPos += v ; } if (keyisdownD) { r = theCustomCameraBase.customCameraRot; r.yaw += 90 * DegToUnrRot; v = Vector(r); v.x *= cameraSpeed; v.y *= cameraSpeed; v.z = 0; theCustomCameraBase.customCameraPos += v ; } //Notice the use of mx/my below //this makes custom camera rotation change //with player mouse movements theCustomCameraBase.customCameraRot.yaw += mx/cameraRotSpeed * DegToUnrRot; if (theCustomCameraBase.customCameraRot.yaw >= 3 60 * DegToUnrRot) theCustomCameraBase.customCameraRot.yaw = 0; if (theCustomCameraBase.customCameraRot.yaw <= -360 * DegToUnrRot) theCustomCameraBase.customCameraRot.yaw = 0; //limit custom camera movement theCustomCameraBase.customCameraRot.pitch += my/cameraRotSpeed * DegToUnrRot; if (theCustomCameraBase.customCameraRot.pitch >= 82 * DegToUnrRot) theCustomCameraBase.customCameraRot.pitch = 82 * DegToUnrRot; if (theCustomCameraBase.customCameraRot.pitch <= -82 * DegToUnrRot) theCustomCameraBase.customCameraRot.pitch = -82 * DegToUnrRot; } // ~~~ End of Custom Camera Related ~~~ //you'll want to control when //the cursor gets centered //or when it can roam free //for now it is always centered //as the camera moves joyHUD.centerCursor(); } DefaultProperties { CameraClass = class'CustomCamera' cameraSpeed = 36 mouseSpeed = 2.2 }
If you are not using the custom camera in this tutorial you don't need WASD, just left mouse.
Note the “exec” in front of the functions above, this means that the function will be looked up by your DefaultInput.ini file and if a matching function name is found in the .ini it gets executed / run.
~~~
Step 2: How to Capture Mouse Movements
I've read sooo many tutorials how how to capture mouse movement.
Here's the utterly simple method that I personally use and figured out on my own:
In your PlayerController Class:
Code:
Simulated Event PlayerTick(float deltaTime) { //Mouse local float mx; local float my; //capture change in mouse movements mx = PlayerInput.aMouseX; my = PlayerInput.aMouseY; //Order Matters, must be after mx/y settings //this little line is very important //this calls the tick for the superclass your //controller is extending from super.playertick(deltatime); //pass the info to the HUD for drawing cursor //the > 0 check prevents mouse from going off screen //in upper left corner. //for lower right corner you can use //customHUD.sizeX and customHUD.sizeY if(customHUD.MousePosition.X + mx * mouseSpeed > 0) customHUD.MousePosition.X += mx * mouseSpeed; if(customHUD.MousePosition.Y + -my * mouseSpeed > 0) customHUD.MousePosition.Y += -my * mouseSpeed; }
Code:
var YourCustomHUDClass customHUD;
Why are we settting HUD variables from within PlayerTick?
PlayerTick is a function that incessantly runs as the game is running, and it is the main interface between the player and the main game camera and controls and the player pawn.
So PlayerTick is the ideal place to capture important player input and pass it to the rest of your classes.
This is the simple setup that I use, anyway.
However my method does require setting up the relationship between the HUD class and the PlayerController class so that they can communicate with each other during game time.
~~~
How to Connect the HUD and the PlayerController Class
Both your custom HUD and your PlayerController class have access to a variable called WorldInfo, which is a GameInfo variable.
Your custom GameInfo class can store variables to your custom classes.
Then you can have each class tell the GameInfo class about itself, when it is first created during game time, using postbeginplay().

This is important because your classes need to know about the INSTANCES of each other, not the pre-game-time class code.
So when your HUD actually gets instanced/created during game time, it can tell your GameInfo about itself, and then the PlayerController can access that custom variable info from your custom GameInfo class.
This is how you can enable the actual in-game instances of your classes to talk to each other during game-time.
So now here's the small but critically important custom GameInfo code you need to do this:
~~~
Custom GameInfo Class
Code:
class CustomGameInfo extends GameInfo; var CustomHUD hudWORLD; var CustomPlayerController playerControllerWORLD; var CustomCameraBase customCameraBaseWORLD; var YourPawnClass playerpawnWORLD; defaultproperties { PlayerControllerClass = class'CustomPlayerController' DefaultPawnClass = class'YourPawnClass' HUDType = class'CustomHUD' bDelayedStart=false } //for packaging/frontend static event class<GameInfo> SetGameType(string MapName, string Options, string Portal) { return class'CustomGameInfo'; } //After game start event PostLogin( PlayerController NewPlayer ) { super.PostLogin(NewPlayer); `log("Yay! : === Your Custom GameInfo Class is Working ==="); }
Custom HUD Class
When your custom HUD first comes into existance during game time / is instanced, it needs to also call PostBeginPlay() so that your PlayerController class can get connected to your HUD intance via the GameInfo class instance (WorldInfo)
Code:
//controller class instance var var customPlayerControllerClass customPlayerController; var Actor drawBorderActor; var box boundingBox; //Cursor Texture2D var Texture2D cursorTile; //Vector2D var Vector2D MousePosition; Simulated Event PostBeginPlay() { super.postbeginplay(); //set Self's worldinfo var CustomGameInfo(WorldInfo.Game).hudWORLD = Self; //retrieve worldinfo var customPlayerController = CustomGameInfo(WorldInfo.Game).playerControllerWORLD; } function centerCursor() { MousePosition.X = CenterX; MousePosition.Y = CenterY; } //Draw 3D Boxes and Lines Code //copy paste here from below //DrawHud function, copy paste here from below //function drawHUD(){} DefaultProperties { cursorTile = Texture2D'UI_HUD.HUD.UTCrossHairs' }
Why is PlayerController PostBegin Play Setting a Timer?
In PlayerController class
Code:
function SetWorldVars(){ //retrieve worldinfo var customHUD = CustomGameInfo(WorldInfo.Game).hudWORLD; } Simulated Event PostBeginPlay() { //very important line super.postbeginplay(); //set Self's worldinfo var CustomGameInfo(WorldInfo.Game).playerControllerWORLD = Self; SetTimer(0.1, false, 'SetWorldVars'); }
If you have any trouble getting your HUD and playercontroller connected you could put the HUD worldinfo request:
Code:
//retrieve worldinfo var customPlayerController = VictoryBallGameInfo(WorldInfo.Game).playerControllerWORLD;
~~~
DrawHUD Function
This is the main function inside your HUD class and it gets called every moment of game time.
The Canvas
The canvas is the space into which you can draw text and pictures and lines, but the Canvas variable is only valid inside of DrawHUD, as DrawHUD is constantly destroying and re-making the Canvas every moment of game time.
So outside of DrawHUD the status of Canvas is indeterminate.
This means we cannot call Canvas functions from other classes, so we should get data from other classes while we are in the DrawHUD function.
This is why the HUD class needs to have access to the PlayerController instance, so that DrawHUD can gather data easily from other classes.
Code:
function DrawHUD() { local vector HitLocation, HitNormal; local Vector cursor3DOrigin; local Vector cursor3DDirection; // ==== Draw Cursor === Canvas.SetPos(MousePosition.X , MousePosition.Y ); //pink tint Canvas.DrawColor = makeColor(255,0,255,255); Canvas.DrawTile(cursorTile, 26 , 26, 380, 320, 26, 26); //If Left Mouse is Down, determine Actors beneath the cursor if(customplayercontroller.keydownLMB){ //the cursor3D var are out vars, they are receiving data from //the DeProject function. Canvas.DeProject(MousePosition, cursor3DOrigin, cursor3DDirection); //in the Trace(), the Hit vars are receiving the data, not providing any. customPlayerController.selectionActor = Self.Trace(HitLocation, HitNormal, cursor3DOrigin + (cursor3DDirection * 100000), cursor3DOrigin, true ); if(customPlayerController.selectionActor != none) { customPlayerController.addSelectedActor(); } //end if LMB down drawBoxesAroundSelectedActors(); } //end DrawHUD
Note that the cursorTile is set to a Texture2D resource that you should already have, as part of the UDK resources.
Code:
DefaultProperties { cursorTile = Texture2D'UI_HUD.HUD.UTCrossHairs' }
Note that we are SETTING a variable that is located in the playercontroller class, from within the HUD, this complete communication between playercontroller and HUD, they are getting and setting each others variables.
Again I do this to enable DrawHUD to have all the info it needs, and to enable the cursor drawn in DrawHUD to tell the playercontroller what the player is doing with the cursor.
in drawHUD()
Code:
customPlayerController.selectionActor = Self.Trace(HitLocation, HitNormal, cursor3DOrigin + (cursor3DDirection * 100000), cursor3DOrigin, true );
Code:
customPlayerController.addSelectedActor();
Notice that drag-selection is automatically implemented here, because DrawHUD is called every moment of game time, and as long as the player is holding down the left mouse button, the bool in the Controller class will be true, and so the DrawHUD will be continously calling the addSelectionActor function in the controller class, making the dynamic array in controller class fill up with Actors that the cursor travels over.
Code:
if(customplayercontroller.keydownLMB){
Code:
function addSelectedActor() { //most recently clicked is in array already if (selectedActorsArray.find(SelectionActor) != -1 ) return;
Draw Colored Thick 3D Boxes
Add this code to your HUD class to draw thick 3D boxes, DrawHUD calls this already. I wrote all this from scratch

Code:
function drawThicker(vector v1, vector v2, color c, int PosFromCenter) { local vector start; local vector end; //so we can modify start = v1; end = v2; start.x += PosFromCenter; end.x += PosFromCenter; draw3DLine(start, end, c); start.y += PosFromCenter; end.y += PosFromCenter; draw3DLine(start, end, c); start.z += PosFromCenter; end.z += PosFromCenter; draw3DLine(start, end, c); start.x -= PosFromCenter*2; end.x -= PosFromCenter*2; draw3DLine(start, end, c); start.y -= PosFromCenter*2; end.y -= PosFromCenter*2; draw3DLine(start, end, c); start.z -= PosFromCenter*2; end.z -= PosFromCenter*2; draw3DLine(start, end, c); } function drawThickLine(vector v1, vector v2, color c, int thickness) { local int thickIncrement; //center of line draw3DLine(v1, v2, c); //draw surrounding layers of thickness from center for (thickIncrement = 1; thickIncrement <= thickness; thickIncrement++ ) { drawThicker(v1, v2, c, thickIncrement); } } function drawBoundingBox(color c, int thickness) { local vector v1, v2; //boundingBox is global //min corner v1 = boundingbox.min; v2 = boundingbox.min; v2.x = boundingbox.max.x; drawThickLine(v1, v2, c, thickness); v1 = boundingbox.min; v2 = boundingbox.min; v2.y = boundingbox.max.y; drawThickLine(v1, v2, c, thickness); v1 = boundingbox.min; v2 = boundingbox.min; v2.z = boundingbox.max.z; drawThickLine(v1, v2, c, thickness); //max corner v1 = boundingbox.max; v2 = boundingbox.max; v2.x = boundingbox.min.x; drawThickLine(v1, v2, c, thickness); v1 = boundingbox.max; v2 = boundingbox.max; v2.y = boundingbox.min.y; drawThickLine(v1, v2, c, thickness); v1 = boundingbox.max; v2 = boundingbox.max; v2.z = boundingbox.min.z; drawThickLine(v1, v2, c, thickness); //others1 v1 = boundingbox.min; v1.y = boundingbox.max.y; v2 = boundingbox.max; v2.z = boundingbox.min.z; drawThickLine(v1, v2, c, thickness); //others2 v1 = boundingbox.min; v1.y = boundingbox.max.y; v2 = boundingbox.max; v2.x = boundingbox.min.x; drawThickLine(v1, v2, c, thickness); //others3 v1 = boundingbox.min; v1.x = boundingbox.max.x; v2 = boundingbox.max; v2.z = boundingbox.min.z; drawThickLine(v1, v2, c, thickness); //others4 v1 = boundingbox.min; v1.x = boundingbox.max.x; v2 = boundingbox.max; v2.y = boundingbox.min.y; drawThickLine(v1, v2, c, thickness); //others5 v1 = boundingbox.min; v1.y = boundingbox.max.y; v1.z = boundingbox.max.z; v2 = boundingbox.max; v2.y = boundingbox.min.y; v2.x = boundingbox.min.x; drawThickLine(v1, v2, c, thickness); //others6 v1 = boundingbox.min; v1.z = boundingbox.max.z; v2 = boundingbox.min; v2.z = boundingbox.max.z; v2.x = boundingbox.max.x; drawThickLine(v1, v2, c, thickness); } // ======== Actor Selection Borders ======= function chooseandDrawBoxColor() { //BLUE-ish BOX for StaticMeshActors if (drawBorderActor.isA('StaticMeshActor')) { drawBorderActor.GetComponentsBoundingBox(boundingBox); drawBoundingBox(makeColor(0, 93, 255, 255), 3); } //PINK BOX - Pawns else if (!drawBorderActor.isa('YourPawnClass')) { drawBorderActor.GetComponentsBoundingBox(boundingBox); drawBoundingBox(makeColor(255, 0, 255, 255), 3); } } function drawBoxesAroundSelectedActors() { local int v; //multi select if(customPlayerController.multiSelectActive) { for (v = 0; v < customPlayerController.selectedActorsArray.length; v++ ) { drawBorderActor = customPlayerController.selectedActorsArray[v]; //none case if ( drawBorderActor == none) continue; chooseandDrawBoxColor(); } } //single select else { if (customPlayerController.selectionActor == none) return; drawBorderActor = customPlayerController.selectionActor; chooseandDrawBoxColor(); } }
What We Have So Far
So now we have a custom HUD class that is drawing a cursor to the screen, and when player is hodling down LMB, whether for a moment or for drag select, the HUD calls playercontroller to add any actors it finds to the selectedActors array.
Then the drawBoxesAroundSelectedActors function draws boxes around any actors that are in the selectedActors array.
Note that the lines do not persist, since the canvas is destroyed each moment of time, so that is why drawBoxesAroundSelectedActors must be constantly called inside DrawHUD.
~~~
The Custom Camera Classes
Both of the camera classes are largely based on pioneering work by Mougli, via the tutorials on his site. Thank you Mougli ♥
~~~
Camera Class
In the PlayerController Class note there is a CameraClass specified
Code:
DefaultProperties { CameraClass = class'CustomCamera' }
Code:
class CustomCamera extends GamePlayerCamera; //GamePlayerCamera.uc is in development/src/gameframework //reference to your playercontroller class var CustomPlayerController playerCam; function PostBeginPlay() { super.PostBeginPlay(); //get world Info vars playerCam = CustomGameInfo(WorldInfo.Game).customPlayerControllerWORLD; //may need this in a timer if pawn postbegin runs after //after this class //I cast to Actor for simplicity, as Actor has //all needed info CustomCameraBase(thirdpersoncam).playerPawn = Actor ( CustomGameInfo(WorldInfo.Game).playerpawnWORLD); //~~~ end of set world vars ~~~~ //for this tutorial, make custom camera //start relative to player camera //Custom Camera location, offset to be overhead, //above playercontroller starting loc CustomCameraBase(thirdpersoncam).customCameraPos.x = playerCam.Location.x - 300; CustomCameraBase(thirdpersoncam).customCameraPos.y = 0; CustomCameraBase(thirdpersoncam).customCameraPos.z = playerCam.Location.z + 1100; //editor rotation, starting with downward angle CustomCameraBase(thirdpersoncam).customCameraRot.pitch = -70 * DegToUnrRot; CustomCameraBase(thirdpersoncam).customCameraRot.yaw = 0; CustomCameraBase(thirdpersoncam).customCameraRot.roll = 0; //thirdpersoncam is a variable that is part of the superclass } protected function GameCameraBase FindBestCameraType(Actor CameraTarget) { //set world info CustomGameInfo(WorldInfo.Game).CustomCameraBaseWORLD = CustomCameraBase(thirdpersoncam); return thirdpersoncam; } DefaultProperties { ThirdPersonCameraClass = class'CustomCameraBase' }
CustomCameraBase Class
Code:
class VictoryBallCameraBase extends GameCameraBase; //located in development/src/GameFramework var float ThirdPersonCamOffsetX; var float ThirdPersonCamOffsetY; var float ThirdPersonCamOffsetZ; var Rotator CurrentCamOrientation; var Rotator DesiredCamOrientation; //toggle var bool customCameraActive; var Actor playerPawn; var vector customCameraPos; var rotator customCameraRot; function setCustomCameraActive(bool b) { customCameraActive = b; } //for verifying its working function init(){ `log("============ CustomCamera Started ============="); `log("============ CustomCamera Started ============="); `log("============ CustomCamera Started ============="); } function UpdateCamera(Pawn P, GamePlayerCamera CameraActor, float DeltaTime, out TViewTarget OutVT) { local float Radius, Height; local vector X, Y, Z; //if using custom camera if(customCameraActive){ //free moving camera OutVT.POV.Rotation = customCameraRot; OutVT.POV.Location = customCameraPos; } //playercontroller main camera else{ playerPawn.GetAxes(DesiredCamOrientation,X,Y,Z); // We will be working with coordinates in pawn space, but rotated according to the Desired Rotation. playerPawn.GetBoundingCylinder(Radius, Height); //Get the pawn's height as a base for the Z offset. //Location OutVT.POV.Location = VInterpTo(OutVT.POV.Location, playerPawn.Location + ThirdPersonCamOffsetX * X + ThirdPersonCamOffsetY * Y + (Height + ThirdPersonCamOffsetZ) * Z, DeltaTime, 7); //Rotation if (DesiredCamOrientation != CurrentCamOrientation) { CurrentCamOrientation = RInterpTo(CurrentCamOrientation,DesiredCamOrientation,DeltaTime,12); } OutVT.POV.Rotation = CurrentCamOrientation; }//end else game mode } function ProcessViewRotation( float DeltaTime, Actor ViewTarget, out Rotator out_ViewRotation, out Rotator out_DeltaRot ) { DesiredCamOrientation = out_ViewRotation + out_DeltaRot; } DefaultProperties { ThirdPersonCamOffsetX=-160.0 ThirdPersonCamOffsetY=8.0 ThirdPersonCamOffsetZ = 20.0 }
Your Custom Pawn Class
Add this code to your player pawn class:
Code:
Simulated Event PostBeginPlay() { //very important line super.postbeginplay(); //set Self's worldinfo var CustomGameInfo(WorldInfo.Game).playerPawnWORLD = Self; }
So in other words, you really must do this for the code I'm providing you with to work, otherwise when you go in game your camera will be confused.
~~~
Conclusion
Using what I've shown you here you can now implement any or all of the following
1. a 3rd person camera
2. a custom free-roaming camera
3. free roaming or centered selection cursor
4. multiselection of actors
5. drawing of 3D boxes around actors
You don't have to utilize all of these features, but now you see how you can!
~~~
Cross Class Communication Review
Reviewing this code you see how you can create all sorts of complex inter-class interactions to really make your game ideas come to life!
♥
Rama
Comment