Lesson 07.2: Adding Monsters to Locations

In this lesson, we will add the monsters to the locations.

 

 

Steps

Step 1: Create \Engine\Models\MonsterEncounter.cs

We want to have the ability to have different types of monsters at a Location, and different odds of encountering each type of monster. This will let us have “rare” monsters, that are difficult to find.

We will use this class to hold the ID of the monster, and the chance of encountering it. The “ChanceOfEncountering” values do not need to total up to a specific value. You can put in any numbers, and our “get random monster” function will handle it,

 

MonsterEncounter.cs

namespace Engine.Models
{
    public class MonsterEncounter
    {
        public int MonsterID { get; set; }
        public int ChanceOfEncountering { get; set; }

        public MonsterEncounter(int monsterID, int chanceOfEncountering)
        {
            MonsterID = monsterID;
            ChanceOfEncountering = chanceOfEncountering;
        }
    }
}

 

Step 2: Modify \Engine\Models\Location.cs

To store the MonsterEncounter objects, we add a new MonstersHere property – which is a List of MonsterEncounter objects.

I created an AddMonster function, to populate this property.

We could directly add values to the property. However, this function lets us check if the monster has already been added to the list. If it has, we change its ChanceOfEncountering value to the new value. If the monster has not already been added to the list, we create a new MonsterEncounter object, and add it to MonstersHere.

 

The GetMonster function will determine which monster the location has, and instantiate a new Monster object for the Player to fight.

If there are not any objects in the MonstersHere list, the function returns null.

Otherwise, if adds up all the ChancesOfEncountering, for the MonsterEncounter objects. It gets a random number between 1 and the total ChancesOfEncountering. Then, it loops through the MonsterEncounter objects until it finds the monster to select.

When it determines the random monster, it instantiates a new Mosnter object, through the MonsterFactory, and returns that object.

If the function hasn’t selected a monster, the final line of the function will return a Monster from the last MonsterEncounter object. This is just in case there is a problem. We want to make sure this function always returns a monster, if there are monsters at the location.

 

Location.cs

using System.Collections.Generic;
using System.Linq;
using Engine.Factories;

namespace Engine.Models
{
    public class Location
    {
        public int XCoordinate { get; set; }
        public int YCoordinate { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public string ImageName { get; set; }
        public List<Quest> QuestsAvailableHere { get; set; } = new List<Quest>();

        public List<MonsterEncounter> MonstersHere { get; set; } =
            new List<MonsterEncounter>();

        public void AddMonster(int monsterID, int chanceOfEncountering)
        {
            if(MonstersHere.Exists(m => m.MonsterID == monsterID))
            {
                // This monster has already been added to this location.
                // So, overwrite the ChanceOfEncountering with the new number.
                MonstersHere.First(m => m.MonsterID == monsterID)
                            .ChanceOfEncountering = chanceOfEncountering;
            }
            else
            {
                // This monster is not already at this location, so add it.
                MonstersHere.Add(new MonsterEncounter(monsterID, chanceOfEncountering));
            }
        }

        public Monster GetMonster()
        {
            if(!MonstersHere.Any())
            {
                return null;
            }

            // Total the percentages of all monsters at this location.
            int totalChances = MonstersHere.Sum(m => m.ChanceOfEncountering);

            // Select a random number between 1 and the total (in case the total chances is not 100).
            int randomNumber = RandomNumberGenerator.NumberBetween(1, totalChances);

            // Loop through the monster list, 
            // adding the monster's percentage chance of appearing to the runningTotal variable.
            // When the random number is lower than the runningTotal,
            // that is the monster to return.
            int runningTotal = 0;

            foreach(MonsterEncounter monsterEncounter in MonstersHere)
            {
                runningTotal += monsterEncounter.ChanceOfEncountering;

                if(randomNumber <= runningTotal)
                {
                    return MonsterFactory.GetMonster(monsterEncounter.MonsterID);
                }
            }

            // If there was a problem, return the last monster in the list.
            return MonsterFactory.GetMonster(MonstersHere.Last().MonsterID);
        }
    }
}

 

Step 3: Modify \Engine\Factories\WorldFactory.cs

Now we can add monsters to the locations, inside the CreateWorld function.

We will add rats (MonsterID = 2) to the farmer’s field (at coordinates -2, -1), snakes (MonsterID = 1) to the herbalist’s garden (at coordinates 0, 2), and spiders (MonsterID = 3) to the Spider Forest (at coordinates 2, 0).

For now, these will be the only locations with monsters, and they will only have one monster each.

 

WorldFactory.cs

using Engine.Models;

namespace Engine.Factories
{
    internal static class WorldFactory
    {
        internal static World CreateWorld()
        {
            World newWorld = new World();

            newWorld.AddLocation(-2, -1, "Farmer's Field", 
                "There are rows of corn growing here, with giant rats hiding between them.", 
                "/Engine;component/Images/Locations/FarmFields.png");

            newWorld.LocationAt(-2, -1).AddMonster(2, 100);

            newWorld.AddLocation(-1, -1, "Farmer's House",
                "This is the house of your neighbor, Farmer Ted.",
                "/Engine;component/Images/Locations/Farmhouse.png");

            newWorld.AddLocation(0, -1, "Home", 
                "This is your home", 
                "/Engine;component/Images/Locations/Home.png");

            newWorld.AddLocation(-1, 0, "Trading Shop",
                "The shop of Susan, the trader.",
                "/Engine;component/Images/Locations/Trader.png");

            newWorld.AddLocation(0, 0, "Town square",
                "You see a fountain here.",
                "/Engine;component/Images/Locations/TownSquare.png");

            newWorld.AddLocation(1, 0, "Town Gate",
                "There is a gate here, protecting the town from giant spiders.",
                "/Engine;component/Images/Locations/TownGate.png");

            newWorld.AddLocation(2, 0, "Spider Forest",
                "The trees in this forest are covered with spider webs.",
                "/Engine;component/Images/Locations/SpiderForest.png");

            newWorld.LocationAt(2, 0).AddMonster(3, 100);

            newWorld.AddLocation(0, 1, "Herbalist's hut",
                "You see a small hut, with plants drying from the roof.",
                "/Engine;component/Images/Locations/HerbalistsHut.png");

            newWorld.LocationAt(0, 1).QuestsAvailableHere.Add(QuestFactory.GetQuestByID(1));

            newWorld.AddLocation(0, 2, "Herbalist's garden",
                "There are many plants here, with snakes hiding behind them.",
                "/Engine;component/Images/Locations/HerbalistsGarden.png");

            newWorld.LocationAt(0, 2).AddMonster(1, 100);

            return newWorld;
        }
    }
}

 

Step 4: Modify \Engine\ViewModels\GameSession.cs

When the player moves to a new location, we want to see if the location has monsters. If so, we will get a monster object and display its information (and image) on the UI.

We’ll start by adding a new CurrentMonster property to hold the Monster object in the ViewModel. We want to use a property with a backing variable, so we can raise an OnPropertyChanged function – to notify the UI that there is a new monster (or no monster).

We also will add a boolean HasMonster property, so we can easily hide and show controls in the UI, depending on whether or not there is a monster for the player to fight.

Instead of using a “set” and “get” for this property, we will use an “expression body”. This is a calculated value – in this case, true (if CurrentMonster is not null) or false (if CurrentMonster is null).

Because this property does not have a backing variable, we need to raise its OnPropertyChanged event inside the CurrentMonster “set” code – like we did for the HasLocationTo properties.

 

When the player moves to a new location, the CurrentLocation “set” code already calls GivePlayerQuestsAtLocation(). We will add a new function “GetMonsterAtLocation”.

This new function only needs to call the GetMonster function on the CurrentLocation. If the location does not have any monsters, the function will return “null”, CurrentMonster will be set to “null”, and HasMonster will be “false”. If there are monsters at the location, GetMonster will instantiate a new Monster object, which will go into GameSession’s CurrentMonster property. When the CurrentMonster is set to a monster, HasMonster will be “true”.

 

GameSession.cs

using System.Linq;
using Engine.Factories;
using Engine.Models;

namespace Engine.ViewModels
{
    public class GameSession : BaseNotificationClass
    {
        private Location _currentLocation;
        private Monster _currentMonster;

        public World CurrentWorld { get; set; }
        public Player CurrentPlayer { get; set; }

        public Location CurrentLocation
        {
            get { return _currentLocation; }
            set
            {
                _currentLocation = value;

                OnPropertyChanged(nameof(CurrentLocation));
                OnPropertyChanged(nameof(HasLocationToNorth));
                OnPropertyChanged(nameof(HasLocationToEast));
                OnPropertyChanged(nameof(HasLocationToWest));
                OnPropertyChanged(nameof(HasLocationToSouth));

                GivePlayerQuestsAtLocation();
                GetMonsterAtLocation();
            }
        }

        public Monster CurrentMonster
        {
            get { return _currentMonster; }
            set
            {
                _currentMonster = value;

                OnPropertyChanged(nameof(CurrentMonster));
                OnPropertyChanged(nameof(HasMonster));
            }
        }

        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 bool HasMonster => CurrentMonster != null;

        public GameSession()
        {
            CurrentPlayer = new Player
                            {
                                Name = "Scott",
                                CharacterClass = "Fighter",
                                HitPoints = 10,
                                Gold = 1000000,
                                ExperiencePoints = 0,
                                Level = 1
                            };

            CurrentWorld = WorldFactory.CreateWorld();

            CurrentLocation = CurrentWorld.LocationAt(0, 0);
        }

        public void MoveNorth()
        {
            if(HasLocationToNorth)
            {
                CurrentLocation = CurrentWorld.LocationAt(CurrentLocation.XCoordinate, CurrentLocation.YCoordinate + 1);
            }
        }

        public void MoveEast()
        {
            if(HasLocationToEast)
            {
                CurrentLocation = CurrentWorld.LocationAt(CurrentLocation.XCoordinate + 1, CurrentLocation.YCoordinate);
            }
        }

        public void MoveSouth()
        {
            if(HasLocationToSouth)
            {
                CurrentLocation = CurrentWorld.LocationAt(CurrentLocation.XCoordinate, CurrentLocation.YCoordinate - 1);
            }
        }

        public void MoveWest()
        {
            if(HasLocationToWest)
            {
                CurrentLocation = CurrentWorld.LocationAt(CurrentLocation.XCoordinate - 1, CurrentLocation.YCoordinate);
            }
        }

        private void GivePlayerQuestsAtLocation()
        {
            foreach(Quest quest in CurrentLocation.QuestsAvailableHere)
            {
                if(!CurrentPlayer.Quests.Any(q => q.PlayerQuest.ID == quest.ID))
                {
                    CurrentPlayer.Quests.Add(new QuestStatus(quest));
                }
            }
        }

        private void GetMonsterAtLocation()
        {
            CurrentMonster = CurrentLocation.GetMonster();
        }
    }
}

 

Step 5: Modify \WPFUI\MainWindow.xaml

The last step of this lesson is to display the monster information on the screen.

Beneath the Location section, we will display the CurrentMonster’s name, image, and current hit points. We do this by creating a grid (like the one for the location section).

The StackPanel control lets us display multiple values on the same line, or in the same column. To display the monster’s current hit points, we want a label that says “Current Hit Points” on the same line (horizontally) as the monster’s current hit points. The StackPanel lets us easily display the two TextBlocks on the same line.

 

To make it easier to identify the different sections of the XAML file, I added comments. A XAML comment starts with “<!–” and ends with “–>” (that’s two hyphens/dashes in there).

 

I also added two new lines in the “Window” section, at the top of the page.

The line with “xmlns:ViewModels” tells the Window that we are using objects in the Engine project (assembly), and in the Engine.ViewModels namespace (folder structure).

The line with “d:DataContext” lets the XAML editor know what type of object is in this window’s DataContext. With this line, we can use IntelliSense, when binding properties.

 

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:viewModels="clr-namespace:Engine.ViewModels;assembly=Engine"
        d:DataContext="{d:DesignInstance viewModels:GameSession}"
        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>

        <!-- Menu -->
        <Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Content="Menu" Background="AliceBlue"/>

        <!-- Player stats -->
        <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>

        <!-- Gameplay -->
        <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>
            
            <!-- Location information -->
            <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>
            
            <!-- Monster information -->
            <Border Grid.Row="1" Grid.Column="1"
                    BorderBrush="Gainsboro"
                    BorderThickness="1">

                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="*" />
                        <RowDefinition Height="Auto" />
                    </Grid.RowDefinitions>

                    <TextBlock Grid.Row="0"
                               HorizontalAlignment="Center"
                               Height="Auto"
                               Text="{Binding CurrentMonster.Name}" />

                    <Image Grid.Row="1"
                           HorizontalAlignment="Center"
                           VerticalAlignment="Center"
                           Height="125"
                           Width="125"
                           Source="{Binding CurrentMonster.ImageName}" />

                    <StackPanel Grid.Row="2"
                                Visibility="{Binding HasMonster, Converter={StaticResource BooleanToVisibility}}"
                                HorizontalAlignment="Center"
                                Orientation="Horizontal">
                        <TextBlock>Current Hit Points:</TextBlock>
                        <TextBlock Text="{Binding CurrentMonster.HitPoints}" />
                    </StackPanel>

                </Grid>

            </Border>

        </Grid>

        <!-- Inventory and Quests -->
        <Grid Grid.Row="2" Grid.Column="0"
              Background="BurlyWood">
        
            <TabControl>
                <TabItem Header="Inventory">
                    <DataGrid ItemsSource="{Binding CurrentPlayer.Inventory}"
                              AutoGenerateColumns="False"
                              HeadersVisibility="Column">
                        <DataGrid.Columns>
                            <DataGridTextColumn Header="Description"
                                                Binding="{Binding Name}"
                                                Width="*"/>
                            <DataGridTextColumn Header="Price"
                                                Binding="{Binding Price}"
                                                Width="Auto"/>
                        </DataGrid.Columns>
                    </DataGrid>
                </TabItem>

                <TabItem Header="Quests">
                    <DataGrid ItemsSource="{Binding CurrentPlayer.Quests}"
                              AutoGenerateColumns="False"
                              HeadersVisibility="Column">
                        <DataGrid.Columns>
                            <DataGridTextColumn Header="Name"
                                                Binding="{Binding PlayerQuest.Name}"
                                                Width="*"/>
                            <DataGridTextColumn Header="Done?"
                                                Binding="{Binding IsCompleted}"
                                                Width="Auto"/>
                        </DataGrid.Columns>
                    </DataGrid>
                </TabItem>
            </TabControl>
        </Grid>

        <!-- Action 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" 
                        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>

 

Now, if you run the game, you should see monsters when the player moves to a location with a monster.

In the next lesson, we will let the player fight the monster and (hopefully) collect loot from them.

 

 

Return to main page

28 thoughts on “Lesson 07.2: Adding Monsters to Locations

  1. I am having issues getting the images and monster names to show up in the game. It has text for current hp but does not show number, image or text for names… I copied all the text as shown from the 7.1 and this one, and still no luck. Not sure what to do now.

  2. I keep getting an error, it may have not been this lession but the previous one. Just noticed the error now though.
    http://prntscr.com/iveda2 It says that World.cs returns a null? I have not strayed off from what you did at all, how could I have gotten this error?

    1. It’s the LocationAt() function that is having a problem. On line 16, the location being created is at “-2, -1”. Line 20 is looking for a location at “-2, 1” (the Y coordinate needs to be “-1”). Because there is no location with those coordinates, LocationAt returns null – causing the error when trying to call the AddMonster function on the expected Location object.

  3. Hi Scott,

    Thanks for the Tut, it’s awesome.
    I have an issue I’m not able to get my head around.

    When creating a monster at a location the ‘CurrentMonster.ImageName’ fails to convert from a string with the error:

    https://pastebin.com/uhzPQVbR

    This only happens for the monster image name. I have tried not formatting the string within creating the object and just loading file names the same way as we do in the location info, but it still doesn’t work. I’ve inspected the properties for both image blocks and there are no differences. I’m really stuck with this one…

     

  4. I suggest you add a two pixel margin to each of the textboxes inside the StackPanel, so that the number and the colon aren’t attached to each other (it actually looks like there’s a space character between them, so I’m rather pleased with the result).
    It’s probably a bit OCD, but I really can’t stand when two strings aren’t separated by a whitespace.

    The revised code is

    Current Hit Points:

    1. Luciano,

      You code was cut off. XAML (and HTML) is usually automatically removed from WordPress comments. If you want to share your changes, https://gist.github.com/ is a good way.

      There are some UI lessons I’m going to do soon. In those, I plan to create a DataTemplate for the Player data and use some styles for the controls. Those will include margins and other formatting for the labels.

  5. Hi Scott,

    I have a similar problem that i’m unable to resolve, the monster’s images aren’t showing correctly :

    System.Windows.Data Error: 6 : ‘TargetDefaultValueConverter’ converter failed to convert value ‘/Engine;component/Images/Monsters/Snake.png’ (type ‘String’); fallback value will be used, if available. BindingExpression:Path=CurrentMonster.ImageName; DataItem=’GameSession’ (HashCode=9866279); target element is ‘Image’ (Name=”); target property is ‘Source’ (type ‘ImageSource’) IOException:’System.IO.IOException: Impossible de trouver la ressource ‘images/monsters/snake.png’.

    https://github.com/Inpachi/SOSCSRPG

    I love c# so far, thank you for this project.

    1. Hello Seikha,

      It looks like sometimes .NET has trouble finding the resource. In Monster.cs, change line 32 to the line below. This worked when I tested the change with your version of the program.

      ImageName = string.Format("pack://application:,,,/Engine;component/Images/Monsters/{0}", imageName);

      Here is more information about this: Pack URIs in WPF

      Please tell me if that does not solve the problem.

  6. For anyone having trouble with the images not showing, and followed all the above directions, I had the same difficulty and solved my issue. Perhaps you did the same thing as me and would like to solve it. Here’s the mistake I made:

    When I unpacked the zip files, I put them in a Monsters folder inside of the project engine location. This is the same location where the files end up being added. This made it so the images would not appear. After deleting the folder and unzipping the files into another location, and following the lesson steps the images appeared in the UI as they should.

    Thank you Mr. Lilly for this project, and the old rpg project. They have been so helpful to me in understanding some of the ways to apply C# fundamentals in a game design.

  7. Hi Scott. I am following your lecture so far and I am beyond impressed.
    I cannot thank you enough for your hard work and passion.
    Mostly I am watching at Youtube but I just started to use text version of your lecture.
    I have a question though.
    I see in MonsterFactory class, there is a function public static Monster GetMonster(int monsterID). (It’s called function right? correct me if I am wrong please.)
    And also in Location class there is public Monster GetMonster() function too.

    And inside the GetMonster() of Location class, it is using GetMonster(int monsterID).
    These two functions have same name as GetMonster and both are public.
    How can they be not conflict to each other?
    Did they have to have same name?

    Code runs smoothly so I am sure this is right way but
    I am a bit confused.
    ^o^;;

    1. Hi SungJune,

      The computer knows which GetMonster function to call because we always have to call GetMonster from a class or object. When we want to call the GetMonster function from the factory, we would run: “MonsterFactory.GetMonster()”. When we want to run GetMonster for a location, we need a Locaiton object (I’ll pretend we have one in a variable named “myLocation”), and we would run: “myLocation.GetMonster()”.

      Out program cannot just have “GetMonster()”, It always needs the static class or object in front of it – and that lets the program know which GetMonster to run.

      Is that clear?

  8. Hi Scott,
    In Location.cs in line 21 you use MonstersHere.Exists(…). Is there any difference to Any() other than Any() being an extension method?

    1. Internally, they probably use different code, because Exists() is for List and Any is for IEnumerable(). But, for our use, they both provide the same results – whether or not there is a matching object.

  9. Hello Scott Lilly Can you help me with what, like how I can find some information about this method or technique.

    (x => m.Something == something)

    1. Hi Anatolio. You can learn more about that by reading about “lambdas” and “delegates” https://docs.microsoft.com/en-us/dotnet/standard/delegates-lambdas.

      You can think of if like a “foreach” loop, for the items in the list or collection. The LINQ function (like “Where” or “Count”) will take each object in the list, assign it to the variable on the left of the lambda “=>”. Then it will use that variable in the equation to the right of the lambda. Putting that variable into the equation returns a true or false, which determines if that object is included in the LINQ function or not.

    1. Check that the images “Copy to Output Directory” property is set to “Copy Always”.

      If they are, try copying the Images folder (and its sub-folders and files) into the WPFUI project. Remember to set them to “Copy Always” in the WPFUI project. If you created the Engine project as .NET Standard or .NET Core, it does things different from .NET Framework.

      1. Thanks Scott. It started working when I deleted images, recopied them, cleaned and rebuilt the solution. Not sure why that worked but it did!

  10. I noticed when displaying the monster hit points the number is next to the colon.
    Is there a way to put a space between the colon and the hit point number in the XML file?

    <TextBlock>Current Hit Points:</TextBlock>

     

    1. Hey James,

      I think you also asked about a solution on Discord. But, for anyone who’s just checking here, you can add Padding to the TextBlock to add some space, like this:
      <TextBlock Padding="0,0,5,0">

  11. Hey Scott,

    Thanks for the great tutorial! I noticed that in the GameSession.cs sample code on line 77, the “=>” characters got replaced with “=&gt”.

Leave a Reply

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