Activating Objects using Bounding Box?

BQubed

Member
I currently have this snippet of code that creates a marker over the player's head when they walk up to an interactable object or npc:

GML:
//Object Interactable Marker
var activateX = lengthdir_x(10, direction);
var activateY = lengthdir_y(10, direction);
var parents = [ObjectParent, NPCParent];
for (var _i = 0; _i < array_length(parents); ++_i)
{
  obj_interact = instance_position(x+activateX, y+activateY, parents[_i]);
  if (obj_interact != noone) break;
}
if position_meeting(x+activateX,y+activateY,obj_interact) and
   !instance_exists(oInteractableMarker) and
   !instance_exists(oInteractMenu)
{
    instance_create_layer(x,y,"Textbox",oInteractableMarker)
}
The problem I'm having is that, while it works, it works about 80% of the time even with the bounding box completely pressed up against the NPC. I believe the issue is that when you use lengthdir you end up using the origin point of the player instead of the bounding box so you need to be more accurate when aiming your character at an NPC. That's my theory anyway. Is anyone aware of a way to use the bounding box instead of the origin point to activate the NPC? Some function I'm not aware of maybe?
 
I currently have this snippet of code that creates a marker over the player's head when they walk up to an interactable object or npc:

//Object Interactable Marker
var activateX = lengthdir_x(10, direction);
var activateY = lengthdir_y(10, direction);
var parents = [ObjectParent, NPCParent];
for (var _i = 0; _i < array_length(parents); ++_i)
{
obj_interact = instance_position(x+activateX, y+activateY, parents[_i]);
if (obj_interact != noone) break;
}
if position_meeting(x+activateX,y+activateY,obj_interact) and
!instance_exists(oInteractableMarker) and
!instance_exists(oInteractMenu)
{
instance_create_layer(x,y,"Textbox",oInteractableMarker)
}


The problem I'm having is that, while it works, it works about 80% of the time even with the bounding box completely pressed up against the NPC. I believe the issue is that when you use lengthdir you end up using the origin point of the player instead of the bounding box so you need to be more accurate when aiming your character at an NPC. That's my theory anyway. Is anyone aware of a way to use the bounding box instead of the origin point to activate the NPC? Some function I'm not aware of maybe?
If I'm understanding this correctly: You are only wanting to know if the player is in contact with an NPC? I think there is a simpler way to achieve this, though I am only going off of the top of my head at the moment.

The first thing that springs to mind, is that you are working through an array of instances, but the position you are testing against all of them never changes:

GML:
var activateX = lengthdir_x(10, direction);
var activateY = lengthdir_y(10, direction);
var parents = [ObjectParent, NPCParent];
for (var _i = 0; _i < array_length(parents); ++_i)
{
obj_interact = instance_position(x+activateX, y+activateY, parents[_i]);
where you have "x + activateX" and "y + activateY" it is going to be static throughout the FOR loop, as it will be the 'x' and 'y' value of the instance calling the code block. 'direction' is another variable that applies to the instance calling it, so unless you have the wrong scope here, the 'x' and 'y' part is unchanging throughout

GML:
var activateX = x + lengthdir_x(10, direction);
var activateY = y + lengthdir_y(10, direction);
var parents = [ObjectParent, NPCParent];
for (var _i = 0; _i < array_length(parents); ++_i)
{
obj_interact = instance_position(activateX, activateY, parents[_i]);
would be a more optimized way to use the values, since they are unchanging within the loop. I have simply put it in two places outside of the loop, so the maths (which isn't necessarily that cheap of a computation that you want to be needlessly repeating it) is only done once, and then the end point can be called as a set position to check.

The second point that occurs to me, is this: You have an array of ObjectParent + NPCParent. Can those two parents not share another parent? Then you change looping through the array, to using a 'WITH' code block that loops through both by virtue of them being children of this final parent.

GML:
var activateX = lengthdir_x(10, direction);
var activateY = lengthdir_y(10, direction);

with (final_parent) // whatever you end up calling this parent object, that encompasses the parents of those you want to check
{
if instance_position(activateX, activateY, other) // 'activateX / Y' are local variables that can be passed through to the 'WITH' instances
{
// Do whatever to itself, as it knows it has collision at the predicted point.
// Though am unsure if instance_position is the correct one to use. Because the 'WITH' instance is the one that's active, and testing for collision
// against the player object (which is 'other', since it is the instance calling those in the 'WITH'), you don't need to return its id.
// Any response is generated from within, and can be moderated by further checks for the current instances object index if you
// want to have different responses etc. So only the most basic of checks is required, due to how this is arranged.
}
}
I should add that I don't have the manual in front of me, so am unsure which is the best one for you to use. I can't recall how each one is applied to the checking instance and those it checks, or how the 'WITH' will change the required scope.

My final point: Are you actually using this for collision purposes? That you have lengthdir_x / y etc shows it is predicted movement, but there is nothing there to say it is affecting the players movement to that actual predicted point. If your NPCs and objects are static, and this code has nothing to do with movement collisions, then you could just have it in the end step of the player (by which point it will have moved to its final point in the step) and do the most basic check, which I think is place_free (?) or maybe position_meeting.

You basically don't need any that return an id, or any that move the instances in question into "future" spots, because (a) the player has already moved into that spot as we check in the end step, and (b) the other instances to check are static (if that is indeed the case) so again, anything that moves them to a predicted position is a waste of time. Essentially your check could be as simple as this:

End step player object
GML:
with (final_parent) // whatever you end up calling this parent object, that encompasses the parents of those you want to check
{
if position_meeting(x, y, other) // the current instance in the 'WITH' loop uses its own x / y position to see if it collides with the player
{
// Do whatever to itself, as it knows it has collision at the predicted point. Or it can apply effects to the player, by using "other.[whatever variable] = whatever value",
// or set the value of variables that are local within 'other's code block
}
}
OR: combine both the collision checking for movement, and the check for those collisions that trigger other responses. Since it would seem you're doing this check twice, when it's the same check regardless of it being movement or other responses.

Step event player (mid step)

GML:
var is_x = x;
var is_y = y;
var activateX = is_x + lengthdir_x(speed, direction);
var activateY = is_y + lengthdir_y(speed, direction);

x = activateX; // move player to predicted position
y = activateY; // ditto

with (final_parent) // whatever you end up calling this parent object, that encompasses the parents of those you want to check
{
if position_meeting(x, y, other) // the current instance in the 'WITH' loop uses its own x / y position to see if it collides with the player
{
if object_index == NPC.object_index
//  the current instance in the 'WITH' loop uses its own object index to see if its an NPC (it might be better to check if the instance is a child of NPCParent, but I don't recall the
// function for that)
{
// do whatever
}
// Do whatever to itself, or to  the player: let it know there was a collision, and what it collided with etc. The player instance can then respond accordingly
other.x = is_x; // is collision, moves back player to before "projected" position
other.y = is_y;
other.speed = 0;
break; // there was collision, and the player has been moved out of collision, so don't need further checks. This will exit the 'WITH' loop, and avoid un-necessary steps.
}
}
In this version, you have the predicted movement, and can check for collision / place the player instance accordingly. And at the same time, do your checks for any interactions.

Sorry for the massive wall of text, and hopefully some of this will solve your problem or be helpful :)
 
Last edited:

BQubed

Member
Hey, thanks for all of this. I took your advice about the parent for the other two parents (I honestly have no idea why I didn't do that) but the rest wouldn't work for my game. However I solved the issue by simply replacing all instance_position with instance_place. Thanks for the effort though. I appreciate it and there's some good stuff there for reference.
 
Top