Phagocytosis DevLog #1 —Abilities, Player Movement, and Modifiable Constants

Zachary Buffone
6 min readJul 8, 2019

--

This is the first devlog for Phagocytosis, a game I am in the early phases of developing. These logs will go into detail on what I worked on that week, and how I implemented some features.

Phagocytosis is a top-down RPG where you take on the role of white blood cell cop Romo, enrolled in the Terry Artery Patrol. Your duty is to protect the denizens of your human’s host body — cells, proteins, and other life molecules — from the hoards of bacteria, viruses, and mutated cells that co inhabit the body. Split, grow, and shoot your cells to take down your enemies.

Abilities

This week, I primarily focused on the abilities system. Romo has a host of abilities that will help you accomplish your duties. The three core abilities are splitting your cells and doubling your cell count at the cost of some mass, growing and increasing the mass of your cells, and shooting your cells to do damage to your enemies. Romo also has special abilities that the player can customize. Some of these abilities include a burst-fire of cells, a semi-circle of cells acting as a shield, and a ability that will give you a big cell to work with.

The core abilities — split, shoot, grow, and shrink.

My goal of this system was to make abilities modular and easy to prototype and implement. At the top of the system is a manager class. This class handles user input, splitting ability, firing ability, and growing ability and dispatching the special abilities. The growing, splitting, and firing abilities are built into the manager because the user will always have access to these core abilities. I may change this later and move them to separate classes in the future. Special abilities like the shield ability are in a separate class derived from a abstract base class Abilities.

public abstract class Ability
{
public abstract IEnumerator DoAbility(AbilityManager am, CellManager cm);
public abstract bool Prerequisites(int atp, int cells);
public abstract string GetName();
public abstract int GetATPCost();
}

The implementation of DoAbility() is where the logic of the ability is. It is called as a co-routine from the AbilityManager component. Prerequisites() is called before DoAbility() to check if the player has the resources (ATP, which is a energy type resource, and cells) to preform the ability. The other two methods are pretty self-explanatory.

Special abilities — shield and burst.

You may notice that this class does not derive from MonoBehaviour and is not a GameObject component. The reason for this is I do not need the Start() , Update() , or StartCoroutine() methods. Designing abilities as objects instead of components help with making this system modular. These abilities are held in a Dictionary variable in the manager class.

private void SetUpAbilities()
{
abilities_dict_ = new Dictionary<int, Ability>();
abilities_dict_.Add(1, new BurstAbility());
abilities_dict_.Add(2, new ShieldAbility());
abilities_dict_.Add(3, new BigCellAbility());
}

In the future, the player will be able to bind abilities to the abilities keys — by default, the 1, 2, and 3 keys. For example, if the shield ability was bound to the 1 key, SetUpAbilities() will set the ability dictionary at 1 to the shield ability object.

When the player presses an ability key, the key he pressed is saved in the variable ability_active_ as an int in the FixedUpdate() method in the manager class. The Update() method checks whether this variable not set to 0, where 0 means no ability was pressed.

void FixedUpdate()
{
...
if(ability_active_ != 0)
{
if(abilities_dict_[ability_active_].Prerequisites(atp_, cell_manager_.GetCellListCount()))
{
abilities_enabled_ = false;
StartCoroutine(abilities_dict_[ability_active_].DoAbility(this, cell_manager_));
ChangeATP(atp_ - abilities_dict_[ability_active_].GetATPCost()); ability_active_ = 0;
}
else
{
abilities_enabled_ = true;
ability_active_ = 0;
}
}
...}

When this check passes, the method checks whether the Prerequisites() method of the object in the abilities dictionary at ability_active_ passes, then calls the abilities DoAbility() method as a co-routine.

Player Movement

The difficulty of doing player movement in this game is handling more than one player object at a time. The player could have up to 64 cells at a time, and they act independently from each other. To solve this problem, each cell has “gravity”, and a target to gravitate to. By default, this target is the player movement target. The player actually moves the player movement target, not the cells.

The code for moving the player movement target is the standard Input.GetAxisRaw() and rb_.MovePosition() implementation. However, there are a few changes to make the movement fluid. When the player is not holding down the movement keys (Input.GetAxisRaw() is zero on both axes), the player movement target’s position is set to the average position of all player cells. If this was not implemented, then the cells will still move when the player lets go of the movement keys. This gives the movement a more fluid feel.

In the cells’ component handling gravity, there is a force applied towards the direction of its target. Again, the default target is the player movement target. This target can change. The shield ability creates targets arranged in a semi-circle for the cells to gravitate to. This is what holds the shield cells in position. This force is calculated by multiplying a normalized cell-to-target Vector2 by a constant force scalar and the distance of the cell-to-target vector. The magnitude of this force is clamped between 0 and 5000. Unity does not like large forces being applied to rigidbodies and will break the physics of your game.

Movement in action. Blue dot is player movement target.

There is also a frictional force applied to the cell when the cell is in motion. This force is calculated by multiplying a normalized vector in the direction opposite the cells velocity by the cells speed and a constant scalar. This is to prevent the cells from orbiting the target. With this force, the cell goes straight to the target.

Modifiable Game Constants

On the fourth of July, I developed a modifiable game constants system. These constants are “modifiable” in the sense that the games progression system will be able to modify the constants based on talents unlockable in the progression system. The goal of this is to encapsulate the constants so that the user can use the constants throughout the game not knowing that their values are being modified.

To do this, I created the structs IntConstant and FloatConstant. These structs hold a default value in the type of either int or float, and a name string. These structs also have a property called Value whose getter will send the name and default value to a modifier. The modifier will apply a function to the default value of based on the name. Right now, because I have not implemented the progression system, the property just return the default value.

public struct IntConstant
{
private string name;
private int default_value;
public int Value
{
get { return default_value; }
}
public IntConstant(string n, int v)
{
name = n;
default_value = v;
}
}

Here is an example of an implementation of the IntConstant class.

public static readonly IntConstant MAX_ATP = new IntConstant("MAX_ATP", 100);

And to use MAX_ATP, you call GameConstants.MAX_APT.Value

I am very happy with this system and I think it will make implementing the progression system nice and easy. I have always wondered how games handle changing constants based on talents and I think this is a good way of doing it, but the test will be when I soon implement the progression system. The reason I am implementing the constants system right now is because I want to move all the constants to a separate class to make it easy to change them during playtesting the combat.

Follow the development of Phagocytosis on my Twitter at https://twitter.com/ZacharyBuffone.

--

--

Zachary Buffone

Indie game dev from Boston, MA. Email me at zacharybuffone@gmail.com. Follow me @ZacharyBuffone.