Press "Enter" to skip to content

Lesson 19.14: Moving the Engine classes to their new projects

Now, we can move the classes from the Engine class into their new classes.

We’ll cut-and-paste the files, update the classes’ namespaces, and update the “using” directives.

The video will show me going through the actual move – in case it’s difficult to determine exactly what to do in the written steps below.

NOTE: When following the steps, make sure you copy files, when the step says FILE or FILES, and folders, where the step says FOLDERS.

Also, when you move the files, you really do not need to update the namespaces. You could leave the namespace unchanged. However, if you add new classes to the new projects, those classes will be created (by default, from Visual Studio) to use the new project’s name. So, some classes in that project would have the old namespace and some would have the new one.

Step 1: Move ViewModels

Cut-and-paste the GameSession.cs FILE from Engine\ViewModels into SOSCSRPG.ViewModels

Step 2: Move Services

Cut-and-paste the FILES from Engine\Services into SOSCSRPG.Services

Cut-and-paste the FOLDER Engine\Factories into SOSCSRPG.Services

Step 3: Move Models

Cut-and-paste the FILES from Engine\Models into SOSCSRPG.Models

Cut-and-paste the FOLDER Engine\Shared into SOSCSRPG.Models

Cut-and-paste the FOLDER Engine\Actions into SOSCSRPG.Models

Step 4: Update namespaces and using directives

Ctrl-Shift-H

Look in: “Entire solution”

File types: !*\bin\*;!*\obj\*;!*\.*

“Engine.Factories” to “SOSCSRPG.Services.Factories”

“Engine.Actions” to “SOSCSRPG.Models.Actions”

“Engine.Shared” to “SOSCSRPG.Models.Shared”

“assembly=Engine” to “assembly=SOSCSRPG.ViewModels”

“Engine.” to “SOSCSRPG.”

Step 5: Update assembly namespace in Startup.xaml

In this XAML Window, we are not binding to a ViewModel. We’re directly binding to a Model – GameDetails.

This is something we probably need to change in the future, to be consistent. But, for now, that means we need to change the xmlns and DataContext lines to use the SOSCSRPG.Models namespace and assembly.

Startup.xaml

<Window x:Class="WPFUI.Startup"
        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"
        xmlns:models="clr-namespace:SOSCSRPG.Models;assembly=SOSCSRPG.Models"
        d:DataContext="{d:DesignInstance models:GameDetails}"
        mc:Ignorable="d"
        FontSize="11pt"
        Title="{Binding Title}" Height="400" Width="400">
 
    <Grid Margin="10,10,10,10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
 
        <Button Grid.Row="0" Grid.Column="0"
                Margin="0,5,0,5"
                HorizontalAlignment="Center"
                Width="125"
                Content="Start new game"
                Click="StartNewGame_OnClick"/>
 
        <Button Grid.Row="1" Grid.Column="0"
                Margin="0,5,0,5"
                HorizontalAlignment="Center"
                Width="125"
                Content="Load saved game"
                Click="LoadSavedGame_OnClick"/>
 
        <Button Grid.Row="2" Grid.Column="0"
                Margin="0,5,0,5"
                HorizontalAlignment="Center"
                Width="125"
                Content="Exit"
                Click="Exit_OnClick"/>
         
    </Grid>
 
</Window>

Step 5: Make classes/functions visible

Change SOSCSRPG.Models\World’s AddLocation() to public

Change SOSCSRPG.Services\Factories\WorldFactory’s CreateWorld() to public

Change SOSCSRPG.Services\Factories\WorldFactory class to public

World.cs

using System.Collections.Generic;

namespace SOSCSRPG.Models
{
    public class World
    {
        private readonly List<Location> _locations = new List<Location>();

        public void AddLocation(Location location)
        {
            _locations.Add(location);
        }

        public Location LocationAt(int xCoordinate, int yCoordinate)
        {
            foreach(Location loc in _locations)
            {
                if(loc.XCoordinate == xCoordinate && loc.YCoordinate == yCoordinate)
                {
                    return loc;
                }
            }

            return null;
        }
    }
}

WorldFactory.cs

using System.IO;
using System.Xml;
using SOSCSRPG.Models;
using SOSCSRPG.Models.Shared;

namespace SOSCSRPG.Services.Factories
{
    public static class WorldFactory
    {
        private const string GAME_DATA_FILENAME = ".\\GameData\\Locations.xml";

        public static World CreateWorld()
        {
            World world = new World();

            if(File.Exists(GAME_DATA_FILENAME))
            {
                XmlDocument data = new XmlDocument();
                data.LoadXml(File.ReadAllText(GAME_DATA_FILENAME));

                string rootImagePath =
                    data.SelectSingleNode("/Locations")
                        .AttributeAsString("RootImagePath");

                LoadLocationsFromNodes(world, 
                                       rootImagePath, 
                                       data.SelectNodes("/Locations/Location"));
            }
            else
            {
                throw new FileNotFoundException($"Missing data file: {GAME_DATA_FILENAME}");
            }

            return world;
        }

        private static void LoadLocationsFromNodes(World world, string rootImagePath, XmlNodeList nodes)
        {
            if(nodes == null)
            {
                return;
            }

            foreach(XmlNode node in nodes)
            {
                Location location =
                    new Location(node.AttributeAsInt("X"),
                                 node.AttributeAsInt("Y"),
                                 node.AttributeAsString("Name"),
                                 node.SelectSingleNode("./Description")?.InnerText ?? "",
                                 $".{rootImagePath}{node.AttributeAsString("ImageName")}");

                AddMonsters(location, node.SelectNodes("./Monsters/Monster"));
                AddQuests(location, node.SelectNodes("./Quests/Quest"));
                AddTrader(location, node.SelectSingleNode("./Trader"));

                world.AddLocation(location);
            }
        }

        private static void AddMonsters(Location location, XmlNodeList monsters)
        {
            if(monsters == null)
            {
                return;
            }

            foreach(XmlNode monsterNode in monsters)
            {
                location.AddMonster(monsterNode.AttributeAsInt("ID"),
                                    monsterNode.AttributeAsInt("Percent"));
            }
        }

        private static void AddQuests(Location location, XmlNodeList quests)
        {
            if(quests == null)
            {
                return;
            }

            foreach(XmlNode questNode in quests)
            {
                location.QuestsAvailableHere
                        .Add(QuestFactory.GetQuestByID(questNode.AttributeAsInt("ID")));
            }
        }

        private static void AddTrader(Location location, XmlNode traderHere)
        {
            if(traderHere == null)
            {
                return;
            }

            location.TraderHere =
                TraderFactory.GetTraderByID(traderHere.AttributeAsInt("ID"));
        }
    }
}

Step 6: Delete project references to Engine project

Delete the Engine project

Double-click on the WPFUI and SOSCSRPG.ViewModels projects and remove the ProjectReference lines to the Engine project

SOSCSRPG.ViewModels.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Fody" Version="6.6.0">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="PropertyChanged.Fody" Version="3.4.0" PrivateAssets="All"/>
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\SOSCSRPG.Models\SOSCSRPG.Models.csproj" />
    <ProjectReference Include="..\SOSCSRPG.Services\SOSCSRPG.Services.csproj" />
  </ItemGroup>

</Project>

WPFUI.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net5.0-windows</TargetFramework>
    <UseWPF>true</UseWPF>
  </PropertyGroup>

  <ItemGroup>
    <ProjectReference Include="..\SOSCSRPG.Core\SOSCSRPG.Core.csproj" />
    <ProjectReference Include="..\SOSCSRPG.Models\SOSCSRPG.Models.csproj" />
    <ProjectReference Include="..\SOSCSRPG.ViewModels\SOSCSRPG.ViewModels.csproj" />
  </ItemGroup>

  <Target Name="PostBuild" AfterTargets="PostBuildEvent">
    <Exec Command="xcopy $(SolutionDir)GameFiles\*.* $(ProjectDir)$(OutDir) /s /y" />
  </Target>

</Project>

Step 8: Test game

NEXT LESSON: Lesson 20.1: Create floating inventory canvas

PREVIOUS LESSON: Lesson 19.13: Remove CombatService dependency from model classes

9 Comments

  1. Jonathan Rodriguez
    Jonathan Rodriguez 2024-06-10

    Hi Scott!

    I followed all the steps, but when I go to GameSession, I encounter the issue that the Core space doesn’t exist and other problems as well. I know it’s a references problem, but I don’t know how to fix it because I didn’t see these kinds of errors in the video: https://github.com/Balsero/Super-Adventure

    Thank you so much.

    • SOSCSRPG
      SOSCSRPG 2024-06-11

      Hi Jonathan,

      I pushed some changes to your GitHub repository that should fix the errors. It looks like the access levels from step 5 weren’t changed and there were some project references that needed to be fixed.

  2. Henry Ward
    Henry Ward 2025-01-17

    Hey Scott,

    When I try to build I get ~62 errors similar to:

    The type or namespace name ‘Trader’ could not be found (are you missing a using directive or an assembly reference?)

    Oddly, when I click on them to go the line in question, the error (and others) disappear.

    However, they all return if I try to build again.

    Somehow, I must have messed up the last two steps with “using statements” or Project References

    Are you able to take a look?

    https://github.com/kudosscience/Scotts-Open-Source-C-Sharp-Role-Playing-Game-.NET5

    Many thanks!

    • SOSCSRPG
      SOSCSRPG 2025-01-17

      Hi Henry,

      It looks like the ItemQuantity class needs to have the “using SOSCSRPG.Services.Factories;” line removed, since the SOSCSRPG.Models project does not need to use any code from there and it does not have a reference to the SOSCSRPG.Service project. By the way, since the SOSCSRPG.Service project has a reference to the SOSCSRPG.Models project, we can’t add a reference in the other direction. That would be a “circular reference”, which is not allowed.

      Please let me know if that does not fix the problem, or if you have any questions.

      • Henry Ward
        Henry Ward 2025-01-18

        Wow, removing that one “using SOSCSRPG.Services.Factories;” line fixed it and now it builds without errors!
        I’m surprised one wrong line could cause such a cascade of build errors!
        Thanks very much!

        • SOSCSRPG
          SOSCSRPG 2025-01-19

          You’re welcome. Interpreting the error messages in Visual Studio is definitely a skill to learn. In cases like this, since the one class couldn’t be built, any other class that uses that class will fail, cascading error messages throughout the application.

  3. Lazie Wouters
    Lazie Wouters 2025-08-18

    Hi Scott,

    I made a change to the code, and it raised a question for me. But first, I need to show you what I did.
    I changed the signature of the RaiseMessage method from this:

    public void RaiseMessage(string message)
    {
    OnMessageRaised?.Invoke(this, new GameMessageEventArgs(message));
    }

    to this, with the aim of creating formatted messages with background color etc:

    public void RaiseMessage(string message, BackgroundColor messageColor = BackgroundColor.Yellow, string gifName = “”,
    double paddingLeft = 2, double paddingTop = 15, double paddingRight = 2, double paddingBottom = 15,
    double marginLeft = 10, double marginTop = 10, double marginRight = 10, double marginBottom = 10,
    double cornerLeft = 10, double cornerTop = 10, double cornerRight = 10, double cornerBottom = 10)
    {
    OnMessageRaised?.Invoke(this, new GameMessageEventArgs(message, messageColor, gifName,
    paddingLeft, paddingTop, paddingRight, paddingBottom,
    marginLeft, marginTop, marginRight, marginBottom,
    cornerLeft, cornerTop, cornerRight, cornerBottom));
    }

    And because of that I needed to adapt the ReportResult method signature from this:

    public event EventHandler OnActionPerformed;
    protected void ReportResult(string result)
    {
    OnActionPerformed?.Invoke(this, result);
    }

    to this:

    public event EventHandler? OnActionPerformed;
    protected void ReportResult(string result, BackgroundColor messageColor = BackgroundColor.Yellow, string gifName = “”,
    double paddingLeft = 2, double paddingTop = 15, double paddingRight = 2, double paddingBelow = 15,
    double marginLeft = 10, double marginTop = 10, double marginRight = 10, double marginBelow = 10,
    double cornerLeft = 10, double cornerTop = 10, double cornerRight = 10, double cornerBottom = 10)
    {
    OnActionPerformed?.Invoke(this, new GameMessageEventArgs(result, messageColor, gifName,
    paddingLeft, paddingTop, paddingRight, paddingBelow,
    marginLeft, marginTop, marginRight, marginBelow,
    cornerLeft, cornerTop, cornerRight, cornerBottom));
    }

    and also adapt the signature of the OnCombatantActionPerformed method from this:

    private void OnCombatantActionPerformed(object sender, string result)
    {
    _messageBroker.RaiseMessage(result);
    }

    to this:

    private void OnCombatantActionPerformed(object? sender, GameMessageEventArgs e)
    {
    _messageBroker.RaiseMessage(e.Message, e.MessageColor, e.GifName, e.PaddingLeft, e.PaddingTop, e.PaddingRight, e.PaddingBottom, e.MarginLeft, e.MarginTop, e.MarginRight, e.MarginBottom, e.CornerLeft, e.CornerTop, e.CornerRight, e.CornerBottom);
    }

    Well, now I’ll post my question: while I was making this change, I wondered why not simplify the code and use _messageBroker.RaiseMessage to pass the information directly to the UI instead of using ReportResult to invoke RaiseMessage within the OnCombatantActionPerformed method, in a chain of events? The code would be simpler. I know there must be a reason; perhaps the way you did it is because of some design pattern. Could you clarify if it’s a design pattern you used to justify this ReportResult event call, passing the information to RaiseMessage, which will ultimately pass the information to the UI, and what the advantages of this would be?

    Thanks!!!

    • SOSCSRPG
      SOSCSRPG 2025-08-19

      Thanks for sharing your code.

      The general idea behind something like this is to not “couple” the code tightly. That’s why I didn’t send the messages directly to the UI. If you wanted to build a bigger, more complex program, you might want to have other things listen for messages, like code that might automatically update changes in the saved game file or database.

      It’s more than this program needs, but a lot of the ideas I show here are largely to introduce people to ideas they may want to use when they build bigger programs.

      Let me know if you have other questions, or if that wasn’t clear.

      • Lazie Wouters
        Lazie Wouters 2025-08-20

        Thanks !!!

Leave a Reply to Lazie Wouters Cancel reply

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