topical media & game development

talk show tell print

lib-unity-tutorial-gameplay-Assets-Scripts-2D-Camera-CameraScrolling.js / js



  // The object in our scene that our camera is currently tracking.
  private var target : Transform;
  // How far back should the camera be from the target?
  var distance = 15.0;
  // How strict should the camera follow the target?  Lower values make the camera more lazy.
  var springiness = 4.0;
  
  // Keep handy reference sto our level's attributes.  We set up these references in the Awake () function.
  // This also is very slightly more performant, but it's mostly just convenient.
  private var levelAttributes : LevelAttributes;
  private var levelBounds : Rect;
  
  private var targetLock = false;
  
  // This is for setting interpolation on our target, but making sure we don't permanently
  // alter the target's interpolation setting.  This is used in the SetTarget () function.
  private var savedInterpolationSetting = RigidbodyInterpolation.None;
  
  function Awake () {
          // Set up our convenience references.
          levelAttributes = LevelAttributes.GetInstance ();
          levelBounds = levelAttributes.bounds;
  }
  
  function SetTarget (newTarget : Transform, snap : boolean) {
          // If there was a target, reset its interpolation value if it had a rigidbody.
          if  (target) {
                  // Reset the old target's interpolation back to the saved value.
                  targetRigidbody = target.GetComponent (Rigidbody);
                  if  (targetRigidbody)
                          targetRigidbody.interpolation = savedInterpolationSetting;
          }
          
          // Set our current target to be the value passed to SetTarget ()
          target = newTarget;
          
          // Now, save the new target's interpolation setting and set it to interpolate for now.
          // This will make our camera move more smoothly.  Only do this if we didn't set the
          // target to null (nothing).
          if (target) {
                  targetRigidbody = target.GetComponent (Rigidbody);
                  if (targetRigidbody) {
                          savedInterpolationSetting = targetRigidbody.interpolation;
                          targetRigidbody.interpolation = RigidbodyInterpolation.Interpolate;
                  }
          }
          
          // If we should snap the camera to the target, do so now.
          // Otherwise, the camera's position will change in the LateUpdate () function.
          if  (snap) {
                  transform.position = GetGoalPosition ();
          }
  }
  
  // Provide another version of SetTarget that doesn't require the snap variable to set.
  // This is for convenience and cleanliness.  By default, we will not snap to the target.
  function SetTarget (newTarget : Transform) {
          SetTarget (newTarget, false);
  }
  
  // This is a simple accessor function, sometimes called a "getter".  It is a publically callable
  // function that returns a private variable.  Notice how target defined at the top of the script
  // is marked "private"?  We can not access it from other scripts directly.  Therefore, we just
  // have a function that returns it.  Sneaky!
  function GetTarget () {
          return target;
  }
  
  // You almost always want camera motion to go inside of LateUpdate (), so that the camera follows
  // the target _after_ it has moved.  Otherwise, the camera may lag one frame behind.
  function LateUpdate () {
          // Where should our camera be looking right now?
          var goalPosition = GetGoalPosition ();
          
          // Interpolate between the current camera position and the goal position.
          // See the documentation on Vector3.Lerp () for more information.
          transform.position = Vector3.Lerp (transform.position, goalPosition, Time.deltaTime * springiness);        
  }
  
  // Based on the camera attributes and the target's special camera attributes, find out where the
  // camera should move to.
  function GetGoalPosition () {
          // If there is no target, don't move the camera.  So return the camera's current position as the goal position.
          if  (!target)
                  return transform.position;
          
          // Our camera script can take attributes from the target.  If there are no attributes attached, we have
          // the following defaults.
          
          // How high in world space should the camera look above the target?
          var heightOffset = 0.0;
          // How much should we zoom the camera based on this target?
          var distanceModifier = 1.0;
          // By default, we won't account for any target velocity in our calculations;
          var velocityLookAhead = 0.0;
          var maxLookAhead = Vector2 (0.0, 0.0);
          
          // Look for CameraTargetAttributes in our target.
          var cameraTargetAttributes = target.GetComponent (CameraTargetAttributes);
          
          // If our target has special attributes, use these instead of our above defaults.
          if  (cameraTargetAttributes) {
                  heightOffset = cameraTargetAttributes.heightOffset;
                  distanceModifier = cameraTargetAttributes.distanceModifier;
                  velocityLookAhead = cameraTargetAttributes.velocityLookAhead;
                  maxLookAhead = cameraTargetAttributes.maxLookAhead;
          }
          
          // First do a rough goalPosition that simply follows the target at a certain relative height and distance.
          var goalPosition = target.position + Vector3 (0, heightOffset, -distance * distanceModifier);
          
          // Next, we refine our goalPosition by taking into account our target's current velocity.
          // This will make the camera slightly look ahead to wherever the character is going.
          
          // First assume there is no velocity.
          // This is so if the camera's target is not a Rigidbody, it won't do any look-ahead calculations because everything will be zero.
          var targetVelocity = Vector3.zero;
          
          // If we find a Rigidbody on the target, that means we can access a velocity!
          var targetRigidbody = target.GetComponent (Rigidbody);
          if (targetRigidbody)
                  targetVelocity = targetRigidbody.velocity;
          
          // If we find a PlatformerController on the target, we can access a velocity from that!
          targetPlatformerController = target.GetComponent (PlatformerController);
          if (targetPlatformerController)
                  targetVelocity = targetPlatformerController.GetVelocity ();
          
          // If you've had a physics class, you may recall an equation similar to: position = velocity * time;
          // Here we estimate what the target's position will be in velocityLookAhead seconds.
          var lookAhead = targetVelocity * velocityLookAhead;
          
          // We clamp the lookAhead vector to some sane values so that the target doesn't go offscreen.
          // This calculation could be more advanced (lengthy), taking into account the target's viewport position,
          // but this works pretty well in practice.
          lookAhead.x = Mathf.Clamp (lookAhead.x, -maxLookAhead.x, maxLookAhead.x);
          lookAhead.y = Mathf.Clamp (lookAhead.y, -maxLookAhead.y, maxLookAhead.y);
          // We never want to take z velocity into account as this is 2D.  Just make sure it's zero.
          lookAhead.z = 0.0;
          
          // Now add in our lookAhead calculation.  Our camera following is now a bit better!
          goalPosition += lookAhead;
          
          // To put the icing on the cake, we will make so the positions beyond the level boundaries
          // are never seen.  This gives your level a great contained feeling, with a definite beginning
          // and ending.
          
          var clampOffset = Vector3.zero;
          
          // Temporarily set the camera to the goal position so we can test positions for clamping.
          // But first, save the previous position.
          var cameraPositionSave = transform.position;
          transform.position = goalPosition;
          
          // Get the target position in viewport space.  Viewport space is relative to the camera.
          // The bottom left is (0,0) and the upper right is (1,1)
          // @TODO Viewport space changing in Unity 2.0?
          var targetViewportPosition = camera.WorldToViewportPoint (target.position);
          
          // First clamp to the right and top.  After this we will clamp to the bottom and left, so it will override this
          // clamping if it needs to.  This only occurs if your level is really small so that the camera sees more than
          // the entire level at once.
          
          // What is the world position of the very upper right corner of the camera?
          var upperRightCameraInWorld = camera.ViewportToWorldPoint (Vector3 (1.0, 1.0, targetViewportPosition.z));
          
          // Find out how far outside the world the camera is right now.
          clampOffset.x = Mathf.Min (levelBounds.xMax - upperRightCameraInWorld.x, 0.0);
          clampOffset.y = Mathf.Min ((levelBounds.yMax - upperRightCameraInWorld.y), 0.0);
          
          // Now we apply our clamping to our goalPosition.  Now our camera won't go past the right and top boundaries of the level!
          goalPosition += clampOffset;
          
          // Now we do basically the same thing, except clamp to the lower left of the level.  This will override any previous clamping
          // if the level is really small.  That way you'll for sure never see past the lower-left of the level, but if the camera is
          // zoomed out too far for the level size, you will see past the right or top of the level.
          
          transform.position = goalPosition;
          var lowerLeftCameraInWorld = camera.ViewportToWorldPoint (Vector3 (0.0, 0.0, targetViewportPosition.z));
          
          // Find out how far outside the world the camera is right now.
          clampOffset.x = Mathf.Max ((levelBounds.xMin - lowerLeftCameraInWorld.x), 0.0);
          clampOffset.y = Mathf.Max ((levelBounds.yMin - lowerLeftCameraInWorld.y), 0.0);
          
          // Now we apply our clamping to our goalPosition once again.  Now our camera won't go past the left and bottom boundaries of the level!
          goalPosition += clampOffset;
          
          // Now that we're done calling functions on the camera, we can set the position back to the saved position;
          transform.position = cameraPositionSave;
          
          // Send back our spiffily calculated goalPosition back to the caller!
          return goalPosition;
  }
  


(C) Æliens 04/09/2009

You may not copy or print any of this material without explicit permission of the author or the publisher. In case of other copyright issues, contact the author.