OK, time to give back to the community, at least a tiny bit. For what it’s worth, I’ve created the following webpage that demonstrates the “object dictionary” approach discussed in this thread as well as a couple other concepts: http://danaburns.home.attbi.com/blend/multidemo.html
Text of the page follows:
<h3>What this is</h3>
I have a blender file that connects to a server and processes commands from it to: add, delete and change blender GameObjects. It is very simple, but can serve as a demonstration of the general use of: python in the game engine, using socket communication, and using the “object dictionary” approach discussed in this thread.
It is not a full multi-user demo, as the server only sends “scripted” commands at this point. A richer demo is in the works.
Current working commands from the server include:
add [objectName] [meshName] [x] [y] [z]
This adds an object to the scene at the specified coordinates, with the specified mesh.
It adds an entry to the object dictionary using objectName as the key
and that objects’ pythonController as the value.
removes the specified object using EndObject
pos [objectName] [x] [y] [z]
sets the position of the specified object
replace [objectName] [meshName]
closes the client socket. no more processing takes place.
<h3>Where to get it and how to run it</h3>
The demo blend file, along with it’s python files, and a simple server written in java, are at: My Blend Page in a file named MultiDemo.zip. Sorry for not being all-python, but the server isn’t very important here and I am more comfortable with java than python at this point.
INSTALL and RUN: unzip, unjar, javac, runs in one window. Or, follow along in the discussion below, using copy/paste with code snippets and make your logicbricks look like the pictures included. run blender in another window (to see print output) and use the P key to start. Let run until you see “exit” being processed.
<h3>Details of how this works:</h3>
layer 1 contains the “me” object (MO) and the “ADDER” object. Both are of type Empty: <img src=me.jpg>
MO: has an always-sensor attached so that it is executed in every game frame. Its’ job is to read commands from the socket and process them. More on this later. Since this socket-reading is all that it does, its’ object type is “Empty”. Here is the pythonController (PCTR) called me.py, but read further along before trying to understand it all.
ADDER: is used to add objects using the AddObjectActuator connected to it. The reason a separate object is used is partly for clarity (see “Where am I going with this” below).
ADDER contains the initialization code (reason below), which is done via an “initSensor”, which is an always sensor with the first button clicked off so it only happens once. The GameLogic object is used to hold all global variables that are initialized. Global variables include: the “object dictionary” (OD, see below), lastCommand (see “2-step process” below), the client socket connection and a “done” boolean. <a href=adder.py>Here is the adder.py PCTR</a>
The reason that ADDER contains the init code and not ME, is that ME has an always sensor attached and would need to test something like GameLogic.isInitYet during every game frame (cycle). ADDER only requires an initSensor, and is also on layer 1, so it is a good place for it.
The “object dictionary” (OD) is a python dictionary that holds references to the objects we create, so that we may edit and delete them later from the me.py script. Actually, the values in the dictionary are references to PCTRs attached to said objects, so we can get to the object (controller.getOwner()) AND the controllers’ sensors and actuators. The act of entering a key/value pair in the OD is called “registering”. These controllers register themselves when their registper.pt PCTR is called just after creation time. This is described in more detail in the “2-step” creation process below.
The “BASE” object is an object of type “Empty” that exists on layer 2. It is the object that gets created during the first part of the 2-step add process. All the logic bricks created on this object will be replicated for each object created, as well as everything else associated with this object. Most important are the initSensor which triggers the self-register PCTR “register.py” and the editting actuators attached to the PCTR. At this point, endact and replaceact are included, which do the EndObject and ReplaceMesh respectively. Later, when real multi-user is developed, we’ll most likely need some sensors for these “THEM” objects.
Note that the namespace for the new objects is maintained and dictated by the server, because only it knows what clients are connecting and what they are doing. The new name (newName) is stored in the global GameLogic during the first step (see “2-step” below) and retrieved by the self-registration script later.
The meshes that will be used in the demo are placed on layer 3 and given names that are “well known” to the server. If you are implementing anything based on this blender file, you need to add your meshes here, name them well and use those names in your protocol/server.
protocol, 2/3 step add process:
- Only one commandline from the server is processed during a game frame. This is both so that the graphics will be smoother, and because some commands take multiple "steps" to complete. By 'step", I mean that control must be returned to the gameEngine in order for it to update it's internal state, before additional work can be done to complete the processing of the command.
- When an "add" command is read from the socket, during the first step, the position of ADDER is set. If I "activate" the addActuator at this point, the object will appear first before the position is changed. The reasons for this require an analysis of the source code of the game engine, which I won't do here. In this case, since BASE is of type Empty, it doesn't matter, it's the replaceMesh that we have to wait to do. So instead, we go ahead and call addActiveActuator for BASE, and then the command is placed in a global "lastCommand" for processing during the next invocation of MO. This is the reason for the processing of lastCommand before socket processing in me.py.
- During the next cycle, addActiveActuator could be called, except that we do not have a reference to the PCTR for the new BASE yet, if it even exists (I think it does). Again, the reasons have to do with the internal gameEngine logic. Basically, the object and all it's logicbricks are created in one cycle, but the detection and action of the "initSensor" doesn't happen until the next cycle. So our 2-step process is actually a 3 step, since we skip again.
- In the third step, we could see from the OD that the new PCTR for the new object has run and registered itself. Now we can look it up from ME, find the replaceMeshActuator attached to it, set the meshName on that actuator, and activate it.
<h3>Where am I (we?) going with this, roughly in priority order:</h3>
I’d like to clean this up a bit and create a real tutorial so I can give something back to the community. Specifically, the naming of things could be better (e.g. register should be called base), me and ADDER “could” be combined (not sure this is good) etc…
fill out the protocol with stuff I know will work easily, e.g. “visible”, “orient”, “force”, etc… and allow for specifically named ADDER’s, which will be cloned from the base ADDER. This allows creating at named locations instead of XYZ’s.
exploration of multiple users
- I need to create a simple “world” in which to play, or borrow a ready-made one.
- “me” camera, motion controls for moving around, probably just “parent” the camera to ME and apply some simple keyboard sensors and motion actuators.
- server logic for controlling simplest MU game possible, probably just moving around looking at each other, which means position/motion updates. What frequency?
- me object will need to update the server with position information, and possibly sensor trigger “events” such as near, collide etc…
- if using simply setPosition to update THEM’s based on the protocol seems jerky, try updating motion stuff to smooth this out
- this is where the rubber meets the road and things get tricky! timing is tricky. what happens when clients collide? Because it’s across the network, things happen in different time-space continuums Did you collide with me, or did I collide with you? Is your motion more important than the motion of the object in my blender-client that represents you? That’s why we keep it REAL simple at first and maybe just have large “cushions” around each other to prevent any sort of weirdness, like client objects being tangled up forever.
- interleaving multi-step commands?
- compressed protocol
- 3-step to 2-step by finding the new controller from scene?
- LOD/visibility optimizations?
- I say “exploration” as I have my doubts about how far this can go, but it should be fun! But, I don’t think that limitations here will stop me from my real goal:
My intentions for this are for a MU game that does not depend on frequent updates from other clients. The server protocol is actually quite old (10 years) and based on telnet. This blender client will only be a simulation of what is going on on the server, and it doesn’t matter if things appear a bit different to each client, at least as far as the 3d stuff is concerned. The internal state of the server has no 3d info. I plan on creating a java proxy server that converts the existing protocol to my blender protocol.
Not sure I’ll ever need this, but realize that I could create multiple base type objects with different logicbricks. What can i change in python wrt logicbricks?