MiniTute : how to write external modules for bge

Code may not be 100% accurate, it is for example only
I’m tired from writing several external python scripts to make future projects easier, and in the process I learned some things.

  1. Blender shares data, a lot of data.

If you have ever tried to change the textures on objects that use the same material, or change vertex colors on two copies of the same object, you may have noticed that both objects have the same changes (even if you just did it for one). When using addObject, each object has separate object data, but share mesh and material data, this makes it easy to change the textures on a group of objects with the same material (like trees), but makes some tasks a bit more daunting. This allows blender to reduce overhead, which is good, but you should be careful of how you write your scripts.

  1. using the four lines, or any blender-specific modules, at the top of an external script is a bad idea.
    (importing any modules at the top of external scripts is probably a bad idea, yet I do it all the time)

The four lines are as follows, and are usually the first four lines in any bge script.


import bge
logic = bge.logic
cont = logic.getCurrentController()
own = cont.owner

They import the bge and allow quick access to the logic sub-module, current controller the script is on, and the object the script is on. From this you have a large degree of access to manipulate the game engine.

Four whole lines, and some actions take as many or more lines to accomplish. So the idea to write external modules is a good one, but may take some planning.
Let’s look at an external module.


import bge
logic = bge.logic
cont = logic.getCurrentController()
own = cont.owner
def colorChange(color):
    own.color = color

Not very impressive, but it will do, it is also wrong. If you put this in an external .py file, import it, and use the module (assuming you passed it a legitimate color); it will work. Then how is it wrong? I believed that the internal script would be evaluated (/run) for each object independently so that each object has different data pointers even if they are using the same script, or are copies of the same object. When you use an external script any lines not inside a function/module might be held as (scriptName.variablename), thus sharing data, and actions across objects. The colorChange module will work, but subsequent objects will have the same colors (either the first or the last object to use the script will set the color for everyone). I noticed this when I was asking for different sensors with objects using the same modules, the first object found it’s sensor, but not the other sensor.

Here is a little better script:



def colorChange(color):
    import bge
    logic = bge.logic
    cont = logic.getCurrentController()
    own = cont.owner
    own.color = color

This will work, but is superfluous. It is better to just pass the color, and the owner like this:


def colorChange(own, color):
    own.color = color

I don’t think that importing modules like “random” at the top of an external script would hurt anything, but it is better to import the modules you need in the modules/functions that are going to use them. Like this:


def colorChange(own):
    import random
    r = random.randrange(1,100)/100
    g = random.randrange(1,100)/100
    b = random.randrange(1,100)/100
    own.color = (r,g,b,1.0)

tip : object color channels should be between 0.0 and 1.0, but can be any real number, this over saturates (or oversouls) the color channels, try it for some neat effects and colors beyond imagining.

  1. You can’t directly pass anything using the python module controller.

The python controller has an option to just use an external module, but it has to be in the format of (script.module), no parenthesis or braces, so let’s look at a good example script to use in the python module controller.



def colorChange():
    import bge
    logic = bge.logic
    cont = logic.getCurrentController()
    own = cont.owner
    color = (own["red"], own["green"], own["blue"], 1.0)
    own.color = color

As you can see, we are passing the colors via properties, but you can just randomly generate them, or use a list.
If this was in a file named color.py the text in the python module controller would be (color.colorChange).
As you noticed, the four lines are in the module/function and can be used on separate objects. You could probably knock the four lines to two if you just need the owner.

#Tip : the string property can hold more than just strings, I’ve placed lists, dictionaries, vertex arrays, and material textures into them from python. I’m not sure if you can just write a list into them, but if it doesn’t work there is always eval().

This is just a bit of what I have learned through trial and error, and I hope it helps someone avoid these mistakes and write code a bit more efficiently. Code re-writes and modifications are no fun.

Thanks agoose77, but the hack to pass values via the python module is a bit complicated.It is easier to use properties, but the hack does have some interesting applications. I’m just not that good with classes.

I agree with agoose77, imports should not be declared within a function. They are static and should be declared at the top of the module. (The result of getCurrentController() is not static and should therefore not appear at module level code as you already mentioned).

You can find some more style guide regarding imports in PEP#008

Hints:
the module controller accepts: [<package>.]<module>.<function>
the function is called with a single argument. The BGE provides the current controller.


def myFunction(controller):
   owner = controller.owner

So using getCurrentController in my external scripts was the problem? So all I’d have to pass is the current controller.

so like this:


import bge

def someFun():
    logic = bge.logic
    cont = logic.getCurrentController()
    own = cont.owner
    color = (1, .5, 0, 1)
    own.color = color

No, it is not a problem. It would be a problem in a module at module level (indentation 0). You can grab the current controller from context or from the argument. It is up to your taste which one to use.

I used to use the argument first, but later I switched to the context. The reason is simply because it reduces the amount of parameters to pass to the various functions. They all have access to the context, so they can grab it from there if needed.

I suggest to be careful with the subject “script”, “module” and “external script”.
I suggest you call it “script” when it is Python code meant to be executed by the Python controller in Script mode.
I suggest you call it “module” when it is Python code meant to be executed by the Python controller in Module mode or imported via “import” statement.

“External” suggests it is data outside of the current .blend. As you can run Python code as script from an “internal” text block only, the the term “external script” makes not much sense. Modules on the other hand can be internal (text blocks) or external (.py files). If they are external modules they need to be in the Python search path.

Thanks, that cleared some things up. I’ve been writing some external py files that have different modules in them to automate script writing. My brief, but successful, use of unity taught me some things, but I noticed that certain things could be done in one line that took blender several. I want to reduce the length of internal scripts and have some stand-alone modules for people that don’t know much about python and want a quick player setup. They are unfinished, but are on google code so I can update them through git.