Search code examples
wpfcanvas2drectangles

WPF 2D Game: Making a Camera that follows an object on a Canvas


I am trying to make a simple 2D Game in WPF, and I've come across a problem I can't solve.

Let's say I have a player on a 700x700 Canvas, but my MainWindow's Width and Height are set to 400. I want to be able to have a camera like feature, that follows a rectangle object on the canvas (this object symbolises the player) and shows the corresponding portion of the canvas whenever the player moves.

In theory how could I implement a feature like this?


Solution

  • Here's a rough sample of how to do that with a ScrollViewer. Use the arrow keys to move the player around while keeping it in the view of the "camera". The canvas' background is set to a radial-brush, to see the camera moving.

    player at left/top corner

    player at right/top corner

    MainWindow.xaml

    <Window x:Class="WpfApp6.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            mc:Ignorable="d"
            Title="MainWindow"
            Background="#222"
            Loaded="Window_Loaded"
            SizeToContent="WidthAndHeight"
            PreviewKeyDown="Window_PreviewKeyDown">
    
        <Grid>
            <ScrollViewer x:Name="CanvasViewer" 
                          HorizontalScrollBarVisibility="Hidden"
                          VerticalScrollBarVisibility="Hidden">
                <Canvas x:Name="Canvas"
                        IsHitTestVisible="False">
                    <Canvas.Background>
                        <RadialGradientBrush>
                            <GradientStop Offset="0"
                                          Color="Orange" />
                            <GradientStop Offset="1"
                                          Color="Blue" />
                        </RadialGradientBrush>
                    </Canvas.Background>
                </Canvas>
            </ScrollViewer>
        </Grid>
    
    </Window>
    

    MainWindow.xaml.cs

    public partial class MainWindow : Window
    {
        double _playerSize;
    
        Rectangle _playerRect;
        Vector _playerPosition;
    
        public MainWindow()
        {
            InitializeComponent();
        }
    
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            InitializeSizes();
            InitializePlayerRect();
        }
    
        #region initialize
        private void InitializeSizes()
        {
            _playerSize = 50;
    
            Canvas.Width = 700;
            Canvas.Height = 700;
    
            CanvasViewer.Width = 400;
            CanvasViewer.Height = 400;
        }
    
        private void InitializePlayerRect()
        {
            _playerRect = new Rectangle
            {
                Fill = Brushes.Lime,
                Width = _playerSize,
                Height = _playerSize,
                HorizontalAlignment = HorizontalAlignment.Left,
                VerticalAlignment = VerticalAlignment.Top
            };
    
            Canvas.Children.Add(_playerRect);
        }
        #endregion
    
        #region move player
        private void Window_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            switch (e.Key)
            {
                case Key.Left: MovePlayerLeft(); break;
                case Key.Up: MovePlayerUp(); break;
                case Key.Right: MovePlayerRight(); break;
                case Key.Down: MovePlayerDown(); break;
            }
        }
    
        private void MovePlayerLeft()
        {
            var newX = _playerPosition.X - _playerSize;
            _playerPosition.X = Math.Max(0, newX);
            UpdatePlayerPositionAndCamera();
        }
    
        private void MovePlayerUp()
        {
            var newY = _playerPosition.Y - _playerSize;
            _playerPosition.Y = Math.Max(0, newY);
            UpdatePlayerPositionAndCamera();
        }
    
        private void MovePlayerRight()
        {
            var newX = _playerPosition.X + _playerSize;
            _playerPosition.X = Math.Min(Canvas.Width - _playerSize, newX);
            UpdatePlayerPositionAndCamera();
        }
    
        private void MovePlayerDown()
        {
            var newY = _playerPosition.Y + _playerSize;
            _playerPosition.Y = Math.Min(Canvas.Height - _playerSize, newY);
            UpdatePlayerPositionAndCamera();
        }
        #endregion
    
        #region update player and camera
        private void UpdatePlayerPositionAndCamera()
        {
            UpdatePlayerPosition();
            UpdateCamera();
        }
    
        private void UpdatePlayerPosition()
        {
            // move the playerRect to it's new position
            _playerRect.Margin = new Thickness(_playerPosition.X, _playerPosition.Y, 0, 0);
        }
    
        private void UpdateCamera()
        {
            // calculate offset of scrollViewer, relative to actual position of the player
            var offsetX = _playerPosition.X / 2;
            var offsetY = _playerPosition.Y / 2;
    
            // move the "camera"
            CanvasViewer.ScrollToHorizontalOffset(offsetX);
            CanvasViewer.ScrollToVerticalOffset(offsetY);
        }
        #endregion
    }