Last updated on

Dev Log: Turn Based Tactical Game (Part 1)


Chapter I: The Foundation

So, I created a new Unreal project, added my plugin and build a first prototype level with some cubes and modeled in Blender. These are all 100cmx100cmx100cm cubes. The difference is that they all have a different amount of beveled edges.

Note

For more information on the grid plugin you can take a look at the corresponding blog entries.

Cube Meshes Cube meshes in Blender

I then created a material that uses world aligned texture for the cubes, that automatically blends between the side material and the top material using a simple mask I made. My first approach simply used the vertex normal as an alpha for the blend, but I wanted a softer transition between the two textures. SO ended up using a custom map. That was still much faster than texturing the cube by hand.

Cube Material Cube material

Next I put together a simple level on a 8x12 grid and generated the corresponding grid including elevations. I then noticed than I also need cubes, that are only half the size of the standard cube for finer elevation levels. So I also created those.

Prototype Level Prototype Level made with cubes

There is still a lot of texture tiling going on, but that’s something for later.

The Player Pawn

The player pawn is not an actor that moves on the grid, but more like an invisible entity that represents the player in the game. It needs to be able to move around the map and interact with the actual characters. All the player input is handled inside this class.

Instead of a free flying camera, where the player can move the camera to any location, I want a constrained camera, that only moves on the x,y plane. To offset the camera from the ground I am using a spring arm component. This results in an orbiting camera that can rotate around a fix point on the map. Furthermore, I can now change the distance from the camera to the ground by a single value.

Player Pawn

After the component hierarchy was done, I started to bind the input using Unreal Engine 5’s Enhanced Input System. With all the inputs bound to their corresponding functions, I was able to navigate my map.

That was all I could do in that class for now.

Turn Based Entities

Now, that I had a pawn to possess, I needed some kind of actor to place on the map and to give commands to. So I created a class for that purpose:

Class Diagram Class diagram for the ATurnBasedPawn class

The ITurnBasedEntityInterface is for the turn managing aspect of the gameplay. It has functions like OnTurnStarts or OnTurnEnds. The IGridEntityInterface is for communicating with the grid. Right now, it only tells a tell that we occupy it. I use the pawn class, so I can have an AI controllers control the pawns that are not controlled by the player.

Turn Action System

I decided that every command or action a pawn could perform, is to be represented by an object. So, I created a class to model a basic action and I called it UTurnAction.

The idea is, that every TurnBasedPawn has an array of different actions. The actions can be attacks, skills and even movement. Next, I created a special movement action, which is to handle the movement for every pawn.

Highlighting Tiles

I then realized, that it’s rather hard to implement movement, without a way of highlighting tiles. I wanted to highlight all tiles that were valid movement targets. So, I took a look at the UDynamicMeshComponent. Using the component and an actor to host the component, I was able to create a pretty simple highlight effect. Using a custom utility function library to get all the tiles within reach, I then draw a rectangle for every reachable tile and combine these into a single mesh. Last, I apply a semi-transparent material to the dynamic mesh (click here for more info).

Moving a Pawn

Next, I started to implement a basic A*-algorithm for path finding. It’s pretty straight forward implementation and is currently lacking obstacle avoidance or filtering of valid step heights. To move my pawn I created a grid movement component. It basically creates a spline at runtime that runs through all the tiles the path is made of. Then, I use the spline to move the actor along it. Constantly updating rotation and speed.

That’s the result:

Pawn moving Pawn moves to clicked tile

At this point, I decided I need to tidy things up a little bit. So, I created a rounded rectangle texture for the tile highlighting, replaced the cube with an old prototype mesh, added a selection highlight decal and used a spline to show a path preview.

Updated Visuals Updated visuals

Turn Manager

But moving one pawn was not enough, I wanted to move multiple pawns and switch between them. I started implementing a team system and a turn manager, that basically tells the player (or AI) pawn, whose turn it is. At this point it was pretty rudimentary, but that was all I needed.

The hierarchy of the key actors now looks as follows:

Diagrams Relations between key objects (this is not an correct UML diagram)

So, the turn manager sits at the top. It has an array of pawns, that contains all the pawns that are part of the fight. This should always be one player pawn (though this is not mandatory) and any number of AI pawns. Right now, I only have one player pawn.

The game manager tells the pawn, when their turn start and the pawn tells the manager, when they end their turn, so the manager can notify the next pawn. It’s pretty straight forward. When it’s not your turn you cannot select any units. You can only move around. Neither the player nor the AI pawns have any visual representation in the world.

At the bottom of the diagram are the turn pawns (the naming of those classes is not ideal, I do realize that). Sometimes they are referred to as units or characters. The are the actual actors, that stand on the map and have a mesh.

Setting Up Some Rules

When I was implementing the movement and turn order, I realized that I already implemented some rules, that govern how the gameplay works.

  • Movement Points determines, how far a unit can move
  • At the start of the turn all resources (movement points is the only resource we have right now) are reset

At the moment, every tile has a movement cost of 1. So, a unit that with 3 movement points can move up to 3 tiles. That is, as long as it moves in a straight line. Diagonal movement cost twice the cost for the tile. I also need to take elevation into account. Going up a tile should also be more expensive than moving on the same elevation. And what about moving down from elevation? Should that be cheaper? I am going to postpone the questions for now.

Instead, I also decided that I wanted units to be able to spend their movement points in any order they liked. So, you can move one tile, then stop, do something and then start moving again. The only limiting factor are your movement points.

Later, I sat down and wrote down the rules in a more compact manner:

  • Tile Movement Cost:
    • Each tile has an associated movement cost.
  • Diagonal Movement:
    • Units can move diagonally.
    • Diagonal movement costs twice the normal movement cost of the tile.
  • Movement Points:
    • Each unit has a set number of movement points.
  • Movement Cost:
    • To move to a tile, a unit must pay the movement cost.
    • The total movement cost is the sum of all tile costs that the unit passes through.
    • At the beginning of a turn, the unit recovers all of its movement points.
  • Movement Actions:
    • A unit can move as many times as it wants, provided it has enough movement points to cover the cost.

Actions

Over the weekend, I worked on an action framework for units. It will be used for all the skills and abilities a single unit can have. I also refactored the movement to be an action. The system tries to make as few assumptions about the underlying mechanics as possible. Furthermore, I put the action code into an extra plugin. The idea is, to inherit from the base classes and implement the actual game-related logic by overriding certain functions. Right now, there is a base class for movement and a class for generic skills.

I also added another attribute to the unit class called Action Cost. It basically determines what a unit can do during its turn. Every skill/action has a cost, and, if you want to use it, its cost must not exceed the unit’s number of action points.

For starters, I created two actions for testing: one melee attack and a range attack.

Melee Skill Preview Selected Melee Skill

Range Skill Preview Selected Range Skill with a minimum range

Damage Exchange

The last bit I got to this month was a rudimentary damage system. It uses an interface to calculate and apply the dealt damage to the corresponding actors.

virtual int32 CalculateDamage(const FDamageData& DamageData) = 0;
virtual void ApplyDamage(const FDamageData& DamageData, const int32 ActualDamageDealt) = 0;

The CalculateDamage function calculates the actual damage on the actor that was hit and returns the result. This is similar to Unreal’s build in damage system. This way, the damaged actor can apply all kinds of modifiers to the amount of damage and return the actual damage that will it will receive. The ApplyDamage function then simply subtracts the damage amount from the current amount of hit points. I’ve put all necessary data inside the FDamageData struct. This contains information like attack direction, attack location, bone name, etc. This also keeps the signature of the function short and I can easily extend the amount of data without changing the interface.

To visualize the damage, I added an actor with a widget component. The actor is spawned at the attacked actor. The widget component renders a widget in screen space, while the widget displays the dealt damage. I also added an animation that moves the number up and fades it out after a certain amount of time.

That’s it for this month.

Next month, I will try to build a complete test level, work on more actions, and hopefully start with AI for enemies.