The Legend of the Tomato Quest Tutorial - Combat
From SwinGame
This part of the tutorial will focus entirely on the combat of the RPG Game. So our character is able to attack, and our enemies can also attack. But when their swords hit their enemies, nothing happens... yet.
Contents |
Hit Sounds
Every sword needs a sound that plays when you hit an enemy, and for a cool effect, we'll have a special sound for when a combatant scores a critical hit.
Download the following sounds.zip file, and extract both Critical.wav and Hit.wav to the Resources\sounds folder in your project directory.
Now open the GameResources.cs sourcefile and add the following lines.
private static void LoadSounds() { NewSound("Swing", "Swing.wav"); NewSound("Heal", "Heal.wav"); //ADD THESE LINES OF CODE NewSound("Critical", "Critical.wav"); NewSound("Hit", "Hit.wav"); }
Now we have a Critical Hit sound, and a normal Hit sound.
Damaging a Character
The next step to getting combat working, is to create a method that damages an character. But before that, we need to make a change to our DamageType enumeration.
In the Character.cs source file, modify the DamageType enumeration to look like the following.
public enum DamageType { Heal, Enemy, Player, Critical, //ADD THIS LINE Evade, None, }
We also need to add some code to the UpdateCharacterStatus() method. Add the following lines of code to the UpdateCharacterStatus() in the Characters class within the Character.cs source file.
... //Player Damage case DamageType.Player: Text.DrawText(Convert.ToString(Damage), Color.White, Resources.GameFont("Arial"), (int)_Sprite.xPos, (int)_Sprite.yPos - 80 + StatusCooldown); break; //ADD THIS CASE //Evade an attack case DamageType.Evade: Text.DrawText("Evaded", Color.White, Resources.GameFont("Arial"), (int)_Sprite.xPos, (int)_Sprite.yPos - 80 + StatusCooldown); break; ...
Now our characters can evade attacks if they are lucky enough.
Now let's move onto the actual method that will damage our characters.
Add the following method to the Characters class in the Character.cs source file
public void DamageCharacter(int healthamount, DamageType type) { //Damage Character Health = Health - healthamount; //Set the damage change Damage = healthamount; //Set the damage type DamageType = type; //Set the status change cooldown StatusCooldown = 60; }
This code is almost identical to the code for the HealCharacter() method, except that we have to pass in what DamageType has occured, since damage could be from the Player, Enemy, or a Critical Hit. The attack could also have been evaded.
First we damage the character health, by subtracting the healthamount pass in from the character's current health. We then store the amount of damage that has occured, and store DamageType that occured as well, so we can use both for our player's notification.
Lastly, we set the StatusCooldown to 60, so the notification doesn't spam.
Setting up our Characters
We need to make a quick change to the RefreshCharacterStats() method. Add the following line of code to the RefreshCharacterStats() method in the Characters class in the Character.cs source file:
... //if the players experience is equal or greater then the next experience level, character gains a new level. if (Experience >= ExperienceNextLevel) { //Increase level by 1 Level++; //Find the remaining experience to carry over to the next level, and set characters experience to it. Experience = Experience - ExperienceNextLevel; //Increase the amount of experience needed to get the next level ExperienceNextLevel = (int)(ExperienceNextLevel * 1.5); //Give the Character some stat Points StatPoints = StatPoints + 5; //Give the Character a skill point SkillPoints++; //ADD THIS LINE OF CODE //Give the Character his health back Health = MaxHealth; //ADD THIS LINE OF CODE //Give the Character his mana back Mana = MaxMana; } ...
What this will do, is give the Player his full health and mana back when he grow's a level.
Random Number
For our Critical Hits, and Evasion, we need the use of random numbers. This is easy to implement.
Add the following field to the Game class in Game.cs
private Random _RandomNumber = new Random(System.DateTime.Now.Millisecond);
This will create an object that will give us random numbers. We pass in the System.DateTime.Now.Millisecond as the parameter, because if we do, if helps make sure that our numbers are always different.
Combat
We need to make a new source file now. Create a source file called Combat.cs and add the following code as a template.
using System; using System.Drawing; using System.Collections.Generic; using SwinGame; using Graphics = SwinGame.Graphics; using Bitmap = SwinGame.Bitmap; using Font = SwinGame.Font; using FontStyle = SwinGame.FontStyle; using Event = SwinGame.Event; using CollisionSide = SwinGame.CollisionSide; using Sprite = SwinGame.Sprite; using GameResources; namespace TomatoQuest { public static class Combat { } }
This will serve as a template for our combat
Critical Hits and Evasion
Now we need to create a function that will determine whether the Character has scored a critical hit.
Add the following method to the Combat class in Combat.cs source file:
private static bool CheckCritical(Character attacker, Random rnd) { //Get a random number, representing 0 to 100% int roll = rnd.Next(100); //If the roll% is within the attacker critical rate %, //the attacker scored a critical hit if (roll <= attacker.CriticalRate) { return true; } return false; }
This method takes in a character, and a random object (the object we created in GameLogic.cs).
We use the random objects, next command to get a random number from 1 to 100. This will be our luck roll, to determine whether we have scored a critical hit.
We then determine if the roll is less then or equal to the Character's critical rate, and if so, he has score a critical hit, and the method returns true, else it returns false.
Next is the evasion method, just like the Critical hit method, Add the following method to the Combat class in the Combat.cs source file:
private static bool CheckEvasion(Character defender, Random rnd) { //Get a random number, representing 0 to 100% int roll = rnd.Next(100); //If the roll% is within the defender's evasion %, the defender has evaded the attack if (roll <= defender.Evasion) { return true; } return false; }
There really isn't that difference between the Evasion method and Critical Hit method, other then we check against the character's evasion instead of the critical rate.
With these methods we can now determine if a character, Evades an attack, or Scores and Critical Hit.
Dealing the Damage
Now we'll implement the method that will actually do the damage.
Add the following method to the Combat class in the Combat.cs source file:
public static void AIHitPlayer(Character thePlayer, List<Character> theAI, Random randomnumber) { //Go through each AI for (int i = 0; i < theAI.Count; i++) { //Check that the AI is alive, On screen, and is attacking if (theAI[i].Alive && !Graphics.IsSpriteOffscreen(theAI[i].Sprite) && theAI[i].Attacking) { //Check that the AI's attack animation is at the final frame if (theAI[i].CurrentSlash.CurrentFrame == theAI[i].CurrentSlash.FramesPerCell.Length - 1) { //Check that the AI and Player is within striking distance if (AIController.CalculateDistance(thePlayer, theAI[i]) < 100) { //Check that the AI sword sprite has collided with the player if (Physics.HaveSpritesCollided(thePlayer.Sprite, theAI[i].CurrentSlash)) { //Check if Player evaded if (CheckEvasion(thePlayer, randomnumber)) { thePlayer.DamageCharacter(0, DamageType.Evade); } //Check if AI score a Critical Hit else if (CheckEvasion(theAI[i], randomnumber)) { Audio.PlaySoundEffect(Resources.GameSound("Critical")); thePlayer.DamageCharacter(theAI[i].Attack * 3, DamageType.Critical); } //Ordinary Hit else { Audio.PlaySoundEffect(Resources.GameSound("Hit")); thePlayer.DamageCharacter((theAI[i].Attack * (100 - thePlayer.Defense)) / 100, DamageType.Enemy); } //Set the attacking state to false theAI[i].Attacking = false; } } } } } }
Also add this following method to the Combat class in Combat.cs
public static void PlayerHitAI(Character thePlayer, List<Character> theAI, Random randomnumber) { //If the player's attack animation is at the last frame, and is attacking if (thePlayer.CurrentSlash.CurrentFrame == thePlayer.CurrentSlash.FramesPerCell.Length - 1 && thePlayer.Attacking) { //for each AI for (int i = 0; i < theAI.Count; i++) { //if AI, is alive, onscreen if (theAI[i].Alive && !Graphics.IsSpriteOffscreen(theAI[i].Sprite)) { //if the AI is within attacking distance if (AIController.CalculateDistance(thePlayer, theAI[i]) < 100) { //If the player's sword has hit the AI if (Physics.HaveSpritesCollided(theAI[i].Sprite, thePlayer.CurrentSlash)) { //Check if AI evaded if (CheckEvasion(theAI[i], randomnumber)) { theAI[i].DamageCharacter(0, DamageType.Evade); } //Check if Player score a Critical Hit else if (CheckCritical(thePlayer, randomnumber)) { Audio.PlaySoundEffect(Resources.GameSound("Critical")); theAI[i].DamageCharacter(thePlayer.Attack * 3, DamageType.Critical); } //If its a normal Hit else { Audio.PlaySoundEffect(Resources.GameSound("Hit")); theAI[i].DamageCharacter((thePlayer.Attack * (100 - theAI[i].Defense)) / 100, DamageType.Player); } //If the AI's health is less then 0, the AI has died //and so, the Player gains experience if (theAI[i].Health <= 0) { thePlayer.Experience = thePlayer.Experience + theAI[i].Experience; } //Set Players Attacking to false thePlayer.Attacking = false; } } } } } }
Now our Player and AI can attack each other.
Finishing Touches
Add the following Lines of code to the Run() method in the Game class in Game.cs source file, within the loop:
... //Interact with Healers and Update Player Status Interaction.InteractWithHealers(_Player, _Healers.Characters); _Player.UpdateCharacterStatus(); //ADD THESE 2 LINE OF CODE //Combat Combat.AIHitPlayer(_Player, _AI, _RandomNumber); Combat.PlayerHitAI(_Player, _AI, _RandomNumber); //Run User Interface _Interface.RunUI(_Player); ...
This will run the checks for the combat.
One final thing needs to be done.
Add the following line of code to the UpdateAI() method in the AIController class in the AIController.cs source file:
...
//If the AI can attack, and is within attacking distance, attack
if (theAI[i].CanAttack && CalculateDistance(theAI[i], thePlayer) < 45)
{
theAI[i].InitiateAttack();
}
//Updates the AI's Walking Animation
theAI[i].UpdateCharacterAnimation();
//Draws the AI
Graphics.DrawSprite(theAI[i].Sprite);
}
theAI[i].UpdateCharacterStatus();
}
}This will add the notifications to the AI.
I've also made a new CharacterInformation() method that shows you your level and how many unused skill points you have, while your in the game.
Add the following method to the UserInterface class of the UserInterface.cs source file:
private void DrawCharacterInformation(Character theCharacter) { Text.DrawTextOnScreen("Level: " + theCharacter.Level, Color.White, Resources.GameFont("Arial"), 20, 60); Text.DrawTextOnScreen("Unused Stat Points: " + theCharacter.StatPoints, Color.Red, Resources.GameFont("Arial"), 20, 85); }
Also add the following line of code to the RunUI() method in the UserInterface class within the UserInterface.cs source file:
//Execute the User Interface public void RunUI(Character theCharacter) { //Toggles the stat page if the Space Bar is hit if (Input.WasKeyTyped(SwinGame.Keys.VK_S)) { ToggleStatPage(); } //Draws the Health Bar and the Stat Page DrawHealthBar(theCharacter); //ADD THIS LINE OF CODE DrawCharacterInformation(theCharacter); DrawStatsPage(theCharacter); }
And that's that, Compile and run the game, now you'll be able to fight your enemies, gain levels, and add stats.
Next tutorial will be looking at making the Legendary Tomato Item, and adding in the functionality to win and lose the game.
The Project so far
The Source code for the tutorial up to this point can be found Here.
It is strongly recommended that you read the source code as you go through the tutorial to get a better understanding of how it works.

