lördag 16 maj 2009

How to create a simple RPG fight engine using php

Now that I'm working on finetuning my fight/raiding engine, I felt I could take a break and write a blogentry about the process of creating one. I decided to share my first version of what started as a turn-based fighting engine between two characters, and later evolved into a real-time fighting engine between 6 or more (read: large-scale war between hundreds or thousands of "soldiers"). I recommend new developers to start with a very simple version of whatever type of engine you're after, and incrementally put new stuff in. Start simple. Then add something. Then add another element. Add another attack, or RNG. Then some more stuff. Then gather the stats from a database, instead of having them predefined inside the script. Voila, you now have a scaling and dynamic engine, so long as your players/users can edit those figures somehow (such as health, damage reduction via armors, magics, or what have you).

Lets have a look at the mighty 60 rows of code. Lets start with defining the stats for our two fighters.


$player1 = array("Name" => "Good-Guy", "Health" => 100, "Damage_min" => 2, "Damage_max" => 4, "Hit_chance" => 80, "Critical_chance" => 5);

$player2 = array("Name" => "Bad-Person", "Health" => 80, "Damage_min" => 3, "Damage_max" => 5, "Hit_chance" => 90, "Critical_chance" => 7);


As you can see, one has lower health, but instead have a higher base damage, chance of actually hitting the enemy, and a higher chance of hitting a vulnerable spot (critical chance). Keep in mind this is a simple engine, which is a base to continue the development of your own engine.

In order to find out who wins, we need to make sure the script continues simulating the fight until one of the competitors are dead. So, we create a loop, which is as follows:


while ($player1['Health'] AND $player2['Health'] >= 1){


This isn't excactly rocket science. This row simply states that "as long as player1 health and player2 health are both at 1hp or more, do this:"

Since this is a turn-based fight, Player1 attacks first. After that Player2 attacks. Then Player1 gets a shot again. And then Player2 again. This will continue until one of them are dead.


#Player 1 attacks:
echo $player1['Name'] ." attacks ". $player2['Name'] ." for ";

# Did he succeed in hitting the enemy?
$hit_chance = rand(1, 100);
if ($hit_chance <= $player1['Hit_chance']) {

# Calculate the damage:
$hit = rand($player1['Damage_min'], $player1['Damage_max']);

# Did he succeed in making a critical hit?
$crit_chance = rand(1, 100);
if ($crit_chance <= $player1['Critical_chance']) $hit *= 2;

# Reduce the amount of health the opposing player has left:
$player2['Health'] -= $hit;

echo $hit ." damage. ". $player2['Name'] ." has ". $player2['Health'] ." health left.
";
}
else {
echo "0 damage (miss!). ". $player2['Name'] ." still has ". $player2['Health'] ." health left.
";
}
if ($player2['Health'] <= 0) break;


You'll have to excuse the lack of indentation, it doesn't show for some reason. Lets go through this segment, piece by piece. This helps to understand the theory behind it. First off we need to find out wether or not Player1 can land the hit on Player2. There's numerous of ways of doing this, I chose a percentage-based system. We rand 1-100 to find a number, and compare it to the players chance of hitting his enemy, which in this case is 80%. As long as the rand is 1-80, he hits. If the rand is 81-100 (read: 20% of the cases), he misses his attack.

If he succeeded in hitting his opponent, our next step is to find out how much he hits for. This is done by randing his minimum and maximum damage output. We can then calculate if he scored a critical hit in the same fashion we found out if he landed his blow, by randing 1-100, and comparing it to his chance to score a critical hit, and if so, we alter the damage he does by a crit-multiplier. I chose x2 damage because of the simplicity.

Regardless of Player1 scoring a critical hit or just landing a normal hit, we need to reduce the health of Player2, along with reporting it.
The else-part is there if Player1 misses his attack. If he misses his attack, there is no need to calculate how much damage he does, or if he crits or not. It will still do no damage. So we report it. We don't need to alter Player2's health in this case, since he didn't get hit. The last row, with the if-part, is there simply to make sure that Player2 is still alive to make his attack. If his health is 0 or lower, he can't make another attack. It's only logical.

Player2's turn to attack. It's the exact same code, but where it previously said Player1 it now says Player2, and where it said Player2, it now says Player1.


#Player 2 attacks:
echo $player2['Name'] ." attacks ". $player1['Name'] ." for ";

# Did he succeed in hitting the enemy?
$hit_chance = rand(1, 100);
if ($hit_chance <= $player2['Hit_chance']) {

# Calculate the damage:
$hit = rand($player2['Damage_min'], $player2['Damage_max']);

# Did he succeed in making a critical hit?
$crit_chance = rand(1, 100);
if ($crit_chance <= $player2['Critical_chance']) $hit *= 2;

# Reduce the amount of health the opposing player has left:
$player1['Health'] -= $hit;

echo $hit ." damage. ". $player1['Name'] ." has ". $player1['Health'] ." health left.
";
}
else {

echo "0 damage (miss!). ". $player1['Name'] ." still has ". $player1['Health'] ." health left.
";
}
if ($player1['Health'] <= 0) break;
}


There is small addition to this part though, which is the curly bracket on the last row, below the last if-part. That one closes the while-loop that we started in the second code-block.

We can add one more piece for closure, which simply will state who won. If you want, you can add statistics, logging misses, crits, and average hit damage inside the loop, and then output it here:


# Someone has 0 or less health left.
if ($player1['Health'] > $player2['Health']) echo "
The winner is ". $player1['Name'] ."!";
else echo "
The winner is ". $player2['Name'] ."!";


If you have questions or comments, feel free to make yourself heard below.

Inga kommentarer:

Skicka en kommentar