Making an RPG in C#: An Indepth Tutorial
An indepth tutorial by frostydowns in C#
The concept and ideas behind an RPG game from my point of view.
Hey all,
I'm here today writing this tutorial up, because I think what I have to say will be of some use to someone (Knowledge is power, so many people take it for granted).
In this tutorial I will be explaining the concept behind my form project, compare the idea to other RPGs, just to give you starter people out there some ideas behind it.
But before we begin the programming language is c#, but the ideas behind everything SHOULD be about the same in any language, that being said, you can comment after wards about it if I'm incorrect after wards PLEASE keep in mind that this is my personal overview what I say there will be people that disagree on things and what not, while viewing this tutorial while the game is BASIC and may look dull, the idea behind it is how, RPG's are basically done from my POV.
Also not ALL of my code will be included, you STILL have to have basic knowledge, I'm just not going to hand feed you all my code, it's, as I said examples and how to go about it.
Now i've that's out of the way lets get one with it!
Contents:
1. The Beginning.
2. Character Screen
3. The mainform
4. Combat
5. Win Screen
1. The Beginning
When designing and RPG, It's always a good idea to plan out your story, character development, not necessarily the whole game, but, to give you an idea of what you and your team are doing, if you're doing it solo and just a project to learn from, and you're a person like me plannings not necessarily, I know I plan it all up in my head then put it to paper, it's how I work, it's what works for me.
First of all when designing an RPG, you might want to start, with a menu system, something basic, so that it's not overly complicated leave save and load till later, just have new game / exit game for now, if you have a look into my program I have globalized just about every array, this IS bad programming for OOP, but this isn't a business ap, it's a game, so in my books it's ok to globalize things like money, exp, level, damage .etc things that you will use through your whole program to get yourself setup with, how would you go about this? you need another class file, this file makes it possible to 'cache' all your forms on load up, set up the important variables that you need to keep things organised! as seen in this part of code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BattleArena
{
static class Varz
{
// Declairs the public variables
public static frmMainMenu menuform = new frmMainMenu(); // Main menu form
public static frmChar charform = new frmChar(); // Main menu form
public static frmMain mainform = new frmMain(); // Main gameform
// Game variables for gold .etc
public static int mygoldamount;
}
}
Notice how the 'static class Varz' is named you will be using this to call your variables, why wouldn't I use form1.show();? because when I was setting this up at the time it would just create an entire new form, say i clicked town then back then back to town i'd have 2 town forms open and 2 main forms open, with globalizeing them it's like setting the forms up into memory so you can use this.hide(); and form1.show() with minimal complications.
Now lets go back to the menu part, you should have 2 buttons, new game and exit game, you want to double click the new game so we can have the initial start up proccess of it all here's the following code for the new game;
public void btnNewGame_Click(object sender, System.EventArgs e)
{
// Hide this form
this.Hide();
// Setup all the variables for the new game click
Varz.mygoldamount = 0;
// Finally show the form
Varz.characterform.Show();
}
Now to call on our variable that we just created, I called mine Varz it's the name that comes after the static class in your class file that i mentioned before, because all of that is globalized anything you set will be set permanently so be careful it's perfect for resetting values such as out gold amount here, you want to hide the form first then do your code the show the next form, let me elaborate;
User click on new game
|
v
Hide the menu
|
v
Setup all your code, reset values behind the doors
we just don't want the user to see the code we're out putting
|
v
show the character select/setup screen
Now because forms don't use alot of memory and you can use it on a 486 you can store just about as much as you like, just keep things hidden like I've done to setup up, if you do manage to "bog down" the form then you may have to consider a loading screen, I hope that's been indepth enough for everybody on the initial start up of everything.
2. Character Screen
After a user click new game it's a goo idea to go a character menu for character creation, which I haven't done, you could probably have a form placement there, then onto the main form, which is where i'll be explaining int he next part what i've done for this.
But if you are incline to make a character setup screen with a name and what not, you will want to add it to your varz file, and make a new varz for name, and character selection if you are to have multiple characters, so we can 'call' what character you have, maybe set up a radio button then if the user clicks on said radio button the varz.charselected you can have as 'bill' or 'harry'.
For the name it's pretty straight forward string value to setup and because it's globalized you can call upon it in ANY form, because if you have what I have setup here, you will goto the main form next then another form for the battleground where as on this for you want to have something like this for when your character attacks: lisbox1.items.add(Varz.Charname + " Hits " + Varz.BaddieNaem + " for " + Varz.Mydamage " Damage!");
This will out put into a list box, "Harry hits rabbite for 10 Damage!".
Global variables are super handy in a game makers perspective, you'll notice I use a listbox that's because it's just a handy way to keep things tidy in my point of view, you could use anything you like really, you will also notice I globalize the damage, that's so I can refer to it for various things with out making a ton of referrals, it's just wayyy easier to do it this way.
Now when you click ok you will want to stick the character name, and the selected character into you're new globalized variable you can do this by:
Varz.Charname = txtCharname.text;
And that's it, it's like any other variable but it's globalized, remembering "Varz" is how we locate our variables you can also, for what ever reason declare the same name variable so you could have "string Charname;" but this wouldn't be global and just like a normal variable, now remember to show our name mainform after you do all of this setup, When displaying to another form you will have to call it like this "Varz.Mainform.txtCharName.Text = txtCharName.Text" this makes it so you call the form then the char name of that form and make it state that it's the characters form name, this will only ever change now if you change it by Varz.CharName, and you can now call upon it like anyother vars.
We will also need to load your selected character in, if you have him displayed in the next form, if you don't want it in the next form use this to load an image for when you do want it, I'm going to use the next form as an example, what you do is make a "pictureBox" int he next form, now draw something in paint, to represent said character and what your going to do is add this into the "Resource" file to do so in your solution explorer, click the properties folder (mines greyed out) and then double click Resources.resx, then click and drag your new painting into this and give it a name.
Now go back to that next button we were doing just a moment ago, and add in:
Varz.mainform.piccharimage.Image = Properties.Resources.charBill;
And that's it! What we have done is preload the image into the resource file and then referring to it, I'm not sure on the memory useage of this, so just use jpg's or something small, the good thing about this is we can refer to it where ever we like it's all preloaded much like your forms, you will need to fix the attributes of the picture to suit what paints is, you can locate that in paint via "image/attributes..." and the width and height is what the size of your character is, so if it's width 50 height 75 then for your image it'll be 50, 75 so H, W.
3. Mainform
You may want to display your character here, his name several stats, you can set these up on your own, one would consider this to be your home where you prepare before battle! Try setting up some button health and mana and int he variables do the same, here's the variable file I use
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BattleArena
{
static class Varz
{
// Declairs the public variables
public static frmMainMenu menuform = new frmMainMenu();
public static frmMain mainform = new frmMain();
public static frmtalents talentform = new frmtalents();
public static frmTown townform = new frmTown();
public static frmItemShop itemshopform = new frmItemShop();
public static frmInventory inventoryform = new frmInventory();
public static frmChar charform = new frmChar();
public static frmArmory armoryform = new frmArmory();
public static frmAbout aboutfrom = new frmAbout();
public static frmWinScreen winfrom = new frmWinScreen();
public static frmBattleGround battlegroundfrom = new frmBattleGround();
public static frmJunkYard junkyardform = new frmJunkYard();
public static frmHobby hobbyform = new frmHobby();
public static frmItemBreaker itembreakerform = new frmItemBreaker();
public static frmItemMaker itemmakerform = new frmItemMaker();
// Equipped stats
public static string headEQ;
public static string sholderEQ;
public static string chestEQ;
public static string golvesEQ;
public static string legsEQ;
public static string feetEQ;
public static string mainhandEQ;
public static string offhandEQ;
public static string twohanderEQ;
public static string weapontypeMHEQ;
public static string weapontypeOHEQ;
public static string weapontypeTHEQ;
public static string weapontypeEQ;
public static int minOHWepDam;
public static int maxOHWepDam;
public static int minMHWepDam;
public static int maxMHWepDam;
public static int minTHWepDam;
public static int maxTHWepDam;
public static int bonArmEQ;
// Stats
public static int TotalGold;
public static int TotalHP;
public static int myMinHP;
public static int myMaxHP;
public static int myMinMana;
public static int myMaxMana;
public static int Strength;
public static int Intellect;
public static int BonusArmor;
public static int BonusArmorEQ;
public static int myCurLevel;
public static int StrStat;
public static int IntStat;
public static int HPStat;
public static int fightNo;
public static int fightNoAmount;
public static string EquippedItem;
public static string EquippedMHItem;
public static string EquippedOHItem;
//public static int Agi;
//public static int Resist;
// Spell
public static int SpellCrit;
public static int SpellDamage;
public static int intlblSpellDam;
public static int manaTicker;
public static int RegenMana;
public static int SAbCrit;
// Heal
public static int healamount;
// Melee
public static int MHDamMin;
public static int MHDamMax;
public static int OHDamMin;
public static int OHDamMax;
public static int MinAvMDam;
public static int MaxAvMDam;
public static int MAbCrit;
public static int MeleeCrit;
// Level thing
public static int nowLevel;
public static int BLvlMin;
public static int BLvlMax;
public static int BLvl;
public static int BLvl2;
public static int CBLvl;
// Other stats
//public static int overkilldmg;
public static int Attack;
public static int RAttack;
public static int Randy;
public static string attackType;
public static int AbilityPointsUsed;
public static int AbilityPointsTotal;
public static int Crit;
public static string lastItem;
public static int level;
public static int abpointsHP;
// Baddies stats
public static int BHPMin;
public static int BHPMax;
public static int BMinDam;
public static int BMaxDam;
public static string TName;
public static int BAttack;
// Exp, Gold stuff
public static int CExp;
public static int NExp;
public static int MaxExp;
public static int ExpGold;
public static int WinExp;
public static int CGold;
public static int WinGold;
public static int NLvl;
// Hobby
// Item Breaker
public static int itemBExp;
public static int itemBNExp;
public static int itemBLevel;
public static int itemBJunkFillSelect;
public static int itemBProgress;
public static int itemBProgressSkip;
public static int itemBCreateMin;
public static int itemBCreateMax;
// Item Maker
public static int itemMExp;
public static int itemMNExp;
public static int itemMLevel;
public static int itemMJunkFillSelect;
public static int itemMProgress;
public static int itemMProgressSkip;
public static int itemMCreateMin;
public static int itemMCreateMax;
}
}
As you can see yes it's huge, but it's within reason, this place players should be preparing for battle, with various 'chilling' style activities to do there's not a lot going so let goto the next chapter of this tutorial.
4. Combat
Right now if we click enter the arena, you will want to up date the variables respectively for the beginning fight, I have a timer on the next form, for when it's enabled it will update all my graphics, then switch itself to disable, doing this allows me to stick that where ever, rather than do a bunch of variables in here I can utilise the timer feature, it will also randomize the bad guy selector and place that into combat and set up a new fight for me.
Then you will want to show the form, it looks a little over whelming but all of this code is quiet easy, first off when you hit attack, i have it check if you have an attack selected if not then display the message, then it will scan for an attack, i'll post part code for you to see what i'm talking about:
public void btnAtt_Click(object sender, EventArgs e)
{
// If no skills are selected
if (lisSkills.SelectedItem == null)
{
lisLog.Items.Add("Select an ability to use");
}
else
{
btnAtt.Enabled = false;
DeclairInts();
// Select which spell you used
// Random my hit from MAX DAMAGE and MIN DAMAGE for now we add both
// the main hand and off hand as a joint damage **NEEDS IMPROVEMENT
// add extra damage for bonus's .etc
if (lisSkills.SelectedItem == "[DPS] Melee 1")
{
attMelee1();
}
I disable the attack button to prevent players from spamming it while the timer's not going through and renable at the end of the regen timer, good thing about this I can create another attribute for attack speed to knock .1 second off or half a second off the cooldown timer allowing players to double hit, pritty neat huh? as you can see also I have "if selected item is equal to [DPS] melee 1 to the code now that code consis of:
public void attMelee1()
{
// Melee damage
Varz.MinAvMDam = Varz.MHDamMin + Varz.OHDamMin;
Varz.MaxAvMDam = Varz.MHDamMax + Varz.OHDamMax + 1;
crithitmelee();
}
Yes, because we globalized everything you can still use all the attributes! without the fuss so i can make as many of these as I like, this will generate my damage, pritty simple stuff there then check if I have critted which is the following code:
public void crithitmelee()
{
// Did the melee Varz.Attack crit?
// Randoms your melee crit
Random CRand = new Random((int)DateTime.Now.Ticks);
Varz.MAbCrit = CRand.Next(1, 100);
if (Varz.MAbCrit < Varz.MeleeCrit)
{
Varz.SAbCrit = 2;
Varz.Crit = 2;
}
}
And that's it, first off we random 1 - 100 (%) then we check if MABCrit is less than your ability to crit, give the crit value for 2, why do we want to do that? well if it's 0 and I add it on the end of my attack it won't affect the damage at all, if it's value is 2 it will double the damage, at the end of the attack I reset this value to 0 so it maybe check again.
Now if we go back to when you first click a button past allt hese checkers you will get:
// Does your damage calculation
MyDamage();
lblMDam.Text = Convert.ToString(Varz.Attack);
lblMDam.Visible = true;
tmrMDam.Enabled = true;
The code will do my damage, that is as follows
public void MyDamage()
{
string skillset = (string)lisSkills.SelectedItem;
tmrRegen.Enabled = true;
if (skillset.Contains("[DPS]"))
{
// Randoms your Varz.Attack
Random RandAtt = new Random((int)DateTime.Now.Ticks);
Varz.RAttack = RandAtt.Next(Varz.MinAvMDam, Varz.MaxAvMDam);
Varz.Attack = Varz.RAttack * Varz.Crit;
Varz.BHPMin = Varz.BHPMin - Varz.Attack;
lblBMinHp.Text = Convert.ToString(Varz.BHPMin);
// if the baddies HP is above or equal to 1
if (Varz.BHPMin >= 1)
{
updatebaddieshp();
}
// if the baddies hp is under or equal to 0
else if (Varz.BHPMin <= 0)
{
Varz.BHPMin = 0;
updatebaddieshp();
}
}
if (skillset.Contains("[Heal]"))
{
Varz.myMinHP = Varz.myMinHP + Varz.healamount;
MaxHPchecker();
lblHPMin.Text = Convert.ToString(Varz.myMinHP);
lisLog.Items.Add("You healed for " + Varz.healamount);
}
}
All this code really does is check if the attack was a heal selected or a dps selected because we don't want the damage to display when we heal, we probably could at some stage to give the healer base classed something to do, but right now, we're not going to, next if the skillset.contains, dps do this. This part of code is interesting because with it I can call things like item level or any text I need to grab, not we random the attack, then my normal attack * crit? (remember if it got set to 2 or 0? ;)) we minus the baddie hp from my attack then do a checker to see if the baddie has any health left, if it's below or equal to 0 then we make it 0 for fear of it being like minus and creating error within the hp bar.
The healing is pritty simple we just add the heal amount, do a checker to check if it's over your max hp, again to avoid errors, and display the healed amount, now lets go back and continure downt he attack line.
// Re-Randomize
Offset();
// Checks to see if you're alive
MeAliveChecker();
// Target loses hp and check if it's below 0
if (Varz.BHPMin <= 0)
{
Varz.BHPMin = 0;
//Do the win the game routine
Winthegame();
Varz.mainform.tmrRefresh.Enabled = true;
goto Outofloop;
}
// Re-Randomize
Offset();
// Does the baddie damage
tmrBWaitHit.Enabled = true;
}
Outofloop:
ScrollDown();
}
We off set the ranomizer, this may of been old code, I just letf it in anyway, we next check if we're alive, if hp min <=0 thing, then do if mob hp <= 0 aka dead we display the win the game routine and then goto outof loop to bypass the baddies doing damage, we re randomize again (old code) then do the baddie timer, to hit, now i have a few timers to deal with all of these they will activate with in 1 second and display damage, you could agian decrease this, then close it up and scoll the list boxes down. Providing everything is updated and ready you should be ok to go, now onto the win screen!
5. Win Screen
When you win the game this has a chance to pop up, if you die a button pops up and you click to exit and then have to heal at the temple, winthegame(); a snipit code is displayed below:
public void Winthegame()
{
LeaveCombat();
// Exp calc and to see if you dinged
if (Varz.CExp >= Varz.NExp)
{
ding();
}
MessageBox.Show(lblBName.Text + " defeated!");
lblBName.Text = "";
this.Hide();
// If there is loot on the mob
int lootyn = randy.Next(1, 10);
if (lootyn <= 5)
{
// Populate the other side with loots
populateLoots();
Varz.winfrom.Show();
}
else
{
Varz.mainform.Show();
}
}
We leave combat to clear up unwanted things to enable/disable buttons .etc, then we do the exp/gold calculationw hich i'm not mentioning because it's too easy, then if current exp is greater or equal to next exp we run the ding code and add stats, we display to the user next that the mob was defeated and then we hide it this screen soon after they click ok.
We random 1-10 I like working with larger random intervals, id it's below or equal to 5 then we display the winscreen if not display the main form, and then we open up the winscreen if we suceeded doing so, we populate the loots on the next screen, the code for that is as follows:
// Convert the money over
Varz.armoryform.lblGold.Text = Varz.mainform.lblGold.Text;
// Declair the ints
int levelEq = Convert.ToInt32(Varz.mainform.lblLvl.Text);
// Randomizers
// Randoms the generator both name selects
Random ArmorGens1 = new Random((int)DateTime.Now.Ticks);
Random ArmorGens2 = new Random((int)DateTime.Now.Ticks);
Random ArmorGens3 = new Random((int)DateTime.Now.Ticks);
Random WeaponGens1 = new Random((int)DateTime.Now.Ticks);
Random WeaponGens2 = new Random((int)DateTime.Now.Ticks);
Random WeaponGens3 = new Random((int)DateTime.Now.Ticks);
Random JunkGens1 = new Random((int)DateTime.Now.Ticks);
// Randoms the gerator to output x amount of Armoritems to the shop
int itemArmorAmount = 0;
int itemArmor1 = ArmorGens1.Next(0, 3);
Just a bunch of random declairingsm converting over the gold amount to be displayed.
// Generate x amount of items to fill out the armorlist
do
{
Varz.nowLevel = levelEq;
ArmorPartSel = ArmorGens1.Next(1, 7);
Namesel = ArmorGens2.Next(1, 4);
Namesel2 = ArmorGens3.Next(1, 4);
// Picks the names
nameArmorPart();
name1partArmor();
name2partArmor();
// Displays the names
Varz.winfrom.lisBDrop.Items.Add(ArmorPart + " " + Name1 + " of " + Name2 + " Level " + Varz.nowLevel);
itemArmorAmount++;
} while (itemArmorAmount < itemArmor1);
Here is part of the name generator, just now level = the level the use is now, and then the ranomizer will pick a value between 1 and 7 if value = 1 then display name bandit, and so on thats what the pick the name part does, then we add the display item into the list box, we loop untill itemArmorAmount is greater than the generated itemamount which is held in itemarmor1, let me explain in more depth:
The amount to displayed gets randomed between 1-15 or how ever many you want to do
|
v
Each loop will fill the list box with 1 item (we have a generator picking if the generated item is either armor, weapon or junk, same method as this)
|
v
Keep looping untill do-while is finished
Pritty simple stuff, repeat this for weapons and junk items or what ever items you want randomly populated use it for shops too, continueing on furthur we have:
// Fill the inventory with this listbox
// Loop into x for the array
int x = 0;
object[] oldArmor = new object[Varz.inventoryform.lisAInv.Items.Count];
for (x = 0; x < oldArmor.Length; x++)
oldArmor[x] = Varz.inventoryform.lisAInv.Items[x];
// Loop the array to fill out
for (int i = 0; i != (x); i++)
{
Varz.winfrom.lisAInv.Items.Add(Varz.inventoryform.lisAInv.Items[i]);
}
This will loop through a list box from your inventory and populate this next screen, give x a value of 0 this will be out loop counter, objact old armor is our length grabber, on the coutned items in the inventory form lisAInv is the armor inventory, then we do the loop of i, x is now the maximum items in our list box.
We go on to clear out inventory now, because our 'new' inventory is on this next screen just put in:
// Clears our inventory
Varz.inventoryform.lisAInv.Items.Clear();
Simple stuff, now ontot he next screen you will want to grab the items, becaus ethey're all in 1 list box, how do we do this? lisBDrops.Contains("[Head]")! if the drops is a head item then you stick it in then other list box with the following code:
// Set up selected items
string lisBDrops = (string)lisBDrop.SelectedItem;
if (lisBDrop.SelectedItem != null)
{
if (lisBDrops.Contains("[Head]") || lisBDrops.Contains("[Shoulder]") || lisBDrops.Contains("[Gloves]") || lisBDrops.Contains("[Chest]") || lisBDrops.Contains("[Legs]") || lisBDrops.Contains("[Feet]"))
{
// Add item tot he head slot
lisAInv.Items.Add(lisBDrop.SelectedItem);
// Remove said item
lisBDrop.Items.Remove(lisBDrop.SelectedItem);
}
if (lisBDrops.Contains("[Main Hand]") || lisBDrops.Contains("[Off Hand]") || lisBDrops.Contains("[Two Handed]"))
{
// Add item tot he head slot
lisWInv.Items.Add(lisBDrop.SelectedItem);
// Remove said item
lisBDrop.Items.Remove(lisBDrop.SelectedItem);
}
if (lisBDrops.Contains("[Junk]"))
{
// Add item tot he head slot
lisJInv.Items.Add(lisBDrop.SelectedItem);
// Remove said item
lisBDrop.Items.Remove(lisBDrop.SelectedItem);
}
See? all i'm doing is seperating the scanned item, and sticking them in the appropreate box! when you leave can you guess what you can do? you populate you're inventory because it's cleard now, and then clear this on leave.
Now you have a fully working inventory.
Thats it for now, i'm a little teird and a bit burnt, this is a bit to take in but it's more or less this simple. I'll stick more chapters in later, with the trade system, and getting an item level, which isn't that hard, it's all scanning list boxes with a looped number variable.