how to program A LOT of skills/spells?

Started by
6 comments, last by Gooey 8 years, 9 months ago

Hello

How do you program a spells/skills system for a game with one character but a lot of spells/skills (maybe 100+)

currently i am using this method: (i am using Unity2D with C# btw)


 void PlayerCharacter()
 {
	 if(SkillNumber1Unlocked)// when he presses the first skill button and it Unlocked
	UseSkillNumber1();

	 if(SkillNumber2Unlocked)// when he presses the second skill button and it Unlocked
	UseSkillNumber2();

 }

void SkillNumber1()
{
	Code;
	Code;
}
void SkillNumber2()
{
	Code;
	Code;
}
Advertisement

A function pointer table and array of unlock flags would be better.

As for how to code them, the more you know upfront, the better. If you've already designed all of them, then go through the list and write down groups of skills that have common elements to them. Then write functions to do the common things. Use data tables when possible, where you index in with the skill ID to get whatever values the common function needs. Doing huge if/else chains or switch statements is ok sometimes too, but tables are usually nicer to work in. Keep the individual functions for each skill short when possible, mostly making calls to common functions. Easier to navigate and work in than a scattering of long and short functions.

Not really an answer to your question but it would probably be better to use else if's as i assume you can only click one thing at a time so if it was skill 2 clicked and you had 100 spells it would only need to chect 2 conditions rather than 100. As for the spells/skills they will have a lot in common so you could put all the common stuff into a base class ie damage amount, damage radius, hp(if spell can be defeated) and mana(to take from you for casting)
Base class with derived skills isnt a terrible idea. But you dont need a class per skill. Group them into similar ones and change them with paramterrs. Examle would be to have a base class of CSkill. Deribe from it say a skill CLaunchProjectile. Now launch projectile can be used for your fireball or your arrow...just change the texture of the actual projectile created.

Also i would not really use a flag system for determining if they have a skill. I would create a list of CSkill and whenever a character earns a new skill add it to the list. Then only allow skills from the list to be used.

You could employ a Prototype pattern: http://gameprogrammingpatterns.com/prototype.html

If you have a lot of if/else statements or a large switch, you should probably start thinking about how to alleviate all those checks.

In addition, if you haven't read through some of those design patterns, I suggest you do. They're pretty awesome and they can make your code a lot more flexible.

Yo dawg, don't even trip.

Before answering I'd like to stress 2 things: 1) I am not familiar with Unity, but AFAIK it uses ECS, so some implementation details may vary; 2) it is an overengineering, but points out seperate concepts and it is for you to decide whether they must be separated or can be merged for simplicity.

I'd structure it as follows:

1. It is not only skills and spells. A command is executed after pressing a key. So you need a class that maps keys to commands and commands to execute. Commands are classes derived from an interface with only one virtual function "execute". You can read about what command pattern is from here: (http://gameprogrammingpatterns.com/command.html it actually partially describes your problem).


interface Command
{
   void execute();
}

class CommandsMap
{
   Dictionary<Keys, Command> m_commands;

   public MapCommand(Keys k, Command c) { m_commands[k] = c; }
   public ProcessInput(Keys k) { m_commands[k].execute(); }
}

In appropriate place (during loading, etc) you fill CommandsMap with Commands and associated keys.

2. Spells/skills are not necessarily commands. Spells/skills are stand-alone entities that are used by Commands. Spells be like:


class TargetedSpell
{
   protected GfxEffect m_targetEffect, m_casterEffect;   
   
   public Actor Caster { get; set; };
   public Actor Target { get; set; };
   
   protected virtual bool CheckPreconditions(); // check if immune etc.
   protected virtual void DoSpellOnTarget(); // apply damage etc.

   public void CastSpell() 
   { 
       if (CheckPreconditions()) 
       {
           m_targetEffect->PlayOn(m_target);
           m_casterEffect->PlayOn(m_caster);
           DoSpellOnTarget();
       }
   }

   public TargetedSpell(GfxEffect casterEffect, GfxEffect targetEffect)
   ...
}

class DamagingTargetedSpell : public TargetedSpell
{
   protected DamageType m_damageType;
   protected int m_damageAmmount;

   protected override bool CheckPreconditions() { return m_target.IsImmuneTo(m_damageType); }
   protected override void DoSpellOnTarget() { m_target.ApplyDamage(m_damageAmmount); }
  
   public DamagingTargetedSpell(DamageType damageType, int damageAmmount, GfxEffect actorEffect...)
}

If you have 100 spells it is not necessary to have 100 spell classes. Here for Fireball, Lighting, Meteor you only need one DamagingTargetedSpell with 3 different gfx effects.

One thing to mention. Spells here work with Caster and Target of Actor type, but Commands (may) know nothing about them. Well, some Commands may know that the Caster is a player, but Target is unknown during creation of commands. Thus we need a class that will give as an information about a target. For example, a Pointer class that returns current actor under cursor (if any). This way creation of commands may look like this:


CommandsMap map = new CommandsMap; // somewhere
...
Pointer pointerToGetTarget = new Pointer;
...
map.MapCommand(ReadKeyForCommandFromOptions("CastFireball"), new CommandThatKnowsHowToCastSpells(pointerToGetTarget, new DamagingTargetedSpell(DamageType::Fire, 20, new GfxEffectFlames, ...)));
map.MapCommand(ReadKeyForCommandFromOptions("CastLighting"), new CommandThatKnowsHowToCastSpells(pointerToGetTarget, new DamagingTargetedSpell(DamageType::Electricity, 20, new GfxEffectShock, ...)));

3. CommandsMap should not know about whether spell/skill is unlocked (it is now obvious, cause instead of spells/skills we have Commands). But spell/skill class should too not know whether it is locked or unlocked, as it is out of its responsibilities. Command should. Thus we introduce CheckIfUnlockedCommand that uses chain-of-responsibility pattern.


class CheckIfUnlockedCommand
{
    protected int m_unlockTableID; // index in locks table
    protected Command m_nextCommand;
    protected LocksTable m_locks;

    public void execute()
    {
        if (m_locks.IsLocked(m_unlockTableID))
        {
            m_nextCommand.execute();
        }
    }

    public CheckIfUnlockedCommand(int id, Command nextCommand)
    ...
}

Then the command creation code is:


map.MapCommand(ReadKeyForCommandFromOptions("CastFireball"), new CheckIfUnlockedCommand(GetIDFor("CastFireball"), new CommandThatKnowsHowToCastSpells(pointerToGetTarget, new DamagingTargetedSpell(DamageType::Fire, 20, new GfxEffectFlames, ...))));

In future you might want to add cooldown alongside with unlock check and you'll not need to modify command or spell classes, just an initialization code or if you'll implement command creation from a data (xml, for example), you'll only need to modify a data.

As I already said it is overengineering. For example, if you know that every spell and skill is unlockable and you only need spells and skills you may end up with something like:


class SpellsSkillsMap
{
   ...
   void ProcessKey(Keys k)
   {
       SpellOrSkillClass c = m_commands[k];
       if (c != null && m_locksTable.IsUnlocked(c.ID))
       {
           c.Perform();
       }
   }
}

Base class with derived skills isnt a terrible idea. But you dont need a class per skill. Group them into similar ones and change them with paramterrs. Examle would be to have a base class of CSkill. Deribe from it say a skill CLaunchProjectile. Now launch projectile can be used for your fireball or your arrow...just change the texture of the actual projectile created.

Also i would not really use a flag system for determining if they have a skill. I would create a list of CSkill and whenever a character earns a new skill add it to the list. Then only allow skills from the list to be used.

It has been a good long while since I have worked on a game that uses spells/skills, however, the general theory is that all "castables" have common specific traits. These traits can include fields such as damage, cool down time, type (AOE, single target, etc), etc. Store all of these in a base class which can then be derived and implemented per-skill. As for implementing a large number of these, there are several good approaches, but the one I use the most is to create a scripting type setup where I define these attributes from external file containing JSON. This way, none of the skills have to be hardcoded.

EDIT: Alex has a solid answer.

"The code you write when you learn a new language is shit.
You either already know that and you are wise, or you don’t realize it for many years and you are an idiot. Either way, your learning code is objectively shit." - L. Spiro

"This is called programming. The art of typing shit into an editor/IDE is not programming, it's basically data entry. The part that makes a programmer a programmer is their problem solving skills." - Serapth

"The 'friend' relationship in c++ is the tightest coupling you can give two objects. Friends can reach out and touch your privates." - frob

I didn't mean there was a need for a class for each but more a suggestion that a base class could hold a very good solution fora lot of cases

This topic is closed to new replies.

Advertisement