[SOLVED] Subclassing KX_GameObject from scene not cont.

That… is the best way I think I can frame my question. Basically, I understand how subclassing works normally, you pass in the controller to the constructor, then mutate the owner to your subclass. However, for my project I will be needing to subclass a large number of objects, and I would rather do it all at load time in one script. So, passing in just cont will only get me the owner back.

I have tried a number of tricks, from passing in a reference to the object itself, to a list of objects to try and grab the one I want. The second method works internally (proven through assertions), but when I try to use the reference I discover the object remains the base KX_GameObject class.

Here is an example of the mutating method from my current setup:


def makeCustom(customList):    for x in range(0, len(customList)-1):
    old_object = customList[x]
    mutated = customGameObject(customList[x])
    
    assert(old_object is not mutated)
    assert(old_object.invalid)
    assert(mutated is customList[x])
    customList[x] = mutated

I’ll keep trying new stuff, but has anyone done this before?

Actually, I just solved this. I’m still curious if there is a more efficient way to do this, but here is what worked out.

First, set up the subclass as usual, my example being customGameObject. See https://docs.blender.org/api/blender_python_api_2_76_2/bge.types.KX_GameObject.html for how to do that. Define a mutation function as follows.


def makeCustom(scene, index):    old_object = scene.objects[index]
    mutated = buildingGameObject(scene.objects[index])
    
    #check that the transition was successful
    assert(old_object is not mutated)
    assert(old_object.invalid)
    assert(mutated is scene.objects[index]) 
    #I use this list to keep track of which objects are mutated into which classes. 
    #Should be defined before this.
    customList.append(mutated)

Then, while initializing the module, the following will hunt for any object with property “custom” and mutate it to your custom class:


for place in range(0, len(scene.objects)):
    x = scene.objects[place]
        if "custom" in x:
            makeCustom(scene, place)

You can then access any member of this instance from customList.

Well, you are on the good track. I mean that there is no real better way of doing it.

Basically you are already doing all that has to be done: get every object to mutate, and apply the class you want.

Although you could do:


for object in scene.objects:
    if "custom" in object:
        customGameObject(object)

… and you are pretty much done.

Maybe put this in a function, in order to call it from a Python Controller in Module mode, done.

That… is so much more concise! Hahaha, thanks. With your permission I’ll use something very similar.

I used to fiddle around with sub-classing the game objects, and then realised that most of the time a game object is just the graphical component of a larger system, and thus it frequently makes more sense for it to be an attribute of an object rather than the object itself. That said, there are some times when subclassing game objects is actually useful.

One useful example I have is when I overloaded the .endObject method of KX_Camera:


class Camera(KX_Camera):

    def setActive(self):
        '''also useful, wonder why its not already in the API...'''
        self.scene.active_camera = self

    def endObject(self):
        super().endObject()

        print('ENDED CAMERA:', self)

        for camera in self.scene.cameras:
            if camera is not self:
                self.scene.active_camera = camera

        if self is self.scene.active_camera:
            raise RuntimeError(
                'BGE crash: deleted active and only camera')

Assuming that every camera is correctly subclassed (which I ensured).

Anyway you are very right, and am considering myself thinking about how to design logic with this “KX_GameObjects are only the the display handle”. Although they have very defining properties, such as their inner game properties, their transformations, or their meshes/materials.

edit: better error message + logic fix
also: I must do the for loop and only actually trigger .endObject after the process because if I were to delete the camera before trying to find a fallback, I have no idea if it was the active camera.

demo blend:
BGE_ActiveCameraDeletion.blend (466 KB)

Hmm. I’m not convinced by that design choice. What are you attempting to do? Prevent unexpected behaviour when removing a camera? Is that the camera’s role? I’m not sure, I would have thought the scene management should take care of that.

Also, that error message is pretty non-descriptive. I would have just left the self.scene.active_camera = self.scene.cameras[0], as if that throws an error die to no remaining cameras, then it will tell you: Index out of range, and even a low-experience coder can guess that if the zero’th index of self.scene.cameras is not there, then there are no cameras.

The only place I’ve found subclassing the game object is when you need to link behaviour that is tied inside the BGE to python code. So for example in a GUI, you may want to cast a ray and get access to the python instance of the GUI widget you clicked on. That and some physics interactions are the only cases I can think of when you need to have a link from the game engine -> python that cannot be achieved from the python side.

Even for non-graphical things like a camera, the in-game “camera” object is very often still the game-engine representation of something else. Eg:


class ThirdPersonCamera(object):
    def __init__(self, scene, target):
        self.camera = scene.addObject('Camera')
        self.target = target

    def update(self):
        ....

    def end(self):
        self.camera.endObject()

You are right, but in the case where you would destroy a game object, which happens to be a camera, the only control we have is over the .endObject() override. I mean, I can’t change how the BGE handle object deletion, and if it happens that deleting the active camera freezes the whole engine, then I’d really like to prevent this in the only method that can catch the problem before it crashes (as it seems like a low level problem).

Also when I wrote the example, I wondered if for the sake of just showing the problem I should have written a better exception message… I guess I should have. It was just to illustrate a point, its just a template.

edit: here is a demo blend
BGE_ActiveCameraDeletion.blend (466 KB)

then I’d really like to prevent this in the only method that can catch the problem before it crashes (as it seems like a low level problem).

If it’s crashing, then that is a bug - and this should be solved in the BGE code rather than worked around in python. File a bug on the bug tracker…
Your blend didn’t crash for me. I pressed space until your logging told me it should have crashed.

Well, on my windows and BFBGE once I delete the cameras, the function registered on pre_draw event stops firing, logic stops too, even tho Blender does not hard crash, it just stops doing anything…

Just tested with UPBGE, same problem, nothing happens once you delete the cameras.

edit: by crash I mean that nothing happens, the BGE stays open, but does nothing at all.
can you confirm that althought Blender does not crash and close, you get the same behaviour ?

Well, it kinds of make sense, because the BGE revolves around having a scene to render. If we remove the camera used for the render, its not really surprising that something would go wrong… Just seemed to me a simple case were object subclassing was an elegant way of fixing the problem (because in my projects I end up mutating every single object anyway).

edit: sorry for the “thread hijack”, but this derives from the argument so…

Last edit:
It seems like the logic keeps on going, but the scene events completely stop. As I am heavily relying on these I thought that everything stopped. Logic seems fine, pre_draw/post_draw events do stop tho. Ok I agree this looks like a bug.

Of course pre_draw and post_draw will stop. There’s no camera so it won’t be rendering.

I would not suggest putting any major logic inside of pre_draw or post_draw. Badness tends to ensue. Why? Because you’re inside a render state, so:

  • Other render calls (such as dynamic textures) will behave unpredictably
  • You’re not in a logic state, so:
    [LIST]
  • bge.logic.getCurrentController(), bge.logic.getCurrentScene() and a couple other logic functions may be unpredictable (not that you’re using those)
  • Button state is either KX_INPUT_ACTIVE or KX_INPUT_NONE - never KX_INPUT_JUST_ACTIVE or KX_INPUT_JUST_RELEASED because the logic step is complete. (At least this was the case a little while back)

[/LIST]

So what should you put inside pre_draw and pos_draw:

  • Things that should influence the currently active render (eg object visibility, shader parameters, camera projection matrices)
  • Things that have to be done after the physics but before the render (eg parent-like behaviour in python without one-frame delay)

If you want to run things every frame without worrying about it:

  • look at the main (?) scene attribute which allows a python script to take (more) complete control over the stages in drawing a scene. I did play with this a while back, but never saw any clear advantage to using it. Perhaps you will.
  • Or use a logic brick the runs every frame (have a single insertion point in your code, and make sure that all scenes run it. Perhaps use some sort of guard boolean inside the pre_draw to prevent logic from running multiple times per frame).
  • Apply this committo your blender version, and then use bge.logic.getLogicCallback().append(some_function). But again, some API calls will fail (eg getCurrentScene and getCurrentController)

I used to want a better way to run logic than the logic bricks (and hence asked ben2610 to implement that logic callback), but after learning to separate test code from production code, having a single logic brick in each file that runs the main entry point is no effort at all. If you really want to, you could link it in from another blend so that if you change it you only have one place to change it.

Thanks for the insight.

Indeed, key states no longer trigger JUST_* events.

The idea was to emulate the LogicCallbacks now available in UPBGE, good thing that they added it.

Sure having one entry point is not so bad, but it becomes a bit weird when you want to have some kind of “master” logic, but it still runs from a object… Also you should not delete it, because you’d end up with the same problem as with the scene callbacks.

I’ll look up what you mentionned.

The idea was to emulate the LogicCallbacks now available in UPBGE, good thing that they added it.

Just to warn people: that commit isn’t in regular UPBGE

Sure having one entry point is not so bad, but it becomes a bit weird when you want to have some kind of “master” logic, but it still runs from a object… Also you should not delete it, because you’d end up with the same problem as with the scene callbacks.

Does that imply that objects occasionally get deleted without warning? And with scene callbacks you have to handle changing scenes.
No matter what approach you take, you always have to do something to ensure the logic runs each frame. It may as well be the logic bricks.