Is it possible to make the hero face the cursor?

Started by Mr Shiek, August 16, 2018, 11:22:25 PM

Previous topic - Next topic
Hello, Solarus World.

    I have been learning Solarus through the online tutorial series by Christopho. It has been incredibly helpful. However, there are many things in my current project that are not covered in any of the tutorial videos and I've not been able to find any info on them online. The API has also been incredibly helpful but I admit I lack a complete understanding of Lua and so I may not fully understand everything as written in the API (for instance, I had trouble with the drawable:draw_region()...the two sets of coordinates confused me, but I figured out through practice what they meant). I am very new to using it but I have been quickly picking things up and reading through previously posted links on this forum for Lua learning resources.

    The problem I am facing right now is my face_cursor script. I wanted to allow the player to change their character's facing direction by moving the mouse around on the screen (and add support for joysticks as well). Essentially, I'm looking to make it behave similar to a two stick shooter game. Is this possible in the engine as it stands? I will attach my script, which is just supposed to be a proof of concept, for your scrutiny..

local function init_cursor_info(game)        --Local Function encapsulating lesser functions and pulling 'game'.

  local map = game:get_map()
  local hero = game:get_hero()
  local h_x, h_y, h_L = hero:get_position()
  local h_dir = hero:get_direction()
  local c_x, c_y = sol.input.get_mouse_position()
  local c_dir

  --The following function should update cursor coordinates continuously.
  function game:cursor_update()
   
    local nc_x, nc_y = sol.input.get_mouse_position()

    if nc_x == c_x and nc_y == c_y then
      return
    else
      c_x = nc_x
      c_y = nc_y
    end
 
    return true

  end

  --The following function should update hero coordinates and direction continuously.
  function game:hero_update()
   
    local nh_x, nh_y, nh_L = hero:get_position()
    local nh_dir = hero:get_direction()

    if nh_x == h_x and nh_y == h_y then
      return
    else
      h_x = nh_x
      h_y = nh_y
      h_L = nh_L
    end

    if nh_dir == h_dir then
      return
    else
      h_dir = nh_dir
    end

    return true

  end

  function game:get_cursor_direction()

    local x_chg
    local y_chg

    x_chg = cursor_x - hero_x
    y_chg = cursor_y - hero_y

    if x_chg > 0 and y_chg > 0 then     --Face Hero South-East.
      cur_dir = 7
    elseif x_chg == 0 and y_chg > 0 then --Face Hero South.
      cur_dir = 6
    elseif x_chg < 0 and y_chg > 0 then --Face Hero South-West.
      cur_dir = 5
    elseif x_chg < 0 and y_chg == 0 then --Face Hero West.
      cur_dir = 4
    elseif x_chg < 0 and y_chg < 0 then
      cur_dir = 3
    elseif x_chg == 0 and y_chg < 0 then
      cur_dir = 2
    elseif x_chg > 0 and y_chg < 0 then
      cur_dir = 1
    elseif x_chg > 0 and y_chg == 0 then
      cur_dir = 0
    else if x_chg == 0 and y_chg == 0 then
      cur_dir = 2

    end

    return true

  end                                                  --End of get_cursor_direction()

  function game:on_started()

    local time = 100

    game:cursor_update()
    sol.timer.start(game, time, cursor_update())
    game:hero_update()
    sol.timer.start(game, time, hero_update())
    game:get_cursor_direction()
    sol.timer.start(game, time, get_cursor_direction())

    print(time)                                        --This print was to test if this segment executed...so far it hasn't printed.
  end
end                                                    --Not sure why I needed two ends here...I was getting an error with only one.
end                                                    --End Local Function init_cursor_info(game)

--The following code was taken from OniLinkBegins. My understanding is that this works as is but I am unsure.
-- Set up face_cursor features on any game that starts.
local game_meta = sol.main.get_metatable("game")
game_meta:register_event("on_started", init_cursor_info)

return true


..I'm sure I am making some rookie mistake, but as I said I'm learning still so any advice is appreciated.
Build a man a fire, he will be warm for the night. Set a man on fire, he will be warm for the rest of his life.
a.k.a. Kamigousu, Xejk, Sr Xejk

August 17, 2018, 01:17:05 AM #1 Last Edit: August 17, 2018, 01:25:23 AM by Diarandor
I recommend to add a function to update the direction of the hero. Something as simple as this should work:
Code (Lua) Select

function hero:face_the_cursor()
  local mouse_x, mouse_y = sol.input.get_mouse_position()
  local dir = hero:get_direction4_to(mouse_x, mouse_y)
  hero:set_direction(dir)
end

You can combine it, for instance, with a 1-millisecond-timer that calls the function "face_the_cursor" when necessary, or otherwise, with potatoes, ketchup and chicken wings.

PS1: Since the function "sol.input.get_mouse_position()" seems to calculate the position relative to the quest size, it may not work. I have never used/tested it. In that case, I leave the problem as an exercise for you.

PS2: my cats usally say catsup instead of ketchup.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

Yes, it is very possible.

Mouse coordinates are given relative to the quest window, which you'll need to convert to map coordinates. Only part of the current map will be visible on the screen, as determined by the camera.

Functions you'll need are:
sol.input.get_mouse_position()
camera:get_position_on_screen()
camera:get_position()
hero:get_position()

Updating the hero's facing direction every 1ms seems like a bad idea. That would essentially make it so that the hero is always facing the cursor. A better implementation would be to only update the direction when the mouse moves. That way the player could choose to use the arrow keys to move the hero, not ever touch the mouse, and all would be well.

Quote from: llamazing on August 17, 2018, 01:51:00 AM
Updating the hero's facing direction every 1ms seems like a bad idea.

Unless he is making a shooter where the hero should always be aiming to the cursor, which we don't know.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

August 17, 2018, 05:09:19 PM #4 Last Edit: August 18, 2018, 04:11:24 AM by Mr Shiek
I really appreciate the fast replies!

@Diarandor: your code make it look so easy...clearly I have much to learn. I'll try implementing that later today, after work. Am I missing a call for this script somewhere though? Because I feel like it isn't being called properly.

@llamazing: I'll definitely keep those functions in mind. Once I can get a proof of concept working for myself, I'd like to add more to it so that it functions properly in a game, and it looks like what you mentioned is going to be extremely helpful. The game I'm working on is going to be styled like a two stick shooter; essentially the wasd would control the hero movement direction and the mouse would control hero facing direction. So I do want the hero to constantly face the cursor, unless it is directly on top of the hero, in which case he should simply look North.

Really hoping I can get this done with your help  because my next task is to add a stamina resource to the game, alongside life and magic. I've been trying to use the anger bar from OLB as a reference but I can't figure out how to add the resource to the game just yet. I'm assuming it has to do with meta tables so I suppose I'll need to get to that point in the book before really understanding how to do that. I feel bad for asking for help on here when I haven't learned wverything I can on my own but I'm glad I wasn't met with negativity. Thanks again and I'll post back whether or not  I get it working tonight!
Build a man a fire, he will be warm for the night. Set a man on fire, he will be warm for the rest of his life.
a.k.a. Kamigousu, Xejk, Sr Xejk

Something like this may work (it is untested, so you should correct it if necessary).
Code (Lua) Select

-- Get the map coordinates of the cursor.
local map_meta = sol.main.get_metatable("map")
function map_meta:get_cursor_coordinates()
  local camera = self:get_camera()
  local mouse_x, mouse_y = sol.input.get_mouse_position()
  local cam_screen_x, cam_screen_y = camera:get_position_on_screen()
  local cam_x, cam_y, _, _ = camera:get_bounding_box()
  local pos_x = mouse_x - cam_screen_x + cam_x
  local pos_y = mouse_y - cam_screen_y + cam_y
  return pos_x, pos_y
end

-- Make the hero face the cursor.
local hero_meta = sol.main.get_metatable("hero")
function hero_meta:face_the_cursor()
  local hero = self
  local pos_x, pos_y = hero:get_map():get_cursor_coordinates()
  local dir = hero:get_direction4_to(pos_x, pos_y)
  hero:set_direction(dir)
end


You still have to import the script that contains these functions, and initialize a timer that will call "hero:face_the_cursor()" when necessary, as mentioned above. If we do all the work for you, you will not learn, so I will be leaving you here. Good luck!
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

I really appreciate the help. I have been working with it for a while now and, while it shows no errors, it doesn't work as intended. By no means do I want nor expect you to do the work for me; as you said, it defeats the purpose of the learning process. I will just have to continue to expand my Lua knowledge until I can understand better where I am making the mistake. Looking forward to learning more Lua and Solarus.

Many thanks, again!
Build a man a fire, he will be warm for the night. Set a man on fire, he will be warm for the rest of his life.
a.k.a. Kamigousu, Xejk, Sr Xejk