Skip to content

Summer of Code

jellysnake edited this page Aug 13, 2018 · 10 revisions

<- back | home
Header

Contents

(Please note that all links in this document point to the latest version of the files rather than static versions.
However, all work on the project will cease between the end of the final evaluations and the announcement of results)

Project objectives

back
The primary purpose of this project was to add an additional game modes to Terasology. In particular, a Tower Defence game. However, the project also has a secondary motivation to showcase how the sandbox nature and extensiblity of Terasology. As such it aimed to implement a Tower Defence in a uniquely Terasology method.

Project Experience

back
This project involved both actual programming in implementing the features, but also design to ensure the systems can be extended, and documentation so they can be used. The project also was almost wholly self managed, with weekly meetings conducted with Terasology Mentors.
In order to deliver all the features listed in the project proposal I had to both carefully allocate and plan the features into weekly objectives, and provide the self-motivation to implement the features in that timeframe.

As part of this project I also intended to combine the work I have produced with that provided in other modules. For instance the pathfinding implementation used was Flexible Pathfinding and both the Economy and Upgrading code are intended to be extracted and used by other projects. Unfortunately that was not possible during the Summer of Code but was/will be completed shortly after.

Overview of work areas

back
The two main areas of work that will be extracted into their own modules are the Shop/Economy systemand the Upgrading systems. There is intentions to merge them with the code from Gookeeper, another GSoC project.
UI work was also added to allow components to be displayed easily and I intend to port that to the base engine.
Systems for the building of towers is also potentially to be placed into a Multiblock modules.

Code that is specific to this project includes:
The enemy and wave managers that control the enemies, spawning and moving them around as needed.
The tower systems that handle integrating and automatically calling all the different blocks in a tower.
The implementations of all the Targeters and Effectors in the project.
The world generation system which produces the game field and dome.
Numerous other small systems which work together to enhance all the above.

Movement & Pathfinding

back
In order to move the enemies I employed a mixture of custom movement code an existing pathfinding code.
Enemies store the next position they are currently trying to move towards. Each frame they have their position directly moved towards this goal. When they reach the goal, the next one in the path is selected. When they reach the end of the path they notify other systems of this.
Rather than use the inbuilt physics system to move the enemies using either forces or velocity I chose to directly alter their coordinates. This is because the enemies will always follow the same path and cannot have their movement altered. Thus the more complex options are overkill and introduce bugs in making the movement smooth.

The actual action of moving the enemies is packaged into a separate system and is used by the visual effects system to move the bullets from tower attacks

Code is located in the movement package & EnemyManager class and partially in the re-pathing PR and climbing PR

Tower Building system

back
Terasology uses an Entity Component System (ECS) architecture, which means that each block in the game which has 'complex' properties (anything beyond just existing really) has an associated entity. In order to bind multiple blocks in a tower together I create an new entity which simply contains a two way link between itself and all the block entities.
This also raises the problem of needing to be able to add or remove blocks from the tower.
To manage this when a block is removed, I destroy the tower entity and re-build the tower block by block. When adding a block I check how many towers it is touching and merge them as needed.

Code is located in the TowerBuildSystem class and partially in the tower manager overhaul PR

Tower Management

back
Towers firing is a coordination between three elements. Attack rates are determined by Targeters so an internal counter is set for each of those that will fire when any given targeter is to fire. On firing, an event is sent against the attacking targeter. As events can be filtered for specific properties, only the system that implements that targeter is called, which handles the process of selecting the enemies to fire.
Then, the Effectors on the tower are sequentially on each enemy, using the same process of sending events. This means that they are automatically called to apply their effect when needed.

As this is one of the main areas that this module can be extended, the code is structured to be as easy to use as possible. New types of either Effectors, Targeters or Cores are automatically found and added to various systems using runtime reflection to scan for subclasses of a base abstract class for each type.
Additionally The method of implementing logic for the block types is the same as applying logic for any other system in Terasology.

Code can be found in the towers package and the Tower Manager Overhaul PR

Upgrading system

back
Each block in the tower can be upgraded using a specific UI screen, (Details of how the upgrades are displayed to the player are in the next sections), and upgrades can be specified in a plaintext asset format. This allows for easy use of this system as it is also the main extension point.
This format contains a link to which part of the entity should be upgraded as well as a key-value list of which fields should be changed by what amount. The system then uses reflection to alter the fields by the given values. Currently the system is limited to numbers, as it doesn't make sense to increase or decrease a String or Enum value.
For example, this is a section of the upgrade data for the Poison Effector (src):

    "upgrades": [
      {
        "upgradeName": "Damage",
        "stages": [
          {
            "cost": 5,
            "values": {
              "damage": 2
            }
          },
          ...
      }, {
        "upgradeName": "Poison Damage",
        "stages": [
          {
            "cost": 5,
            "values": {
              "poisonDamage": 1
            }
          },
          ...
      }, {
        "upgradeName": "Poison Duration",
        "stages": [
          {
            "cost": 5,
            "values": {
              "poisonDuration": 500
            }
          },
          ...

The code for this is contained within the upgrading package and the Upgrading PR

UI Component Parsers

back
In order to display both the stats from the tower blocks, as well as the changes from a given upgrade the values from the upgrades need to be changed into human-readable formats. An example of this is changing multipliers from 1.5 to 150%, attack speeds from 200 to 4 attacks/s or time from 1500 to 1.5s.
In order to achieve this I create a new class called a UIParser which contains two default methods. The first returns the class this parser applies to and the second a mapping of all the fields to display and the names to use when displaying them. Then, for each field returned in the map, a method should exist on the parser which has a return type of String and two parameters. The first a boolean and the second the type of the field in question. This is then used to convert from the raw value into the human-readable string.
These UIParsers are registered automatically using reflection an likewise the fields are automatically called based on a specific name format and the parameters.

For example, this is what the parser for the Poison Effector looks like. (I've removed javadoc to shrink it down) (src):

public class PoisonParser extends DamageParser {
    @Override
    public Class<? extends Component> getComponentClass() {
        return PoisonEffectorComponent.class;
    }
    @Override
    public Map<String, String> getFields() {
        Map<String, String> result = new HashMap<>();
        result.put("damage", "Initial Damage");
        result.put("poisonDamage", "Damage Over Time");
        result.put("poisonDuration", "Poison Duration");
        return result;
    }
    public String poisonDuration(boolean isUpgrade, int value) {
        return (isUpgrade ? "+" : "") + convertDuration(value);
    }
    public String poisonDamage(boolean isUpgrade, int value) {
        return (isUpgrade ? "+" : "") + String.valueOf(value);
    }
}

This code can be found in the upgrading package and the Tower Screen PR

Major UI Screens

back
The major UI screens are a mixture of both completely new screens and tweaks made to existing ones. For instance the [Death Screen] and [Shop Screen] both override and edit existing screens.
This is done by utilising Terasology's "delta" and "overrides" systems which allows for assets to be altered or overwritten. As the UI format is specified in asset files this allowed me to make structural changes to the screens.
For the logical changes I either changed the implementing class in the asset files to point to a new class, or used existing methods to overwrite values in the given screens.
All UI uses Terasology's custom 'Nui' ui system. There was one change required to enhance on of the screens as well, which resulted in a PR to the engine.

Code can be found in the ui package or one of the PR's for the screens

Links to work

back