Example Advanced Camera
|
What this page covers
This page serves as an example of how you might want to implement a linear interpolated camera and smooth mouse wheel zoom. Instead of snapping the cameras rotation instantly to the desired position/rotation it will break down the desired position/rotation in to small sections and transition through them over a small time interval. The end result is that it makes the camera's movement look much smoother for the user. This page is setup for HeroCloud users that do not already have a linear interpolated camera. This code is written such that it can be copied and pasted directly in to a default HeroCloud world and have the linear interpolated camera work right away. If you already have overridden some of the scripts used in this example you will need to change some of these steps to a game specific implementation. Note: Newer HeroCloud worlds may already have this type of camera implemented.
What you will need to know
This page is intended for users who know how to use the DOM editor and the HSL Editor to create custom DOM definitions and Scripts. While this page is setup so a new user could copy paste the majority of the code with out any issues if your are no longer using the default scripts for your game you will to change some of these steps to a game specific implementation for your world.
What this page does not cover
This page does not cover the exact details of how and why the the camera is implemented this way. It is here to serve as a working example of how one might implement a linear interpolated camera and may not be the right way to implement the camera depending on the needs of you game.
Created New Client DOM definitions
Note:All DOM definitions must be uniquely named in HeroEngine. The names given are specific enough to have a very low chance of already existing in your world. If you wish to use different names or if these names already exist in your world be sure to replace those names as needed in the sample scripts
Create These CLIENT DOM definitions.
New Class
- ADV_LerpCamera
New Fields
- ADV_DragRotateCurrentLERPPosition - vector3
- ADV_DragRotateDesiredEndPos - vector3
- ADV_DragRotateStartingCamRot - vector3
- ADV_DragRotateTimer - timer
- ADV_MouseWheelCurrentOffset - float
- ADV_MouseWheelDesiredEnd - float
- ADV_MouseWheelDirection - string
- ADV_MousewheelStartPosition - float
- ADV_MouseWheelTimer - timer
Once all of these fields are created add them to the ADV_LerpCamera Class
Edit Scripts
Make a new empty script for the ADV_LerpCamera class and copy paste the code below in to it. After that is done compile and submit the code.
method ADV_Lerp( end as Vector3, change as Float) as Vector3 total_distance as Vector3 = end total_distance *= change me.ADV_DragRotateCurrentLERPPosition = total_distance total_distance += me.ADV_DragRotateStartingCamRot return total_distance . method ADV_DragRotateTimer_tick() total_time as TimeInterval = 00:00:00.1 total_elapsed as TimeInterval = SYSTEM.TIME.LOCAL - me.ADV_DragRotateTimer.startTime if total_elapsed < total_time total_milli as Float = total_time.millisecondsTotal total_elapsed_milli as Float = total_elapsed.millisecondsTotal change as Float = total_elapsed_milli / total_milli new_rot as Vector3 = me.ADV_Lerp(me.ADV_DragRotateDesiredEndPos, change) me.ADV_ClampCameraYRotation( new_rot ) SetNodeRotation( me, new_rot ) return . me.ADV_DragRotateCurrentLERPPosition = (0,0,0) me.ADV_DragRotateTimer.stop() . method ADV_DragRotateCamera( cam as NodeRef , fy as Float , fx as Float , fz as Float ) if me.ADV_DragRotateTimer.timerState == OFF me.ADV_DragRotateCurrentLERPPosition = (0,0,0) me.ADV_DragRotateDesiredEndPos = (fy,fx,fz) me.ADV_DragRotateStartingCamRot = GetNodeRotation( cam ) me.ADV_DragRotateTimer.fireRate = 00:00:00.001 me.ADV_DragRotateTimer.setTimeSource("LOCAL") me.ADV_DragRotateTimer.start() else //timer was running stop it and change values me.ADV_DragRotateTimer.stop() me.ADV_DragRotateStartingCamRot = GetNodeRotation( cam ) me.ADV_DragRotateDesiredEndPos = (fy,fx,fz) + (me.ADV_DragRotateDesiredEndPos - me.ADV_DragRotateCurrentLERPPosition) me.ADV_DragRotateCurrentLERPPosition = (0,0,0) me.ADV_DragRotateTimer.start() . . method ADV_ClampCameraYRotation( rotation references Vector3 ) if rotation.x > 85 rotation.x = 85 else if rotation.x < -85 rotation.x = -85 . . method ADV_WheelLERP( start as Float ,end as Float, change as Float ) as Float total_distance as Float = end - start total_distance *= change return total_distance . method ADV_ClampCameraOffset( value references Float) if value > 1.333 value = 1.333 else if value <.06 value = .06 . . method ADV_MouseWheel( desired_change as Float ) if desired_change > 0 //moving out me.ADV_MouseScrollCheckReverse("out") else me.ADV_MouseScrollCheckReverse("in") . if me.ADV_MouseWheelTimer.timerState == OFF me.ADV_MouseWheelCurrentOffset = 0 me.ADV_MouseWheelStartPosition = GetCameraOffset("*") me.ADV_MouseWheelDesiredEnd = desired_change me.ADV_MouseWheelTimer.fireRate = 00:00:00.001 me.ADV_MouseWheelTimer.setTimeSource("LOCAL") me.ADV_MouseWheelTimer.start() else me.ADV_MouseWheelTimer.stop() me.ADV_MouseWheelStartPosition = GetCameraOffset("*") me.ADV_MouseWheelDesiredEnd = desired_change + (me.ADV_MouseWheelDesiredEnd - me.ADV_MouseWheelCurrentOffset) me.ADV_MouseWheelCurrentOffset = 0 me.ADV_MouseWheelTimer.start() . . method ADV_MouseScrollCheckReverse( direction as String ) if direction != me.ADV_MouseWheelDirection me.ADV_MouseWheelTimer.stop() me.ADV_MouseWheelDirection = direction . . method ADV_MouseWheelTimer_tick() total_time as TimeInterval = 00:00:00.5 total_elapsed as TimeInterval = SYSTEM.TIME.LOCAL - me.ADV_MouseWheelTimer.startTime if total_elapsed < total_time total_milli as Float = total_time.millisecondsTotal total_elapsed_milli as Float = total_elapsed.millisecondsTotal change as Float = total_elapsed_milli / total_milli new_offset as Float = me.ADV_WheelLERP( 0, me.ADV_MouseWheelDesiredEnd, change) me.ADV_MouseWheelCurrentOffset = new_offset // println(new_offset) new_offset += me.ADV_MouseWheelStartPosition me.ADV_ClampCameraOffset( new_offset ) SetCameraOffset("*",new_offset) return . me.ADV_MouseWheelCurrentOffset = 0 me.ADV_MouseWheelTimer.stop() . method ADV_StopDragRotate() me.ADV_DragRotateTimer.stop() me.ADV_DragRotateDesiredEndPos = (0,0,0) .
Next what you will need to do is make sure your "game" camera is now of type ADV_LerpCamera. This should done when the camera is created. In HeroCloud worlds this happens in the client side E_ACCCOntrollerClassMethods. The method _HE_ACCC_NavigateLoaclline line 1383 cam as NodeRef of Class HBNode = AddCamera("GAME")
makes the Default game camera NOTE: the line number may be different depending on if you've edited this file or if we have updated it in a recent update. Immediately after that we want to glom our ADV_LerpCamera on to the camera node so insert the follow code below the line that makes the camera.
if not (cam is kindof ADV_LerpCamera) GlomClass( "ADV_LerpCamera", cam ) .
Lastly you will need to edit the script that handles camera input. In HeroCloud worlds this is the script Input_Camera by default. If you have edited the Input_Camera script or if you are using a different input script and command layer the code below may not work for your game. What you will need to do in that case is find where your camera is being rotated and instead of using the external functions to rotate the camera node use the ADV_LerpCamera method ADV_DragRotateCamera(cameraNode as noderef,fy as float ,fx as float ,fz as float)
Below is the full implementation of Input_Camera just copy paste it in to Input_Camera, then compile and submit it.
function OnMouseMove(mx as Integer, my as Integer, dx copies Integer, dy copies Integer) as Boolean if (GetCmdState("Camera","DragRotate")) cam as NodeRef = GetActiveCamera() 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 if $INPUT._InputGameMouseSensitivity > 0 // dx = round( dx * $INPUT._InputGameMouseSensitivity ) // dy = round( dy * $INPUT._InputGameMouseSensitivity ) fx = fx * $INPUT._InputGameMouseSensitivity fy = fy * $INPUT._InputGameMouseSensitivity . if cam is kindof ADV_LerpCamera cam.ADV_DragRotateCamera(cam,fy,fx,0) else RotateNode(cam,fy,fx,0) . return true else if (GetCmdState("Camera","DragRotateCamera")) SetCmdState("Camera","DragRotateCameraEngaged",true) cam as NodeRef = GetActiveCamera() 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 if $INPUT._InputGameMouseSensitivity > 0 // dx = round( dx * $INPUT._InputGameMouseSensitivity ) // dy = round( dy * $INPUT._InputGameMouseSensitivity ) fx = fx * $INPUT._InputGameMouseSensitivity fy = fy * $INPUT._InputGameMouseSensitivity . playerCharacter as NodeRef of Class HBNode = GetPlayerCharacterNode() if playerCharacter <> None ACCController as NodeRef of Class _ACCController ro as Vector3 where playerCharacter is kindof _ACCControllerOwner ACCController = playercharacter._getACCController() if ACCController <> None ro.y = ACCController._getGameCameraRotationalOffset().y + dx ACCController._setGameCameraRotationalOffset(ro) . . . if cam is kindof ADV_LerpCamera cam.ADV_DragRotateCamera(cam,fy,fx,0) else RotateNode(cam,fy,fx,0) . return true . return false . function OnMouseWheel(delta as Integer) as Boolean // Used to determine the offset to use for the camera // w as Float = delta * .0008 gco as Float = GetCameraOffset("*") gco = gco + w if (gco > 1.3333) gco = 1.3333 else if (gco < 0.06) gco = 0.06 . cam as NodeRef = GetActiveCamera() if cam is kindof ADV_LerpCamera w *= -1 cam.ADV_MouseWheel( w ) else SetCameraOffset("*",gco) . return true . function onCmdStart(cmd as String) as Boolean if (cmd=="DragRotate" or cmd=="DragRotateCamera") if GetCmdState("Camera", "DragRotateCameraEngaged") SetCmdState("Camera","DragRotateCameraEngaged",false) playerCharacter as NodeRef of Class HBNode = GetPlayerCharacterNode() if playerCharacter <> None ACCController as NodeRef of Class _ACCController ro as Vector3 where playerCharacter is kindof _ACCControllerOwner ACCController = playercharacter._getACCController() if ACCController <> None ro.y = ACCController._getGameCameraRotationalOffset().y ACCController._clearGameCameraRotationalOffset() . . cam as NodeRef = GetActiveCamera() RotateNode(cam,0,-ro.y,0) . . if $PLAYMODE._getPlayModeState() == ON $Cursor._setCustomCursorVisibility(false) else SetCursorVisibility(false) . // SetIgnoreCursor(true) return true . if (cmd=="ToggleFullscreen") SYSTEM.EXEC.CPULIMIT = 0:00:30 NotifyStateChange("TOGGLE_FULLSCREEN") return true . if (cmd=="ClearRotateCamera") playerCharacter as NodeRef of Class HBNode = GetPlayerCharacterNode() if playerCharacter <> None ACCController as NodeRef of Class _ACCController ro as Vector3 where playerCharacter is kindof _ACCControllerOwner ACCController = playercharacter._getACCController() if ACCController <> None ro.y = ACCController._getGameCameraRotationalOffset().y ACCController._clearGameCameraRotationalOffset() . . cam as NodeRef = GetActiveCamera() RotateNode(cam,0,-ro.y,0) . return true . if (cmd=="ToggleAspectRatio") NotifyStateChange("TOGGLE_ASPECTRATIO") return true . if (cmd=="RotateCameraUp" or cmd=="RotateCameraDown" or cmd=="ZoomIn" or cmd=="ZoomOut") return true . if (cmd=="Click") return false . return false . function onCmdStop(cmd as String) as Boolean if (cmd=="DragRotate" or cmd=="DragRotateCamera") cam as NodeRef = GetActiveCamera() if cam is kindof ADV_LerpCamera cam.ADV_StopDragRotate() . if $PLAYMODE._getPlayModeState() == ON $Cursor._setCustomCursorVisibility(true) else SetCursorVisibility(true) . SetIgnoreCursor(false) return true . if (cmd=="ToggleFullscreen" or cmd=="ClearRotateCamera" or cmd=="ToggleAspectRatio") return true . if (cmd=="RotateCameraUp" or cmd=="RotateCameraDown" or cmd=="ZoomIn" or cmd=="ZoomOut") return true . if (cmd=="Click") return false . return false . function onFrameUpdate(elap as TimeInterval) if (GetCmdState("Camera","RotateCameraUp")) dX as Float = .12 * elap.millisecondsTotal cam as NodeRef = GetActiveCamera() RotateNode(cam,dX,0,0) else if (GetCmdState("Camera","RotateCameraDown")) dX as Float = -.12 * elap.millisecondsTotal cam as NodeRef = GetActiveCamera() RotateNode(cam,dX,0,0) else if (GetCmdState("Camera","ZoomIn")) w as Float = elap.millisecondsTotal * .0007 gco as Float = GetCameraOffset("*") gco = gco - w if (gco > 2.0) gco = 2.0 else if (gco < 0.06) gco = 0.06 . SetCameraOffset("*",gco) else if (GetCmdState("Camera","ZoomOut")) w as Float = elap.millisecondsTotal * .0007 gco as Float = GetCameraOffset("*") gco = gco + w if (gco > 2.0) gco = 2.0 else if (gco < 0.06) gco = 0.06 . SetCameraOffset("*",gco) . .
Other Considerations
With this setup the external function setIgnoreCursor( On as boolean )
can cause odd behavior with very high DPI mice and tablets. You will want to make sure not to turn on setIgnoreCursor with out modifying the implementation if this affects you. In default HeroCloud worlds this is turned on when you drag rotate the mouse on so you will want to turn it off.
Final Result
Now you should have a fully functional linear interpolated camera. When you go in to character mode your camera should rotate smoothly as well as when you use the mouse wheel to zoom in and out.