Please click on a title to expand or collapse the information on it.
ITSP Smart Camera
ITSP Smart Camera
The camera system in ITSP was made with Lua scripting.
-- turn this on to view the in game debug drawing local cameraDebugDraw = true -- when the camera target enters a point of interest, a certain amount of influence will be put on the -- camera to go towards the point of interest instead of the camera target. local currPointOfInterestPercentage = 1 local toNewPosX = 0 -- we will be moving the camera to a desired location, this is the movement on the x axis local toNewPosY = 0 -- camera movement on the y axis local currRCamOffset = {x = 0, y = 0, z = 0} --[ryan] this is the amount of offset we will put on the camera target due to right stick input. -- we want to move the right stick influence smoothly and accurately, so we are keeping track of the last 100 frames of right stick input. -- this way if the player quickly fires a shot in a different direction, we don't swing the camera around wildly. local averageSticks = {} local numSticks = 100 -- the amount of frames we are going to keep track of local totalSticks = {x = 0,y = 0} -- the total amount of stick input used for making the average local lastStickHead = {x = 0,y = 0} -- we can just remove one stick, and add a new stick value local averageStick = {x = 0,y = 0} -- the average stick value used for calculating right stick camera offset local stickHead = 1 -- instead of reordering an array, we will just overwrite what is currently the last indexed value -- initialize all the stick inputs to 0 for i = 1, numSticks do averageSticks[i] = {x = 0,y = 0} end local lStickCameraInfluence = 1 -- we are going to move the camera based of the velocity of the camera target local rStickCameraInfluence = 6 -- we need to weight the amount we use for right stick influence vs velocity influence local toSmartCamZoomSpeed = 0.35 -- this is how fast the camera will change it's zoom local cameraMoveSpeed = 0.05 -- this is the base speed that the camera will use to move to it's desired position local toPlayerInputSpeed = 0.075 -- we want to move the camera a little faster to the player input position -- we can't let the camera target get off screen, this is the safe zone border we will be using in screen space local safeZoneX = 513 local safeZoneY = 288 function updateSmartCamera(ms,currCameraTarget) local targetVel = currCameraTarget:getVelocity() local targetPos = currCameraTarget:getPosition() local newCamPos = currCameraTarget:getPosition() -- this will end up being the position of the camera -- retrieve the inputs from the camera target local lStickX = currCameraTarget.lStickX local lStickY = currCameraTarget.lStickY local rStickX = currCameraTarget.rStickX local rStickY = currCameraTarget.rStickY -- if the camera target has a shooter, then we are going to use the shooters input values, this is used for the missile gates. if(currCameraTarget.shooter ~= nil) then lStickX = currCameraTarget.shooter.lStickX lStickY = currCameraTarget.shooter.lStickY rStickX = currCameraTarget.shooter.rStickX rStickY = currCameraTarget.shooter.rStickY end -- initialize the destination variables local desiredStickPosition = currCameraTarget:getPosition() local dummyCamPos = dummyCameraTarget:getPosition() local smartCamPos = smartCameraTarget:getPosition() -- we need to find out where the points of interest are, because the smart camera target will move -- based on how close to a POI you are. The point of interest will tell the camera target how much camera influence it desires. -- If the camera is in more than one POI, we need to average those amounts to put the camera in the correct position. local pointOfInterestPos = nil local pointOfInterestPercentage = 1 local avgCamPos = { x = 0, y = 0, z = 0} local poiCount = 0 local totalPointOfInterestInfluence = 0 local currentPOIZoom = 0 -- the idea here is that we can't just take the average between all the points of interest position alone. -- If we did we would get a jumping effect as the player transitioned out of many into one. -- The higher the camere influence is on a POI, the stronger we should pull the camera to that one. if(currCameraTarget.pointsOfInterest ~= nil) then -- let's get the total amount of camera influence being applied this frame. for _,currPOI in pairs(currCameraTarget.pointsOfInterest) do totalPointOfInterestInfluence = totalPointOfInterestInfluence + currPOI.cameraInfluence end -- Now get the average position and zoom based on the influence being applied by each POI. for _,currPOI in pairs(currCameraTarget.pointsOfInterest) do if(currPOI.enabled == true) then pointOfInterestPos = currPOI.pos --[ryan] the center of the POI -- This is the desired camera position determined by this POI. newCamPos.x = (currPOI.pos.x - newCamPos.x) * currPOI.cameraInfluence + newCamPos.x newCamPos.y = (currPOI.pos.y - newCamPos.y) * currPOI.cameraInfluence + newCamPos.y -- we need to make sure we avoid a divide by 0 case. Just don't use POI influence if that happens. if totalPointOfInterestInfluence == 0 then avgCamPos.x = dummyCamPos.x avgCamPos.y = dummyCamPos.y else avgCamPos.x = avgCamPos.x + (newCamPos.x * currPOI.cameraInfluence / totalPointOfInterestInfluence) avgCamPos.y = avgCamPos.y + (newCamPos.y * currPOI.cameraInfluence / totalPointOfInterestInfluence) end -- We will start with the ambient zoom, and get an average of points of interest zooms -- if the poi doesn't have a zoom set it should use the current zoom. A zoom value of -1 means it is not set. if(currPOI.targetZoom == -1) then currentPOIZoom = currentPOIZoom + CameraLib.newZoom else currentPOIZoom = currentPOIZoom + currPOI.percentageZoom end poiCount = poiCount + 1 end end end -- If we have a cinematic camera set, we want to return smart camera control smoothly. We will interpolate from the cinematic camera -- to the smart camera. A cameraSmoothedVal of 1 means the smart camera has complete control. local cameraSmoothedVal = 1 if(toSmartCameraInterpolant < 1) then toSmartCameraInterpolant = toSmartCameraInterpolant + toSmartCameraSpeed cameraSmoothedVal = UtilLib.easeIn(toSmartCameraInterpolant) end local cameraZoomSmoothedVal = 1 if(toSmartCameraZoomInterpolant < 1) then toSmartCameraZoomInterpolant = toSmartCameraZoomInterpolant + toSmartCameraZoomSpeed cameraZoomSmoothedVal = UtilLib.easeIn(toSmartCameraZoomInterpolant) end -- if there are any points of interest, we need to find the desired zoom level. if( poiCount > 0) then newCamPos.x = avgCamPos.x newCamPos.y = avgCamPos.y pointOfInterestPercentage = totalPointOfInterestInfluence / poiCount currentPOIZoom = currentPOIZoom / (poiCount) CameraLib.interpolateCameraZoom(currentPOIZoom,toSmartCamZoomSpeed * cameraZoomSmoothedVal) else -- if we are leaving a point of interest, or the camera target has hit a zoom trigger, we need to approach that new zoom level -- using an ease function. if UtilLib.withinThreshold( getCameraZoom(),CameraLib.ambientZoom,0.1) == false then CameraLib.interpolateCameraZoom(CameraLib.newZoom,toSmartCamZoomSpeed* cameraZoomSmoothedVal) --[ryan] if no POI's go to the ambient zoom end end -- this is how we slowly move to the point of interest position, instead of snapping immediately to it. local toDesiredPOIpercentage = (pointOfInterestPercentage - currPointOfInterestPercentage) * cameraMoveSpeed currPointOfInterestPercentage = currPointOfInterestPercentage + toDesiredPOIpercentage pointOfInterestPercentage = currPointOfInterestPercentage --We have a desired velocity based offset, a desired right stick input offset, and a point of interest offset. --We will be moving the camera to the average of these influences. The further the camera is from the desired target, the faster it will move. local velocityInfluence = lStickCameraInfluence * pointOfInterestPercentage smartCameraTarget:setPosition(targetPos.x + targetVel.x * velocityInfluence ,targetPos.y + targetVel.y * velocityInfluence,newCamPos.z) smartCamPos = smartCameraTarget:getPosition() local aimingInfluence = rStickCameraInfluence * pointOfInterestPercentage rSmartCameraTarget:setPosition(targetPos.x + currRCamOffset.x * aimingInfluence,targetPos.y + currRCamOffset.y * aimingInfluence,newCamPos.z) local rSmartCamPos = rSmartCameraTarget:getPosition() --we are getting the average of all the stick inputs for the last 100 frames and choosing the average position lastHead = averageSticks[stickHead] totalSticks.x = totalSticks.x - lastHead.x + rStickX totalSticks.y = totalSticks.y - lastHead.y + rStickY averageSticks[stickHead].x = rStickX averageSticks[stickHead].y = rStickY stickHead = stickHead + 1 --instead of rewriting and adding up the entire array again, just update the last element if( stickHead > numSticks) then stickHead = 1 end averageStick.x = totalSticks.x / numSticks averageStick.y = totalSticks.y / numSticks local toDesiredRCamPosX = (averageStick.x - currRCamOffset.x) * cameraMoveSpeed local toDesiredRCamPosY = (averageStick.y - currRCamOffset.y) * cameraMoveSpeed currRCamOffset.x = currRCamOffset.x + toDesiredRCamPosX currRCamOffset.y = currRCamOffset.y + toDesiredRCamPosY -- get the average position between the velocity target and the aiming target if(useRightStickOffset == true) then desiredStickPosition.x = (smartCamPos.x + rSmartCamPos.x) / 2 desiredStickPosition.y = (smartCamPos.y + rSmartCamPos.y) / 2 else desiredStickPosition.x = (smartCamPos.x + targetPos.x) / 2 desiredStickPosition.y = (smartCamPos.y + targetPos.y) / 2 end -- the vector to the player input target plus the vector to the desired point of interest spot local toNewPosX = ((desiredStickPosition.x - dummyCamPos.x) * toPlayerInputSpeed) * (pointOfInterestPercentage) + (newCamPos.x - dummyCamPos.x) * cameraMoveSpeed local toNewPosY = ((desiredStickPosition.y - dummyCamPos.y) * toPlayerInputSpeed) * (pointOfInterestPercentage) + (newCamPos.y - dummyCamPos.y) * cameraMoveSpeed -- move the camera dummyCameraTarget:setPosition(dummyCamPos.x + toNewPosX * cameraSmoothedVal, dummyCamPos.y + toNewPosY * cameraSmoothedVal, dummyCamPos.z) -- if we are already returned to the smart camera, then check to see if we are within acceptable bounds, the "safe zone" of the screen. if(toSmartCameraInterpolant >=1 ) then local movedCamPos = dummyCameraTarget:getPosition() -- let's do a border check here... if the ship has crossed the border we need to force camera movement. local newX = movedCamPos.x local newY = movedCamPos.y local targetPos = currCameraTarget:getPosition() local centerX, centerY = GameLib.screenToWorld( 0, 0 ) local maxWorldDeltaX, maxWorldDeltaY = GameLib.screenToWorld( safeZoneX, safeZoneY ) -- extents of the 'safe zone' maxWorldDeltaX = math.abs(maxWorldDeltaX - centerX) maxWorldDeltaY = math.abs(maxWorldDeltaY - centerY) local worldDistY = newY - targetPos.y local worldDistX = newX - targetPos.x -- if the camera target is outside the safe zone, then force the camera to move into an acceptable position. if worldDistY < -maxWorldDeltaY then newY = targetPos.y - maxWorldDeltaY end if worldDistY > maxWorldDeltaY then newY = targetPos.y + maxWorldDeltaY end if worldDistX < -maxWorldDeltaX then newX = targetPos.x - maxWorldDeltaX end if worldDistX > maxWorldDeltaX then newX = targetPos.x + maxWorldDeltaX end dummyCameraTarget:setPosition(newX, newY, newCamPos.z) dummyCamPos = dummyCameraTarget:getPosition() end if(cameraDebugDraw == true) then GameLib.drawDebugBox2D( desiredStickPosition.x -0.1, desiredStickPosition.y -0.1, desiredStickPosition.x+0.1, desiredStickPosition.y +0.1, 0, 1, 1 ) --where the ship is at GameLib.drawDebugSphere2D(1,newCamPos.x,newCamPos.y,newCamPos.z,0,0,0) --where the left stick is currently pointing if(lStickX ~= nil) then GameLib.drawDebugSphere2D(0.3,newCamPos.x + lStickX ,newCamPos.y + lStickY,newCamPos.z,1,1,0) end --where the right stick is currently pointing GameLib.drawDebugSphere2D(0.3,newCamPos.x + rStickX ,newCamPos.y + rStickY,newCamPos.z,1,0,0) if( pointOfInterestPos ~= nil) then GameLib.drawDebugBox2D( pointOfInterestPos.x -0.4, pointOfInterestPos.y -0.4, pointOfInterestPos.x+0.4, pointOfInterestPos.y +0.4, 0.5, 0.4, 0.2 ) GameLib.drawDebugBox2D( newCamPos.x -0.1, newCamPos.y - 0.1, newCamPos.x + 0.1, newCamPos.y + 0.1, 0.5, 0.4, 0.2 ) GameLib.drawDebugLine(newCamPos.x,newCamPos.y,0,pointOfInterestPos.x,pointOfInterestPos.y,0, 0.5, 0.4, 0.2) else GameLib.drawDebugLine(dummyCamPos.x,dummyCamPos.y,0,dummyCamPos.x + toNewPosX,dummyCamPos.y + toNewPosY,0,1,0,0) end GameLib.drawDebugBox2D( newCamPos.x + averageStick.x -0.1, newCamPos.y + averageStick.y -0.1, newCamPos.x + averageStick.x+0.1, newCamPos.y + averageStick.y +0.1, 0, 1, 0 ) GameLib.drawDebugBox2D( newCamPos.x + currRCamOffset.x -0.1, newCamPos.y + currRCamOffset.y -0.1, newCamPos.x + currRCamOffset.x+0.1, newCamPos.y + currRCamOffset.y +0.1, 0.5, 0.5, 0 ) GameLib.drawDebugBox2D( smartCamPos.x -0.4, smartCamPos.y - 0.4, smartCamPos.x+ 0.4, smartCamPos.y +0.4, 1, 1, 0 ) GameLib.drawDebugBox2D( rSmartCamPos.x -0.4, rSmartCamPos.y - 0.4, rSmartCamPos.x+ 0.4, rSmartCamPos.y +0.4, 1, 0, 0 ) local left,top = GameLib.screenToWorld(256,144) local right,bottom = GameLib.screenToWorld(1024,576) local centerX,centerY = GameLib.screenToWorld(640,360) GameLib.drawDebugBox2D( left,top, right, bottom , 1, 0, 0 ) end end
2D Target Seeking Using Unity3D
using UnityEngine; using System.Collections; using System.Collections.Generic; public class Enemy_Bridge : Enemy_Module { //these are the modules that make up this enemy ship. They are hooked up in the editor. public List<GameObject> leftEngines = new List<GameObject>(); public List<GameObject> rightEngines = new List<GameObject>(); public List<GameObject> forwardEngines = new List<GameObject>(); private GameObject playerBridge; //how far can this ship see? When the player is in range, this enemy will take action. public float visionRange = 60.0f; private float forwardThreshold = 0.75f; private float tooCloseDistance = 5.0f; //Initialize the ship, and it's modules public override void Start () { base.Start(); AssignBridge(forwardEngines); AssignBridge(rightEngines); AssignBridge(leftEngines); } //the modules need to know about the bridge that is controlling it void AssignBridge(List<GameObject> engineList) { foreach(GameObject engine in engineList) { engine.SendMessage("SetBridge",gameObject,SendMessageOptions.DontRequireReceiver); } } //If this object gets a message to remove an engine, we can just quickly //check each list to remove it since they are indexed by object id. //Some engines exist in more than one list. //The remove engine call will come from the engine module script when it is destroyed. //removing it from this list prevents calling functions on a null object. void RemoveEngine(GameObject engineToRemove) { forwardEngines.Remove(engineToRemove); rightEngines.Remove(engineToRemove); leftEngines.Remove(engineToRemove); } // Update is called once per frame public override void Update () { base.Update(); //The goal of this enemy is to destroy the player bridge. If we don't have one, find one. //If a bridge doesn't exist, then it has been destroyed so take no more action. if(playerBridge == null) { playerBridge = GameObject.FindWithTag("PlayerBridge"); return; } //if we have found a player bridge, check to see if it is within vision range. float dist = Vector3.Distance(gameObject.transform.position,playerBridge.transform.position); if(dist > visionRange) { return; } //Make two checks, one to see if the player is in front of the enemy // and another to see which side of the enemy the player is on Vector3 toBridge = Vector3.Normalize(playerBridge.transform.position - gameObject.transform.position); float forwardCheck = Vector3.Dot(gameObject.transform.up, toBridge); float sideCheck = Vector3.Dot(gameObject.transform.right, toBridge); //if the player is in front of the ship and it is not too close, then move at it. if(forwardCheck > forwardThreshold && dist > tooCloseDistance) { { foreach( GameObject engine in forwardEngines) { if(engine) engine.GetComponent<Enemy_Engine>().ActivateFromBridge(); } } } else if(sideCheck > 0) // turn counter clockwise { foreach( GameObject engine in rightEngines) { if(engine) engine.GetComponent<Enemy_Engine>().ActivateFromBridge(); } } else if(sideCheck <= 0) // turn clockwise { foreach( GameObject engine in leftEngines) { if(engine) engine.GetComponent<Enemy_Engine>().ActivateFromBridge(); } } } }
Other Samples
Other Samples
Additional C++, C#, and Lua samples available on request.