Objects can communicate with each other just fine by updating each other's data, in theory. It is a much better system, however, to allow for event-driven logic to guide programming in addition to general-purpose procedural programming. A number of messages can be initiated, either by sprite collisions or other objects, to directly affect the course of objects.
The object that sends the message fires off information to one or more other objects. The program flow of the sender is unaffected. If the sender used an "order" command to send the message, the sender just continues to execute more commands. If the sender and receiver are the same object, the object won't jump to the message label specified.
The object that receives the message jumps to the appropriate
message-related label. If the receiver does not have the correct label, the
message is ignored. If the receiver does have it, the resume
information is set to the previous object script location, and
hobj (if present) becomes a pointer to the sender.
When the receiver gets its turn to execute its object script, a variety of
things may occur. The receiver, which had jumped to the label location in
the script, may choose to continue at this new script location or abort the
location. If continuation is picked, the resume information is set to
invalid at the end of the object's turn. If not, the "resume" command is
executed. This statement tells the command interpreter to forget the
message and proceed where it left off before the message, reseting the
resume information in the process.
The advantage of this system is that an object can test the message
for significance, usually by checking the object type that generated the
message. If it is deemed insignificant, the message can be ignored even
after it is generated.
A message from one object to another accomplishes only one
type of interaction: a two-object interaction. Binary communication systems
obviously fall short of three-object, four-object, or more-object
interactions. If two or more objects try to send a message to a destination
object at the same time, there can only be one piece of "origin" information
deposited in hobj at the time of the message.
A few options are open to solve a problem like this. One way to do it is to
synchronize objects so that tertiary interactions never occur. Seeing
how difficult this is, it is also a solution to log every sender of
a message with the idea of processing information from all objects that
tried to communicate with the receiver. This requires a lot of memory and
time, unfortunately, plus the added uncertainty of how literally every type
of tertiary reaction would pan out.
My solution is simple, even if not always satisfactory. The message control
block tells what order to evaluate the messages. For example, a hit message
is most likely to happen continuously if it is ignored for just the first
1/60th of a second the collision occurs. Other messages, such as triggered,
will happen only a single time and cause a big disturbance if they are
ignored. The most significant message evaluates first, the next significant
message evaluates next, all the way down to the least significant one, which
has the least precedence. Some messages will indeed be ignored, but their
relative importance determines the physics of the game.
Tertiary interactions can be important when dealing with games that involve
things such as rapid-fire damage, where it makes a significant difference
how much damage an object takes whether or not multiple hits per frame cause
a summed amount of damage. Nibbler is not a game that insists on such
"summing" demands; it is geared toward binary interactions and discrete
puzzle solutions.
To order an object to jump to a message label, write this:
order anobjptr, alabel;
Object pointers are self-explanatory, but "alabel" must be declared in the
inter-object message control block. It is not possible to send any "custom"
message label to the receiving object. For that matter, it is not really
necessary--the same object pointer may be used to set a variable in the
receiving object that can be interpreted as additional communication
information. For example, a snake can find out what hit it instantly by
inspecting its "deathmethod" variable, which is often set by objects that
are coded to use specific pointers to index a snake object's local variables.
Any label other than a "hit" label (which can also be sent using an order
command) will trump a "hit" label. In other words, two messages sent to
the same object, one "hit", one not, will result in the non-"hit" label
being transferred.
The "hit" label is the target label whenever a sprite collision
occurs. A "move", "moveoff", or "moveobj" command by either the sender
or the receiver generates the message in both objects, provided the label
is available to jump to. A change of sprite or the creation of an object
will not initiate a "hit" label.
Collision logic works by the following steps:
1) Check the two objects' block flags.
2) If one is clear, forget it. If both are set, proceed.
3) Check the rectangular boundaries of the sprites, doing a subtraction
on the center points and the sprite dimensions.
4) If the sprites are outside of each other, forget it. If touching each
other (overlapping, even by the slightest pixel), proceed.
5) Consult the priority table. Read the current object as the row and
the destination object as the column.
6) Get a numerical value. If another object has generated a "hit" message,
compare the priority table entry of that object to the one that was just
checked. The greatest numerical value in the priority table is the object
whose message "wins."
In some ways, the priority table does act on tertiary interactions. However,
it still obeys my "most important message takes all" algorithm rather than a
summing data process algorithm. Priority table entries cannot be changed
on the fly, but must be preset when it comes time to compile the script.
Somewhere in the object scripts is a directive #ptable "file",
which defines the priority table in text format. The text table is actually
a spreadsheet that is saved in an easily compiled format. It took a LONG
TIME to create the priority table. This is one tedium of creating a
game with so many interacting physics--the physics have to be divided into
various levels of significance.
Each table entry has two possible attributes: a number from zero to 63.
The number defines the precedence of the object-object message
interaction, the higher numbers being the more significant.
All these values are weighed relative to each other on the same row--they
don't have actual significance on a larger scale. A value of zero says
that particular object-object interaction will be ignored, which is
useful for cutting down on excessive object recognition logic.
The #ptable directive must be placed strategically. All objects
capable of sprite collision logic must appear after the
directive. All objects that will not use the logic (have block flag=clear
always) should appear before the directive. The same order and number
for the priority table and the "hitscannable" objects must be observed.
Objects still have to interpret numbers and run routines; labels are just one way to pass along information. Labels are more commonly used for looping and general controls in the confines of a single object script. An object usually declares various local variables to be used as counters and fields to be updated for other objects and sometimes by other objects. Ultimately, the programmer must know how the objects will interface with each other before deciding which object will get what label at what time.