Skip to content

Instantly share code, notes, and snippets.

@FaronBracy
Created August 23, 2021 12:34
Show Gist options
  • Save FaronBracy/47360c1365d5f0a868938a0287c18db9 to your computer and use it in GitHub Desktop.
Save FaronBracy/47360c1365d5f0a868938a0287c18db9 to your computer and use it in GitHub Desktop.
Camera with Pan and Zoom
using Microsoft.Xna.Framework;
using ZoomEngine.Animation;
namespace Game
{
public class Camera
{
private ClockManager _clockManager;
// position of the camera
public Camera( int viewportWidth, int viewportHeight, Vector2 cameraPosition, ClockManager clockManager )
{
ViewportWidth = viewportWidth;
ViewportHeight = viewportHeight;
Position = cameraPosition;
_clockManager = clockManager;
Zoom = 1.0f;
}
// Centered Position of the Camera.
public Vector2 Position { get; private set; }
public float Zoom { get; private set; }
public float Rotation { get; private set; }
// height and width of the viewport window which should adjust when the player resizes the game window.
public int ViewportWidth { get; set; }
public int ViewportHeight { get; set; }
// Center of the Viewport does not account for scale
public Vector2 ViewportCenter
{
get
{
return new Vector2( ViewportWidth * 0.5f, ViewportHeight * 0.5f );
}
}
// create a matrix for the camera to offset everything we draw, the map and our objects. since the
// camera coordinates are where the camera is, we offset everything by the negative of that to simulate
// a camera moving. we also cast to integers to avoid filtering artifacts
public Matrix TranslationMatrix
{
get
{
return Matrix.CreateTranslation( -(int) Position.X, -(int) Position.Y, 0 ) *
Matrix.CreateRotationZ( Rotation ) *
Matrix.CreateScale( new Vector3( Zoom, Zoom, 1 ) ) *
Matrix.CreateTranslation( new Vector3( ViewportCenter, 0 ) );
}
}
public void AdjustZoom( float amount )
{
Zoom += amount;
if ( Zoom < 0.1f )
{
Zoom = 0.1f;
}
}
public void MoveCamera( Vector2 cameraMovement, bool clampToMap = false )
{
Position += cameraMovement;
if ( clampToMap )
{
// clamp the camera so it never leaves the visible area of the map.
var cameraMax = new Vector2( Global.GameModel.Map.Width * Global.CellWidth - ViewportWidth,
Global.GameModel.Map.Height * Global.CellHeight - ViewportHeight );
Position = Vector2.Clamp( Position, Vector2.Zero, cameraMax );
}
}
public void Shake( double duration )
{
StateManager.StartAnimation();
var positionAnim = new Vector2FromToAnimation( _clockManager )
{
From = new Vector2( Position.X + 1, Position.Y + 1 ),
To = Position,
Duration = duration,
ProgressTransform = ProgressTransforms.Elastic( 3, 10 ),
Apply = f => Position = f
};
positionAnim.Complete += ( s, e ) =>
{
StateManager.StopAnimation();
};
positionAnim.Start();
}
public void PanTo( Point location )
{
Vector2 originalPosition = Position;
Vector2 targetPosition = CenteredPosition( location );
if ( originalPosition.Equals( targetPosition ) )
{
return;
}
StateManager.StartAnimation();
float distanceBetween;
Vector2.Distance( ref originalPosition, ref targetPosition, out distanceBetween );
distanceBetween = ( distanceBetween / Global.CellHeight );
double duration = MathHelper.Clamp( .05f * distanceBetween, .05f, .3f );
var positionAnim = new Vector2FromToAnimation( _clockManager )
{
From = originalPosition,
To = targetPosition,
Duration = duration,
ProgressTransform = ProgressTransforms.Snake( 1 ),
Apply = f => Position = f
};
positionAnim.Complete += ( s, e ) =>
{
StateManager.StopAnimation();
};
positionAnim.Start();
}
public Rectangle ViewportWorldBoundry()
{
Vector2 viewPortCorner = ScreenToWorld( new Vector2( 0, 0 ) );
Vector2 viewPortBottomCorner = ScreenToWorld( new Vector2( ViewportWidth, ViewportHeight ) );
return new Rectangle( (int) viewPortCorner.X, (int) viewPortCorner.Y, (int) ( viewPortBottomCorner.X - viewPortCorner.X ),
(int) ( viewPortBottomCorner.Y - viewPortCorner.Y ) );
}
public void CenterOn( Vector2 position )
{
Position = position;
}
public void CenterOn( Point location )
{
Position = CenteredPosition( location );
}
private Vector2 CenteredPosition( Point location, bool clampToMap = false )
{
var cameraPosition = new Vector2( location.X * Global.CellWidth, location.Y * Global.CellHeight );
var cameraCenteredOnTilePosition = new Vector2( cameraPosition.X + Global.HalfCellWidth,
cameraPosition.Y + Global.HalfCellHeight );
if ( clampToMap )
{
// clamp the camera so it never leaves the visible area of the map.
var cameraMax = new Vector2( Global.GameModel.Map.Width * Global.CellWidth - ViewportWidth,
Global.GameModel.Map.Height * Global.CellHeight - ViewportHeight );
return Vector2.Clamp( cameraCenteredOnTilePosition, Vector2.Zero, cameraMax );
}
return cameraCenteredOnTilePosition;
}
public Vector2 WorldToScreen( Vector2 worldPosition )
{
return Vector2.Transform( worldPosition, TranslationMatrix );
}
public Vector2 ScreenToWorld( Vector2 screenPosition )
{
return Vector2.Transform( screenPosition, Matrix.Invert( TranslationMatrix ) );
}
public void HandleInput( InputState inputState, PlayerIndex? controllingPlayer )
{
// Move the camera's position based on WASD or Right thumbstick input
Vector2 cameraMovement = Vector2.Zero;
if ( inputState.IsScrollLeft( controllingPlayer ) )
{
cameraMovement.X = -1;
}
else if ( inputState.IsScrollRight( controllingPlayer ) )
{
cameraMovement.X = 1;
}
if ( inputState.IsScrollUp( controllingPlayer ) )
{
cameraMovement.Y = -1;
}
else if ( inputState.IsScrollDown( controllingPlayer ) )
{
cameraMovement.Y = 1;
}
// to match the thumbstick behavior, we need to normalize non-zero vectors in case the user
// is pressing a diagonal direction.
if ( cameraMovement != Vector2.Zero )
{
cameraMovement.Normalize();
}
// scale our movement to move 25 pixels per second
cameraMovement *= 25f;
// move the camera
MoveCamera( cameraMovement );
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment