Simple 2D Camera Script?

Hello. I was wondering if anybody here had a simple 2D camera script I could follow? I tried Oyyou’s method, but it didn’t work for me. I just want a simple camera that I can put in the Update function and have it follow the player around. If anybody could link me theirs, that would be great.

The Oyyou one should work.
If you make a camera transform matrix and use Matrix.CreateTranslation and give it the x and y amounts to move the camera and pass it in the spriteBatch.Begin, it should work. Personally I always just draw only what’s visible using a span through an array section. Each frame you can update the x world coordinate(where on the map the player is) by some amount and offset the drawing position by the opposite amount (ie: player moves ahead 2, drawing position start goes back 2). Then when you pass a “tile size” amount (ie: 64 pixels), you increase the starting position in the tile array by 1 and reset the starting draw position to zero and repeat that process when the player holds down the right arrow.

I tried implementing Oyyou’s camera again, but for some reason it said that the “target was null.”

System.NullReferenceException: ‘Object reference not set to an instance of an object.’

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using BallGame;
using BallGame.Sprites;
using BallGame.Models;
using BallGame.Core;
namespace BallGame.Core
{
public class Camera
{

    public Matrix Transform { get; private set; }
    public void Follow(Sprite target)
    {
        var position = Matrix.CreateTranslation(
          - target.Position.X - (target.Rectangle.Width / 2),
          - target.Position.Y - (target.Rectangle.Height / 2),
          0);
        var offset = Matrix.CreateTranslation(
            MainGame.ScreenWidth / 2,
            MainGame.ScreenHeight / 2,
            0);
        Transform = position * offset;
    }
}

}

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System;
using BallGame.Models;
using System.Collections.Generic;
using BallGame.Sprites;
using BallGame.Core;
namespace BallGame
{
///


/// This is the main type for your game.
///

public class MainGame : Game
{

    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;
    public static int ScreenWidth;
    public static int ScreenHeight;
    
    private List<Component> components;
    
    private Camera Camera;
    public PlayerTexture Player;
    private List<Sprite> Sprites;
    public MainGame()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
    }
    protected override void Initialize()
    {
        ScreenHeight = graphics.PreferredBackBufferHeight;
        ScreenWidth = graphics.PreferredBackBufferWidth;
        ScreenHeight = 1508;
        ScreenWidth = 384;
        base.Initialize();
    }
    /// <summary>
    /// LoadContent will be called once per game and is the place to load
    /// all of your content.
    /// </summary>
    protected override void LoadContent()
    {
        // Create a new SpriteBatch, which can be used to draw textures.
        spriteBatch = new SpriteBatch(GraphicsDevice);
        Camera = new Camera();
        var PlayerTexture = Content.Load<Texture2D>("Player");
        var GroundTexture = Content.Load<Texture2D>("Ground");
        
        components = new List<Component>();
        Sprites = new List<Sprite>()
  {
    new PlayerTexture(PlayerTexture)
    {
      Input = new Input()
      {
        Left = Keys.A,
        Right = Keys.D,
        Up = Keys.W,
        Down = Keys.S,
      },
      Position = new Vector2(100, 100),
      Colour = Color.Blue,
      Speed = 4,
    },
   new Ground(GroundTexture)
   {
       Position = new Vector2(100, 300),
       
   },
   new Ground(GroundTexture)
   {
       Position = new Vector2(146, 300),
   },
    new Ground(GroundTexture)
   {
       Position = new Vector2(192, 300),
   },
   new Ground(GroundTexture)
   {
       Position = new Vector2(238, 300),
   },
    new Ground(GroundTexture)
   {
       Position = new Vector2(284, 300),
   },
     new Ground(GroundTexture)
   {
       Position = new Vector2(330, 300),
   },
       new Ground(GroundTexture)
   {
       Position = new Vector2(376, 300),
   },
         new Ground(GroundTexture)
   {
       Position = new Vector2(422, 300),
   },
           new Ground(GroundTexture)
   {
       Position = new Vector2(468, 300),
   },
             new Ground(GroundTexture)
   {
       Position = new Vector2(514, 300),
   },
               new Ground(GroundTexture)
   {
       Position = new Vector2(558, 300),
   },
        new Ground(GroundTexture)
   {
       Position = new Vector2(600, 300),
   },
        new Ground(GroundTexture)
   {
       Position = new Vector2(730, 300),
   },
  };
        // TODO: use this.Content to load your game content here
    }
    /// <summary>
    /// UnloadContent will be called once per game and is the place to unload
    /// game-specific content.
    /// </summary>
    protected override void UnloadContent()
    {
        // TODO: Unload any non ContentManager content here
    }
    /// <summary>
    /// Allows the game to run logic such as updating the world,
    /// checking for collisions, gathering input, and playing audio.
    /// </summary>
    /// <param name="gameTime">Provides a snapshot of timing values.</param>
    protected override void Update(GameTime gameTime)
    {
        foreach (var component in components)
        {
            component.Update(gameTime);
        }
        Camera.Follow(Player);
        
      
        base.Update(gameTime);
    }
    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.NavajoWhite);
        spriteBatch.Begin(transformMatrix: Camera.Transform);
        foreach (var component in components)
            component.Draw(gameTime, spriteBatch);
       
        spriteBatch.End();
        base.Draw(gameTime);
    }
}

}

I have both classes write here.

The Camera class is good. The Game class has a couple bugs. The sprite list is used instead of components list so that’s a minor change I’d make - just comment out or remove the component stuff in the Game1 class (altho can keep the class since sprite uses it)
I’d call the PlayerTexture class Player instead and use var player just to make the code seem more clear since Player would be a sprite type which holds texture and other things too. (not really a bug)
The graphics resolution in init is set by assigning graphics.PreferredBackBufferWidth = ScreenWidth = 1508 (for example)
Small bug:
Then you use graphics.Apply();
If you change the ScreenHeight and ScreenWidth variables after instead of before, it doesn’t set the actual resolution.

bug:
Need to assign player = new Player(PlayerTexture) { // input keys, position, color, speed }
This is especially since player is undefined and then later will try to access a null object in Update which will cause an error.
And then after go:
sprites = new List() {
player,
// ground stuff after
};
There’s might be a possibility of naming ambiguity which could mess up the way this part compiles.
No need for the component list since sprite inherits it and so it’ll be built in.

bug:
In update
Need to replace the foreach components with:
foreach (var sprite in sprites) { sprite.Update(gameTime); }

bug:
In Draw:
Need to replace the foreach components with
foreach (var sprite in sprites) { sprite.Draw(gameTime, spriteBatch); }

Hopefully that helps.
Here’s what I would have:

public class MainGame : Game
{
    GraphicsDeviceManager graphics;
    SpriteBatch       spriteBatch;       
    public static int ScreenWidth;
    public static int ScreenHeight;        
    private Camera camera;
    public  Player player;
    //private List<Component> components; // Not using - using Sprites list instead
    private List<Sprite> sprites;

    public MainGame()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
    }

        
    protected override void Initialize()
    {
        ScreenWidth  = graphics.PreferredBackBufferWidth = 1508;
        ScreenHeight = graphics.PreferredBackBufferHeight = 384;
        graphics.ApplyChanges();
        base.Initialize();
    }


    protected override void LoadContent()
    {
        spriteBatch = new SpriteBatch(GraphicsDevice);
        camera = new Camera();
            
        var player_tex = Content.Load<Texture2D>("Player");
        var ground_tex = Content.Load<Texture2D>("Ground");

        player = new Player(player_tex)
        {
            Input = new Input()
            {
                Left  = Keys.A,
                Right = Keys.D,
                Up    = Keys.W,
                Down  = Keys.S,
            },
            Position = new Vector2(100, 100),
            Colour = Color.Blue,
            Speed = 4,
        };            
        sprites = new List<Sprite>()
        {
            player,    
            new Ground(ground_tex) { Position = new Vector2(100, 300), },
            new Ground(ground_tex) { Position = new Vector2(146, 300), },
            new Ground(ground_tex) { Position = new Vector2(192, 300), },
            new Ground(ground_tex) { Position = new Vector2(238, 300), },
            new Ground(ground_tex) { Position = new Vector2(284, 300), },
            new Ground(ground_tex) { Position = new Vector2(330, 300), },
            new Ground(ground_tex) { Position = new Vector2(376, 300), },
            new Ground(ground_tex) { Position = new Vector2(468, 300), },
            new Ground(ground_tex) { Position = new Vector2(514, 300), },
            new Ground(ground_tex) { Position = new Vector2(558, 300), },
            new Ground(ground_tex) { Position = new Vector2(600, 300), },
            new Ground(ground_tex) { Position = new Vector2(730, 300), },
        };
    }
    protected override void UnloadContent() { }

        
    protected override void Update(GameTime gameTime)
    {
        if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape)) Exit();           
        foreach (var sprite in sprites) { sprite.Update(gameTime); }
        camera.Follow(player);
        base.Update(gameTime);
    }


    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.DarkGreen);

        spriteBatch.Begin(transformMatrix: camera.Transform);            
        foreach (var sprite in sprites) { sprite.Draw(gameTime, spriteBatch); }
        spriteBatch.End();

        base.Draw(gameTime);
    }
}

And Camera class should be ok.
Sprite class was probably something like this:

public class Sprite : Component
{
    protected Texture2D _texture;       
    public Vector2 Position { get; set; }
    public Color Colour;
    public Rectangle Rectangle {
        get { return new Rectangle((int)Position.X,(int)Position.Y,_texture.Width,_texture.Height); }
    }
    public override void Draw(GameTime gameTime, SpriteBatch spriteBatch)
    {
        spriteBatch.Draw(_texture, Position, Colour);
    }
    public Sprite(Texture2D texture)
    {
        _texture = texture;
        Colour = Color.White;
    }
    public override void Update(GameTime gameTime)
    {
            
    }
}

And player class something like this:

public class Player : Sprite
{
    public Input Input;
    public float Speed;

    public Player(Texture2D texture) : base(texture) {}       

    public override void Update(Microsoft.Xna.Framework.GameTime gameTime)
    {
        var velocity = new Vector2();            
        if (Keyboard.GetState().IsKeyDown(Input.Up))         velocity.Y = -Speed;
        else if (Keyboard.GetState().IsKeyDown(Input.Down))  velocity.Y = Speed;
        if (Keyboard.GetState().IsKeyDown(Input.Left))       velocity.X = -Speed;
        else if (Keyboard.GetState().IsKeyDown(Input.Right)) velocity.X = Speed;

        Position += velocity;
    }
}

Ground class probably virtually empty and inherits sprite. Could probably add properties to it later.

Hope it works :slight_smile:

This is the best solution I’ve found over the years:

http://www.david-amador.com/2009/10/xna-camera-2d-with-zoom-and-rotation/

It was made for XNA, it may not work directly, but here is a working example that I did:

1 Like

I got it. Thanks you guys.

1 Like