function vs. function:enemy

Started by Bagu, June 09, 2018, 05:29:35 AM

Previous topic - Next topic
This is super basic but I've looked around and not found an answer and it's really bugging me.

So I find the following to be much cleaner, neater, and easier to deal with than the alternative:

Code ( lua) Select
function pause()
  local sprite = enemy:get_sprite()
  sprite:set_animation("idle")
  enemy:stop_movement()
  sol.timer.start(enemy, math.random(1, 2000), jump)
end

function jump()
  local sprite = enemy:get_sprite()
  enemy:stop_movement()
  sprite:set_animation("jumping")
  local movement = sol.movement.create("jump")
  movement:set_direction8(math.random(0, 7))
  movement:set_distance(math.random(12, 64))
  movement:start(enemy)
  sol.timer.start(enemy, 800, pause)
end


Whereas if I use "function enemy:blahblahblah" I appear to accomplish the same thing, yet the syntax on things like timers is longer and more restrictive:

Code ( lua) Select
function enemy:pause()
  local sprite = enemy:get_sprite()
  sprite:set_animation("idle")
  enemy:stop_movement()
  sol.timer.start(enemy, math.random(1, 2000), function()
    enemy:jump()
  end)
end

function enemy:jump()
  local sprite = enemy:get_sprite()
  enemy:stop_movement()
  sprite:set_animation("jumping")
  local movement = sol.movement.create("jump")
  movement:set_direction8(math.random(0, 7))
  movement:set_distance(math.random(12, 64))
  movement:start(enemy)
  sol.timer.start(enemy, 800, function()
    enemy:pause()
  end)
end


Then if I start mixing both in the same script, it gets much worse because I'll tend to forget and mismatch the syntaxes to the wrong functions and I spend extra time correcting stupid mistakes to clean up all the error messages.

I see the latter method used in a lot of scripts in enemies by the Solaris team, so there must be some advantage to it, or some reason why it's bad to avoid it. What am I missing? Is there a reason not to use the first approach for all my enemy scripts?

Sorry to ask about such basic stuff all the time!

Your first script is declaring the functions as globals which is probably not the behavior you want. Global functions can be used by every script, and the reason you don't want to use it is you'll have conflicts if multiple scripts try to define the same named global function. i.e. if you have a tektite enemy script and a frog enemy script, both with a global jump() function, then one of the scripts will overwrite the other function and you won't get the behavior you intended.

You can get around this by making the functions local instead:
Code (lua) Select
local function jump()
but with local functions the scripts will only be available to the script that defines the function, so if you want the functions to be available to use in other scripts, the second example you gave is the best way to go. For example, if you wanted to have the enemy jump any time the player hits a hammer on the ground, you wouldn't be able to do that with a local jump function because the script where you define the hammer action wouldn't have access to the local jump function defined in the other enemy script.

So when it comes to defining enemy behaviors, you'll almost always want to go with the second example. If you have a function that you positively know will never need to be called from a different script then you could use a local function. You probably wouldn't ever want to use a global function when defining enemy behaviors. For something you want to apply to all enemies, better to use meta-tables instead.

Ok, that pretty much explains everything. In theory if I know an enemy script won't have its functions references by any other scripts I could use local functions for everything, but if that changes it would be extra work to convert it back to enemy:functions.

One other question I have: what is the difference of calling i.e. self:jump() instead of enemy:jump()? I've seen both used and they seem to work interchangeably, but I'm sure I'm missing some distinction there.

June 10, 2018, 08:50:25 PM #3 Last Edit: June 10, 2018, 08:56:22 PM by llamazing
There's probably some limitation I'm forgetting when it comes to using local functions in an enemy script. You really are better off in the long run using enemy:functions.

Quote from: Bagu on June 10, 2018, 06:48:27 PM
what is the difference of calling i.e. self:jump() instead of enemy:jump()? I've seen both used and they seem to work interchangeably, but I'm sure I'm missing some distinction there.

When you have a function enemy:jump(), that is shorthand for writing it as enemy.jump(self), so self is a hidden first parameter that will be a reference to enemy. If you have hierarchy in your scripts, then self could be something other than enemy (but where self behaves like enemy and would have all the same functions enemy does). This is how you can do inheritance in lua.

For your purposes, though, you most likely aren't doing any hierarchy, so you can think of self:jump() and enemy:jump() as the same, but using self is better for having a robust script in case some day you do want to implement hierarchy.

EDIT: Looking through some of the Solarus Team quests it seems that self is generally used instead of enemy, but I see that zelda-roth-se uses enemy instead of self. Bottom line is using self is better, but I don't think you'll run into any issues if you use enemy.

Thanks llamazing! I've been wondering about this for a while too, I thought it was for a completely different reason. I feel better about mainly mixing them randomly now, lol.

Indeed, thanks for these explanations llamazing, it's been really helpful!

The keyword here is "scope". You should learn about this concept.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."