More "Pick" Conditions for Object Selection

So, I’m not 100% sure on this, but my minimap example works by checking collisions on a set of created objects, and those objects always end up with the same object # in the debugger every time it is launched. I’m not sure this is pure luck or if objects are always going to test in the same order, or what.

So this presents itself in numerous different ways, but I THINK it is always collision focused. Here’s a few examples I remember from the discord:

  • You are making a tetris-like game/block like game. All of your different piece objects are in a “tetromino” group. You want to set up an event that is “Separate objects” between two different members of the group, and only want to force the one that is already moving to be separated. In other engines, the one that is moving is the one treated as “initiating” the collision, so it is the first in the object list. You’d pick the “first” object and move only that one away, or if you wanted to move the “stationary” object you’d pick the second/third/last/etc object. Even with object IDs today, you cannot effectively target the different objects.
  • Similar to the above, you’re making a jigsaw puzzle game. All of your events require the puzzle pieces to be in a group, but when you drop a puzzle piece you want the piece dropped to be moved/snapped to the correct position (separated from the existing jigsaw piece) and the stationary piece to remain stationary.

I found that a lot of engines have this same type of issue, but have this pick nth behavior as a way to solve it. I feel like it could be important to clear up the above type of confusion:

https://manual.yoyogames.com/#t=GameMaker_Language/GML_Reference/Asset_Management/Instances/instance_position_list.htm&rhsearch=pick%20instance&rhhlterm=pick%20instance

With all of the above in mind, I do also think there are benefits of something that says “Pick Nth object” as a condition in general, even ignoring just collisions, as it would let you pick whatever order of object meets the conditions in the same box. I would think this would just work based off whatever order they show up as in the debugger, since that does truly seem static for me:

With the Multiplayer Platformer with gamepads example, the first coin on the lower left is always #40 for me, no matter how many times I restart, or even if I create a whole new example project.


1 Like

So this presents itself in numerous different ways, but I THINK it is always collision focused. Here’s a few examples I remember from the discord:

  • You are making a tetris-like game/block like game. All of your different piece objects are in a “tetromino” group. You want to set up an event that is “Separate objects” between two different members of the group, and only want to force the one that is already moving to be separated. In other engines, the one that is moving is the one treated as “initiating” the collision, so it is the first in the object list. You’d pick the “first” object and move only that one away, or if you wanted to move the “stationary” object you’d pick the second/third/last/etc object. Even with object IDs today, you cannot effectively target the different objects.
  • Similar to the above, you’re making a jigsaw puzzle game. All of your events require the puzzle pieces to be in a group, but when you drop a puzzle piece you want the piece dropped to be moved/snapped to the correct position (separated from the existing jigsaw piece) and the stationary piece to remain stationary.

For Zuma-like and Bust-a-Move-like examples, I used 2 objects:

  • a thrown bubble
  • board bubbles

When the thrown bubble is added to the board, it’s replaced by a board bubble. I think it allows clear events and I wouldn’t do it otherwise even if I were using another programming language.

With all of the above in mind, I do also think there are benefits of something that says “Pick Nth object” as a condition in general, even ignoring just collisions, as it would let you pick whatever order of object meets the conditions in the same box. I would think this would just work based off whatever order they show up as in the debugger, since that does truly seem static for me:

With the Multiplayer Platformer with gamepads example, the first coin on the lower left is always #40 for me, no matter how many times I restart, or even if I create a whole new example project.

The instance order probably follows this logic:

  • It’s the order they were added in the editor which is impossible to change in the editor without removing every instance and adding them one by one (and this index is should stay hidden).
  • When instances are filtered by conditions, the indexes are changing to feel the wholes.
  • None of the 2 previous points are part of any contract so they can change when needed for features or optimizations.

This is why I agree with reservations expressed by Florian and I think that relying on this index is against good practices and should not be used in extensions.

For context here, order also seems to stay the same if created via events with repeats, so I think the logic is still sound. Maybe due to potential risks, removing Nth may make sense, but first v last seems like something that would make sense to have an option for, if nothing else.

Also, your multi-object solution solves that specific scenario, but doesn’t solve collision between same instances or many other use cases. The solution also seems pretty uncommon compared to the other engines out on the market from a logic perspective, by which I mean GDevelop does not have a native function for this compared to:

While I recognize the method used in the current community extension may not be ideal, rendering it inoperable reduces not just ease of use for game developers, it reduces functionality of GDevelop.

I’m not saying that the current method must be maintained, but some method needs to be maintained and should not be a far future roadmap item. Whether that’s a different iteration of the current concept from the extension, adding some form of “This” and “Other” instance concept, adding a publicly visible UID, or something else entirely is fine, but some method that matches what is available on the engine market elsewhere is important.

Edit:
Interestingly, GMS 2 actually maintains a list of objects within collision altogether, which you can also pull from directly. collision_line_list

I don’t understand how “first” and “last” can be useful for collision when the same object is used in both parameters because what make them different from each other?

It doesn’t help for your previous example, does it?

Similar to the above, you’re making a jigsaw puzzle game. All of your events require the puzzle pieces to be in a group, but when you drop a puzzle piece you want the piece dropped to be moved/snapped to the correct position (separated from the existing jigsaw piece) and the stationary piece to remain stationary.

From what I can tell, in other engines the “first” is the first item in the collision list, the last is the last item. How that’s defined is different per engine. For this specific use case, what you would want to happen is to pick whichever object was first added to the currently selected object list.

I know that the current implementation isn’t exactly that (I think it’s picking the first created instance?), but it is still more useful than a Pick Random, or in many cases pick based off ZOrder.

What I meant is that is that both instances are from the same object so why would one be treated differently than the other based on “last” or “first”?

For instance, if we suppose that only one collision happens at the same time, I could want to:

  • check collision between instances of the same object
  • pick the instances that have not the greatest speed to explode them

But, I think the condition will always be something specific about the instances state and never about “first” and “last”.
Can you explain what “first” and “last” means with a practical case?

Yep, here are some of the examples I’ve seen when digging up the above links:
Matching the current method from the extension (First created/last created):

  1. Having a mirrored instance player character that does the opposite of the player’s inputs while the player is moving around normally. The instance gets created/destroyed depending on the scenario, but by having the “last instance” condition/logic they were able to keep all of their event logic for damage/attacks/etc and just invert the movement controls if it is (or is not) the last instance.
  2. A game where they were playing a duck being followed by a bunch of little ducklings. Every egg you found would create another duckling following you, and if you took damage the last created duckling would get destroyed.

Could you do some of these by manually creating IDs and then looping through them all to find the highest ID? Sure, but that also would be much less efficient and much more variables to maintain manually. Maybe a better name would be “Pick first created instance” and “Pick last created instance” to match the current scenario. Same with Pick Nth created instance.

Matching the other scenario I mentioned above (First to the current list/last to the current list) from some of the discussion I saw while digging around the GMS2 stuff, it was mostly the collision scenario I had mentioned before.

It looks like a boolean variable “IsCloned” would allow the same thing and:

  • It would be easier to understand the events
  • It would also work for with several players or NPC using a group and an object link.

This can be done with the “object stack” behavior:
https://wiki.gdevelop.io/gdevelop5/extensions/object-stack

It allows to queue objects. It needs a bit of setup because the queue needs an object to hold the stacks but it has many features:

  • Instances can be dispatched in several stacks
  • Instances in a stack can comes from a group and you still have full control over the instance order
  • Stacks can contains stacks (instances from a stack can have the behavior too and contains other instances)
  • Stacks can be split and merged
  • Instances can be inserted or removed at a given index
  • Stacks can be shuffled
  • Instances from a range of indexes can be picked

I did a Zuma-like and a Klondike with this extension. They have complicated mechanics that couldn’t be built just relying on creation order.

I don’t understand the use-cases about collision. What you said about accessing each group of instances in collision is interesting but I guess it was not your point.
Could you do a screenshot of some events? I think I’ll understand better this way.

So I follow what you’re saying on these, but I think we might be speaking to slightly different points. This isn’t so much that some of these things are impossible to do today, it’s just much simpler with more picking options.

The above doesn’t seem to be true from what I can tell running through it. Having an isCloned boolean is another event action and condition you have to track, manage, and flip as needed. Vs a single condition of “Pick last (created) of Blah” in the 4 movement events for the mirrored object.

To reiterate, it’s not that these things can’t be done without this pick functionality, it’s that the level of effort and user experience is much simpler via something that you can use to just say “pick the last item”.

The object stack extension is very powerful and useful. but it contains much more setup, (behind the scenes) event overhead, and manual event maintenance than a single “pick last” or “pick first” would be for the duckling scenario above.

Overall the additional picking options help users articulate and implement the base concepts they’re trying to do much faster. If they need to get into more involved scenarios, then I agree that the high level picking conditions/actions wouldn’t apply. That same thing could be said for stuff like “pick random” or “pick all”, however.

Pick First/Last is more of a user experience/quality of life need, even though there are other more complex/alternate ways to accomplish them technically.

Pick “nth” might be an actual technical need (the ability to pick a specific instance out of a list of them) that you cannot easily accomodate today (without manually IDing each instance). Surfacing the “Creation order” (Or UID, if that increments?) as an attribute of object instances may accommodate this, but that’s probably a deeper conversation than the above.

There are no antipatterns from the above (the concepts, not necessarily how it’s enabled in this extension), the creation order of objects are already something that exists somewhere in the backend of the engine, the UIDs of the objects are are already somewhere in the backend of the engine, etc. The need is more about exposing those in a user friendly way for folks that need access to that more immediately.

Other engines accomodating this in the ways mentioned above show that it isn’t an isolated need, either.

The difference I see is that

  • the boolean must be set after creating the clone (but variables are a key concept of GDevelop)
  • and the “pick last” (which is a new concept to learn) is replaced by a variable condition
  • it also avoids a comment like “pick the cloned instance because it was created last” because the variable name speak for itself

So UX side, I think variables are better for this case at least.

Allowing concise events is important but there is also to consider:

  • readability, saving time writing a code is often not a good idea if it’s hard to understand it later
  • flexibility, conditions and actions that can allow to build from simple to advance logic is less concept to learn

For these 3 aspects, I need use-cases to have an idea.

The extension was relying on the picking index. The picking index can change according to algorithms used to filter instances (R-trees completely change instances order for instance).
Relying on the creation order (the UID) would indeed make a lot more sense. But, use-cases must prove its usefulness. Because for UX, less is better.

Features don’t necessarily come from needs. Maybe it was added because it’s in every engines and easy to implement. I want to make sure it can be better than any existing solution at least for a common use-case.

I think the UX side is fixed if the condition is updated to “Pick lastest created instance (object name)” or something similar.

Then you have a single condition to accommodate the mirrored player scenario, and don’t need to add comments to explain comment. Less variables to maintain in this method, too.

I think the ducklings scenario actually speaks to flexibility and legibility.
As an example, it really is just a single event with conditions of:

  • PlayerDuck is in collision with DamagingObject
  • Pick Last Created instance of Ducklings
  • Trigger once (or a timer, or a knockback variable, etc)

And action of:

  • Delete last duckling

That’s a pretty clean event, makes sense at a glance, and doesn’t require comments. No setup required for tracking the ducklings, etc.

As far as exposing the UID in some way (even if it is a translation from the backend UID to just a truncated “ID” parameter on objects that’s just an incrementing number), that eliminates a lot of manual variable work if you’re applying IDs on creation, or a For Each event at the beginning of the scene if you’re applying IDs after loading data, etc.

You do have to comment because the reader won’t guess that the latest created instance is the cloned one. So avoiding only one action to end-up with unclear events is not a win in my opinion.

How do you make the Ducklings follow the PlayerDuck? It may need a way to know their exact order, not only the latest.
I guess, It could be done by keeping the same distance from the player each frame. This way we don’t need to know the order, but it might give strange results (and it doesn’t seems like an obvious solution).

It think I would use an extension to keep an history of the trail and place them at a given distance according to their index in the object stack and separate them in case the trail is looping, but without using the object stack extension I don’t see a good solution.

Not sure I follow on this one. The person creating the game can comment this however they please? It’s the ease of events that makes this a better solution. I’m not looking at this as a one-size fits all for games and all templates (to be clear, there isn’t a one-size fits all solution for this today anyway)

Sadly I don’t have an idea how they did this part in the original discussion as I don’t have access to the project being discussed in the Construct thread. For this example, it was specifically just speaking to the deletion of the last created instance.

Cleanest would probably be if we had Pick Nth and an expression to access the instance creation order. Have a behavior on the ducklings, have it Pick Nth of (Object.Nth-1), and then move it towards that instance.

If we had access to UIDs. I’d probably track the last created UID, upon creation of a new duckling add that UID to an instance variable, then a for each where the instance follows the position of the instance with the UID in the variable.

I probably made the stack object extension sound more complicated than it really is.
The events would only be something like this:

or alternatively this:

(note that it is cards wording which sounds odd for ducks but I think cards speak to anyone)

and it would be very easy to make it works for several ducks just by adding a “for each” loop on Duck.

I understand about the complexities (or not) of Object stack, but the underlying overhead of the code seems like it would be less with Pick last/first created (and potentially pick Nth or at least surfacing the UID) because these quantities already exist in the engine?

Less items being stored beyond what already exists. I don’t think they always replace one another, but I do believe the Pick options are important to have available from a perspective of flexibility for the user and potential avenues of building out events.

You would still need to loop on Duckling to set their position, so only the “add” action could be avoided at the price of not being able to handle more than 1 duck.

I don’t see the benefits. Not on this example either at least. Do you have any other examples in mind?

Sure, from some googling:

  • An infinite runner game where there is a desire to spawn powerups on the newest platform created but only after the last pickup has been grabbed. Can’t spawn the powerup at the time of the platform creation in this use case, and pick last will enable this to work without having to track object IDs as a separate variable. (On collision with powerup + Pick Last Created Platform > Spawn Powerup at Platform.Y+5 or something similar)
  • A “speed challenge” game or “racing” game with randomly generated maps made up of different instances of a “map piece” that has different orientations and collisions depending on which animation frame is randomly selected upon spawning, outside of the first and final piece which count as “Start” and finish. Being able to pick last was the desired scenario as it made it much easier to determine if you’ve hit the finish line (or if going in reverse, pick first) than tracking multiple variables.

Overall, I think even just exposing the instance UIDs via expressions (Object.UID) and some conditions (Pick newest UID, pick oldest UID, pick UID) would help accommodate most of these scenarios, and likely other scenarios.

Sorry for the delay, I missed the notification.

If I were to do this, I would check the number of powerups in the scene when creating a new platform and create a new one only if there is none. The powerup wouldn’t be created on the same platform as in your solution, but I guess it doesn’t matter much and I think the logic is easier to follow this way. Also, if the user wants to create it on a special platform (higher than usual) or at a specific location in a map piece, the “pick last” wouldn’t work.

The finish line will probably be at a specific location of the map piece. Wouldn’t it make more sense to use an object for it and leave it only when creating the last piece?

I am not getting notifications for this thread now despite being set to watching, so it’s definitely being weird.

I don’t know that this achieves the same desired outcome, and I would say it doesn’t seem simpler than adding a subevent that just says “pick last created (platform) > create X at Platform.X+abc or Y()”

Maybe, but then you have to account for more objects and potentially add additional conditions to account for that rather than an event that is an inverted “Pick last” for the start, and a pick last event for the finish.

For the 2 cases, both your solution and mine are about the same number of events. But, I think that the logic is easier to follow if it’s done at the creation rather than using a “last created” condition to find back the instance and do the logic then.

It’s also probably more efficient for the CPU because running the “last created” condition every frame is not free. For the finish line, it’s better to have an object marker with only one instances rather than picking the last of many because the generated code will build a list with all the instances of the object to give to conditions.

As the previous ones, these 2 cases don’t convince me that a “last created” condition would make users lives better.