Show Posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Messages - Bagu

Pages: [1] 2
Development / Re: function vs. function:enemy
« on: June 11, 2018, 07:10:32 pm »
Indeed, thanks for these explanations llamazing, it's been really helpful!

Your scripts / Re: Classic Tektite Random Jumping AI
« on: June 11, 2018, 04:25:50 am »
Yes, it works perfectly now, thanks! I also sorted out movement:on_finished for closing the jump function.

Development / Re: function vs. function:enemy
« on: June 10, 2018, 06:48:27 pm »
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.

Your scripts / Re: Classic Tektite Random Jumping AI
« on: June 10, 2018, 06:43:25 pm »
llamazing, your version looks awesome, but when I try it, self[random_choice](self) is calling a nil value. Maybe I did something wrong, but I didn't think I could mess much up with what little I added to it:

Code: Lua
  1. local enemy = ...
  3. function enemy:on_created()
  4.   enemy:set_life(3)
  5.   enemy:set_damage(2)
  6.   enemy:create_sprite("enemies/" .. enemy:get_breed())
  7. end
  9. local choices = {
  10.   pause = 25,
  11.   jump = 75,
  12. }
  14. -- these are functions the enemy will choose from randomly proportional to the value associated with each one
  16. local random_max = 0 --sum of all choice values, calculate once when script is first run (in this example, radom_max = 25 + 75 = 100)
  17. for _,weight in pairs(choices) do random_max = random_max + weight end
  19. --this function could be moved to a different script for common utility functions
  20. local function random_choice()
  21. local choice_value = math.random(1, random_max) --choose random number between 1 and random_max
  23.  --step through list of choices to find which choice correspondes to the randomly chosen value
  24. local cumulative = 0 --sum of current choice entry plus all previous
  25. for choice,weight in pairs(choices) do
  26.   cumulative = cumulative + weight
  27.   if choice_value <= cumulative then
  28.     return choice --current entry corresponds to the randomly chosen value
  29.    end
  30. end
  32. --should have returned a value before getting to this line, as contingency returns some choice (undefined which one)
  33. --you may prefer to throw an error here instead
  34. local choice = next(choices)
  35.         return choice
  36. end
  38. function enemy:random_action()
  39.   self[random_choice](self) --this line calls a random function from the "choices" table at the top
  40. end
  42. function enemy:on_restarted()
  43.   self:random_action()
  44. end
  46. function enemy:pause()
  47.   local sprite = enemy:get_sprite()
  48.   --sprite:set_animation("idle")
  49.   enemy:stop_movement()
  50.   sol.timer.start(enemy, math.random(1, 2000), function()
  51.     self:random_action()
  52.   end)
  53. end
  55. function enemy:jump()
  56.   local sprite = enemy:get_sprite()
  57.   enemy:stop_movement()
  58.   --sprite:set_animation("jumping")
  59.   local movement = sol.movement.create("jump")
  60.   movement:set_direction8(math.random(0, 7))
  61.   movement:set_distance(math.random(24, 64))
  62.   movement:start(enemy)
  63.   sol.timer.start(enemy, 800, function()
  64.     self:random_action()
  65.   end)
  66. end

Your scripts / Re: Classic Tektite Random Jumping AI
« on: June 09, 2018, 10:02:42 pm »
I'm not very good at lua yet either!  I tried to replicate several different methods of randomization I googled and failed utterly at anything beyond printing strings until llamazing fixed my code. However, if you're interested in investigating, here's a couple examples:

The main reason I used the weighting method I did is because I lifted it from other scripts of mine with many unique functions. I like starting with this one because if I decide to add more functions I can just replace some of the duplicates in the list.

Your suggested method looks like it would work, and could be an effective basic means of weighting things. When I tried to research various methods of weighting random selections in lua, most were extremely complex (here's a good example) so I gave up and stuck to what I had working.

Your scripts / Classic Tektite Random Jumping AI
« on: June 09, 2018, 08:21:21 pm »
I always liked the random jumping of tektites in Zelda 1 and ALTTP, and was a little disappointed by the tektites in Return of the Hylian which simply walked at the player. Thus once I figured out how to set up random jumping for some other enemies I figured I'd share this extremely basic and simple example of Nintendo tektite behavior.

This also would work for replicating Bits/Bots from Zelda 2 or Zols and Gels from Zelda 1.

example video:

Code: Lua
  1. local enemy = ...
  3. local choices = {"pause","jump","jump","jump"}
  4. -- these are functions the enemy will choose from randomly. I added extra copies of the jump function so he would move 75% of the time instead of 50/50. However, you could create more functions and place each one in the list only once.
  6. function enemy:on_created()
  7.   enemy:set_life(3)
  8.   enemy:set_damage(2)
  9.   enemy:create_sprite("enemies/" .. enemy:get_breed())
  10. end
  12. function enemy:on_restarted()
  13.     enemy[ choices[math.random( 1, #choices )] ](enemy) --this line calls a random function from the "choices" table at the top
  14. end
  16. function enemy:pause()
  17.   local sprite = enemy:get_sprite()
  18.   --sprite:set_animation("idle")
  19. -- uncomment the line above if you want to use a custom pause animation, otherwise it'll use "walking"
  20.   enemy:stop_movement()
  21.   sol.timer.start(enemy, math.random(1, 2000), function() --the timer lasts randomly between 1 and 2000 ms. You can change the minimum and maximum to taste.
  22.     enemy[ choices[math.random( 1, #choices )] ](enemy) --again picks a random function from the choices table, you could also just call enemy:restart
  23.   end)
  24. end
  26. function enemy:jump()
  27.   local sprite = enemy:get_sprite()
  28.   enemy:stop_movement()
  29.   --sprite:set_animation("jumping")
  30. -- uncomment the line above if you want to use a custom jump animation, otherwise it'll keep using "walking"
  31.   local movement = sol.movement.create("jump")
  32.   movement:set_direction8(math.random(0, 7)) -- picks a random direction to jump in
  33.   movement:set_distance(math.random(24, 64)) -- sets a distance between 24 and 64px
  34.   movement:start(enemy)
  35.   sol.timer.start(enemy, 800, function()  -- 800ms is the typical time for the enemy to land from a 64px jump.
  36.     enemy[ choices[math.random( 1, #choices )] ](enemy)
  37.   end)

I probably should have used movement:on_finished() instead of a timer to close the jump function, because if you use distances greater than 64 the jump begins to get cut-off early and it looked bad.

Art-wise, if you use something like Return of the Hylian's tektite sprite, it'll look a little weird because there's a shadow drawn into the animations. Thus the shadow floats through the air with the character as he jumps, instead of staying on the ground. The easiest solution is to simply erase any shadows from your animation frames, though this will make it visually ambiguous whether the character is jumping through the air or sliding across the ground. For the best results, you'd want to use a custom entity or something to place a separate shadow sprite under the character.

Special thanks to llamazing for helping me sort out the random function selection call!

Development / Re: Choosing a Function Randomly
« on: June 09, 2018, 07:35:07 pm »
llamazing, you're right - that's exactly what I needed and it works perfectly. Thanks!!!

Development / function vs. function:enemy
« on: June 09, 2018, 05:29:35 am »
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
  1. function pause()
  2.   local sprite = enemy:get_sprite()
  3.   sprite:set_animation("idle")
  4.   enemy:stop_movement()
  5.   sol.timer.start(enemy, math.random(1, 2000), jump)
  6. end
  8. function jump()
  9.   local sprite = enemy:get_sprite()
  10.   enemy:stop_movement()
  11.   sprite:set_animation("jumping")
  12.   local movement = sol.movement.create("jump")
  13.   movement:set_direction8(math.random(0, 7))
  14.   movement:set_distance(math.random(12, 64))
  15.   movement:start(enemy)
  16.   sol.timer.start(enemy, 800, pause)
  17. 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
  1. function enemy:pause()
  2.   local sprite = enemy:get_sprite()
  3.   sprite:set_animation("idle")
  4.   enemy:stop_movement()
  5.   sol.timer.start(enemy, math.random(1, 2000), function()
  6.     enemy:jump()
  7.   end)
  8. end
  10. function enemy:jump()
  11.   local sprite = enemy:get_sprite()
  12.   enemy:stop_movement()
  13.   sprite:set_animation("jumping")
  14.   local movement = sol.movement.create("jump")
  15.   movement:set_direction8(math.random(0, 7))
  16.   movement:set_distance(math.random(12, 64))
  17.   movement:start(enemy)
  18.   sol.timer.start(enemy, 800, function()
  19.     enemy:pause()
  20.   end)
  21. 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!

Development / Choosing a Function Randomly
« on: June 09, 2018, 03:03:23 am »
I've looked at a few different methods of making a random selection from a table, as a means to make an enemy AI choose a function as its next action. For example:

Code: Lua
  1. choices = {"enemy:go_hero()","enemy:random_pause()","enemy:jump_pause()","enemy:go_random()"}
  3.   sol.timer.start(enemy, math.random(1, 3000), function()
  4.     choices[math.random( 1, #choices )]
  5. end)

Code: Lua
  1. function enemy:pickaction()
  2. math.randomseed(os.time())
  3. local a = {"go_hero","random_pause","jump_pause","go_random"}
  4. a[math.random(1,#a)]
  5. end
  7.   sol.timer.start(enemy, math.random(1, 3000), function()
  8.     enemy:pickaction()
  9.   end)

I've tried about 20 different variations of this approach and always wind up with an error message like "= expected near end" or "function arguments expected near [".

I can get it to print the desired results to the console but the engine doesn't seem to want to accept them as anything remotely resembling a function. What am I doing wrong?

Your scripts / Re: Dodge/Dash/Roll Action
« on: June 03, 2018, 11:08:48 pm »
That looks great! I like your big sprites, how's that going?

Thanks! The big sprites are fun but there's lot of hit detection bugs to work out. I haven't set correct sizes for enemies yet, and I haven't figured out how to change the true launch "height" for hero projectiles like arrows and boomerangs, so I faked it by lowering the sprite origin, so of course weird stuff happens. Still though, there are fewer problem than you'd think considering how much of the engine is built around 16x16.

I think whether or not you have the dash as its own item would have a lot to do with how you intend the player to use it in combat. Is combat designed around the player being able to dodge enemy attacks, or is this just an add-on? I've found (to me) that Link to the Past's combat isn't amazing. I image it was great for the time, and it's functional if enemies have a wide variation of different movements, attacks, patterns, weaknesses, but it's never great (maybe some bosses might reach that level). Hopefully, with abilities like dodging, an active shield (like Link's Awakening or Minish Cap), and different enemy behaviors, we can make combat more engaging for a modern audience. Side note, I think the game Blossom Tales went a long way toward more engaging combat just by not freezing the hero when they're attacking.

Yeah I totally agree. I loved ALTTP when I was a kid, but as I grew older I began to feel like it's a little too simple and forgiving... even combat in Zelda 1 and 2 is a lot more challenging IMO. There's a lot of different ways I'd like to build on the formula, though I know it will take time to implement everything. That's one of the reasons I wanted to at least implement a cooldown ASAP... until my AI is as challenging as i.e. Hyper Light Drifter, being able to dash constantly will likely feel OP and make things too easy.

I love the fact in Link's Awakening that almost everything is an assignable item, including stuff like the shield, gauntlets, and boots which were previously passive abilities. Being able to assign and "use" an item is inherently more fun and makes you appreciate it more, and it forces the player to make tough decisions about which items they want equipped at a given time. Your equipment menu also pretty much becomes your keybinding menu at that point, which potentially streamlines the configuration process.

That being said though, as you say it all depends on the style of game you're going for. For example if your game requires a ton of jumping, then at some point having a dedicated jump button as in Beyond Oasis or Landstalker is preferable to an assignable item like Zelda's feather. At that point being able to unassign such an important command could become a frustrating n00b trap rather than an interesting equipment choice. The same would be true of a crouch command if you need to frequently duck to avoid high attacks or kill short enemies, and of course if the player needs to dash away from enemy attacks constantly, it may not make sense to let players unassign their dash.

However for the moment, I haven't made any progress creating an assignable equipment menu either, so it's kind of a moot point... my dash command will definitely be a dedicated key for the foreseeable future.  ;)

Your scripts / Re: Dodge/Dash/Roll Action
« on: June 03, 2018, 09:21:09 pm »
I got this working in my project over the weekend and it feels pretty nice:

One side effect I noticed is that the shield sprite appears to stay in the walking animation during the dash, instead of using an alternate dash animation. I added a blank dashing animation to my shield sprite and it definitely wasn't being called. Not the end of the world, but it's something people might have to work out depending on the poses in their hero animations.

I later added a cooldown timer to mine by changing a few lines, just so you can't just spam the dash constantly. It's pretty basic but here's what I did if it's helpful to anyone:

Code: Lua
  1. -- Add this somewhere OUTSIDE the "game:on_key_pressed" function (I put it right under game:start):
  3. local can_dash = true
  5. -- Then add it to the initial if/then statement:
  7. if game:has_item("dandelion_charm") and effect == nil and state == "free" and game:is_suspended() == false and can_dash == true then
  9. -- add this to the m:start like so:
  11.       m:start(hero, function()
  12.         hero:unfreeze()
  13.         can_dash = false
  14.         sol.timer.start(hero, 500, function()
  15.           can_dash = true
  16.         end)
  17.       end)
  19. -- and do the same to your on_obstacle_reached function:
  21. function m:on_obstacle_reached()
  22.         hero:unfreeze()
  23.         sol.timer.start(hero, 500, function()
  24.           can_dash = true
  25.         end)
  26.       end
  28. -- obviously you can change "500" to any timing you want. You could also make the timing longer or shorter for on_obstacle_reached to either punish or forgive the player for slamming into a wall.

Eventually I'd like to make it use MP, convert it to an assignable item, and polish up the animations... but either way, it's great to have dash functionality in Solarus!

Your scripts / Re: Dodge/Dash/Roll Action
« on: May 30, 2018, 08:54:51 pm »
This is awesome as I'd wanted to include dashing in my project but wasn't sure where to begin. I actually like that it crosses gaps as I'm a big Hyper Light Drifter fan, but I understand why you'd want to be able to turn that off. Great job, Max!

Thanks again for the help. I suppose there's probably ways I can work around the bug until it's eventually fixed.

Thanks for the help. Here's a dropbox link:

Development / Shield Animations Reverse Direction for Sword Attacks
« on: May 13, 2018, 07:11:18 pm »
I'm in the process of creating a custom "tall" link sprite, and I'm running into a strange issue with shield animations. The shield's walking animations work fine, but durring sword attacks, the directions get reversed.

In other words, the shield plays the "down" animation during an upwards sword strike, and the "up" animation during a downwards sword strike. I believe Solarus is reversing the shield's left and right sword animations as well, though it's hard to tell since they appear to be clipping behind Link's body.

What could be causing this? You can see from the video that the animation frames and directions are set up just like any other situation.

(Please excuse the WIP art, the frames are very rough and I still have a lot of polishing to do.)

Pages: [1] 2