Most efficient way to create spellcasting system?

I am trying to rescript a spellcasting type system that was made by another scripter that I am working with that has three different elements. Each element has its own set of moves each with different stamina and cooldown values. The current code uses a LocalScript and a ServerScript with all the moves in a giant elseif statement, but I would like to streamline it and make it more readable.

I heard using OOP is an efficient way, and I have read up on how to use OOP, see here:

But I am not sure how I could make this apply to my use case, if anyone has any experience using OOP or has a more efficient way of creating a system like this, please provide me information on how I would go about setting this up.

Here is what I have so far:

local Move = {}
Move.__index = Move

-- Creates framework for all future moves
function Move.new(Player, Element, Cooldown, Stamina)
	local newmove = {}
	setmetatable(newmove, Move)
	newmove.Player = Player
	newmove.Element = Element 
	newmove.Cooldown = Cooldown
	newmove.Stamina = Stamina
	return newmove
end
11 Likes

For a spellcasting system, efficiency is not much of a concern. Generally speaking you only need to do anything when they actually activate a spell, so the most intensive part will typically be the actual animating of the spell.

In your specific case it might be beneficial to also keep a field in Move specifying when the spell was last used in order to ensure that they can’t re-use it until now - then > Cooldown.

To continue, you might add a new method to the Move class called DoMove or something that ensures you can use the spell, update the “last used” field, and does whatever the actual spell needs to do (perhaps in a new thread if you don’t want it to be blocking). For example, you might have the Move constructor accept a new parameter that is the actual function to call that deals with the animating of the spell and everything else and DoMove would call that:

function Move:DoMove()
    local now = tick()
    if now - self.LastUsed > self.Cooldown then
        self.LastUsed = now
        coroutine.wrap(self.HandleMove)(self) -- HandleMove initialized via the constructor
        -- if you want it to be blocking, just do self:HandleMove() instead
    end
end

So all you have to do when they want to “cast a spell” would just be to call DoMove.

2 Likes

First tip would be that you primarily design a game to be well organised and easily expanded upon, anything beyond some basic common-sense efficiency measures are generally applied as needed.

If I were doing this I’d probably have a superclass for all spells which just sets up the absolute basics, and then you’d have multiple classes of spells that inherit from from that super class. You’d then also have a module somewhere that defined what each spell is, so I have something like this:

module = {}
 module.FireBall = {
  Name = "Fire Ball",
  Class = Spells.Projectile,
  ManaCost = 10,
  Cooldown = 5,
  Damage = 100,
  AOE = 10,
 }

return module

Then have something like

function CastSpell(Player, Definition)
 local NewSpell = require(Definition.Class).new(Player, Definition)
  --etc.
end
CastSpell(Player, SpellDefinitions.FireBall)

So the spell class can then refer to all the information about itself by looking at that definition, instead of passing tons of parameters for damage, cooldown, cost, range, etc.

This isn’t the only way of doing things, but it’s worked well for me.

24 Likes

Thank you for your input, I had an idea where I wanted to go, but I didn’t exactly know how I wanted to get there, I will definitely keep this in mind.

6 Likes