Sometimes the scope of a newly created object is inexplicable. When an object is created, depending on the event nesting, that object may not be referenced properly.
In the RND test project one of the most important systems I developed was the interaction trigger system.
This system is simply a method of binding an action (ie “Interact”) and a specifier, and then wrapped to make a ‘broadcast signal’.
This broadcast signal is then sent. Because the broadcast signal can optionally contain a ‘target’, only those matching the target description can be made to respond to the signal.
The importance of a system like this is the ability to make level-specific scripts. I’ll give a test case from the RND project.
- In Tiled, a marker is created with a name. This is the trigger name, which can be anything as long as it can be uniquely identified.
- In C2, a ‘On GridMove reach target’ action is bound so that it wraps the reaching of the tile with the trigger name of the marker it has reached.
- On reach target, the trigger is sent to a BroadcastTrigger function, which accepts the trigger name, and the intended target of the trigger, if any. The target is comma-delimited, so multiple targets can be specified.
- The BroadcastTrigger function looks at the targets, tokenises them, and then applies the ‘receivedtrigger’ variable of each of the instances that are able to accept triggers. It applies them only to the targets specified, or all instances if no target was specified.
- Note that a family called f_trigger_receiver was made and the receivedtrigger variable is called ‘f_receivedtrigger’ in order that BroadcastTrigger can efficiently send it to those concerned.
- In the level-specific script, the intended target is waiting for its specific f_receivedtrigger to change. BroadcastTrigger would have changed it.
- When it does, it fires off the events there.
In addition to the trigger, level-specific behaviours are specified, and can override the default AI of any object. This is why this is important, because the scripting is done in a separate event sheet (ie logic) and not predefined in the main logic.
Now, other actions are bound, as needed, to the BroadcastTrigger. For example, in the RND project, the On reach target trigger condition was the first one I implemented. But quickly afterwards, it was easy enough to bind the TalkToNPC function, or the InteractWithNPC function to the broadcast.
Of course, the trigger name changed. In the TalkToNPC trigger, the trigger name was
"talk "&cmover.name in which the ‘talk’ keyword was appended by the actual variable name of the NPC that was talked to. The name of the NPC talked to was embedded in the signal and no target was specified because the logic was that either the player or the game world was the receiver. But, it is also possible, or even more beneficial if indeed the recipient of the ‘talk’ action was put in to the trigger target, as I did with the next implementation.
I implemented an ‘InteractWithNPC’ action in the same way, but included the recipient of the ‘interact’ action as the target. In the level script it was intended to add to the accomps to keep track who had been interacted with.
The BroadcastTrigger concept is just a concept, but seems to be a very flexible one, as I am using it currently to design a generic kind of interaction behaviour between a single ‘Useitem’ action to a host of different possible objects, each with their varying results. It’s this reason why BroadcastTrigger is useful, because behaviours are defined in the event sheet, and can be contextual as well as part of the main logic.
The basic problem is that when moving diagonally, the edges are ignored because edges only exist at the sides of the square, and not at the corners.
The solution to this was to query both the start tile (slg.PreTileUID) and the target tile (slg.TileUID) and from there, determine the GridMove/Board direction index that the current path was taking.
Using the principal direction (let’s assume it was a diagonal value of 6 (as demonstrated in the above image), the two other directions flanking the main one was also queried.
In the above image, the movement from the start tile is at direction 6. Direction 2 and 3 were derived from this by a lookup that defined two flanking direction for any given direction.
Then the target tile was also queried, but this time, the principal direction was the reverse of the start tile (computing for the reverse tile was made simpler by using a lookup, although a simple modulus would have done it, too). In other words, 4 is the direction. Using the same lookup, 0 and 1 were looked up as the flanking reverse directions.
Then a check is made against existing edges: are there any edges on the 2 and 3 directions of the start tile? If so, movement cost is BLOCKING. If not, then check the target tile’s 0 and 1 direction edges if they exist. And if they do block the movement.
There is a gotcha with using SLG movement and edges which relates to cache cost (a parameter in SLG movement).
The image above describes how a movement from tile [1,13] to [1,12] should yield a path that comes around. However if the cache cost parameter is set to Yes then it will refuse to yield a path at all.
Setting it to No will solve the issue.
Not sure what this is about, but noting it down for future reference.
In the RND test project, I ran into performance issues with ZSorter due to the high number of instances I was using. I’ve easily re-implemented the C2 ‘native’ action of z sorting and the performance is much better.
However there is a gotcha.
The native version, despite what seems to be a vague reference of layering behaviour in the manual (search for Sort Z order) moves instances which might have been in other layers to layer that had been sorted last. If the sorting is done per tick, there is no way to force a particular instance to another layer and expect that to render accordingly. (For example, I created a ghost clone over my main sprite. The ghost sits on a top layer, but is an instance of that sprite. Because the ghost belongs to the same sort family in which I base the sort, the ghost also gets sorted. And because the action moves the ghost into the same layer as the other instances, it loses its layer blending purpose.
The solution looks more like a hack: in cases like the above, where instances need to be separated, the sort reference variable (the variable that the action uses as a basis for a sort), should be overridden. In the case above, the ghost instance’s sort reference variable had an extremely large value so that the sort engine will always have to place it on top.