6
\$\begingroup\$

I'm a novice programmer but I have completed a pre-university in web prog so I am not a total noob. I'm using Unity, but this is just C# for now.

This is my first big project and I need some professional insight to make sure the design is not flawed. Currently working on abilities in an turn based rpg game.

What you might need to know: Abilities need to be learned/chosen, they may have requirements(levels, weapontype, more/etc.) New abilities will be added to the game with updates.

This is still the primitive stage, but very important to me because this project is more about learning to do it right rather than just getting it done.

Here are the following classes where we create abilities:

public abstract class Ability {
  public readonly int id, multi, manaCost;

 public Ability(int id, int multi, int manaCost) {
   this.id = id;
   this.multi = multi;
   this.manaCost = manaCost;
 }
 public virtual void resolve(Actor user, Actor target){}
 //Would create more methods with different parameters based on different abilities
 //Example for multi target ability
 public virtual void resolve(Actor user, Actor[] targets){}
}

Pretty simple stuff, Actor is just a in-game character. resolve is where the ability effects will be coded

using UnityEngine;
public class Freeze : Ability {
  public Freeze() : base(0, 0, 3) { }

  public override void resolve(Actor user, Actor target) {
    int damage = 2 * user.attribute[2];
    Debug.Log (damage);
    target.damage(damage);

    /*System.Random rnd = new System.Random();
    if(rnd.Next(100) <= 35)
    target.state = new Frozen();*/
  }
}

the way I was planning to use abilities is, in the main script of the battle system there would be an array that has all the abilities such as this:

Ability[] all_ability;
public void createAbilities(){
    all_ability = new Ability[2]; //size increased as we create more abilities
    all_ability[0] = new Freeze();
    all_ability[1] = new FirstStrike(); //and so on as we create more
}

The index of the array is also the id of the ability which allows us to easily access abilities.

So if an Actor wanted to use freeze, the id of freeze would be stored in Actor i.e. p1.actors[0].ability_ids array

so let's say i've got what ability the player chose, it would be something like this

int aId = p1.actors[i].ability_ids[j]; //would return 0 assuming he chose freeze
all_ability[aId].resolve(p1.actors[i], p2.actors[k]);

Everything works so far, no errors, and damage gets calculated correctly but this is a very simple ability and still much more to add like damage counters, animations, status effects etc.

What should I look out for? is there a much easier way to do this I dont know about? I guess my main concern is, as we learn in web programming, it is said to be very bad to hard code values and everything should come from databases, but, is that also true for a game since each ability can be quite different? As you can see, everything is hard coded here. I guess I just want tips on things I may have done wrong/if there is a clear better way to do this. Thanks for any and all replies!

\$\endgroup\$

3 Answers 3

3
\$\begingroup\$

You sound like you're off to a good start! However, you asked a pretty general question, so I can only really offer some general pointers that might help!

To start with, I'd use a List instead of an array ( using System.Collections.Generic). You can still do abilityList[index] like with arrays, but you get the added benefit of not having to handle resizing if you run out of rooms (since it's handled by the List<T> class), and LINQ queries, if you're into that!

After that, you should make sure your Actor has an "AddAbilities" method. Then, on load (or creation), you can simply go down the list, adding the abilities appropriately (instead of hardcoding them). For instance, if your character load data for Darth Vader is:

Force Lightning, Force Choke

(Not knowing how you intend to store stuff, let's just assume you somehow parse out a list of strings)

foreach(string abil in savedCharAbilities)
{
  //You can use reflection or an AbilityFactory that takes a string parameter to create
  // an instnace of the class based on it's name.
  // If you have an enumeration of all of the possible abilities 
  // (only do this if you have a small total amount!), you could set up a switch statement
  // to get your new Ability instead.

  character.AddAbility(createdAbility);
}

Finally, you may also want to look into making your abilities MonoBehaviors that implement an Ability Interface instead of inheriting from an abstract class - that way you can make use of Unity's built in Enable properties (for toggle-able abilties), it's Update method (for quickly changing effects), and Unity's Invoke call (which calls the passed method X seconds later, or every X seconds if you call InvokePeriodic).

\$\endgroup\$
4
  • \$\begingroup\$ I had considered reflection before, but people seemed to say to stay away from it, but it does make more sense with lists and reflection to create abilities as needed rather than having them all. So would you still suggest I use reflection? Interfaces seem like a good option, but I have never used them before, I wonder though, I probably won't use the methods you described since this is a turn based game and it is very easy to program over-time effects based on turns passed, is there any other reason to do so? I also read somewhere that having many monobehaviors class might not be a good idea? \$\endgroup\$ Commented Oct 3, 2013 at 10:32
  • \$\begingroup\$ @NoviceUnityGameDev Nothing wrong with having lots of monobehaviors (at least, as far as I've heard). Rather than reflection, I suggest making an "AbilityFactory" class. Since C# can switch on strings, you can simply call the factory with the string name of your ability, and have the factory class return an appropriate ability instance. \$\endgroup\$ Commented Oct 3, 2013 at 11:13
  • \$\begingroup\$ @NoviceUnityGameDev And in general, don't worry about performance until it becomes an issue. Trying to code around performance issues that aren't there is a surefire way to burn out! \$\endgroup\$ Commented Oct 3, 2013 at 11:14
  • \$\begingroup\$ I have made an AbilityFactory class and everything is working as intended :D, Thanks for the help, I didn't know about the factory pattern so simple I should've known! \$\endgroup\$ Commented Oct 5, 2013 at 9:24
9
\$\begingroup\$

it is said to be very bad to hard code values and everything should come from databases, but, is that also true for a game

Yes. You should read up on data-driven design and use it everywhere it seems to make sense.

Ideally your ability system's code should only define the logic of abilities and then data files can define the specific abilities and their parameters. Your code might include a DamageEffect, StatusEffect, SpawnEffect, ArmorEffect, etc. Then in a data file you can have something like:

<ability name="freeze">
  <int name="cost">100</int>
  <int name="level">6</int>
  <effect type="DamageEffect">
    <int name="amount">100</int>
    <string name="type">ICE</string>
  </effect>
  <effect type="StatusEffect">
    <string name="type">STASIS</string>
    <int name="time">10</int>
  </effect>
  <effect type="ParticleEffect">
    <string name="particleSystemTemplate">frost_particles.vfx</string>
  </effect>
</ability>

Now you can add new abilities easily. Users can mod your game more easily. You can build a nice little tool to make adding new abilities much easier than editing code and recompiling, especially for any non-coders on your design team.

\$\endgroup\$
3
  • \$\begingroup\$ considering the game has to switch through parameters to do its effect, how would that affect performance? \$\endgroup\$ Commented Oct 3, 2013 at 10:28
  • 4
    \$\begingroup\$ It won't meaningfully impact performance, assuming it isn't implemented horrifically wrong. This is not the area of a game where bottlenecks appear. Worry about performance in the places you will get large gains, not micro gains. Worry about power and flexibility and iteration speed everywhere else. \$\endgroup\$ Commented Oct 3, 2013 at 16:23
  • \$\begingroup\$ Data-Driven really seems to be the way to go for long-term and bigger projects with at least few ppl working on project. Though, I have decided against using that pattern for this project since i'm doing most of the work. Still thanks for pointing me in the right direction, will definitely do this in the next project \$\endgroup\$ Commented Oct 5, 2013 at 9:27
2
\$\begingroup\$

I am currently creating a versatile system for this and while it's not fully functional yet it looks promising. I just use a normal Ability class where basic things like the cost, name, etc are being stored. It will also hold a list of AbilityActions or I could say stack where each of these actions is performed in order. The trick here is to create these actions as basic as possible to keep it versatile. Some of my AbilityActions are:

  • Projectile
  • Damage
  • StatusEffect
  • AreaOfEffect

Based upon this I will create my abilities. I used a build pattern for my ability class to help myself out. Just look it up, it's easy to create. Here I create a stun shot.

Ability stunShot = new Ability(
  //Mandatory fields, this one costs energy
  new Ability.Build("Stun Shot", Ability.Type.Energy, cost) 
  //Add anything that is needed.
  //I won't use range since I will get that from the user when it uses this ability.
  //The string I pass is used for the log later on to say something like
  //"You have been hit by a Stun Bullet"
  //Perhaps you want all your projectiles have static damage then add it to it's constructor.
  .addAction(new ProjectileAction(range, "Stun bullet") 
  //So what do we need more? Yes a effect action
  //I have these made in the same way as the
  //so I can have stun and entangle both use
  //MoveEffect but to keep it simple here I just add a new StunEffect
  .addAction(new EffectAction(new StunEffect(duration))));
);

Here is a fireball spell:

Ability fireball = new Ability(new Ability.Build(
"FireBall", Ability.Type.Mana, cost) //Uses mana
.addAction(new ProjectileAction(maxRange, "ball of fire"))
.addAction(new AreaOfEffectAction(radius, "blast") //caught in "blast" cought in "ice storm" etc.
.addAction(new DamageAction(damage))
.addAction(new EffectAction(new BurningEffect())));

This could also be created from external data, then I would create the builder separately, feed it from your xml data and insert it in ability.

Ability.Build abilityBuilder = new ActionBuilder("fill with xml/json/database/etc");
if (xmlData = "Projectile")
{
  actionBuilder.addAction(new ProjectileAction("More xml data"));
}
//...etc, then put them in a map or dictionary.
abilityMap.put("ability name from XML", new Ability(abilityBuilder));
//And look them up by name or int or whatever you choose.
abilityMap.get("fireball");

So the fireball ability is created and the player chooses to use it on a target, in my case this can be a position to shoot at something he cannot see.

  • player calls fireball.perform(player, position).
  • projectile action starts and once it is done (hits something) it provides a position or perhaps a creature it hits.
  • The area of effect uses that position or the position of the creature to look for stuff that is caught in the radius. It will store or return a list of these when done.
  • With this list we can apply damage to all the creatures or perhaps furniture and set them on fire with the effect.

I can create as many actions as I want but keep them basic so you can combine them with others. Well that is not really necessary but if I would give my ShootAction also damage then I could not use it for a stun shot that does no damage at all.

Getting the creatures effected by actions like ShootAction() or AreaOfEffect carried over to the next action can be handled in several ways. I first had a trigger on these types of actions so I would go back in the list to find the latest lists (for me only creatures) of things to effect.

Now I have this list in Ability and pass it to each action and modify it in these type of actions. This removed a bunch of if statements and backtracking but I need two perform methods for each Action. One that takes the actual user and position and one that takes the user and a list of creatures. If there are creatures in the list we will always use the latter. If for example the DamageAction ever gets a tile position it knows nothing has been hit.

I know the original answer is from 2013 but I think this will help a lot of people out. I am currently also prototyping this and if it all works together I will consider to clean it up and make it available.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .