Skip to main content Link Search Menu Expand Document (external link)

Day 1: Design Document and Project Scope

Today, we defined our learning goals, specified the scope of the project, setup a unity project, class class library project, xUnit test project, and defined a few interfaces: ISkilledEntity, ISkillNode, and ISkillTree.

Table of contents
  1. Today’s Tasks
  2. Learning Goals
  3. Project Scope
  4. Project Setup
  5. Start Interface Definitions

Today’s Tasks

  1. Define Learning Goals and Scope Document
  2. Setup Libs / Unity Project / Dependencies
  3. Start interface definitions

Learning Goals

We began by defining a “Learning Goals and Scope” document.

Learning Goals

For those who are not fluent in chicken scratch, let me translate for you:

  • Visualization for Graph Data Structure

More specifically, I would like to be able to generate a UXML file representing a defined Skill Tree that can then be modified in the UI Builder.

  • Exploring Generics to Generalize the SkillTree library

More specifically, can we discover and define what the reusable components of a skill tree system exist. This is challenging because there are vastly different skill trees. In our version, we will be implementing a specific implementation.

  • USS Variables

We have yet to use style sheet variables in our previous projects. It would be great to find a good spot to use one.

Project Scope

Scope

Once again, let me translate:

The primary goal of this project is to implement a re-usable skill tree system in 6 days! I estimate that we will have roughly 14 hours of “working” time while streaming.

The Skill Tree system we will implement will have the following properties:

  1. Nodes representing skills with names and descriptions
  2. Each Node has children which require the parent node to acquire
  3. In addition to the parent node requirement, they may specify additional requirements
  4. If a single node has two parents, both parents are required

With this in mind, the following scope was defined:

  • ISkill - An interface representing the idea of a skill. A skill simply provides a name and a description.

  • ISkillNode - An interface which represents the idea of a skill within a tree. It contains methods for checking if a character meets the requirements for acquiring the skill as well as children information.

  • ISkillTree - An interface which has access to the rook ISkillNode.

  • ISkilledEntity - An interface which represents an entity that can acquire skills. This interface tracks the skills that a character has acquired thus far.

That’s it for this project! We intentionally kept the scope small to hopefully ensure that we can finish in the short time period.

Project Setup

With our learning goals defined and a scope in mind, it was time to set up the project so we could actually code.

In the crafting system we learned that we could utilize C# 11 (or another language version) in our non-unity related code by creating a project outside of Unity and adding in a compiled dll to the Unity project. Additionally, this speeds up Unity compile times and helps to keep our code organized for more general reuse purposes. With this knowledge available, we started by adding a submodule to our project referencing the CaptainCoder.Core that was created during the last project.

More specifically, we created a branch that we could use while working on the inventory system. At the time of writing, that branch was available here: grid-based-inventory. However, it should be merged in to the main branch after the inventory system is completed and the link may not work at a later date.

Another benefit gained by defining the non-unity portions of the project outside of Unity is that we can more easily utilize different testing frameworks such as xUnit. In Unity, the main (and only?) testing framework is nUnit which is fine but not my personal preference.

Lastly, we put together a simple build script that will build and add the compiled dlls to Unity when changes are made to the project code:

#!/bin/bash
dotnet build CaptainCoder.Core/ -c Release
CORE_PATH="CaptainCoder.Core/CaptainCoder/Core/bin/Release/netstandard2.1"
CORE="CaptainCoder.Core"
SKILLTREE_PATH="CaptainCoder.Core/CaptainCoder/SkillTree/bin/Release/netstandard2.1"
SKILLTREE="CaptainCoder.SkillTree"
UNITY_DLL_PATH="Unity Skill Tree/Assets/Plugins/CaptainCoder"
cp "$CORE_PATH/$CORE.dll" \
    "$CORE_PATH/$CORE.xml" \
    "$SKILLTREE_PATH/$SKILLTREE.dll" \
    "$SKILLTREE_PATH/$SKILLTREE.xml" \
    "$UNITY_DLL_PATH/"

Start Interface Definitions

With our project definitions ready to go, it was time to implement the interfaces that we would use for the ISkill, ISkillNode, ISkillTree, and ISkilledEntity:

/// Represents a skill
public interface ISkill
{
    /// The name of this skill
    public string Name { get; }
    /// A description of this skill
    public string Description { get; }
}
/// An <see cref="ISkilledEntity{T}"/> represents an entity that can acquire
/// skills of the specified type.
public interface ISkilledEntity<T> where T : ISkill
{
    /// A HashSet of skills that this entity has acquired
    public HashSet<ISkillNode<T>> Skills { get; }
}
public interface ISkillTree<T> where T : ISkill
{
    /// The root node of this skill tree
    public ISkillNode<T> Root { get; }
}
/// Represents a skill node within a skill tree.
public interface ISkillNode<T> where T : ISkill
{
    /// A list of requirements that must be met to gain this skill
    public IReadOnlyList<IRequirement> Requirements { get; }

    /// A list of children skill nodes
    IEnumerable<ISkillNode<T>> Children { get; }

    /// Checks if the specified <paramref name="character"/> meets the
    /// requirements to acquire this skill.
    public bool CheckRequirements(ISkilledEntity<T> character)
    {
        foreach (IRequirement req in Requirements)
        {
            if(!req.MeetsRequirement(character)) { return false; }
        }
        return true;
    }

    /// A Requirement acts as a predicate on a character.
    public interface IRequirement
    {
        /// Checks if the specified <paramref name="character"/> meets this
        /// requirement.
        public bool MeetsRequirement(ISkilledEntity<T> character);
    }
}

And that was it for today! With these data types in place, we should hopefully be able to write a relatively straight forward implementation tomorrow and spend the remaining time focused on developing the UI interactions. Will we be able to do it? Only time will tell! See you tomorrow!

Join the Discussion

Before commenting, you will need to authorize giscus. Alternatively, you can add a comment directly on the GitHub Discussion Board.