First Person Camera Tutorial
- This page discusses how you make a generic first-person camera in HeroEngine. This is a intermediate to advanced level tutorial that requires basic understanding of HSL and how to browse the repository. For information on these topics see
HeroEngine has a very robust camera system that allows for making many types of cameras. A very common camera type is the first-person camera which can be used to make first-person shooters. This tutorial will guide you through the process of making a generic first-person camera. This tutorial provides a good foundation for any FPS camera but it is only intended as an example. Each section of the tutorial describes the code line-by-line for that code block. More detail is provided for some functions so it is clearer as to their use in setting up a first-person camera. At the end of the tutorial the source code is listed in one code block for easy reference. In order to complete the tutorial you will need a basic understanding of how to use the Heroscript editor and repository browser.
What this tutorial covers
- Making a new command layer
- Creating a new script
- Responding to input commands in script
- Using the console and CLI to call a script
What this tutorial doesn't cover
This tutorial only covers making a first-person camera. The character controller will still move and animate your character with its default behavior. In order to make a character behave and move as a character does in a modern day FPS the default HeroEngine advanced customizeable character controller needs to be modified as well, but that is beyond the scope of this tutorial. Also this tutorial's scripts will need to be called every time the first-person camera is to be used. The method of hooking up a first-person camera so that it is the default activate camera is again beyond the scope of this tutorial.
First-Person Camera Tutorial
Making a new command layer
The first step in making a FPS camera is to make a new command layer to handle movement messages from the mouse. All inputs in HeroEngine go through the command layer stack. In the default engine setup there are 5 different layers. Each layer handles inputs from your keyboard or mouse and then forwards the message up to the next layer in the stack unless the layer says the command was handled. You can read more information about this on the HSL Input System and Keybindings page.
To make a new command layer, the first step is to open the repository browser and download the gamekeybindings.ini. Open this file in a text editor and there will be the 5 default input layers listed here. We will add another and name it FPS_Camera. We will set its callback script to be FPS_Camera_Callback. At the end of the text file, we will define your new command layer by adding in these lines of code. NOTE:With out defaultbinding the callback script may not get all of its proper callbacks.
[FPS_Camera] = FPS_Camera_Callback DefaultBinding = MOUSE.LEFT
Save the file and upload it back into the repository. It will ask you if you want to overwrite the existing file, say yes.
Now that the command layer is defined you will need to add that layer into the command layer stack. We will do this by calling a function we define in the FPS_Camera_Callback script. First make the script, if it does not yet exist, by opening the heroscript editor in heroblade, clicking: file, new, client script, select make empty script, and type in "FPS_Camera_Callback" for the script name. You add add a command layer into the stack by making a call to
PushCmdLayer(layername as string) and then you activate it with
ActivateCmdLayer( layername as string, activate as boolean ). If you do not activate the layer after adding it to the stack the layer's callback script will not receive input messages. Add the following code to the script so you may push, activate, and deactivate the command layer as needed.
function PushAndActivateFPS_CameraLayer() PushCmdLayer("FPS_Camera") ActivateCmdLayer( "FPS_Camera", true ) . function DeactivateFPS_CameraLayer() ActivateCmdLayer( "FPS_Camera", false ) ActivateCamera( "GAME" ) .
Defining Camera Settings
Now that you have a place to get input messages, the next step is to create a new camera and define the basic properties of the camera. The next two paragraphs detail step-by-step how to setup the camera in script. If you wish you can copy the full function from the bottom of this section into the FPS_Camera script as made in the previous section. There are also external functions to modify the default camera FOV but we will not use them here since the default FOV is fine. You can read more about those on the Camera Functions page.
First, we will define another function that adds a camera and sets its settings. Cameras are made by making a call to
AddCamera( cameraName as string ) as noderef. This makes a camera with the name you pass in and returns a reference to the camera. The next thing to do is to position the camera. We will want this to be set to the active player's position. We can get a reference to the active player with the external function
GetPlayerCharacterNode() as noderef. Next, we will get the player's position with a call to
GetNodePosition(node as noderef,position references vector3). This function returns a value by reference so we will define a vector3 to hold the reference. Once we have the position of the player, we can make a call to
SetCameraPosition( cameraName as string, position as vector3) to position the camera. We will want to hide the character as well, since this will put the camera inside the character's geometry. Setting the player's render property to false with this code,
player["Render"] = false, will do this for us.
Next, we will setup different parameters of the camera so it follows the character and is not offset from the player at all. Cameras can follow nodes by making a call to
SetCameraFollow(cameraName as string, followNode as noderef). In this case the follow node will be the active character. The external function
SetCameraOffset(name as String, offset as Float) controls how far away the camera should be from the node it is following. We will set this to be 0. The camera also needs to be set to freelook mode with a call to
FreeLook(name as String, steady as Boolean). This means that the camera can look around wherever it wants without being pulled back to look at something else. Lastly you need to active the camera by calling
ActivateCamera( cameraName as string ) .
function SetupFPS_Camera() camera as noderef = AddCamera("FPS_Camera") character as noderef = GetPlayerCharacterNode() character_position as vector3 GetNodePosition( character, character_position ) SetCameraPosition( "FPS_Camera", character_position ) character["Render"] = false SetCameraFollow( "FPS_Camera",character ) SetCameraOffset( "FPS_Camera", 0.0 ) FreeLook( "FPS_Camera", true ) ActivateCamera( "FPS_Camera") .
Controlling the Camera
The last step is setting up the camera to be rotated when you move your mouse. In HeroEngine, by default, you can only rotate the camera when the mouse button is held down. We are going to add code to our FPS_Camera_Callback script that will respond whenever you move the mouse. Again, the next section describes line-by-line how to set this up and the full code block is at the end of this section.
When you move the mouse, an event is generated and sent into the command layer stack. Layers on the bottom of the stack get the message first in different call back scripts. Moving the mouse generates an event in the function
OnMouseMove(mx as Integer, my as Integer, dx copies Integer, dy copies Integer) as Boolean. mx and my are the mouse's absolute position on the viewport. dx and dy are how much the mouse has changed since the last call to OnMouseMove. We will only concern ourselves with dx and dy since we only care about mouse movement and not its position.
Heroblade allows users to invert movement of the mouse. We will first check if the user wants to reverse mouse movement with a call to
GetTweakableBoolean("GameInvertMouseY"). If those return true, we will invert the movement. Next we will convert the values to floats as well as invert dy, since vertical screen movement is in the opposite direction of the rotation we want. We convert the values to floats to allow for more precision when adjusting for mouse sensitivity. After that, we will check the defined mouse sensitivity and apply that to the new float values.
The last part is to get a reference to the camera with a call to
GetActiveCamera(). This will return a reference to the currently active camera. Since we have activated our camera in a previous step, this will be "FPS_Camera". Next, with a call to
RotateNode(node as noderef, pitchDelta as float, headingDelta as float, rollDelta as float) we adjust the cameras rotation by the adjusted float values. Lastly, we must decide if this command was handled or not by returning true or false. In our case we will return true, since we do not want any other command layers to handle mouse movement.
function OnMouseMove(mx as Integer, my as Integer, dx copies Integer, dy copies Integer) as Boolean if (GetTweakableBoolean("GameInvertMouseX")) or $INPUT._InputGameInvertMouseX dx = dx * -1 . if (not GetTweakableBoolean("GameInvertMouseY")) and ( not $INPUT._InputGameInvertMouseY ) dy = dy * -1 . fx as Float = dx fy as Float = dy * -1 if $INPUT._InputGameMouseSensitivity > 0 fx = fx * $INPUT._InputGameMouseSensitivity fy = fy * $INPUT._InputGameMouseSensitivity . camera as NodeRef = GetActiveCamera() RotateNode(camera,fy,fx,0) return true .
Putting it all together
Now that we have everything setup, we just need to create a function that will call our functions to push the command layer and setup the camera. Add in the following function to the FPS_Camera script:
function StartFPS_Camera() PushAndActivateFPS_CameraLayer() SetupFPS_Camera() .
After you have this function added, call it to see your new camera. The easiest way to do that is to open up the console window in Heroblade and call it directly. Type this into the console to call the function:call fps_camera_callback startfps_camera. You should notice your viewport change as soon as the function is called. When you move your mouse you will see the camera move as in a FPS game. When you want to go back to the normal game camera, call the deactivate_fpscamera function from the console. It will turn off the fps_camera command layer and swap your camera back to the normal game camera.
FPS_Camera_Callback Script Source Code
This is the full callback script source code.
function PushAndActivateFPS_CameraLayer() PushCmdLayer("FPS_Camera") ActivateCmdLayer("FPS_Camera", true) . function DeactivateFPS_CameraLayer() ActivateCmdLayer("FPS_Camera",false) . function SetupFPS_Camera() camera as NodeRef = AddCamera("FPS_Camera") character as NodeRef = GetPlayerCharacterNode() character_position as Vector3 GetNodePosition( character, character_position ) SetCameraPosition( "FPS_Camera", character_position ) character["Render"] = false SetCameraFollow( "FPS_Camera",character ) SetCameraOffset( "FPS_Camera", 0.0 ) FreeLook( "FPS_Camera", true ) ActivateCamera( "FPS_Camera") . function onmouseleaveviewport() println("Left viewport in fps_camera") . function OnMouseClick(lmb as Boolean, rmb as Boolean, mmb as Boolean, mb4 as Boolean, mb5 as Boolean) as Boolean println("Click") return false . function onMouseMove(mx as Integer, my as Integer, dx copies Integer, dy copies Integer) as Boolean println("Moving the mouse") if (GetTweakableBoolean("GameInvertMouseX")) or $INPUT._InputGameInvertMouseX dx = dx * -1 . if (not GetTweakableBoolean("GameInvertMouseY")) and ( not $INPUT._InputGameInvertMouseY ) dy = dy * -1 . fx as Float = dx fy as Float = dy * -1 if $INPUT._InputGameMouseSensitivity > 0 fx = fx * $INPUT._InputGameMouseSensitivity fy = fy * $INPUT._InputGameMouseSensitivity . camera as NodeRef = GetActiveCamera() RotateNode(camera,fy,fx,0) return true . function StartFPS_Camera() PushAndActivateFPS_CameraLayer() SetupFPS_Camera() .