What is so hard about messaging?
Here are some good reasons you should use messages:
1. All multiplayer instructions from server to client are sent in the form of messages.
2. All objects in the game can respond to messages thus making it extremely powerful.
3. All game engine internals can be modified via messages.
4. Using messages allows you to make quick demos for your game.
5. Using messages allows you to put the control in the hands of the level creator.
6. Using messages allows the game engine to be coded separately from the game data.
Shakti hit on exactly what we are doing. In Windows each window or application has a message queue and a message process that Windows sends messages to. In fact every Windows app must have at least a rudimentary MsgProc function in order to work correctly.
So how does it work in Windows? Let's say you click a button in a window.
1. Windows determines what window the mouse is over based on mouse coords and z order of windows. It looks in the registered windows to retrieve the hWnd or handle and it also looks in the register area to find the MsgProc function pointer.
2. The message for a left mouse click is WM_LBUTTONDOWN. It sends this message to the window containing the button. The parent window then routes the message to it's child button which is another window. The child window (button) can now respond to the message or the parent can respond to it - it can be setup both ways.
3. All of this is done with 1 message function and 2 params. Each message has an associated wParam and lParam indicating certain parameters for each type of message ID. The SDK will tell you what those mean for each message Windows sends. Some newer messages use other constructs as well, but for the most part MS has stuck to its original design.
Ok this is very very powerful. First, Windows does not respond to the button click. It simply tells the windows, hey dummy someone clicked a button and the button is in your window. Respond to it if you want.
When a window needs painted, it simply sends a WM_PAINT message to the window needing painted and the window then responds. If the window does not respond it does not get painted, hence this is why programs that have crashed or stopped responding cease to be painted and/or updated.
This design is brilliant even though it did come from Microsoft.
Ok so let's look at this from a game perspective. We have a bunch of objects sitting out there that we want to move, manipulate, destroy, etc, etc. Well imagine each object as it's own window or even it's own process. So if we want to move an object we don't call a function to move it because that's hard-coding the process. Instead we send a message to the object telling it to move and giving it parameters on how to move or what to move towards, etc.
Now this move command will result in the object moving no matter how the message gets sent. You can send it from a script, from a multiplayer server, from the local game, or from anything you want. As long as the move message arrives at the object, the object will move. This means you create a base class for objects that can respond to most every message the message system can send it. This would be called default processing.
Let's say we want to vector bad guy A to player. Assume the player has an ID of 32767. Assume the bad guy has an ID of 1. In order to produce a vector to intercept the player we need to know several things.
1. The position of the player.
2. The position of the bad guy.
3. The length of the vector from the bad guy to the player.
The length can be computed, the bad guy position is local to the object class, and the player position can be sent from the message system since it knows anything with an ID of 32767 is the player.
So the message boils down to something like this:
If OBJ_MOVE_TO has a message ID of 100 then the command is this:
We set it up something like this:
So the code might look like this:
The MsgProc would look like this:
//Indicate we need to turn instead of snapping to vector
The ObjectContainer or ObjectManager would route the message. The MessageSystem would tell the Object container the message, ID of the object being acted on, and the ID of the object to vector to. The ObjectManager then would construct a Message structure consisting of the object to vector to and the message ID. It would then use the ID of the object being acted on that was sent from the Message system as an index into it's array of objects.
HRESULT CObject::MsgProc(Message MSG)
We have determined that for binary and trinary message operations dwParam1 will always be the object being acted on and dwParam2 will be the target object. For unary operations, dwParam1 is always the object being acted on. Trinary operations are not encountered very often in our setup but normally when they are, dwParam3 acts as a modifier for the action.
bool CObjectManager::SendMessage(Message MSG)
if (MSG.dwParam1>Objects.size()) return true;
//Reconstruct message for object
//Call object's MSG proc
A unary operation.
DESC: kills object #(ID)
BIN: 11h, imm32
A binary operation
DESC: Moves object #ID1 to waypoint list #ID2
BIN: 100h, imm32
A trinary operation
DESC: Moves object #ID1 to waypoint list #ID2 after time seconds have elapsed
BIN: 101h, imm32,imm32
I hope that explains it well enough.