Is there a way to get the height and width of the collided object?
Something like:
local height = go.get("#collisionobject", "height")
or
function on_message(self, message_id, message, sender)
-- check for the message
if message_id == hash("contact_point_response") then
-- get the other object's height
local other_height = go.get(message.other_id, "height")
end
end
I am working with the getting started tutorial (the frog infinite/endless runner) and I think that it would be nice to add the feature of “Ledge Grab”:
It looks fun, doesn’t it?
But I haven’t found how to retrieve the exact size and position of the collided platform to determine if the hero is grabbing the ledge.
I am thinking that I have to “play” with a work around, like using different collision objects in my game object, like:
Yeah I would go with two collisionobjects.
One full and one for the “head”.
If both boxes are triggered then there will be no ledge grabbing.
If only full body is triggered (it means that head part is above ledge) it will grab.
Remember that you will only be able to know which box that got triggered by looking in sender. There is no such information in the collision message (did take me quite a while to find out )
Ok, after a few unsuccessful tries with the suggestion of the two objects, and getting inspiration from the images I uploaded on my initial post, I am thinking on adding another collision object to the player, a thin object, like a hat, with the same width of the full body collision object but maybe only 5 pixels high, using another group, let’s say “player_top”, and another mask, let’s say “ledge”, then add another collision object to the platform, a small box on the upper corner, with the corresponding inverse group and mask: “ledge” and “player_top”, so the events will trigger apart from the other “usual” collisions, like:
function on_message(self, message_id, message, sender)
if message_id == hash("contact_point_response") then
if message.group == hash("geometry") then
handle_geometry_contact(self, message.normal, message.distance)
elseif message.group == hash("ledge") then
-- do ledge grab stuff here
-- ...
end
end
end
Another way (which I was planning on trying) is to use ray casts. One from above and one to the side. If their hit points are close enough, and the hit point normals have the dot product of 0, they should be able to hint that there’s a ledge there.
Another way (if you would use tilemaps, and no moving platforms), could be to check what the tiles next to the player actually are (e.g. empty or not empty etc)
Hey @Mathias_Westerdahl, I believe ray cast is a more elegant solution, but do you have any idea on what would be the cost? more checking operations for the processor?
Aaand, could you talk a little more about your idea on using tilemaps, please?
Tilemaps are the next step where I want to try to implement this ledge checking, but I haven’t got too deep into tilemaps yet, so I was just thinking on how to do it…
I have seen that you can use the tiles in the tilemap to create a collision object, so, “technically”, If you create a tile with a thin bottom border and you place it above a “ground” tile, it would give you a collision_response for that group, right?
Here’s a different approach that could work if you want “fancy” grabs, where the jump turns into a grab animation-wise long before the avatar is actually at the ledge, maybe even at the start of the jump (i.e. special grab-jump). It might also be a bit more complicated math-wise to implement.
Use collision triggers for the ledges with sphere shapes, the radius would be the max distance from where the player could jump towards the ledge.
You collect all ledge-triggers the player is in contact with, this way you already know about all the surrounding ledges.
When the player jumps (or during the course of the jump), you analytically check the arc to detect if the player would end up “close enough” the ledge to (in the future) grab it. This depends on your jump algorithm, but it’s common to have a simpler function for the x-movement than y. That would mean to calculate time to impact x-wise, and then solve y to check if the player would be close enough at the ETA.
This is what you typically do in Assassin’s Creed style games and it would work well and be a lot easier to do in the 2D case.
function on_message(self, message_id, message, sender)
if message_id == hash("contact_point_response") then
if message.group == hash("collision_platform") then
handle_geometry_contact(self, message.normal, message.distance)
elseif message.group == hash("collision_ledge") then
-- do ledge grab stuff here...
elseif message.group == hash("collision_pre_ledge") then
-- change animation to "close_to_ledge"
-- but continue jumping or falling...
-- aaaaand "...calculate time to impact x-wise, and then solve y to check
-- if the player would be close enough at the ETA..."
end
end
end
Exactly! Read up on the trigger responses, you use them more to store all the surrounding ledges. Then you use them at different times (not when the messages arrive), but when you say start the jump, or continuously during the jump to check when the arc would put you close enough. This also means that when you know which ledge you will target, you should attempt to adjust the jump arc to make the avatar smoothly end up exactly at the ledge, which makes it look really slick.
function on_message(self, message_id, message, sender)
if message_id == hash("contact_point_response") then
if message.group == hash("collision_platform") then
handle_geometry_contact(self, message.normal, message.distance)
elseif message.group == hash("collision_ledge") then
-- do ledge grab stuff here...
end
elseif message_id == hash("trigger_response") then
-- check for the trigger response message
if message.group == hash("collision_pre_ledge") then
if message.enter then
-- take action for entry: store the id of the trigger
table.insert(trigger_ids, message.other_id)
-- maybe get the origin property?...
local other_origin = go.get(message.other_id, "origin") -- ????
trigger_ids[message.other_id] = {}
trigger_ids[message.other_id].origin = other_origin
else
-- take action for exit: remove the id of the trigger
table.remove(trigger_ids, message.other_id)
end
end
end
end
BUT…
As in the inspirational video and as you stated it in your post, you have to check the presence of a trigger BEFORE you start the jump, but then… the radius of the trigger must be huge! and how will you get the origin of the trigger to make the correction in the arc?
Yes, they have to be large but that will most likely be ok, and you can get the origin through go.get_world_position etc: go.get_world_position(message.other_id)
function on_message(self, message_id, message, sender)
if message_id == hash("contact_point_response") then
if message.group == hash("collision_platform") then
handle_geometry_contact(self, message.normal, message.distance)
elseif message.group == hash("collision_ledge") then
-- do ledge grab stuff here...
end
elseif message_id == hash("trigger_response") then
-- check for the trigger response message
if message.group == hash("collision_pre_ledge") then
if message.enter then
-- take action for entry: store the id of the trigger
table.insert(trigger_ids, message.other_id)
-- get the origin of the trigger
trigger_ids[message.other_id] = {}
trigger_ids[message.other_id].origin = go.get_world_position(message.other_id)
else
-- take action for exit: remove the id of the trigger
table.remove(trigger_ids, message.other_id)
end
end
end
end
Hey @Ragnar_Svensson, I managed to implement the ledge grab, thank you!
Right now it only works when falling, I am still thinking what “move” should I use for jumping.
And when I’m done with that I will try to implement the bigger triggers, although not the arc stuff because it is an endless runner, so I think that you don’t need to “think that much” on which ledge to go for