Subscribe to Martyr2's Programming Underground        RSS Feed
-----

Applying Enchantments for PHP RPG Games - One Approach

Icon Leave Comment
So you want to build a PHP game and are just now learning how to do it. You fired up your browser and began searching for various game articles and perhaps you have found very little. What you did find might not explain a flexible way to apply magical spells to units you create. After all, what is an RPG game without enchantments you can cast on your (or enemy's) units?!?! Below I have outlined a nice flexible experimental approach that you can take to apply a range of enchantments on a particular unit modifying their stats collectively.

This approach has the following benefits...

1) Apply as many enchantments as you want to a unit and they are applied in order (easily build in order manipulation too)
2) Enchantments can kill that unit
3) Enchantments you apply can be unit type specific ("WarCry" might be tailored for soldiers, not mages)
4) Supports multiple types of mutation options (we show adding stats and subtracting stats. You can however dynamically multiply, divide, apply logarithmic functions etc to alter stats)
5) Allows us to apply multiples of the same enchantment
6) Provides an easy extension path to add your own spells, stats etc to fit your game

For our example we are going to make a Paladin class who, as you might of guessed, is a soldier unit type. When we create him, we are going to make him 5/6 (that is 5 attack, 6 defense). In this class we are going to save his stats privately so that no other classes can meddle around in his life (only his god can do that). Here we could easily add other things like magic defense, charisma or whatever you want but make sure to extend the various interfaces to support those. That will be outside the target of this blog post.

We are going to also create an interface called "Spell" which several enchantment classes can implement to work with our units. The beauty of this approach is that at any time in the future we can add more enchantments and, as long as they implement the functions in our interface, they can plug right into the game. It is a great way to expand your spell library later as your game progresses in popularity.

Now I could have also done an abstract/child inheritance situation here, but I thought an interface would be a bit nicer in that you won't have a huge hierarchy of classes floating around where you have to later figure out where your new spell fits. Interfaces seem to flatten that out in this design. You could go with the class inheritance approach as well if you think that will fit better. I leave that up to you.

In the example further below we will also create several spells, some strengthen our Paladin, some weaken him. Some may even kill him and we will see what that means for our beloved Paladin. Perhaps add an extra parameter to your interface to allow resurrection? There is an idea.

Here is our Paladin...

class Paladin {
	private $targetType = "soldier";
	private $life = 1;
	private $currentEnchantments = array();
   
	public function __construct($power = 5, $toughness = 6) {
		$this->attackPoints = $power;
		$this->defensePoints = $toughness;
	}
   
	public function getAttack() { return $this->calculateEnchantedAttack(); }
	public function getDefense() { return $this->calculateEnchantedDefense(); }
	public function getLifeStatus() { return ($this->life == 1) ? "Yes" : "No"; }
   
	// Where we add our enchantments which of the interface type "Spell"
	public function addEnchantment(Spell $enchantment) {
		$this->currentEnchantments[] = $enchantment;
		$this->calculateEnchantedDefense(); // Just to see if this is going to kill him.
	}
   
   
	// Loops through spells and applies them to character's attack in order.
	// Returns modified attack rating
	private function calculateEnchantedAttack() {
	    $enchantAttackPoints = $this->attackPoints;
		
		foreach ($this->currentEnchantments as $key => $value) {
			if ($this->life == 1) {
				// Only calculate those which target this type of unit.
				if ($value->getTargetType() == $this->targetType) {

					// Growth type of spell
					if ($value->getSpellModificationType() == "additive") {
						$enchantAttackPoints += $value->getAttackModifier();
					}
					else if ($value->getSpellModificationType() == "subtractive") {
						// Weaken type of spell. Weaken too much and kills unit.
						$enchantAttackPoints -= $value->getAttackModifier();
						if ($enchantAttackPoints <= 0) { $enchantAttackPoints = 0; }
					}
				}
			}
			else { return 0; }
		}
		
		return $enchantAttackPoints;
	}
	
	// Loops through spells and applies them to character's defense in order.
	// Returns modified defense rating
	private function calculateEnchantedDefense() {
	    $enchantDefensePoints = $this->defensePoints;
		
		foreach ($this->currentEnchantments as $key => $value) {
			if ($this->life == 1) {
				// Only calculate those which target this type of unit.
				if ($value->getTargetType() == $this->targetType) {
				
					// Growth type of spell
					if ($value->getSpellModificationType() == "additive") {
						$enchantDefensePoints += $value->getDefenseModifier();
					}
					else if ($value->getSpellModificationType() == "subtractive") {
						// Weaken type of spell. Weaken too much and kills unit.
						$enchantDefensePoints -= $value->getDefenseModifier();
						if ($enchantDefensePoints <= 0) { $enchantDefensePoints = 0; $this->life = 0; } // <--- He dies
					}
				}
			}
			else { return 0; }
		}
		
		return $enchantDefensePoints;
	}
}



This unit may seem a bit complex for the little gain we get. The magic here is in the two private methods for calculating the attack and defense after enchantments are applied. For simplicity sake (and to save you from having to see another class) I made the methods local. However, they are written so that they can be used in a base class as well with little if any modification. Then all your units "hopefully" inherit from this base. Take note that the addEnchantment method takes a parameter that implements our interface called "Spell". This will allow us to have simple spells and terribly complex spells that, as long as they share the interface, will be accepted and applied to the unit.

Here is our interface...

interface Spell {
    public function getTargetType();
    public function getSpellModificationType();
    public function getAttackModifier();
    public function getDefenseModifier();
}



Quick explanation of these interface methods:

getTargetType is what we are going to use to determine if this spell can even target our unit's type. To apply to our Paladin, the spell is going to have to return "soldier" from its implementation of the getTargetType method.

getSpellModificationType is going to tell us how the spell is going to change our unit. If it is Additive, the modifiers from Attack and Defense are going to be added to the "current" points for attack and defense. I say current here because this may not be the base points. These points may have been calculated from enchantments up until we get to this particular spell in the list of enchantments. In other words, if the spell before this weakened the unit, they may have lower attack / defense than their base points when we reach this enchantment.

getAttackModifier will return the points to apply to the unit. If the spell modification type is additive, these would be the points we add to attack. If it is subtractive, these would be the points that we subtract from attack. Yes you could apply negative points to additive and cut out subtractive, but I thought I would be explicit here on how you can implement other modification types.

getDefenseModifier like getAttackModifier, these are the points to add / subtract to / from the unit's defense.

So let's now whip up some spells. We will create 5 spell classes. 3 additive, 2 subtractive, 4 that apply to soldiers and 1 that applies to mages. Of the two subtractive spells one is surely going to kill him (in the order we apply them).

class WarCry implements Spell {
    public function getTargetType() { return "soldier"; }
    public function getSpellModificationType() { return "additive"; }
    public function getAttackModifier() { return 4; }
    public function getDefenseModifier() { return 0; }
}

class MagicShield implements Spell {
    public function getTargetType() { return "soldier"; }
    public function getSpellModificationType() { return "additive"; }
    public function getAttackModifier() { return -1; }
    public function getDefenseModifier() { return 3; }
}

class BookofKnowledge implements Spell {
    public function getTargetType() { return "mage"; }
    public function getSpellModificationType() { return "additive"; }
    public function getAttackModifier() { return 2; }
    public function getDefenseModifier() { return 2; }
}

class Poison implements Spell {
    public function getTargetType() { return "soldier"; }
    public function getSpellModificationType() { return "subtractive"; }
    public function getAttackModifier() { return 3; }
    public function getDefenseModifier() { return 1; }
}

class Harm implements Spell {
    public function getTargetType() { return "soldier"; }
    public function getSpellModificationType() { return "subtractive"; }
    public function getAttackModifier() { return 2; }
    public function getDefenseModifier() { return 8; }
}



If we apply a spell like "Poison" to our Paladin it is going to be good to target him because he is a soldier (from our Enemy's point of view). It will then apply a subtractive type effect which will reduce his attack by 3 and his defense by 1. At any time we reduce the defense we are going to check if his defense is at or below zero. If it is, that is going to kill him and we set his life status to 0 (aka dead). From then on any enchantment calculations will be skipped because he is dead.

Now to apply some spells and what that looks like. I hope our Paladin can take it!

// Just for printing Vitals
function printStats($ourSoldier) {
     echo "Current stats: " . $ourSoldier->getAttack() . " / " . $ourSoldier->getDefense() . " Is Alive: " . $ourSoldier->getLifeStatus() . "<br/><br/>";
}

// Create our Paladin (defaults to 5/6)
$ourSoldier = new Paladin();

printStats($ourSoldier);

echo "Add WarCry...<br/>";
$ourSoldier->addEnchantment(new WarCry());

printStats($ourSoldier);

echo "Add Book of Knowledge...<br/>";
$ourSoldier->addEnchantment(new BookofKnowledge());

printStats($ourSoldier);

echo "Add Poison...<br/>";
$ourSoldier->addEnchantment(new Poison());

printStats($ourSoldier);

echo "Add MagicShield...<br/>";
$ourSoldier->addEnchantment(new MagicShield());

printStats($ourSoldier);

echo "Add Harm, should kill him...<br/>";
$ourSoldier->addEnchantment(new Harm());

printStats($ourSoldier);

echo "Add another WarCry, did it work?<br/>";
$ourSoldier->addEnchantment(new WarCry());

printStats($ourSoldier);



As you will see the results of this testing look like this...

Current stats: 5 / 6 Is Alive: Yes

Add WarCry...
Current stats: 9 / 6 Is Alive: Yes

Add Book of Knowledge...
Current stats: 9 / 6 Is Alive: Yes

Add Poison...
Current stats: 6 / 5 Is Alive: Yes

Add MagicShield...
Current stats: 5 / 8 Is Alive: Yes

Add Harm, should kill him...
Current stats: 3 / 0 Is Alive: No

Add another WarCry, did it work?
Current stats: 0 / 0 Is Alive: No



As you can see things are working and he has gone through quite a bit of changes. Carrying 6 enchantments (1 after you are dead) can really do a lot to you. So step through this class and interface design a little bit, try some things out and you will see that this approach may be one that will work well with your design. If not, you can certainly take pieces of it and apply them. The one thing I do want to stress here is that your Paladin, as with all your units, should probably inherit from a base class that will contain all your generic non-unit specific methods (like the calculate attack / defense).

I hope you found this little blog post of use to you the next time you are thinking of making that next RPG. Whether it is knights and dragons or mafia gangsters and police. Thanks for reading! :)

If you want more blog entries like this, check out the official blog over on The Coders Lexicon. There you will find more code, more guides and more resources for programmers of all skill levels!

0 Comments On This Entry

 

September 2014

S M T W T F S
 123456
78910111213
1415 16 17181920
21222324252627
282930