Games for fun here!

This article is the second part of a series covering my experience building a simple combat system for a runner/beat'n'up/rpg/rogue-like game on Unity3D. Just for fun.

Please check part 1 before reading this, otherwise you may become confused.


I have ended part 1 with a hook to upgrading the combat system so it can handle more dynamic weapons, effects, attacks, etc.

Let's remeber IAttack and ICombatTarget:

public interface ICombatTarget
{
    //something is attacking me so I must defend
    void Defend(int attack);
}

public interface IAttacker
{
    //The attacker returns its damage
    int Attack();
    //and must have a target
    ICombatTarget GetTarget();
}

And a simply implementation of Attack() on the player would be like this:

public int Attack()
{
    int attacks = 0;
    //the player base attack, calculated using STR attribute or something. 
    attacks += this.GetPlayerBaseAttack();
    //the current equiped weapon on the player
    attacks += this.GetPlayerWeapon().GetAttack();
    return attacks;
}

And from part 1:

"The player is leveling up, geting gold and buying new weapons with special effects. Can you imagine what would happen if the player weilded weapons on both hands, or if the player used some weapons with elemental effects or with double hit?"

If the only issue was to increment the attack value based on specific calculatioins owned by weapons, a nice solution would be to use something like a simple decorator pattern to decorate the IAttacker.Attack() result. Situation example: Player hits with a flaming sword that hits twice on a single attack for 10 damage each hit

  1. Player hits with 10 fire damage * 2 = 20 fire damage once
  2. Enemy is resistent to fire and damage is reduced to 10 fire damage once
  3. Enemy has 1 base defense plus 1 armor defense, with total defense of 2
  4. The remaining 5 fire damage once, is reduced from the enemy total defense: 10 - 2 = 7 fire damage once.
  5. Enemy loses 7 health points from 7 fire damage once.

Works good, right? Huuuum, not yet. If the mechanics go further and consider special effects, double hits, etc, Attack() cannot simply return and int value because defenders may defend each kind of attack differently. Let's see the same case again, but defending each attack individualy:

  1. Player hits with 10 fire damage twice
  2. Enemy is resistent to fire and damage is reduced to 5 fire damage twice
  3. Enemy has 1 base defense plus 1 armor defense, with total defense of 2
  4. The remaining 5 fire damage twice, is reduced from the enemy total defense: 5 - 2 = 3 fire damage twice.
  5. Enemy loses 3 + 3 health points from 3 fire damage twice.

So by handling each attack individualy, the monster take a total of 6 damage against 7 damage if the attack was just summed. Can you figure out other variations of the case above? Try that on your head for summing the attacks and defending each individualy:

  • player hits 10 fire damage twice on an enemy with no fire defense
  • player hits 10 fire damage twice on an enemy vulnerable to fire
  • player hits 10 normal damage twice on an enemy that always ignore odd attacks on an entire combat

The examples goes far away as much as you want to imagine.

Now, down to business. The next step is transforming the attack from an integer value to an interface. But note here that I will leave attack types for part 3:

IAttack

public interface IAttack
{
    //get the total damage 
    int GetDamage();
    //adds more damage
    void AddDamage(int damage);
}

And a simple implementation of a concrete attack (or abstract If I want to extend it in the future) would be like this:

class BasicAttack : IAttack
{

    protected int damage = 0;
    
    public BasicAttack(int damage = 0) 
    {
        SetDamage(damage);
    }

    public int GetDamage()
    {
        return damage;
    }
    
    public void AddDamage(int damage)
    {
        this.damage += damage;
    }
}

And now that the project has an interface for attacks, IAttacker.Attack() should return a list of IAttack objects and ICombatTarget.Defend() should accept a list of IAttack as well:

public interface IAttacker
{
    //The attacker returns its damage
    List<IAttack> Attack();
    //and must have a target
    ICombatTarget GetTarget();
}

public interface ICombatTarget
{
    //something is attacking me so I must defend
    void Defend(List<IAttack> attack);
}

Now I am missing a definition for weapons. Thinking about the attack interface I just defined above, the weapon implementation should use it. Let's have a look at it:

public interface IWeapon
{
    //get the IAttacks of a weapon
    List<IAttack> Attack();
    //and add IAttacks to a weapon of course
    void AddAttack(IAttack attack);
}

This post is getting longer and full of detail, but stay with me, we are getting there.

Let's check the player action manager again with the new interfaces:

Player Actions Manager

public class PlayerActions : MonoBehaviour, IAttacker, ICombatTarget 
{ 

    //a lot of attributes goes here
    ...
    //a lot of other methos goes here
    ...
	
    public List<IAttack> WeaponAttacks()
    {
        /*
        here the method could iterate over the weapons and their 
        IAttacks every time an Attack() happens, or the player could have an attribute 
        with a list that is populated every time it changes the equiped 
        weapons and return it here.
        Personally, I would go for the second option, but for the sake of the example:
        */
	List<IAttack> attacks = new List<IAttack>();
	foreach(IWeapon weapon in this.GetEquipedWeapons())
        {
	    attacks.AddRange(weapon.Attack());
        }
        return attacks;
		
    }

    public List<IAttack> Attack()
    {
        List<IAttack> attacks = new List<IAttack>();
        //the player base attack, calculated using STR attribute or something. 
	int baseAttack = this.GetPlayerBaseAttack();
		
        //and for every weapon, get its IAttacks
	foreach(IAttack attack in this.WeaponAttacks())
        {
            //weapons damage must be added with the player base attack.
            //A decorator could be used here of course.
	    attack.AddDamage(baseAttack);
	    attacks.add(attack);
	}
        return attacks;
    }

    public ICombatTarget GetTarget()
    {
        //this one doesn't change
        return this.GetClosestFocusedEnemy();
    }

    //a fight method used by a combat manager or something like this...
    public void Fight(){
        // this doesn't change, however, Defend() is now getting a 
        //list of IAttack as seem above for the player implementation
        this.GetTarget().Defend(this.Attack());
    }

    public void Defend(List<IAttack> attacks)
    {
        
        int damage = 0;
        int armor = this.GetArmor();
        foreach(IAttack attack in attacks)
        {
	    damage = attack.GetDamage() - armor;
	    /*
	    An option here would be to sum the damage and take all at once. 
            It depends on your project.
	    */
            if(damage > 0)
	    {
	        this.TakeDamage(damage);
	    }
        }
    }
}

Finally for the enemy classes the implementation is almost the same, so I 'll leave it to you.

With this design, now every combatent game object will be able to wield weapons and fight each other. Another hidden bonus feature here, is that by using IWeapon and IAttack, the game will be able to define its weapons and attack styles on text file or any other outer source, being able to change it on runtime.

On part 3 I will try to show you the special effects and attributes for weapons. And probably by part 4, a better armor design similar to weapons will be there too.

That's all for now and see you next time.