Interaction between multiple object instances

This script creates 5 instances of a sphere object, and allows to move 2 of them.
I would like to set the color of those spheres which are less than 3 distance from each
other to red, others should be green. My problem is that it is not working for all of the spheres.
It should be like this:

from direct.showbase.ShowBase import ShowBase

class Game(ShowBase):
    def __init__(self):
        super().__init__()
        self.set_background_color(0.1, 0.1, 0.1, 1)
        self.cam.setPos(10, -60, 0)
        self.taskMgr.add(self.update, "update")

        self.sphere = self.loader.loadModel("models/sphere")
        for i in range(5):
            self.spheres = self.render.attachNewNode("Spheres" + str(i + 1))
            self.spheres.setPos(i * 5, 0, 0)
            self.sphere.instanceTo(self.spheres)

    def update(self, task):
        is_down = self.mouseWatcherNode.is_button_down

        for x in range(1, 3):
            spheres_a = self.render.get_child(x)
            if is_down("w"):
                spheres_a.set_z(spheres_a, 0.1)
            if is_down("s"):
                spheres_a.set_z(spheres_a, -0.1)
            if is_down("a"):
                spheres_a.set_x(spheres_a, -0.1)
            if is_down("d"):
                spheres_a.set_x(spheres_a, 0.1)

            for x in range(3, 6):
                spheres_b = self.render.get_child(x)
                distance = spheres_b.getDistance(spheres_a)
                if distance < 3:
                    spheres_b.setColor(1, 0, 0, 1)
                    spheres_a.setColor(1, 0, 0, 1)
                else:
                    spheres_b.setColor(0, 1, 0, 1)
                    spheres_a.setColor(0, 1, 0, 1)

#        for x in range(1, 6):
#             spheres_all = self.render.get_child(x)
#            distance = spheres_all.getDistance(spheres_all)
#            if distance < 3:
#                spheres_all.setColor(1, 0, 0, 1)
#            else:
#                spheres_all.setColor(0, 1, 0, 1)

        return task.cont


Game().run()

If you use the “instanceUnderNode” method instead of “instanceTo”, it should return a new NodePath that stands between the instance and the parent-node. That NodePath can then be used to set per-instance states (like colours), I believe.

Of course, that does presumably then call for either storing these NodePaths in some way, or searching them out in the scene graph. (I would be inclined to suggest the former, being likely easier to work with.)

Thank you! I solved it differently.

1 Like

I solved it, there is a small problem I commented in the script.

from direct.showbase.ShowBase import ShowBase
from direct.task.Task import Task


class Game(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)
        self.setBackgroundColor(0.1, 0.1, 0.1, 1)
        self.cam.setPos(10, -60, 0)
        self.taskMgr.add(self.update, "update")

        self.num = 5 #number if instances
        self.objects = []
        for i in range(self.num):
            object = self.loader.loadModel("models/sphere")
            object.setPos(i * 5, 0, 0)
            object.reparentTo(self.render)
            self.objects.append(object)

    def update(self, task):
        is_down = self.mouseWatcherNode.is_button_down

        for x in range(1, 3):
            group = self.render.get_child(x)
            if is_down("w"):
                group.set_z(group, 0.1)
            if is_down("s"):
                group.set_z(group, -0.1)
            if is_down("a"):
                group.set_x(group, -0.1)
            if is_down("d"):
                group.set_x(group, 0.1)

        for obj_a in self.objects:
            for x in range(len(self.objects)):
                obj_b = self.objects[x]

                if obj_a != obj_b:
                    distance = obj_a.getDistance(obj_b)
                    if self.num - 1 > 0:
                        obj_a.setColor(0, 1, 0, 1)
                        obj_b.setColor(0, 1, 0, 1)
                        self.num -= 1
                    if distance < 3:
                        obj_a.setColor(1, 0, 0, 1)
                        obj_b.setColor(1, 0, 0, 1)
                    if distance > 3 and distance < 4 : # It still works unproperly within these narrow conditions. "if distance > 3:" would be better, but if I use this not all of the objects change their color when they should, I don't know why.
                        obj_a.setColor(0, 1, 0, 1)
                        obj_b.setColor(0, 1, 0, 1)

        return Task.cont


Game().run()

Okay, I think that I see two problems in your code:

First–and this may not be a problem if it’s intended–the if-statement that checks “self.num - 1 > 0” will only succeed on the first four updates. This is because you’re decrementing “self.num” each time that the code-section is run, meaning that it will eventually fall to a value of “1”, at which point the check will fail and the code will not be run.

Second, while your distance-check may work, the results of any given check may be overridden by a later one.

So, for example, let’s say that there are three objects, “alpha”, “beta”, and “gamma”. Further, let’s say that “alpha” and “beta” are closer than three units from each other, and that “alpha” and “gamma” are further than three units from each other.

So, the code will start with “alpha” as “obj_a”.

It will then to examine “beta” (having skipped re-examining “alpha”). Finding a distance of less than three, it will set the colours of “alpha” and “beta” to be red.

It will then go on to examine “gamma”. Finding a distance greater than three, it will set the colours of “alpha” and “gamma” to be green.

This means that the original setting of “alpha”'s colour to red will be overridden.

Similarly, if the distances were reversed, the results would be reversed, too: the initial setting of “alpha”'s colour to green would be overridden by a subsequent setting of its colour to red.

Furthermore, when you code goes on to place “beta” into the position of “obj_a”, anything done to it in the first iteration will be overridden.

And so on as the code examines the various objects.

In the end, I think that the only results will be those of the last object in the list, compared to each of the other objects.

1 Like

Thank you! I corrected it, and it works properly.

1 Like

Here is the corrected script:

from direct.showbase.ShowBase import ShowBase
from direct.task.Task import Task


class Game(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)
        self.setBackgroundColor(0.1, 0.1, 0.1, 1)
        self.cam.setPos(10, -60, 0)
        self.taskMgr.add(self.update, "update")

        self.objects = []
        for i in range(5):
            object = self.loader.loadModel("models/sphere")
            object.setPos(i * 5, 0, 0)
            object.reparentTo(self.render)
            self.objects.append(object)

    def update(self, task):
        is_down = self.mouseWatcherNode.is_button_down

        for x in range(1, 3):
            group = self.render.get_child(x)
            if is_down("w"):
                group.set_z(group, 0.1)
            if is_down("s"):
                group.set_z(group, -0.1)
            if is_down("a"):
                group.set_x(group, -0.1)
            if is_down("d"):
                group.set_x(group, 0.1)

        for obj_a in self.objects:
            for x in range(len(self.objects)):
                obj_b = self.objects[x]

                if obj_a != obj_b:
                    distance = obj_a.getDistance(obj_b)
                    if distance < 3:
                        obj_a.setColor(1, 0, 0, 1)
                        break
                    if distance > 3:
                        obj_a.setColor(0, 1, 0, 0)

        return Task.cont


Game().run()