# Spell and Terrain Interaction in Game

I am currently thinking about working on a small game where the player has to get rid of mob waves in an arena. The game is going to be 2D top-down.

What I want from this game is the player to be able to use the terrain and combine spells to his advantage.

Example of spell/spell interaction: if the player casts an ice wall and then the player fires a fire ball I want the fireball to disappear and the ice wall to melt into a water puddle. Then if the player sends a lightning bolt on the puddle, I want the water puddle to become electrified and affect all the mobs currently on it with some damage.

Example of spell/terrain interaction: If a player sends a fireball on a tree, I want the tree start burning (i.e dealing dmg to nearby mobs) and eventually die (i.e unable to burn anymore). If the player then sends a water jet on the tree I want the tree to be able to grow back again (and potentially be able to burn down again).

Now that you have an idea of what I want, I'll tell you what I've done so far.

I've created a Cast class which represent my spells. This class has three key attributes to govern the interactions with other spells:

1 - attributes: a list of which contains the properties relevant for the Cast (eg: 'flammable' or 'conductive') 2 - states: a dictionary which contains keys which are: 'ablaze', 'frozen', 'conductive', 'blooming' 3 - col-lvl: the collision level, to prevent things on the ground interacting with things mid-air.

for the moment I call the following function at every frame of my game, in order to update the states of each cast in the game:

def update_states(casts_group):

'''make cast attribute lists'''
flammables = []
freezables = []
fertiles =[]
conductives = []
for c in casts_group:
if 'flammable' in c.attributes: flammables.append(c)
if 'freezable' in c.attributes: freezables.append(c)
if 'fetile' in c.attributes: fertiles.append(c)
if 'conductive' in c.attributes: conductives.append(c)

'''resolve cast states'''
'''for flammables'''

for f in flammables:
for c in flammables:
if c.col_lvl == f.col_lvl:
if c is not f and f.states['ablaze']:
c.states['ablaze'] = True

for c in freezables:
if c.col_lvl == f.col_lvl:
if f.states['ablaze']:
if c.states['frozen']:
c.states['frozen'] = False
f.states['ablaze'] = False

for c in fertiles:
if c.col_lvl == f.col_lvl:
if f.states['ablaze'] and c.states['fertile']:
c.states['fertile'] = False

for w in freezables:
for c in freezables:
if c.col_lvl == w.col_lvl:
if c is not w and w.states['frozen']:
c.states['frozen'] = True

for c in fertiles:
if c.col_lvl == w.col_lvl:
if w.states['frozen']:
c.states['blooming'] =  False
elif not w.states['frozen']:
c.states['blooming'] = True

for c in conductives:
if c.col_lvl == w.col_lvl:
if not w.states['frozen'] and c.states['conductive']:
w.states['conductive'] = True
elif w.states['frozen']:
w.states['conductive'] = False

'''for fertiles'''

for f in fertiles:
for c in fertiles:
if f is not c and c.col_lvl == f.col_lvl:
if f.states['blooming']:
c.states['blooming'] =  True

for c in conductives:
if c.col_lvl == f.col_lvl:
if f.states['blooming'] and c.states['conductive']:
f.states['blooming'] = False

'''for conductives'''

for e in conductives:
for c in conductives:
if c.col_lvl == e.col_lvl:
if e is not c and e.states['conductive']:
c.states['conductive'] =  True


Each cast will then have a series of method which will dictate what should happen based on which states are currently active.

My question to you is : How can I improve this ??

When I run the following test my computer takes 0.8 seconds to solve for about 2000 Cast. That will never hold if I want a decent FPS. I am worried because this is just one function of the game and it already uses up a lot of time. Although I don't expect to have that many casts at any one time in the game, I will have to add a similar step to deal with Terrain/Spell interactions ... and the whole remainder of the game.

Thank you for your help,

BTW if you think this is a question best suited for the CodeReview community let me know, I wasn't quite sure.

TEST:

setup = """
class Cast():
def __init__(self, attr, states):
self.attributes = attr
self.states = states
self.col_lvl = 0

def update_states(casts_group):
#for c in casts_group: print c.states

'''make cast attribute lists'''
flammables = []
freezables = []
fertiles =[]
conductives = []
for c in casts_group:
if 'flammable' in c.attributes: flammables.append(c)
if 'freezable' in c.attributes: freezables.append(c)
if 'fetile' in c.attributes: fertiles.append(c)
if 'conductive' in c.attributes: conductives.append(c)

'''resolve cast states'''
'''for flammables'''

for f in flammables:
for c in flammables:
if c.col_lvl == f.col_lvl:
if c is not f and f.states['ablaze']:
c.states['ablaze'] = True

for c in freezables:
if c.col_lvl == f.col_lvl:
if f.states['ablaze']:
if c.states['frozen']:
c.states['frozen'] = False
f.states['ablaze'] = False

for c in fertiles:
if c.col_lvl == f.col_lvl:
if f.states['ablaze'] and c.states['fertile']:
c.states['fertile'] = False

for w in freezables:
for c in freezables:
if c.col_lvl == w.col_lvl:
if c is not w and w.states['frozen']:
c.states['frozen'] = True

for c in fertiles:
if c.col_lvl == w.col_lvl:
if w.states['frozen']:
c.states['blooming'] =  False
elif not w.states['frozen']:
c.states['blooming'] = True

for c in conductives:
if c.col_lvl == w.col_lvl:
if not w.states['frozen'] and c.states['conductive']:
w.states['conductive'] = True
elif w.states['frozen']:
w.states['conductive'] = False

'''for fertiles'''

for f in fertiles:
for c in fertiles:
if f is not c and c.col_lvl == f.col_lvl:
if f.states['blooming']:
c.states['blooming'] =  True

for c in conductives:
if c.col_lvl == f.col_lvl:
if f.states['blooming'] and c.states['conductive']:
f.states['blooming'] = False

'''for conductives'''

for e in conductives:
for c in conductives:
if c.col_lvl == e.col_lvl:
if e is not c and e.states['conductive']:
c.states['conductive'] =  True

#for c in casts_group: print c.states

c_group = [
Cast(['flammable'],
{'ablaze' : True,
'frozen' :  False,
'blooming' :  False,
'conductive': False}),
Cast(['freezable','conductive'],
{'ablaze' : False,
'frozen' :  True,
'blooming' :  False,
'conductive': False}),
Cast(['conductive'],
{'ablaze' : False,
'frozen' :  False,
'blooming' :  False,
'conductive': True})]

d_group = []
for x in range(667):
d_group += c_group

"""

test = """update_states(d_group)"""

from timeit import timeit

n = 1

print timeit(stmt = test, setup = setup, number = n)


Well, this is going to depend on some definitions. So first, let me say that by my definition you have not described terrain in the classic sense of game programming. Terrain is typically the ground and other immutable objects (a lake or ocean). Things such as trees or puddles would often be props, decorations, or some other descriptor for things that are added into a scene.

Going with that definition, it's easy to consider any two objects interacting with one another. Typically, if you have a tree and a fireball is cast at it, you might set a flag on the tree to "burning", and generate a fire particle effect or something similar at the location of the tree. This also allows the tree to be extinguished in the opposite direction with a water spell or something similar, stopping the particle effect and removing the flag.

The same could go for puddles if they are spawned in as objects sitting on the terrain. In this way, they can be electrified in the same manner as a tree could be set ablaze, or even evaporated with a fire spell, or created with a water spell (use water to create puddles, then use chain lightning to electrify them all).

As for improving, there are a few ways to do this. Ideally, the effect of casting would be part of the spell itself, and the spell would then interact with the objects it crosses. If you cast a fireball or flame spell, and it collides with another object, if that object is flammable it sets it ablaze. Maybe this could be done with a listener on the objects, with a message sent by the fireball when it collides. Something like:

when (collide)
send.message("fire") to collided


The spell sends a "fire" message (or "water", or "electric", or whatever) to each object it touches, and the listener on the object says "oh, I'm flammable and that was fire... poof!", or "oh, that was an electric message and I'm made of dirt... no poof!".

The benefit of this is that you aren't constantly polling all possible combinations for objects that don't match the element being presented. Plus, some objects could have multiple listeners if multiple elements can effect it. Obviously some trial and error with this would be needed, but I'd imagine you'd cut down quite a bit of processing. It might lag a frame or two being if things get hectic, but it probably wouldn't even be noticeable on draw.

• Thank you for the answer. Would you recommend keeping the attributes or just use states ? Say a tree gets the message "fire" is there still a use in checking if the tree is "flammable" or could I just set the state of the tree to "ablaze" ? It seems a bit redundant to keep attributes now, but maybe that it will come in handy later ? Oct 24 '16 at 19:27
• I would keep the attributes as flags, but have the listener on the object "flip" the value rather than the spell itself. The spell object should basically just send messages as it collides. That way different objects can behave differently to the same element, e.g. - a tree might be set on fire, but ice might melt and a puddle might evaporate. The fire element is the same for all of these, but the behavior of the receiver is different. Oct 24 '16 at 20:11
• Thanks, but how to deal with effects is resolved first? Say a fireball and an icebolt reach a tree at the same time ? If the fireball and tree interaction is resolved first, the tree will set on fire, and send a fire message. Then the icebolt will either turn off the fireball or the tree but not both. But a bit later in the game if the same situation arises again and the fireball, icebolt iteraction is resolved first, the tree will not be set on fire Oct 24 '16 at 20:14
• I'm not sure I understand. If you mean, what happens when the particle system dies before an encounter - well, it shouldn't. Increase its lifetime. The particle system and the object (even if it's just an invisible collider) that it's attached to needs to last for the duration and die at the same time. Oct 24 '16 at 20:16
• just updated my comment sorry. Btw, I do like your response. I'm just trying to understand some of the problems I come across when I implement it. Oct 24 '16 at 20:18