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.

Topics - Max

Pages: [1]
Development / Moving a dynamically-created entity
« on: March 15, 2018, 12:38:18 am »
So I'm designing basically a Hinox enemy, who will throw bombs at you if you are within range. Everything is going great, and I just have a small problem that I'd like to know if I can fix a different way than I did.

Basically, I use map:create_bomb(), and then create a jump movement and "throw" the bomb toward the player that way. However, if I end up creating a new bomb before the old one explodes, the code "throws" the first bomb again instead of the newly created one, since the newly created bomb isn't called hinox_bomb, it's called hinox_bomb_2 since the first one is still around.

I got around this by just detonating the first bomb if it's still lying around before throwing a second one, which works well. But is there a better way to do this that anyone can think of?

The code for when the enemy throws bombs is this:

Code: Lua
  1.         function enemy:shoot()
  2.     --first, check if the hero is in the same region
  3.           local map = enemy:get_map()
  4.           local hero = map:get_hero()
  5.           if not enemy:is_in_same_region(hero) then
  6.                 return true  -- Repeat the timer.
  7.           end
  9.           local sprite = enemy:get_sprite()
  10.           local x, y, layer = enemy:get_position()
  11.           local direction = sprite:get_direction()
  13.           sprite:set_animation("shooting")
  14.           enemy:stop_movement()
  16.     --destroy an old bomb before creating a new one (mainly so we don't move the old bomb with our movement)
  17.     if map:has_entity("hinox_bomb") == true then
  18.       local bombx, bomby, bombl = map:get_entity("hinox_bomb"):get_position()
  19.       map:create_explosion({x = bombx, y = bomby, layer = bombl, })
  21.       map:get_entity("hinox_bomb"):remove()
  22.     end
  24.     --now let's create a new bomb after we give the enemy a second to pull it out of his pocket
  25.           sol.timer.start(enemy, 400, function()
  26.       map:create_bomb({
  27.         name = "hinox_bomb", x = x, y = y, layer = layer,
  28.       })
  29.     --and throw the new bomb toward the player
  30.       local bomb_toss = sol.movement.create("jump")
  31.       local dir_to_hero = self:get_direction8_to(hero)
  32.       bomb_toss:set_direction8(dir_to_hero)
  33.       bomb_toss:set_distance(dist_hero + 16)
  34.       bomb_toss:set_speed(90)
  35.       bomb_toss:start(map:get_entity("hinox_bomb"))
  37.           sprite:set_animation("walking")
  39.       --now the enemy can go back to what it was doing before it threw the bomb
  40.       self:go_random()
  41.       self:check_hero()
  43.           end)
  44.         end

If anyone is interested in the whole code, I can post that for general use also if nobody's already made an enemy like this. It's based on the Solarus Team's scripts, and is actually pretty fun to battle because the best strategy is to pick up its bombs and throw them back. This could also be used for a "King Moblin" type enemy, which has appeared in a few Zelda games.

Development / Enemy Timers
« on: March 14, 2018, 02:52:46 am »
Hey, question. So timers associated with an enemy are destroyed when enemy:on_restarted() is called. However, enemy:on_restarted is called when the enemy is damaged, right? Therefore, it seems impossible to create a timer that sends an enemy through phases without you interrupting the phases whenever you hit the enemy.

For example, how would we have an enemy that becomes invulnerable after 10 seconds? You'd need to start that timer in enemy:on_restarted(), but that timer would be destroyed and restarted every time you damage the enemy.

Has anyone done anything like this in designing enemies?

Development / Enemy Obstacle Behavior: Swimming
« on: March 07, 2018, 07:11:26 pm »
Hey, so I was trying to make a zora kind of guy, and I set his behavior to swimming. Now he will swim through the ground with impunity. Does the swimming behavior allow enemies to walk on the ground also? Is there a behavior that keeps them in the water? Thanks!

Game art & music / Oceansheart Graphics
« on: March 06, 2018, 04:04:57 am »
Hey! I wanted to do something to help out the community. I like to think graphic art is one of my talents, and I've put in many many hours of work on tiles for the game I'm working on. I figure some of them might come in handy for others.

They're not necessarily compatible stylistically with Link to the Past graphics, because I taught myself pixel art looking at Minish Cap, haha, but at least the perspective is similar. I like to think these are a little more flexible in terms of topographical layout. Feel free to use these however you like, but please give me credit if you use them. It'd also be super cool if you let me know where my art goes, just because that's fun to track and see other people getting use out of my work : )

If you have any advice or feedback, I'm always looking to improve my talents and will welcome it.

Development / Enemies drowning animation?
« on: March 02, 2018, 10:14:06 pm »
Hey all.

So when you knock an enemy or throw a carried item into a hazard (water, pit, probably lava too), the engine recognizes this and will remove it and play an appropriate sound. However, it doesn't show any animation. I'm not sure if I just need to create one in the right location or what, but is there a default animation for items or enemies to fall into hazards?

I'm sure one could code a script that checks for enemies or items tossed into hazards, but I'm really just curious for now if that's something the engine can already do that I'm just missing resources for.

Your scripts / Leever-type enemy
« on: February 27, 2018, 04:45:24 pm »
So I've been learning how the solarus team does enemy scripts, and based on that system and their scripts, I've made one I think some people might find useful. It's enemy behavior modeled after the leevers from Zelda, but I've also found it useful for ghosts, and if you made some projectile addition, you could use it for a zora-type enemy too.

This one is made to follow the player if they're close enough to the enemy, but I've taken that part out for some more indifferent enemy types. One problem I've found with this script is that if you attack the enemy, it resets the timer for going underground. So if you keep hitting the enemy quickly, they won't have a chance to escape underground. I'm not 100% sure how to address that.

Code: Lua
  1. local behavior = {}
  3. --This enemy is like Zelda's Leever. It moves randomly, and periodically will burrow below ground to later pop up. It is invulnerable when below ground. Special properties of this enemy type are time_underground, time_aboveground, and burrow_deviation. Time below/above ground are minimums, with the deviation being the random amount (in ms) to add to that. That keeps them from popping in and out in swarms if you have multiple. You could also use this for a ghost that phases in/out.
  4. --Required sprites are walking, burrowing, and underground (which can be an empty graphic if you don't want they player to know where they are.
  5. --You'll also need a sound called "burrow1" so they can make a noise and they go underground.
  7. -- The properties parameter is a table.
  8. -- All its values are optional except the sprite.
  10. function behavior:create(enemy, properties)
  12.   local going_hero = false
  13.   local underground = false
  16.   -- Set default properties.
  17.   if properties.size_x == nil then
  18.     properties.size_x = 16
  19.   end
  20.   if properties.size_y == nil then
  21.     properties.size_y = 16
  22.   end
  23.   if == nil then
  24. = 2
  25.   end
  26.   if properties.damage == nil then
  27.     properties.damage = 2
  28.   end
  29.   if properties.normal_speed == nil then
  30.     properties.normal_speed = 32
  31.   end
  32.   if properties.faster_speed == nil then
  33.     properties.faster_speed = 48
  34.   end
  35.   if properties.hurt_style == nil then
  36.     properties.hurt_style = "normal"
  37.   end
  38.   if properties.pushed_when_hurt == nil then
  39.     properties.pushed_when_hurt = true
  40.   end
  41.   if properties.push_hero_on_sword == nil then
  42.     properties.push_hero_on_sword = false
  43.   end
  44.   if properties.ignore_obstacles == nil then
  45.     properties.ignore_obstacles = false
  46.   end
  47.   if properties.detection_distance == nil then
  48.     properties.detection_distance = 80
  49.   end
  50.   if properties.obstacle_behavior == nil then
  51.     properties.obstacle_behavior = "normal"
  52.   end
  53.   if properties.movement_create == nil then
  54.     properties.movement_create = function()
  55.       local m = sol.movement.create("random_path")
  56.       return m
  57.     end
  58.   end
  59.   if properties.time_underground == nil then
  60.     properties.time_underground = 2000
  61.   end
  62.   if properties.time_aboveground == nil then
  63.     properties.time_aboveground = 3000
  64.   end
  65.   if properties.burrow_deviation == nil then
  66.     properties.burrow_deviation = 6000
  67.   end
  68.   if properties.burrow_sound == nil then
  69.     properties.burrow_sound = "burrow1"
  70.   end
  73. --create enemy properties
  74.   function enemy:on_created()
  76.     self:set_life(
  77.     self:set_damage(properties.damage)
  78.     self:create_sprite(properties.sprite)
  79.     self:set_hurt_style(properties.hurt_style)
  80.     self:set_pushed_back_when_hurt(properties.pushed_when_hurt)
  81.     self:set_push_hero_on_sword(properties.push_hero_on_sword)
  82.     self:set_obstacle_behavior(properties.obstacle_behavior)
  83.     self:set_size(properties.size_x, properties.size_y)
  84.     self:set_origin(properties.size_x / 2, properties.size_y - 3)
  86.   end
  89.   function enemy:on_movement_changed(movement)
  91.     local direction4 = movement:get_direction4()
  92.     local sprite = self:get_sprite()
  93.     sprite:set_direction(direction4)
  94.   end
  97.   function enemy:on_restarted()
  98.     self:go_random()
  99.     self:check_hero()
  101.   end
  105.   function enemy:burrow_down()
  106.     sol.timer.stop_all(self)
  107.     self:get_sprite():set_animation("burrowing")
  108.     --play the sound if you're close enough
  109.     local hero = self:get_map():get_entity("hero")
  110.     local _, _, layer = self:get_position()
  111.     local near_hero = self:get_distance(hero) < 140
  112.     if near_hero then end
  113.     sol.timer.start(self, 800, function() self:go_underground() end)
  114.   end
  116.   function enemy:burrow_up()
  117.     self:get_sprite():set_animation("burrowing")
  118.    --play the sound if you're close enough
  119.     local hero = self:get_map():get_entity("hero")
  120.     local _, _, layer = self:get_position()
  121.     local near_hero = self:get_distance(hero) < 140
  122.     if near_hero then end
  123.     sol.timer.start(self, 800, function() self:go_aboveground() end)
  124.   end
  126.   function enemy:go_underground()
  127.     underground = true
  128.     self:get_sprite():set_animation("underground")
  129. --this code is so you don't get chased by an underground dude
  130.     local m = properties.movement_create()
  131.     if m == nil then
  132.       -- No movement.
  133.       self:get_sprite():set_animation("stopped")
  134.       m = self:get_movement()
  135.       if m ~= nil then
  136.         -- Stop the previous movement.
  137.         m:stop()
  138.       end
  139.     else
  140.       m:set_speed(properties.normal_speed)
  141.       m:set_ignore_obstacles(properties.ignore_obstacles)
  142.       m:start(self)
  143.     end
  144. --]]
  145.     self:set_can_attack(false)
  146.     sol.timer.start(self, (properties.time_underground + math.random(properties.burrow_deviation)), function() self:burrow_up() end)
  148.   end
  150.   function enemy:go_aboveground()
  151.     underground = false
  152.     self:set_can_attack(true)
  153.     self:get_sprite():set_animation("walking")
  154.     self:check_hero()
  155.   end
  158.   function enemy:check_hero()
  159.     local hero = self:get_map():get_entity("hero")
  160.     local _, _, layer = self:get_position()
  161.     local _, _, hero_layer = hero:get_position()
  162.     local near_hero =
  163.         (layer == hero_layer or enemy:has_layer_independent_collisions()) and
  164.         self:get_distance(hero) < properties.detection_distance
  166.     if near_hero and not going_hero then
  167.       if underground == false then self:go_hero()
  168.       else self:go_random()
  169.       end
  170.     elseif not near_hero then
  171.       self:go_random()
  172.     end
  173. --and repeat this every 150ms
  174.     sol.timer.start(self, 150, function() self:check_hero() end)
  176.   end
  180.   function enemy:go_random()
  181.     going_hero = false
  182.     sol.timer.start(self, properties.time_aboveground+math.random(properties.burrow_deviation), function() self:burrow_down() end)
  183.     local m = properties.movement_create()
  184.     if m == nil then
  185.       -- No movement.
  186.       self:get_sprite():set_animation("stopped")
  187.       m = self:get_movement()
  188.       if m ~= nil then
  189.         -- Stop the previous movement.
  190.         m:stop()
  191.       end
  192.     else
  193.       m:set_speed(properties.normal_speed)
  194.       m:set_ignore_obstacles(properties.ignore_obstacles)
  195.       m:start(self)
  196.     end
  197.   end
  201.   function enemy:go_hero()
  202.     going_hero = true
  203.     sol.timer.stop_all(self)
  204.     sol.timer.start(self, (properties.time_aboveground + math.random(properties.burrow_deviation + 2000)), function() self:burrow_down() end)
  205.     local m = sol.movement.create("target")
  206.     m:set_speed(properties.faster_speed)
  207.     m:set_ignore_obstacles(properties.ignore_obstacles)
  208.     m:start(self)
  209.     self:get_sprite():set_animation("walking")
  210.   end
  214. end
  216. return behavior

Development / Displaying an image on pause
« on: February 27, 2018, 04:32:05 pm »
Hey all, sorry to kind of flood the board with questions. I've been saving them up for a year trying to figure them out myself first but there's a few I need help with.

So, I've been trying to learn Solarus and lua coding for a year and a half, and I still can't figure out how to create a pause menu. I've basically given up and accepted this as a design limitation to constrain myself with, so my game only has two items. But anyway, all my playtesters have given me the feedback that with all the side quests in my game, they'd really like a main quest log kind of feature because they forget what they were supposed to be doing. I think that just displaying the current stage of the main quest on the pause screen is the way to go.

So from studying the various solarus team scripts, I think that the way to show an image would be something like this:
Code: Lua
  1. function game:on_paused()
  2.   local pause_infra_img = sol.surface.create("hud/pause_infra.png")
  3.   local pause_surface = sol.surface.create()
  4.   pause_infra_img:draw(pause_surface)
  5. end

However, I'm really confused about how all this works. It seems like you can create a surface, but it won't be immediately drawn? So you have to draw it, but you need a surface to draw it on. But wouldn't the second surface need to be drawn on a surface too? Ahh!

Development / Creating a respawn point on map changed?
« on: February 27, 2018, 04:23:51 pm »
Hi guys!

One thing I dislike about A Link to the Past is that you have to restart at your house or the Pyramid every time you die in the overworld. In the game I'm working on, you spend quite a lot of time in the overworld doing side-quests, and I don't want to set the player back so far if they're killed. I'd like to make it so if you die, you can be taken back whatever map you're currently on.

I know if the player is transported to a new map via a destination, you can save that location for restarting. However, if you use the side of the map scrolling feature, there is no destination to go back to, so one workaround is to design my overworld so there's never any side of the map teletransporters, but I'd like to avoid that if possible.

I also tried dynamically creating a destination using game:on_map_changed() and map:create_destination(), however it seems like the game erases this destination when a gameover happens, because the hero isn't taken back to it. I had this function in my game_manager script. This was my code that doesn't seem to work:

Code: Lua
  1. --Set Respawn point whenver map changes
  2. function game:on_map_changed()
  3.   local map = game:get_map()
  4.   local hero = game:get_hero()
  5.   local x, y, layer = hero:get_position()
  6.   map:create_destination({
  7.     name = "respawn",
  8.     x = x, y = y, layer = layer,
  9.     direction = hero:get_direction(),
  10.     save_location = "yes",
  11.     default = true,
  12.   })

But yeah, it seems like that destination is erased whenever a gameover happens, which makes it kind of pointless. Any ideas?

Development / Activating a switch from a script?
« on: February 27, 2018, 02:15:30 am »

I've been looking over the Solarus Team's bow/arrows script, which is super cool, and developed a script using it that shoots fire arrows. But one problem I'm encountering is that it uses switch:set-activated() to allow arrows to interact with switches. However, this method doesn't trigger the switch:on_activated() event.

I've found a workaround with setting a timer that checks to see if the switch has been activated ever 100ms or so and acts accordingly, but is there a better way to do this? Is there a way to trigger the event from a script?


Your projects / Ocean's Heart
« on: February 27, 2018, 01:59:34 am »
Hey guys,

I've been lurking on your forums for like a year and a half playing around with solarus and thought I've reached a point where I'm comfortable sharing my work. I'm doing a little project tentatively titled "Ocean's Heart" in order to really learn the engine. As of now, I'm estimating I'm about 40% done with the game, there's a few dungeons, a bunch of side quests, several islands to explore, etc.

The game follows the journey of a girl whose father left their town about a year previously to defeat pirates that attacked the town. She spends most of the game following his trail and uncovering the plot that's kept him away for so long. I'm aiming for a cross between Zelda: Wind Waker, and the Witcher 3.

The first demo is available here:
LINK!(Not that Link)(It's not a Zelda Game)

If you do all the sidequests and explore everything, I think the game is about 3 hours long so far, maybe more.

Arrow keys - Move
Space Bar - Talk / action
X - Bow
C - Sword
V - Bombs
D - Pause / Save / Show Inventory
M - Show Map

If you give this a play, I'd love to know what you think! Thanks.

Here's a short trailer of some areas:
(there's no way to embed youtube videos on this forum, is there?)

And some screenshots, I'll figure out a way to make these a bit smaller, they're huge on my screen...

Pages: [1]