Announcement

Collapse
No announcement yet.

[Video] How to Make 3rd Person Camera, Free Roam Camera, Mouse Cursor, & Drag Select

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    [Video] How to Make 3rd Person Camera, Free Roam Camera, Mouse Cursor, & Drag Select

    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.

    Code:
    PlayerController.SetRotation
    rotates main player camera
    Code:
    PlayerController.SetLocation
    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”
    Code:
    .Bindings=(Name="GBA_LeftMouseButton",Command="LMBDown | OnRelease LMBUp")
    Put the below code in the section of ini called “Game Keyboard/Mouse Bindings” toward the bottom
    Code:
    .Bindings=(Name="LeftMouseButton",Command="GBA_LeftMouseButton")
    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
    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”)
    your function names in your PlayerController Class must match the names listed above, or change the names listed above
    Code:
    Command=”yourkeydownFunc | OnRelease keyupYourFuncName”)
    This code goes in same place as the code for Name=”LeftMousebutton”
    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
    }
    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:
    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;
    }
    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:

    Code:
    var YourCustomHUDClass customHUD;
    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

    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');
    }
    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:

    Code:
    //retrieve worldinfo var
    customPlayerController = VictoryBallGameInfo(WorldInfo.Game).playerControllerWORLD;
    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.


    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
          );
    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.

    Code:
    customPlayerController.addSelectedActor();
    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.

    Code:
    if(customplayercontroller.keydownLMB){
    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.

    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'
    }
    Here is that class:
    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;
    }
    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

    #2
    Bumping as Community resource, because I'm seeing some threads with questions that this tutorial helps address.



    Rama

    Comment


      #3
      OMG thanks for the bump

      Comment


        #4
        hee hee!



        Rama

        PS: spent about 6 hours preparing this tutorial trimming my original code and making video

        Comment


          #5
          Well it was worth it.
          Thanks.

          Comment


            #6
            awww thanks



            Rama

            Comment


              #7
              bumping as community resource



              Rama

              Comment


                #8
                Bumping as Community Resource



                Rama

                Comment


                  #9
                  Bumping, seeing related questions in the forum



                  Rama

                  Comment


                    #10
                    Your script explanation is a good overview of perplexing issues for noobs like me. Sadly scripting tuts are often vague. Even help here is like, duh, you forgot the $E@#$#@. Or why don't you write it like this: _+_#%$ instead of this: @$@#! ? But it's all the same to a blind bat XD I know writing tuts isn't easy, I've failed in some myself by missing obvious necessaries.

                    Your overview helps clarify for me the HUD connection, PlayerController and GameType &c. I bookmarked this page for further ref. Thanks again for taking the time

                    Comment


                      #11
                      I'm glad my writing style is helping you out!

                      I am rather new to Unrealscript so I can easily remember when all the things I am writing about made no sense to me

                      Also I focus on writing tutorials for things that I did NOT know how to do, and could not find answers to, so I wrote my own tutorial as I figured it out myself.

                      So therefore I can easily relate the things that don't make obvious sense cause I was stumbling over the issue myself till I found the solutions!



                      Rama

                      Comment


                        #12
                        Thx, I've been really confused how the classes can tie together. Daunting for me to pick it up, Kismet is also helping me understand it a little more also...
                        Tether effects look great......

                        Comment


                          #13
                          Hey Rama, well I took your advice and have been working on trying to get your code to work. I couldn't seem to get the tether to work right so I restarted all my code and went to this tutorial to understand how you got the Gameinfo, Playercontroller, and Hud to work. Well I went through the complete tutorial and I keep getting this error ...Classes\CustomPlayerController.uc(26) : Error, 'KeydownW' conflicts with 'BoolProperty CustomGameInfo.CustomPlayerController:KeydownW". I have no clue what I need to do to fix this so I posted my code hoping you could show me what I'm doing wrong.

                          CustomGameInfo.uc
                          Code:
                          class CustomGameInfo extends GameInfo;
                          
                          var CustomHUD hudWORLD;
                          var CustomPlayerController playerControllerWORLD;
                          var CustomCamera customCameraBaseWORLD;
                          var CustomPawn playerpawnWORLD;
                          defaultproperties
                          {
                              PlayerControllerClass = class'CustomPlayerController'
                              DefaultPawnClass    = class'CustomPawn'
                             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 ===");
                          }
                          CustomCamera.uc
                          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'
                              
                          }
                          CustomHUD.uc
                          Code:
                          class CustomHUD extends MobileHUD;
                          //controller class instance var
                          var CustomPlayerController 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
                          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();
                              }
                              
                          }
                          
                          //DrawHud function, copy paste here from below
                          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
                          }
                          
                          DefaultProperties
                            {
                               cursorTile = Texture2D'UI_HUD.HUD.UTCrossHairs'
                            }
                          CustomPlayerController.uc
                          Code:
                          class CustomPlayerController extends GamePlayerController;
                          var CustomHUD customHUD;
                          var CustomCamera theCustomCameraBase;
                          var array<actor> selectedActorsArray;
                          
                          var actor selectionActor;
                          
                          var bool multiSelectActive;
                          
                          var bool keydownLMB;
                          var bool keydownA;
                          var bool keydownD;
                          var bool keydownS;
                          var bool keydownW;
                          
                          exec Function LMBDown() {
                          
                              keydownLMB = true;
                          }
                          
                          exec Function LMBUp() {
                                  clearSelectedActors()
                              keydownLMB = false;
                          }
                          
                          exec Function keydownW() {
                              keydownW = true;
                          }
                          
                          exec Function keyupW() {
                              keydownW= false;
                          }
                          
                          exec Function keydownA() {
                              keydownA= true;
                          }
                          
                          exec Function keyupA() {
                              keydownA= false;
                          }
                          
                          exec Function keydownS() {
                              keydownS = true;
                          }
                          
                          exec Function keyupS() {
                              keydownS= false;
                          }
                          
                          exec Function keydownD() {
                              keydownD = true;
                          }
                          
                          exec Function keyupD() {
                              keydownD= false;
                          }
                          
                          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
                          }
                          CustomPawn.uc
                          Code:
                          class CustomPawn extends Pawn;
                          
                          var DynamicLightEnvironmentComponent LightEnvironment;
                          
                          Simulated Event PostBeginPlay() {
                             //very important line
                             super.postbeginplay();
                          
                             //set Self's worldinfo var
                             CustomGameInfo(WorldInfo.Game).playerPawnWORLD = Self;
                          }
                          
                          defaultproperties
                          {
                             WalkingPct=+0.4
                             CrouchedPct=+0.4
                             BaseEyeHeight=38.0
                             EyeHeight=38.0
                             GroundSpeed=440.0
                             AirSpeed=440.0
                             WaterSpeed=220.0
                             AccelRate=2048.0
                             JumpZ=322.0
                             CrouchHeight=29.0
                             CrouchRadius=21.0
                             WalkableFloorZ=0.78
                             
                             Components.Remove(Sprite)
                          
                             Begin Object Class=DynamicLightEnvironmentComponent Name=MyLightEnvironment
                                bSynthesizeSHLight=TRUE
                                bIsCharacterLightEnvironment=TRUE
                                bUseBooleanEnvironmentShadowing=FALSE
                             End Object
                             Components.Add(MyLightEnvironment)
                             LightEnvironment=MyLightEnvironment
                          
                             Begin Object Class=SkeletalMeshComponent Name=WPawnSkeletalMeshComponent
                                 //Your Mesh Properties
                                SkeletalMesh=SkeletalMesh'CH_LIAM_Cathode.Mesh.SK_CH_LIAM_Cathode'
                                AnimTreeTemplate=AnimTree'CH_AnimHuman_Tree.AT_CH_Human'
                                PhysicsAsset=PhysicsAsset'CH_AnimCorrupt.Mesh.SK_CH_Corrupt_Male_Physics'
                                AnimSets(0)=AnimSet'CH_AnimHuman.Anims.K_AnimHuman_BaseMale'
                                Translation=(Z=8.0)
                                Scale=1.075
                                //General Mesh Properties
                                bCacheAnimSequenceNodes=FALSE
                                AlwaysLoadOnClient=true
                                AlwaysLoadOnServer=true
                                bOwnerNoSee=false
                                CastShadow=true
                                BlockRigidBody=TRUE
                                bUpdateSkelWhenNotRendered=false
                                bIgnoreControllersWhenNotRendered=TRUE
                                bUpdateKinematicBonesFromAnimation=true
                                bCastDynamicShadow=true
                                RBChannel=RBCC_Untitled3
                                RBCollideWithChannels=(Untitled3=true)
                                LightEnvironment=MyLightEnvironment
                                bOverrideAttachmentOwnerVisibility=true
                                bAcceptsDynamicDecals=FALSE
                                bHasPhysicsAssetInstance=true
                                TickGroup=TG_PreAsyncWork
                                MinDistFactorForKinematicUpdate=0.2
                                bChartDistanceFactor=true
                                RBDominanceGroup=20
                                bUseOnePassLightingOnTranslucency=TRUE
                                bPerBoneMotionBlur=true
                             End Object
                             Mesh=WPawnSkeletalMeshComponent
                             Components.Add(WPawnSkeletalMeshComponent)
                          
                             Begin Object Name=CollisionCylinder
                                CollisionRadius=+0021.000000
                                CollisionHeight=+0044.000000
                             End Object
                             CylinderComponent=CollisionCylinder
                          }

                          Comment


                            #14
                            Oops, good find!

                            I corrected the code in the OP, here's a sample of the main section that had to be corrected:

                            Code:
                            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;
                            }

                            Comment


                              #15
                              You are simply awesome! Thanks for sharing this great tutorial! I was searching for something like that for my course project . I will give it a try and share my results!

                              Comment

                              Working...
                              X