Side scrolling functionality

Started by wizard_wizzle (aka ZeldaHistorian), June 25, 2015, 04:24:13 AM

Previous topic - Next topic
June 25, 2015, 04:24:13 AM Last Edit: January 18, 2021, 05:21:31 PM by wizard_wizzle (aka ZeldaHistorian)
As a proof-of-concept, I've been figuring out how to build side-scrolling functionality with Solarus (like the sections of Link's Awakening). I can get jumping, gravity and movement pretty well down, but the part I can't figure out is enemy collisions.

I'd like to have it work like Mario where if you jump on certain enemy's heads, they are hurt, but I can't figure out how to detect that the hero has touched only the top of the enemy. If the hero touched the side of the enemy, the hero should be hurt.

I'm guessing that this can't be accomplished with the built-in enemy class, but even with a custom entity and add_collision_test(), I don't see how to detect what was touching what.

EDIT (1/18/21): Don't use this - it's old and there are much better versions out there now. Check out the one PhoenixII54 posted on 1/18/21.
https://gitlab.com/zeldaforce/zelda-alttd/-/blob/dev/data/scripts/maps/sideview_manager.lua

June 25, 2015, 04:26:18 AM #1 Last Edit: June 28, 2015, 12:20:59 AM by wrightmat
If anyone is curious, I have a game_manager script that takes care of most of the heavy lifting. The script will cause gravity to act on the hero and any entity with a name prefaced by "g_". This will effect any map of the game, but could be limited to only certain ones easily enough.

Updated code is in a post below...

Wow, I did not suspect that it was possible to implement side-scrolling only with scripts!
There is an old issue for that: https://github.com/christopho/solarus/issues/118

To detect if the hero is above the enemy, you can probably call enemy:get_angle(hero) from a callback and check if the angle is in a specific range.

These are good news. I was planning trying to script something like this in the future. I guess that the script can be modified to simulate diving with this side scrolling (like in Link's awakening), which would be cool.

I have been working on several scripts with other functionalities that I would like to share when I finish making the game to the end of the first level. (I don't have too much free time, and drawing all the sprites takes a lot of time and patience, so it will probably be ready by the end of this year or a bit later).

Some scripts I have done allow to have 3 heroes and switch between the heroes on the current map with space bar (or using a switching menu in case the heroes are in different maps, but this menu is not done yet). I also have scripts to carry custom entities which are not destroyed when thrown (I make them bounce when they fall), and they can be thrown from one hero to another (they are picked in the air). Also I made custom buttons that can be pressed by any of the three heroes or custom entities (like weights that can be carried and thrown). Platforms also work well with custom entities that can be carried and the secondary heroes.

The only technical problem I had is that if you leave one map with some secondary hero and come back, the destructible entities, platforms, etc are reseted, which can make a secondary hero to appear overlapping a destructible entity (that had been destroyed before) or to appear over a hole (in case a platform was moved), etc. I solved this easily using a script that saves the state/position of the entities if necessary (it does not work in the current version of Solarus because of a bug that has already been fixed by Christopho, but I hope it to work in the next release).

These are some of the things I made, just in case you are interested. My (maybe too ambitious) goal is to make a hybrid game between Lost Vikings 2 and Link's awakening, but it will take some years of work until it is finished.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

Oh it could be so cool ! I really like these parts in Zelda games, but I've always thought they should have been more polished. Maybe the focus wasn't put on them to be sure the game would be different than Metroid or Castlevania.
Solarus might become a good Metroidvania engine someday.  ;)

Christopho, your idea worked! I believe I got the basics working, if anyone would like to use it.

game_manager.lua (gravity and jumping):
local game_manager = {}
local map_metatable = sol.main.get_metatable("map")

local gravity = 2
local jump_height = 8
local jumping = false
local i = 0

function game_manager:start_game()

  local exists = sol.game.exists("save1.dat")
  local game = sol.game.load("save1.dat")
  if not exists then
    -- Initialize a new savegame.
    game:set_max_life(1)
    game:set_life(game:get_max_life())
  end
  game:start()

  function game:on_command_pressed(command)

    if command == "up" and not jumping then
      -- override default behaviour and make the hero jump up!
      jumping = true
      i = 0
    end
  end
end

function map_metatable:on_update()

  -- gravity: move entities down one pixel on every update if there's no collision
  --          (like with the ground or a platform)
  local x,y,l = self:get_game():get_hero():get_position()
  if not jumping then
    if not self:get_game():get_hero():test_obstacles(0,gravity) then
      self:get_game():get_hero():set_position(x,y+gravity,l)
    end
  else
    self:get_game():get_hero():set_animation("jumping")
    for i=1, jump_height do
      if not self:get_game():get_hero():test_obstacles(0,-1) then
        self:get_game():get_hero():set_position(x,(y-1),l)
      end
    end
    sol.timer.start(50*jump_height, function()
      jumping = false
      if self:get_game():is_command_pressed("right") or self:get_game():is_command_pressed("left") then
        self:get_game():get_hero():set_animation("walking")
      else
        self:get_game():get_hero():set_animation("stopped")
      end
    end)
  end

  for entity in self:get_entities("g_") do
    local gx,gy,gl = entity:get_position()
    if not entity:test_obstacles(0,gravity) then
      entity:set_position(gx,gy+gravity,gl)
    end
  end

end

return game_manager



monster.lua (example of a goomba-like enemy that you jump on to kill):
local enemy = ...

local state = "stopped"  -- "stopped", "moving", "going_back" or "paused".
local initial_xy = {}
local activation_distance = 24

function enemy:on_created()
  self:set_life(1)
  self:set_damage(2)
  self:create_sprite("enemies/monster")
  self:set_size(32, 32)
  self:set_origin(16, 29)
  initial_xy.x, initial_xy.y = self:get_position()
end

function enemy:on_update()
  local hero = self:get_map():get_entity("hero")
  if state == "stopped" and self:get_distance(hero) <= 192 then
    -- Check whether the hero is close.
    local x, y = self:get_position()
    local hero_x, hero_y = hero:get_position()
    local dx, dy = hero_x - x, hero_y - y

    if math.abs(dy) < activation_distance then
      if dx > 0 then
self:go(0)
      else
self:go(2)
      end
    end
    if state == "stopped" and math.abs(dx) < activation_distance then
      if dy > 0 then
self:go(3)
      else
self:go(1)
      end
    end
  end
end

function enemy:go(direction4)
  local dxy = {
    { x =  8, y =  0},
    { x =  0, y = -8},
    { x = -8, y =  0},
    { x =  0, y =  8}
  }

  -- Check that we can make the move.
  local index = direction4 + 1
  if not self:test_obstacles(dxy[index].x * 2, dxy[index].y * 2) then
    state = "moving"
    self:get_sprite():set_animation("walking")
    local x, y = self:get_position()
    local angle = direction4 * math.pi / 2
    local m = sol.movement.create("straight")
    m:set_speed(40)
    m:set_angle(angle)
    m:set_max_distance(104)
    m:set_smooth(false)
    m:start(self)
  end
end

function enemy:on_obstacle_reached()
  self:go_back()
end

function enemy:on_movement_finished()
  self:go_back()
end

function enemy:on_collision_enemy(other_enemy, other_sprite, my_sprite)
  if other_enemy:get_breed() == self:get_breed() and state == "moving" then
    self:go_back()
  end
end

function enemy:on_attacking_hero(hero, enemy_sprite)
  -- If hero is above the enemy (jumping on its head), kill it; otherwise, hurt the hero
  if self:get_angle(hero) >= 0.8 and self:get_angle(hero) <= 2.2 then
    self:remove_life(2)
  else
    hero:start_hurt(self, 1)
  end
end

function enemy:go_back()
  if state == "moving" then
    state = "going_back"
    self:get_sprite():set_animation("walking")
    local m = sol.movement.create("target")
    m:set_speed(32)
    m:set_target(initial_xy.x, initial_xy.y)
    m:set_smooth(false)
    m:start(self)
  elseif state == "going_back" then
    state = "paused"
    self:get_sprite():set_animation("immobilized")
    sol.timer.start(self, 500, function() self:unpause() end)
  end
end

function enemy:unpause()
  self:get_sprite():set_animation("immobilized")
  state = "stopped"
end

Wow! That's some great work indeed! Now maybe some more difficult 2D scrolling parts a la Link's Awakening can be done? That would be awesome. But, the game did have some in depth use of the 2D scrolling besides mere underground passages linking 2 places: Manbo's Cave and 2 boss battles. That would be sweet if it could be implemented as smoothly as you're all working on it! A ZeldaII/Link to the Past hybrid sounds pretty effin' epic!

Quote from: Diarandor on June 25, 2015, 10:21:15 PM
These are good news. I was planning trying to script something like this in the future. I guess that the script can be modified to simulate diving with this side scrolling (like in Link's awakening), which would be cool.

I have been working on several scripts with other functionalities that I would like to share when I finish making the game to the end of the first level. (I don't have too much free time, and drawing all the sprites takes a lot of time and patience, so it will probably be ready by the end of this year or a bit later).

Some scripts I have done allow to have 3 heroes and switch between the heroes on the current map with space bar (or using a switching menu in case the heroes are in different maps, but this menu is not done yet). I also have scripts to carry custom entities which are not destroyed when thrown (I make them bounce when they fall), and they can be thrown from one hero to another (they are picked in the air). Also I made custom buttons that can be pressed by any of the three heroes or custom entities (like weights that can be carried and thrown). Platforms also work well with custom entities that can be carried and the secondary heroes.

The only technical problem I had is that if you leave one map with some secondary hero and come back, the destructible entities, platforms, etc are reseted, which can make a secondary hero to appear overlapping a destructible entity (that had been destroyed before) or to appear over a hole (in case a platform was moved), etc. I solved this easily using a script that saves the state/position of the entities if necessary (it does not work in the current version of Solarus because of a bug that has already been fixed by Christopho, but I hope it to work in the next release).

These are some of the things I made, just in case you are interested. My (maybe too ambitious) goal is to make a hybrid game between Lost Vikings 2 and Link's awakening, but it will take some years of work until it is finished.

That sounds amazing! Have you been able to finish the script that at least makes liftable objects bounce and not break?

Yes and no. It works "almost" fine. The only problem is that I get a crash sometimes after the item falls if you walk where the shadow was removed, because the shadow entity I used was not removed correctly or something like that. I think that bug was already solved by Christopho in the development version, so my script should work in the next version (or it may become an error of my script that I should be able to correct).

I want to put my scripts on github after the next version of solarus is released (to check if it works and repair it if necessary), but I can put it now if you want. (Note that to make this script work you will also need to modify slightly the game:on_command_pressed() event, but this is not a problem.)
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

Sure! Just tell me what I'd need to do! I have the latest version of Solarus Quest Editor installed (1.4.2).

Hi DementKirby!

I have created my first github repository and put some of my scripts. You have the one you want in entities/generic_portable.lua. You should study the code and delete/modify the things you don't need. Also you need the sprites and animations and sounds to be compatible with the script. (Recall that there is still a crash with these entities. I will try to repair it someday.)

You will find my repository here: https://github.com/Diarandor

Also, you will find the scripts for entities button and weight. Weights are generic_portable entities (so these can be carried, etc) that can push my button entities. I will put more stuff in the repository someday soon.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

Thanks! I will study them shortly and see what it is you're accomplishing from a coding standpoint. As for the sprites, I take it you're working it around something like the heavy metal ball in the Eagle's Tower in Link's Awakening?

Yes, I'm using that script to make things similar to that metal ball (Link's awakening is great!), but also more complex entities with several sprites that can be carried. (I haven't tried to script the flying rooster since I don't need it, but it should be scriptable, with a lot of work of course.)

Also, in my game, instead of pickable, keys will be carried, so I will be able to annoy the hero with bird enemies that steal the key you are carrying, or forcing you to throw the keys between the three heroes (since they have different abilities and must take different paths sometimes), or using other things like conveyor belts and platforms with keys, or even using the magneto with metal keys. I think this would give more originality/personality to my game than usual keys.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

Wow! That seems pretty ambitious and original! I can't wait to see it finished!

Do you think you're having problems with the shadow and whatnot because you're trying to make a bouncing effect? What if they just fell on the ground and didn't bounce before getting still? Would that solve your problem?

The problem can be avoided if you do not create the shadow. I still don't know what produces the crash.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."