Press "Enter" to skip to content

Lesson 04.3: Moving in the game world

In this lesson, we will add buttons to let the player move to different locations in the game world.

Step 1: Remove the old test button.

Open MainWindow.xaml. Find, and delete, this line:

<Button Grid.Row="6" Grid.Column="1" Content="Add XP" Click="ButtonBase_OnClick"></Button>

Open MainWindow.xaml.cs. Delete the function that was called for the button Click event.

private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
    _gameSession.CurrentPlayer.ExperiencePoints = _gameSession.CurrentPlayer.ExperiencePoints + 10;
}

Step 2: Add the buttons to MainWindow.xaml

Find the label for the Combat/Movement controls, and delete it.

<Label Grid.Row="2" Grid.Column="1" Content="Combat/Movement Controls" Background="Lavender"/>

In its place, add this grid (with an inner grid) and the movement button controls.

<Grid Grid.Row="2" Grid.Column="1"
      Background="Lavender">
    <Grid.RowDefinitions>
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="255" />
    </Grid.ColumnDefinitions>
            
    <Grid Grid.Row="0" Grid.Column="1">
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Button Grid.Row="0" Grid.Column="1" Content="North"/>
        <Button Grid.Row="1" Grid.Column="0" Content="West"/>
        <Button Grid.Row="1" Grid.Column="2" Content="East"/>
        <Button Grid.Row="2" Grid.Column="1" Content="South"/>
    </Grid>
</Grid>

To make the movement button look the same, set the Height, Width, and Margin attributes for each of them:

<Button Grid.Row="0" Grid.Column="1" 
        Height="25" Width="65" Margin="10" 
        Content="North"/>
<Button Grid.Row="1" Grid.Column="0" 
        Height="25" Width="65" Margin="10" 
        Content="West"/>
<Button Grid.Row="1" Grid.Column="2" 
        Height="25" Width="65" Margin="10" 
        Content="East"/>
<Button Grid.Row="2" Grid.Column="1" 
        Height="25" Width="65" Margin="10" 
        Content="South"/>

Step 3: Now we need to make the movement buttons work.

For each movement button, add the Click attribute, at set their value to the name of functions we will create, which will be run when the player clicks the button.

<Button Grid.Row="0" Grid.Column="1" 
        Height="25" Width="65" Margin="10" 
        Click="OnClick_MoveNorth"
        Content="North"/>
<Button Grid.Row="1" Grid.Column="0" 
        Height="25" Width="65" Margin="10" 
        Click="OnClick_MoveWest"
        Content="West"/>
<Button Grid.Row="1" Grid.Column="2" 
        Height="25" Width="65" Margin="10" 
        Click="OnClick_MoveEast"
        Content="East"/>
<Button Grid.Row="2" Grid.Column="1" 
        Height="25" Width="65" Margin="10" 
        Click="OnClick_MoveSouth"
        Content="South"/>

Next, we need to create the functions that the button Click event will call. These will be in MainWindow.xaml.cs.

They can be “private”, because they will only be called by the buttons in MainWindow.xaml. The returning datatype for these functions is “void”, because the functions will not return any values.

The Click event sends two parameters to its function, so we need to accept them. For Click events, the two parameters are the sender and the event arguments. The “sender” is the object that “sends” the event (in this case, the button in MainWindow.xaml). The event arguments are any additional information that the sending object includes. The Click event sends event arguments whose datatype is “RoutedEventArgs”.

We are not going to do anything with these event arguments, in these functions (we will with other events, in the future). So, I won’t go into detail about them now. However, we do need to include the parameters with the function, because the Click event will send them. So, the function needs to have a place to accept them.

private void OnClick_MoveNorth(object sender, RoutedEventArgs e)
{
}
private void OnClick_MoveWest(object sender, RoutedEventArgs e)
{
}
private void OnClick_MoveEast(object sender, RoutedEventArgs e)
{
}
private void OnClick_MoveSouth(object sender, RoutedEventArgs e)
{
}

If we wanted to, we could change the player’s location inside these functions. But, these functions are in the View. It’s better to put the game logic inside the ViewModel, or a Model (this makes it easier to test, or connect the game to different UIs in the future).

So, we will create new functions inside the GameSession class, which will be called by the event-handling functions we just created.

public void MoveNorth()
{
}
public void MoveEast()
{
}
public void MoveSouth()
{
}
public void MoveWest()
{
}

Now we can go back to MainWindow.xaml.cs, and make the Click event functions call the functions in GameSession.cs

private void OnClick_MoveNorth(object sender, RoutedEventArgs e)
{
    _gameSession.MoveNorth();
}
private void OnClick_MoveWest(object sender, RoutedEventArgs e)
{
    _gameSession.MoveWest();
}
private void OnClick_MoveEast(object sender, RoutedEventArgs e)
{
   _gameSession.MoveEast();
}
private void OnClick_MoveSouth(object sender, RoutedEventArgs e)
{
    _gameSession.MoveSouth();
}

Next, we’ll add the code to move the player, inside the new functions in GameSession.cs.

Each of these functions will use the CurrentLocation’s X and Y coordinates, add (or subtract) 1 to the appropriate coordinate for the movement, and get the location at the new coordinates – using the LocationAt function of CurrentWorld.

public void MoveNorth()
{
    CurrentLocation = CurrentWorld.LocationAt(CurrentLocation.XCoordinate, CurrentLocation.YCoordinate + 1);
}
public void MoveEast()
{
    CurrentLocation = CurrentWorld.LocationAt(CurrentLocation.XCoordinate + 1, CurrentLocation.YCoordinate);
}
public void MoveSouth()
{
    CurrentLocation = CurrentWorld.LocationAt(CurrentLocation.XCoordinate, CurrentLocation.YCoordinate - 1);
}
public void MoveWest()
{
    CurrentLocation = CurrentWorld.LocationAt(CurrentLocation.XCoordinate - 1, CurrentLocation.YCoordinate);
}

If we run the game now, the player’s location does not change on the screen. That’s because the UI did not receive a notification that the CurrentLocation property has changed. We need to make the GameSession class implement INotifyPropertyChanged, and raise a PropertyChanged notification when the CurrentLocation changes.

To do this, we’ll do the same thing we did for the Player class.

First, at the top of the class, add “: INotifyPropertyChanged”, to declare that this class will implement the INotifyPropertyChanged interface – which lets the WPF UI know it needs to watch for PropertyChanged events from this object.

Next, we need to add the PropertyChanged event, and the OnPropertyChanged function, to implement the interface. We can copy those from the Player class.

Finally, we need to change the CurrentLocation property to use a backing variable, and call the PropertyChanged function when it gets a new value – just like we did with the properties in the Player class.

GameSession.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Engine.Models;
using Engine.Factories;
namespace Engine.ViewModels
{
    public class GameSession : INotifyPropertyChanged
    {
        private Location _currentLocation;
        public World CurrentWorld { get; set; }
        public Player CurrentPlayer { get; set; }
        public Location CurrentLocation
        {
            get {  return _currentLocation;}
            set
            {
                _currentLocation = value;
                
                OnPropertyChanged("CurrentLocation");
            }
        }
        public GameSession()
        {
            CurrentPlayer = new Player();
            CurrentPlayer.Name = "Scott";
            CurrentPlayer.CharacterClass = "Fighter";
            CurrentPlayer.HitPoints = 10;
            CurrentPlayer.Gold = 1000000;
            CurrentPlayer.ExperiencePoints = 0;
            CurrentPlayer.Level = 1;
            WorldFactory factory = new WorldFactory();
            CurrentWorld = factory.CreateWorld();
            CurrentLocation = CurrentWorld.LocationAt(0, 0);
        }
        public void MoveNorth()
        {
            CurrentLocation = CurrentWorld.LocationAt(CurrentLocation.XCoordinate, CurrentLocation.YCoordinate + 1);
        }
        public void MoveEast()
        {
            CurrentLocation = CurrentWorld.LocationAt(CurrentLocation.XCoordinate + 1, CurrentLocation.YCoordinate);
        }
        public void MoveSouth()
        {
            CurrentLocation = CurrentWorld.LocationAt(CurrentLocation.XCoordinate, CurrentLocation.YCoordinate - 1);
        }
        public void MoveWest()
        {
            CurrentLocation = CurrentWorld.LocationAt(CurrentLocation.XCoordinate - 1, CurrentLocation.YCoordinate);
        }
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Step 4: Now that we can move, we need a way to prevent the player from moving in a direction where there is no valid location. The way we’ll do that (for now) will be to hide the movement buttons when the player cannot move in a direction.

To do this, we’ll use the “Visibility” attribute on the buttons. This attribute can be set to “Collapsed”, “Hidden”, or “Visible”. “Collapsed” means the control should not be displayed, and it should not take up any space in the screen. “Hidden” means the control should not be displayed, but it will still use the same amount of space as it would if it was visible – although the space will be blank. And, we can “Visible” when we want the control displayed.

As the player moves to different locations, the hidden and visible buttons will change – depending on which directions have valid locations, from the CurrentLocation. So, we will add four new properties on the GameSession class. These will be “Boolean” properties (which hold true/false values), and will see if the World contains a location in each direction from the CurrentLocation.

If there is a location in the direction, the property for that direction will be “true”/ If there isn’t, that property will be “false”.

For these properties, we’re going to only use the “get” – no “set”. And, we will calculate the value to return for the “get”.

The calculation will look in the CurrentWorld, and use the LocationAt function to get the location in the property’s direction. If there is a Location in that direction, LocationAt will return the Location. If there is not a Location in that direction, LocationAt will return “null” (nothing).

So, we want the “HasLocation” properties to return “true”, when LocationAt returns an object (is not null). It will return “false”, when LocationAt returns “null – because there isn’t a Location in that direction. The “get” values for these properties will change as the CurrentLocation changes.

Here is the code for the four new properties:

public bool HasLocationToNorth
{
    get
    {
        return CurrentWorld.LocationAt(CurrentLocation.XCoordinate, CurrentLocation.YCoordinate + 1) != null;
    }
}
public bool HasLocationToEast
{
    get
    {
        return CurrentWorld.LocationAt(CurrentLocation.XCoordinate + 1, CurrentLocation.YCoordinate) != null;
    }
}
public bool HasLocationToSouth
{
    get
    {
        return CurrentWorld.LocationAt(CurrentLocation.XCoordinate, CurrentLocation.YCoordinate - 1) != null;
    }
}
public bool HasLocationToWest
{
    get
    {
        return CurrentWorld.LocationAt(CurrentLocation.XCoordinate - 1, CurrentLocation.YCoordinate) != null;
    }
}

Next, we need to connect these properties to the UI. But, we need a way to convert from the Boolean property values to Hidden/Visible. XAML has a built-in converter function we can use to do this.

Open MainWindow.xaml, and add this between the opening <Window> tag, and the first <Grid> control.

<Window.Resources>
    <BooleanToVisibilityConverter x:Key="BooleanToVisibility" />
</Window.Resources>

That will let us use the built-in XAML converter that converts “true” to “Visible”, and “false” to “Hidden”.

Now, we’ll set the Visibility attribute on the buttons, binding the Boolean properties, and using the BooleanToVisibility converter to convert “true” to “Visible”, and “false” to “Hidden”.

We use Binding, just like we did with the Player properties, but we need to add the Converter, to get the Visibility values. So our buttons will look like this now:

<Button Grid.Row="0" Grid.Column="1" 
        Height="25" Width="65" Margin="10" 
        Click="OnClick_MoveNorth"
        Visibility="{Binding HasLocationToNorth, Converter={StaticResource BooleanToVisibility}}"
        Content="North"/>
<Button Grid.Row="1" Grid.Column="0" 
        Height="25" Width="65" Margin="10" 
        Click="OnClick_MoveWest"
        Visibility="{Binding HasLocationToWest, Converter={StaticResource BooleanToVisibility}}"
        Content="West"/>
<Button Grid.Row="1" Grid.Column="2" 
        Height="25" Width="65" Margin="10" 
        Click="OnClick_MoveEast"
        Visibility="{Binding HasLocationToEast, Converter={StaticResource BooleanToVisibility}}"
        Content="East"/>
<Button Grid.Row="2" Grid.Column="1" 
        Height="25" Width="65" Margin="10" 
        Click="OnClick_MoveSouth"
        Visibility="{Binding HasLocationToSouth, Converter={StaticResource BooleanToVisibility}}"
        Content="South"/>

Step 5: Notify the UI when the HasLocation properties change.

If we run the game right now, the buttons never disappear – even when there is no Location in their direction. That’s because we haven’t configured the GameSession class to raise a notification when the HasLocation properties’ values change.

We need to add the same type of PropertyChanged notification that we did for the CurrentLocation property. However, the HasLocation properties don’t have a “set”, which is where we raised the PropertyChanged notification for CurrentLocation.

Fortunately, the values for the HasLocation properties will change at the same time the CurrentLocation property changes. So, we can add the PropertyChanged notifications for the HasLocation properties inside the CurrentLocation property’s setter.

Change the CurrentLocation property’s code to this:

public Location CurrentLocation
{
    get {  return _currentLocation;}
    set
    {
        _currentLocation = value;
                
        OnPropertyChanged("CurrentLocation");
        OnPropertyChanged("HasLocationToNorth");
        OnPropertyChanged("HasLocationToEast");
        OnPropertyChanged("HasLocationToWest");
        OnPropertyChanged("HasLocationToSouth");
    }
}

Now, when the CurrentLocation changes, the UI will receive notification that the HasLocation properties have changed, and will hide (or show) the direction buttons.

Step 6: Run the game, and try moving around in the worlld. You should see the buttons hide/appear, as you move. You should also see the Location image and description change.

Final Source Code

GameSession.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Engine.Models;
using Engine.Factories;
namespace Engine.ViewModels
{
    public class GameSession : INotifyPropertyChanged
    {
        private Location _currentLocation;
        public World CurrentWorld { get; set; }
        public Player CurrentPlayer { get; set; }
        public Location CurrentLocation
        {
            get {  return _currentLocation;}
            set
            {
                _currentLocation = value;
                
                OnPropertyChanged("CurrentLocation");
                OnPropertyChanged("HasLocationToNorth");
                OnPropertyChanged("HasLocationToEast");
                OnPropertyChanged("HasLocationToWest");
                OnPropertyChanged("HasLocationToSouth");
            }
        }
        public bool HasLocationToNorth
        {
            get
            {
                return CurrentWorld.LocationAt(CurrentLocation.XCoordinate, CurrentLocation.YCoordinate + 1) != null;
            }
        }
        public bool HasLocationToEast
        {
            get
            {
                return CurrentWorld.LocationAt(CurrentLocation.XCoordinate + 1, CurrentLocation.YCoordinate) != null;
            }
        }
        public bool HasLocationToSouth
        {
            get
            {
                return CurrentWorld.LocationAt(CurrentLocation.XCoordinate, CurrentLocation.YCoordinate - 1) != null;
            }
        }
        public bool HasLocationToWest
        {
            get
            {
                return CurrentWorld.LocationAt(CurrentLocation.XCoordinate - 1, CurrentLocation.YCoordinate) != null;
            }
        }
        public GameSession()
        {
            CurrentPlayer = new Player();
            CurrentPlayer.Name = "Scott";
            CurrentPlayer.CharacterClass = "Fighter";
            CurrentPlayer.HitPoints = 10;
            CurrentPlayer.Gold = 1000000;
            CurrentPlayer.ExperiencePoints = 0;
            CurrentPlayer.Level = 1;
            WorldFactory factory = new WorldFactory();
            CurrentWorld = factory.CreateWorld();
            CurrentLocation = CurrentWorld.LocationAt(0, 0);
        }
        public void MoveNorth()
        {
            CurrentLocation = CurrentWorld.LocationAt(CurrentLocation.XCoordinate, CurrentLocation.YCoordinate + 1);
        }
        public void MoveEast()
        {
            CurrentLocation = CurrentWorld.LocationAt(CurrentLocation.XCoordinate + 1, CurrentLocation.YCoordinate);
        }
        public void MoveSouth()
        {
            CurrentLocation = CurrentWorld.LocationAt(CurrentLocation.XCoordinate, CurrentLocation.YCoordinate - 1);
        }
        public void MoveWest()
        {
            CurrentLocation = CurrentWorld.LocationAt(CurrentLocation.XCoordinate - 1, CurrentLocation.YCoordinate);
        }
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}
MainWindow.xaml
<Window x:Class="WPFUI.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"
        xmlns:local="clr-namespace:WPFUI"
        mc:Ignorable="d"
        FontSize="11pt"
        Title="Scott's Awesome Game" Height="768" Width="1024">
    <Window.Resources>
        <BooleanToVisibilityConverter x:Key="BooleanToVisibility" />
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="225"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="250"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Content="Menu" Background="AliceBlue"/>
        <Grid Grid.Row="1" Grid.Column="0" Background="Aquamarine">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
            
            <Label Grid.Row="0" Grid.Column="0" Content="Name:"/>
            <Label Grid.Row="0" Grid.Column="1" Content="{Binding CurrentPlayer.Name}"/>
            <Label Grid.Row="1" Grid.Column="0" Content="Class:"/>
            <Label Grid.Row="1" Grid.Column="1" Content="{Binding CurrentPlayer.CharacterClass}"/>
            <Label Grid.Row="2" Grid.Column="0" Content="Hit points:"/>
            <Label Grid.Row="2" Grid.Column="1" Content="{Binding CurrentPlayer.HitPoints}"/>
            <Label Grid.Row="3" Grid.Column="0" Content="Gold:"/>
            <Label Grid.Row="3" Grid.Column="1" Content="{Binding CurrentPlayer.Gold}"/>
            <Label Grid.Row="4" Grid.Column="0" Content="XP:"/>
            <Label Grid.Row="4" Grid.Column="1" Content="{Binding CurrentPlayer.ExperiencePoints}"/>
            <Label Grid.Row="5" Grid.Column="0" Content="Level:"/>
            <Label Grid.Row="5" Grid.Column="1" Content="{Binding CurrentPlayer.Level}"/>
        </Grid>
        <Grid Grid.Row="1" Grid.Column="1"
              Background="Beige">
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="3*"/>
                <ColumnDefinition Width="2*"/>
            </Grid.ColumnDefinitions>
            
            <Border Grid.Row="0" Grid.Column="1"
                    BorderBrush="Gainsboro"
                    BorderThickness="1">
                
                <Grid Margin="3">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="*"/>
                        <RowDefinition Height="Auto"/>
                    </Grid.RowDefinitions>
                    
                    <TextBlock Grid.Row="0"
                               HorizontalAlignment="Center"
                               Text="{Binding CurrentLocation.Name}"/>
                    
                    <Image Grid.Row="1"
                           HorizontalAlignment="Center"
                           VerticalAlignment="Center"
                           Height="125"
                           Width="125"
                           Source="{Binding CurrentLocation.ImageName}"/>
                    
                    <TextBlock Grid.Row="2"
                               HorizontalAlignment="Center"
                               Text="{Binding CurrentLocation.Description}"
                               TextWrapping="Wrap"/>
                </Grid>
                
            </Border>
        </Grid>
        <Label Grid.Row="2" Grid.Column="0" Content="Inventory/Quests" Background="BurlyWood"/>
        <Grid Grid.Row="2" Grid.Column="1"
              Background="Lavender">
            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="255" />
            </Grid.ColumnDefinitions>
            
            <Grid Grid.Row="0" Grid.Column="1">
                <Grid.RowDefinitions>
                    <RowDefinition Height="*" />
                    <RowDefinition Height="*" />
                    <RowDefinition Height="*" />
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
                <Button Grid.Row="0" Grid.Column="1" 
                        Height="25" Width="65" Margin="10" 
                        Click="OnClick_MoveNorth"
                        Visibility="{Binding HasLocationToNorth, Converter={StaticResource BooleanToVisibility}}"
                        Content="North"/>
                <Button Grid.Row="1" Grid.Column="0" 
                        Height="25" Width="65" Margin="10" 
                        Click="OnClick_MoveWest"
                        Visibility="{Binding HasLocationToWest, Converter={StaticResource BooleanToVisibility}}"
                        Content="West"/>
                <Button Grid.Row="1" Grid.Column="2" 
                        Height="25" Width="65" Margin="10" 
                        Click="OnClick_MoveEast"
                        Visibility="{Binding HasLocationToEast, Converter={StaticResource BooleanToVisibility}}"
                        Content="East"/>
                <Button Grid.Row="2" Grid.Column="1" 
                        Height="25" Width="65" Margin="10" 
                        Click="OnClick_MoveSouth"
                        Visibility="{Binding HasLocationToSouth, Converter={StaticResource BooleanToVisibility}}"
                        Content="South"/>
            </Grid>
        </Grid>
    </Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Engine.ViewModels;
namespace WPFUI
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private GameSession _gameSession;
        public MainWindow()
        {
            InitializeComponent();
            _gameSession = new GameSession();
            DataContext = _gameSession;
        }
        private void OnClick_MoveNorth(object sender, RoutedEventArgs e)
        {
            _gameSession.MoveNorth();
        }
        private void OnClick_MoveWest(object sender, RoutedEventArgs e)
        {
            _gameSession.MoveWest();
        }
        private void OnClick_MoveEast(object sender, RoutedEventArgs e)
        {
            _gameSession.MoveEast();
        }
        private void OnClick_MoveSouth(object sender, RoutedEventArgs e)
        {
            _gameSession.MoveSouth();
        }
    }
}

NEXT LESSON: Lesson 04.4: Improving the World – Inheritance and INotifyPropertyChanged

PREVIOUS LESSON: Lesson 04.2: Creating the World

18 Comments

  1. Alex Utkin
    Alex Utkin 2022-06-08

    Hello! In your video you say that Click events change color when handler is added. In my IDE (VS 2022) the color remains the same regardless of handler being has been added or not. Could you please tell how to fix it?

    • SOSCSRPG
      SOSCSRPG 2022-06-08

      Hi Alex,

      My VS Community 2022 still shows different colors, depending on whether or not the handler function exists. I do have to move the cursor out of the name of the function, for the color to change.

      Do you have ReSharper, or some other editor plugin installed? Have you changed Visual Studio’s color theme? Those might be a source of the problem. There might also be solutions in this StackOverflow post: https://stackoverflow.com/questions/24254362/visual-studio-intellisense-color-coding-not-working

      • Alex Utkin
        Alex Utkin 2022-06-10

        I dont have Reshaper, but I have Sonar installed. I use Dark theme, but tried default light theme as well. Colorcoding works with all other stuff, like turns yellow for bindings, and grey with plain text. It is just events like “Click” that remain the same color regardles of handler presense.

        Well, tried thing on StackOverflow, seems just gotta be extra watchful for adding handlers then.

    • Ryan Fuller
      Ryan Fuller 2022-11-30

      Same on my end, It’s only the click. I’ll have to look into it sometime and If I find a reason I’ll update here

  2. Jakob Stever
    Jakob Stever 2022-06-18

    Inputting “_gameSession.MoveNorth();” results in “MoveNorth” getting the error CS1061. It says ” ‘GameSession’ does not contain a definition for ‘MoveWest’ and no accessible extension method ‘MoveWest’ accepting a first argument of type ‘GameSession’ could be found (are you missing a using directive or an assembly reference?)”.

    • SOSCSRPG
      SOSCSRPG 2022-06-19

      Hi Jakob,

      That message probably means one of this things is happening:
      1. The WPF project does not have a reference to the Engine project.
      2. The GameSession class, or MoveNorth function, is not “public”
      3. There’s a typo in the MoveNorth function name (function names are case-sensitive)

      Check on those and let me know if that doesn’t lead to a solution. If it doesn’t, can you upload your solution (including the directories under it, and all the files in those directories) to GitHub, Dropbox, or some other file-sharing location so I can look at it?

  3. Jason
    Jason 2023-01-24

    So I copied and pasted the code from this page and I am receiving 4 errors. I get one for each direction, here is one:

    CS1061 ‘MainWindow’ does not contain a definition for ‘OnClick_MoveNorth’ and no accessible extension method ‘OnClick_MoveNorth’ accepting a first argument of type ‘MainWindow’ could be found (are you missing a using directive or an assembly reference?) WPFUI F:\Programming\SOSCSRPG\WPFUI\MainWindow.xaml

    I would greatly appreciate any advice because this tutorial is fantastic!

    FWIW, I am using VS 2022

    • SOSCSRPG
      SOSCSRPG 2023-01-24

      Hi Jason,

      Does your MainWindow.xaml.cs have the second code block from Step 3 (which are changed in the fourth code block of step 3)?

      If that doesn’t solve the problem, can you upload your solution (including the directories under it, and all the files in those directories) to GitHub, Dropbox, or some other file-sharing location so I can look at it?

      If you haven’t used GitHub before, here is some information (and a video) on how to upload your solution to GitHub and share it with me. https://codingwithscott.com/how-to-connect-visual-studio-community-edition-2022-to-github/

      • Jason
        Jason 2023-01-25

        First off, thank you so much for answering so quickly, I greatly appreciate it!

        My MainWindow.xaml.cs matches your Final Source Code.

        I unloaded and reloaded the solution, and that seems to have resolved the issue I was having. However, now I am getting an error on GameSession.cs:

        System.NullReferenceException

        Engine.ViewModels.GameSession.CurrentLocation.get returned null.

        Repository: https://github.com/ADKCourage/SOSCSRPG

  4. Alex
    Alex 2024-01-24

    Hello! Could you clarify this. It seems to me that many classes might require INotifyPropertyChanged inteface to be implemented. In case of WPF app, is it just easier to have every class implement INotify jsut in case or there is a logic there?

    • SOSCSRPG
      SOSCSRPG 2024-01-24

      Hi Alex!

      You could have every class implement INotifyPropertyChanged when any of their properties changes. But, there are many “service” classes that do work that isn’t displayed in the UI. Or, the work they do eventually changes a property in one of the “model” classes that is displayed in the UI and does implement INotifyPropertyChanged. So those programs don’t really need to implement INotifyPropertyChanged. On a small program like this, it won’t make much of a difference. But, if this was a larger program and speed was more important, you wouldn’t want to raise all those events for properties that nothing is listening for.

      There is a programming style where all (or most) communication between objects is done with events (property changed events and others), but that can quickly become difficult to understand. When looking at the code, it is more difficult to see/understand the “flow” of the program – what function calls what other function.

  5. Barney Haywood
    Barney Haywood 2024-02-27

    Hi Scott,
    I just wanted to drop you a line to thank you for this series of tutorials. I’m old (hehe) and I had ZERO experience of programming in any language except BASIC in my youth, 40 years ago. I decided to start learning C# as a hobby last month, and I’ve found your tutorials really useful & easy to follow.
    As a test, to check I understood what you were doing in this lesson, I decided to add a “warp home” button to the UI. (It might not fit in with the game – hehe – but I wanted to check my understanding. )
    I added the following code:

    To MainWindow.xaml
    <Button Grid.Row=”1″ Grid.Column=”1″ Margin=”10″ Click=”OnClick_WarpHome” Text=”Warp Home” TextWrapping=”Wrap” TextAlignment=”Center” / >

    To the code behind / MainWindow.xaml.cs
    private void OnClick_WarpHome(object sender, RoutedEventArgs e)
    {
    _gameSession.WarpHome();
    }
    and to GameSession.cs
    public void WarpHome()
    {
    CurrentLocation = CurrentWorld.LocationAt(0, -1);
    }
    and it worked perfectly first time! So, many thanks for these lessons – I’m clearly understanding and learning!

    • SOSCSRPG
      SOSCSRPG 2024-02-27

      You’re welcome (from another old guy)!

      That’s a cool feature to add, and thank you for sharing it. It’s cool to see people expand on the game and make it their own.

      I got your follow-up message and tried to fix the XAML formatting. Please let me know if it needs a correction.

      • Barney Haywood
        Barney Haywood 2024-02-29

        Thanks for your prompt reply!
        Just a note that I couldn’t get the text “Warp Home” to wrap in the button without defining a TextBlock inside the button definer. I can’t get wrap to work in the Button property without putting a TextBlock in there as it tells me that the properties “‘Text’ & ‘TextWrapping’ are not found in type ‘Button'”. But if you make a TextBox inside the button, it works.
        (It wouldn’t be a problem if I just made the text shorter, of course (“Home”, for example) , but I like creating little problems for myself to solve. 🙂 hehe )
        Many thanks again!

        • SOSCSRPG
          SOSCSRPG 2024-03-01

          You’re welcome. Something layout and formatting in WPF can be a bit tricky. If you share code in the future, https://gist.github.com/ is nice. WordPress (what this site is using) is not the best for code – even inside the pages.

Leave a Reply

Your email address will not be published. Required fields are marked *