Learning Gamedev the hard way Episode 0 Part II

Published May 15, 2021
Advertisement

Learning Gamedev the hard way

In "Learning Gamedev the hard way" I want to share my journey so far in learning to make games. I had a very slow progression due to some big mistakes I made by being hard-headed. I started years ago and I am still a beginner with very little to show for. I will dive into my memory and bring up stuff I experienced along the way. Maybe you can avoid some of the mistakes I made. Or learn something from the things I did right.

"Epsiode 0": The very first attempt I ever made at coding games.

Part II: the game concept

Like I promised last time, we are going to talk more about what exactly I intended to do with the game, and what I actually ended up doing. As mentioned, the initial game idea was heavily influenced by Warcraft II. However I also took some inspiration from a game called "War Wind", which was released in the late 90's and which had similar core mechanics.

The game model of War Wind is similar to that of Warcraft II. In both games there are "top down" pixel Art graphics, similar to many 2D RPGs, with a square tiled world map. But War Wind has more fancy features than WC2: Buildings can be entered by units, some enemies drop items that your units can pick up, you have heroes, all of your units gain XP, and they can be improved in training ground buildings. I loved the game, but it suffers from terrible controls that make it very hard to play today.

The plan for my game was to also use a tiled top down pixel Art style. I wanted it to be an RTS but also add some RPG flavor. Instead of using base-building, I wanted to have neutral buildings on the map that you could recruit units from. The money for recruitment would be earned by killing neutral monsters on the map. The game was intended to be multiplayer. My goal however, of how the game shold look in the end, was rather fuzzy.

War Wind and WC2 have fixed resolution, typically 640x480. The tiles are 32x32. I changed the specifications to be 1024x768, and I used slightly bigger 40x40 tiles. Fixing the resolution to a 4x3 format was a bad decision in light of modern wide screen monitors.

Basic World Model

I implemented the world model as a 2D array of instances of a tile class. These instances stored the information of each tile, like the texture type and a pointer to the unit on the tile. This way, every unit in the game always "lives" on only one specific tile. Moving a unit simply means moving the pointer from one tile to the next. Rendering the world means to iterate over the array of tiles, starting from the top left corner of the screen, and to render the texture of each tile accordingly. The screen position is the position of the tile in the upper left corner. Scrolling can only occur in steps off "full tiles".

Movement on discrete Tiles

To make the movement of the units appear smooth, I used the following process: When a unit leaves one tile and enters the next, it enters a moving state with some duration specific to the unit type. From the perspective of the game model, the unit immediately jumps from one tile to the next. Then it takes some time before it jumps again, or some other action occurs. To make the movement appear smooth to the player, the graphic is displaced by some offset towards the tile that the unit came from. The offset is proportional to the remaining time until the next move.

with smoothing
without smoothing

HUD

I divided the screen into an upper part, which shows the game world, and a lower part for the HUD. On the left side of the HUD, I displayed a minimap. On the right side, there was a grid for all the commands that can be given to the units, and in the middle, there was another grid-like structure to show all selected units.

In War Wind and WC2, the HUD was on the side and had terrible readablilty. More modern RTS games have their HUD elements on the bottom, which increases readability.

how the HUD looked

Unit Action States

The units could fight each other in 3 different ways: Melee attacks, single target ranged casts, and area of effect ranged casts.

Before introducing the specifics of the attacks, we need to talk about the "states" that the units could be in. The theory behind state machines is well developed mathematically and from a software architectural standpoint, but I didn't know that at the time. If I had done some research, it would have helped me make less of a mess from a coding perspective.

What I did was the following:

Every unit has an integer value for the current state that the unit is in, and a time_remaining float. When time_remaining hits 0, some action is performed, depending on the state that the unit is in. Then a next action is loaded from an action queue. If the action queue is empty, the Rest Action (idle action) is loaded. When a command is given to the unit by the user, the corresponding action depends on the state that the unit is in.

Each decision (what happens when time reaches 0, what happens on user input), was performed by huge switch statement trees.

The following action states exist in the game:

ACTION_DYING

ACTION_HOLDPOSITION

ACTION_REST

ACTION_WAIT

ACTION_MOVE

ACTION_ATTACKMOVE

ACTION_INTERCEPT

ACTION_FIGHT

ACTION_CAST

ACTION_REST is the idle action that a unit enters once no further action has to be performed. In this state, the unit only checks whether an enemy is within range, and if so, it starts intercepting this enemy.

ACTION_DYING only exists in order to show a death animation; once done, the unit is deleted.

ACTION_WAIT is injected into the movement if a target tile is blocked (this helps with the movement of larger groups). ACTION_HOLDPOSITION is very similar to ACTION_REST, but the unit does not move towards enemy units in range. ACTION_MOVE, ACTION_ATTACKMOVE and ACTION_INTERCEPT are all states of movement. Intercept locks onto an enemy unit and updates the target if the enemy moves, attackmove checks after every tile whether an enemy is in range and needs to be attacked (similar to ACTION_REST) and move is a simple movement towards a goal tile. ACTION_FIGHT is the melee attack and ACTION_CAST is the ranged attack state.

For example: If the unit is in state ACTION_REST and the user gives a move command, the path to the target is calculated and the unit changes its state to ACTION_MOVE to the next tile on the path. All following ACTION_MOVEs to the following tiles in the path are put into the queue. However, if the unit is already in ACTION_MOVE when receiving a move command, the current action has to finish first, before the unit can start moving in the new direction.

When a melee attack is performed, the target unit has to be in one of the neighbouring tiles, and when the time expires, some amount of damage is dealt to the target. For ranged attacks, a projectile is spawned after the animation, which moves towards the target at set speed and damages it.

When a cast command is given to a group of units, only the unit that is closest to the target and has enough mana executes the cast (smartcasting).

melee attack


ranged attack

Overall the system I chose is not bad. However, it would have been advisable to implement it with the State Design Pattern instead of huge switch trees. See https://en.wikipedia.org/wiki/State_pattern for more information.

Pathfinding

The pathfinding, which I mentioned in last week's episode, uses the A* Algorithm to lead each unit in a group seperately to the same goal. (For more details about A*, see https://en.wikipedia.org/wiki/A*_search_algorithm.)

Sometimes the units got stuck when they all tried to move to the same goal. Therefore, I made them wait for a short time if the next tile is blocked, to give the other unit the chance to move away.

Multiplayer Support

I also added basic TCP/IP multiplayer support. The server had the full game model, and playing as the server felt like playing singleplayer. The client on the other hand sent all commands to the Server and received back all changes made to the game model. This was the easiest solution, however it had some unpleasant side effects. For example, the unit movement tended to look "off" with increasing lag. The units suddenly jumped when a change in position was registered at a time not yet expected by the client. But at least it worked!

On the low-level side it would be better to use a custom reliable ordered package system on top of UDP to make the traffic faster. (There is a tutorial on how to this here: https://gafferongames.com/.)

For the game-level side, the "correct" solution for RTS games is to use a "deterministic lockstep" system. Both players send their respective commands to each other, acknowledge the receipt of said commands, and run the same game simultaneously. A good in depth tutorial on the deterministic lockstep system can be found at https://www.gamasutra.com/view/feature/131503/1500_archers_on_a_288_network_.php

How it all ended

Looking back, I would say that I got pretty far. I had a multiplayer supported RTS game with units that could move, attack and cast spells. I had made 3 different types of units, including the graphics. However, I soon realized how utterly unattainable my initial goal was. I would have had to add buildings, items, heroes, and a bunch of neutral units. After spending so much time and energy I wasn't even remotely close to finishing the game.

I pivoted to making a simpler "arena" version of the game. Each player would have a fixed amount of money to buy different units before the game, and then they would battle it out on a small map. In a sense, this was supposed to be a "minimal viable version" of the game. I almost finished this version, but then I did something stupid: Instead of finishing the minimal viable version, I moved back to the original version, added buildings and dreamt big again. This ultimately killed the project: I just stopped working on it one day.

Next time, I will tell the story of how I picked up gamedev again some years later.

Find me on itch: https://daju.itch.io/.

0 likes 2 comments

Comments

Lexus67

I have read a lot of articles about how it is best to actually start making games, but all those articles talk about using Unity and other engines. I am not interested in using an engine. I want to do it from the first principles. I found this article that talks about learning Gamedev the hard way which is exactly what I want. I already asked the best assignment writing service to compile different adopted methodologies for creating the best game ever in the science-fictional category. It is going to be awesome.

March 19, 2022 10:27 AM
Lexus67
April 29, 2022 11:03 AM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement