Hiding/showing object instances created from addObject

So I have been creating a script that generates grass tiles on the first frame of the game so that I don’t have to have a billion dupes in my blender file, like so:

if own['framCounter']==1:
    spawnObject.worldPosition=[0,0,0];
    myscene.addObject("grassTile","spawnobject");
    spawnObject.worldPosition=[0,4,0];
    myscene.addObject("grassTile","spawnobject");
    #etc

later on in my game script, I have a function that hides the grass tiles when they are far away (calling stuff by its name as a string):

def distanceHide(scenename,objPrefix,threshold):
    scene=bge.logic.getSceneList(scenename);
	for obj in scene.objects:
		if obj.name.startswith(prefix):
			dist=obj.getDistanceTo(scene.active_camera);
			if dist<threshold:
				obj.setVisible(True);
			else:
				obj.setVisible(False);

However, when I run the game, only the first instance of the object spawned in the scene will hide/show when I move the camera, but the others stay visible.

is there a way to have this function affect all of the newly spawned instances?

when you add objects put them in a list.

my_list = []

added_obj = scene.addObject('an_object', None, 0)
added_obj.worldPosition = position
my_list.append(added_obj)

now don’t go trough all scene objects but instead go trough the list

for obj in my_list:
    if player.getDistanceTo(obj) <= treshold:
        obj.visivble = False
    else:
        obj.visible = True
4 Likes

This seems to be working! Thank you. To take this a step further, would it also be possible to apply a shader to the newly created object mesh.materials? I tried creating a list of materials for the objects as well with this method and applying the shader to all of them like so, but this didn’t work as I expected:


if not'matlist'in own:
	own['matlist']=[];

for obj in own['spawnlist']:
	for mesh in obj.meshes:
		for material in mesh.materials:
			own['matlist'].append(material);

for material in own['matlist']:
	shader = material.getShader()
	shader.setSource(VERTEX_SHADER, FRAGMENT_SHADER, True)
	shader.setUniform1f("width", render.getWindowWidth())
	shader.setUniform1f("height", render.getWindowHeight())
	shader.setUniform1f("pixelSize", pixelSize)
	shader.setUniform1f("tex0Enabled", 1)

However this only seems to work on the first instance :confounded:

I’m not into shaders so i can’t help you there, but if it works for 1 object then i assume you run it in script mode and not in module/function mode, then indeed only 1 object can use it.

so try this:

def change_shader(cont):

    own = cont.owner

    if not'matlist'in own:
	own['matlist']=[];

    for obj in own['spawnlist']:
	for mesh in obj.meshes:
		for material in mesh.materials:
			own['matlist'].append(material);

    for material in own['matlist']:
	shader = material.getShader()
	shader.setSource(VERTEX_SHADER, FRAGMENT_SHADER, True)
	shader.setUniform1f("width", render.getWindowWidth())
	shader.setUniform1f("height", render.getWindowHeight())
	shader.setUniform1f("pixelSize", pixelSize)
	shader.setUniform1f("tex0Enabled", 1)

If this is not working then i can’t help you further with the shader stuff.

Also some lines need to be altered with the tab spaces, edited it in this text editor so their not all in place

1 Like

ok thanks. I’m going to try this too and I will update if I find success

Well. thank you for the help. Anyways, yeah I tried and this isn’t working for me…out of curiosity I used the add object actuator and discovered shaders don’t seem to work either on objects that are spawned. Also tried “instantAddObject” too with no luck…sigh
I think it might be back to square 1 with having to make a hundred copies of the grass tile for the debug room.

Materials in BGE are a bit fiddly, but you should be able to set the material for a created object.

The thing is that blender links materials and meshes internally. You may notice that if you duplicate an object with alt+d and then change the material on one of them, it will change it for both of them. The same thing happens in BGE: if you assign a custom shader to an object, all objects with that mesh will share it. There are ways around this using LibNew…


So, with this understanding, you don’t have to build a list of materials - they are all already sharing a material. All you have to do is change one of their materials and they all should follow suit. Probably what is happening is you’re setting the source of the shader a couple dozen times and this is confusing things. You could probably throw a deleteShader() in, or just make sure you only change the materials shader once.

Digging through some of my old code, I used to use:

        shader = material.getShader()
        if shader != None:
            if not shader.isValid():
                shader.setSource(VERTEX_SHADER, FRAGMENT_SHADER, 1)

To ensure it was set only once or if the shader had failed previously.


My suggestion is to do the material assignment at game start and to operate on the un-added object. Something like (untested):

# Get a reference to the object before it is added to the scene:
scene = bge.logic.getCurrentScene()
grass_obj_inactive = scene.objectsInactive["grassTile"] 

# Assign the material:
for mesh in obj.meshes:
    for material in mesh.materials:
        shader = material.getShader()
        shader.setSource(VERTEX_SHADER, FRAGMENT_SHADER, True)
        shader.setUniform1f("width", render.getWindowWidth())
        .....

# Now any time you add it it will have the new material
spawnObject.worldPosition=[0,0,0];
myscene.addObject(grass_obj_inactive,spawnObject); # Note that you can use the objects directly

Another possible source of error is that an object is only added to the scene when the frame changes. So an object added this frame won’t be accessible using scene.objects until the next frame. So if you are trying to change the material using:

for obj in scene.objects:
    if obj.name == "grass_tile":
        set_material(obj)

It won’t be able to find any objects. One solution is to track the objects yourself:

new_grass_objects = []

...
spawnObject.worldPosition=[0,0,0];
new_grass_objects.append(myscene.addObject("grassTile","spawnobject"));
spawnObject.worldPosition=[0,4,0];
new_grass_objects.append(myscene.addObject("grassTile","spawnobject"));

for obj in new_grass_objects:
    do_something()

Note that you can use this reference to the newly created tiles to move them without having to fiddle with the spawn object’s position. IIRC you can omit the spawn object parameter…

new_grass_objects = []

for i in range(10):
    new_grass = myscene.addObject("grassTile");
    new_grass.worldPosition = [0,i,0]
    new_grass_objects.append(new_grass)

I haven’t worked with BGE for quite a while, so hopefully I’m remembering things correctly…

1 Like

Ok thank you for this. I will try implementing this today and will post the results.

I should have mentioned a few extra details: Since the shader is based off of the window width/height, I run it every 60 frames to “refresh” just in case the window size is changed, but not often enough for it to impact performance. Even while iterating through the manually listed instances of the spawned single grass tile, the shader only affects the first grass tile whos visibility is turned on while the camera moves around.

Something occurred to me too. I am not 100% sure, but when objects are added while it game is running it makes sense for them to be a duplicate instance of the original object (not unique, but with data tied to it in the same fashion as pressing alt+D instead of shift+D when in the 3d view). So any changes that occur when calling the object only seem to point to the 1st loaded object via the dictionary. Since the shader uses aspects of the object that cannot be rewritten or renamed and since I am calling them by name (mesh > [material]), there is no way to make each new instance a unique object/key in the dictionary the game engine uses to identify things in the scene.

That’s my guess as to why this isn’t working. But maybe it will work with your approach. I will update if it does!

This means you need to update the shaders uniforms, but not that you need to re-compile the shader each frame. Each time you call shader.setSource() you are sending the shader to the GPU and it is recompiling it. (A couple years back this was a memory leak that would eventually crash the engine - probably still is). The solution is either:

  • to check if it already has the shader using shader.isValid() so you can check if you need to call setSource or not.
  • store a reference to the shader somewhere with something like obj["grass_shader"] = material.getShader()

Setting uniforms is really fast. Every single object rendered has a separate WorldTransform and this is send via a uniform every frame! So if you have a reference to the shader, you can set pretty much however many uniforms you want whenever you want.

Something occurred to me too. I am not 100% sure, but when objects are added while it game is running it makes sense for them to be a duplicate instance of the original object (not unique, but with data tied to it in the same fashion as pressing alt+D instead of shift+D when in the 3d view). So any changes that occur when calling the object only seem to point to the 1st loaded object via the dictionary.

The first part, yup! AddObject makes non-unique copies (alt+d style). You can see this if you use BGE’s mesh API to deform the mesh. This means that changes to any object should affect all of them, so it is curious it’s only affecting one of them.

Since the shader uses aspects of the object that cannot be rewritten or renamed and since I am calling them by name (mesh > [material]), there is no way to make each new instance a unique object/key in the dictionary the game engine uses to identify things in the scene.

Internally, the game engine doesn’t use dictionaries - there is no concept in plain C++. Mostly it has lists of things.

One possible source of confusion is that In BGE you cannot have per-object uniforms - all copies of a mesh will share the same shader, and all copies of that shader will share the same uniforms. Getting around this limitation is possible (LibNew to create new meshes), but hard.


Do you have the shader sucessfully working for a single object? Make sure you’ve got that compiling/running first otherwise there may well be some weirdness.


I feel that there is some information I am missing about how you are storing the grass tiles to work with them. Are you iterating through the objects in the scene each frame? Are you storing them in a dictionary?
The most likely issue is that the name of an object is not unique - if you add an object called “grass_tile” into the scene, you can’t use the object names as keys in your own dictionary.


Ages back I made this demo:

It creates a bunch of objects, ensures they have different meshes/materials, and moves them around. Unfortunately this was coupled into a larger project, so it isn’t a standalone file, but you can see the relevant source code here:

Thank you. I opened the file this morning and made some of these adjustments, and now the shader is working on the first 2 objects…but not all of the spawned tiles. also it seems to be rapidly switching between them? It’ll probably be easier if I just upload it, here:
new026.blend (604.2 KB)

I usually only use windowed mode for this because there can be differences between how it looks/behaves in the 3d view vs in the popout, not sure if these are obscure bugs. I’ve been using blender 2.79 for everything because I have had the most stability with it in general.

also I used the keys F1 and F2 for toggling to full screen and left control toggles the debug properties

Aside from the shader demo theres some other misc stuff going on in this file like a pixelate overlay that can be toggled with “R” (this works fine so far, my only roadblock right now is this problematic shader). the camera controls are WASD and left shift and left control to rotate/zoom.

If your using group instances I would suggest using the function KX_GameObject.groupMembers() to get a list of the group instances.
Remember, group instance objects are replaced with copies of the original instance, meaning for every object there is a second object.
I believe you’ve been attempting to apply material sources to the first objects, not the second objects…

No luck. They don’t seem to be returning as groups.
image
image

I had a look at the file…

One thing I notice is that lots of functions refer to objects by name, eg:

def posL(objname,x,y,z):#_______________________________________position local
	obj=getobj(objname);
	vec=[x,y,z];
	obj.localPosition=vec;
def posw(objname,x,y,z):#_______________________________________position world
	obj=getobj(objname);
	vec=[x,y,z];
	obj.worldPosition=vec;

Bear in mind that because adding objects adds objects with the same name, all these functions will not work on added objects.


There are two things I can see that are, if nothing else, probably interesting to you:

  1. The uniforms are only updated the first frame anyway, so it won’t resize correctly
  2. It’s being run a bunch of times on the same material.

You can fix #1 by changing:

	if shader!=None:
		if not shader.isValid():
			shader.setSource(VERTEX_SHADER, FRAGMENT_SHADER, 1)
			shader.setUniform1f("width", render.getWindowWidth())
			shader.setUniform1f("height", render.getWindowHeight())
			shader.setUniform1f("pixelSize", pixelSize)
			shader.setUniform1f("tex0Enabled", 1)

into:

	if shader!=None:
		if not shader.isValid():
			shader.setSource(VERTEX_SHADER, FRAGMENT_SHADER, 1)

		shader.setUniform1f("width", render.getWindowWidth())
		shader.setUniform1f("height", render.getWindowHeight())
		shader.setUniform1f("pixelSize", pixelSize)
		shader.setUniform1f("tex0Enabled", 1)

This way the shader is compiled only the first time, but the uniforms are updated every time. Throw some print statements in so you can check when these various things are happening.

#2 is harder to prove/fix. Do you know of python’s “id” function? It returns the memory address of an object so you can check if they are the same object.
Consider:

a = object()
b = object()
c = a

assert id(a) != id(b) # variable a points somewhere different to variable b
assert id(a) == id(c) # variable a and c point at the same object

Now consider:

#run shader every second
if own['fc']%60==0:
	for sce in f.scenes():
		for obj in sce.objects:
			if not obj.name.startswith("!s_"):
				if not obj.name.startswith("render"):
					print("obj:", id(obj))
					for mesh in obj.meshes:
						for material in mesh.materials:
							print("mat:", id(material))
							f.run_shader(material);

We’re printing out the memory locations of all the objects and the materials. What do you expect to happen? Well, it outputs is something like:

mat: 139683507143136
obj: 139683502315008
mat: 139683507143136
obj: 139683502317504
mat: 139683507143136
obj: 139683502317408
mat: 139683507143136
...

Note that the numbers will be different on your computer

Now, why is this useful?

  1. We now know that we are correctly iterating through all the objects because each object id is different.
  2. All the mat values are 139683507143136 - this proves that all the materials are actually the same - so there’s no reason to run the run_shader() function for each object :slight_smile:

In fact, we can just do:

#run shader every second
if own['fc']%60==0:
	f.run_shader(grass_obj_inacv.meshes[0].materials[0])

And it is exactly the same.


While there’s nothing wrong with:

	f.posw(gspawn,0,0,0);		f.addobj(scdebug,gtile,gspawn);
	f.posw(gspawn,0,4,0);		f.addobj(scdebug,gtile,gspawn);
	f.posw(gspawn,0,8,0);		f.addobj(scdebug,gtile,gspawn);
	f.posw(gspawn,0,12,0);	f.addobj(scdebug,gtile,gspawn);
	f.posw(gspawn,0,16,0);	f.addobj(scdebug,gtile,gspawn);
	f.posw(gspawn,0,20,0);	f.addobj(scdebug,gtile,gspawn);

It’s rather cumbersome, and the use multiple statements on the same line is frowned upon.
It can be replaced with:

if own['fc']==1:
	AREA_START = 0
	AREA_END = 24
	TILE_SPACING = 4
	grass_positions = range(AREA_START, AREA_END, TILE_SPACING)
	for x in grass_positions:
		for y in grass_positions:
			added_object = f.getscene(scdebug).addObject(gtile, gspawn)
			added_object.worldPosition = [x, y, 0.0]
			own['spawnlist'].append(added_object)

And now you can easily change the size of the grassed area, size of tile etc. etc.


But none of this is actually pointing at what the actual problem could be. Why is the shader not being applied correctly to the added objects? I couldn’t see anything that should be causing it, so I decided to create my own test file. The result was … almost the same:

TestShaders.blend (500.1 KB)

However, I notice one thing. In my file, the original tile is a plain green. The shader makes it red, and a uniform makes it purple. BUT as you can see, most of the tiles are white. If you look carefully, the white tiles are per-vertex shaded rather than pixel-shaded. If I remember correctly, this is what happens when there is no valid shader for an object. (eg calling delSource)

However, checking shader validity with:

for tile in own["tiles"]:
    material = tile.meshes[0].materials[0]
    print(material.getShader().isValid())

Reveals that the engine thinks the shaders for all the objects are valid.

Now, I know there was a thing about needing to store references to textures inside the object to get them to work properly, so maybe something similar with shaders?

def run_shader(obj):#______________________________________run shader
    material = obj.meshes[0].materials[0]
    if "shader" not in obj:
        obj["shader"] = material.getShader()
    shader = obj["shader"]

    if shader!=None:
        if not shader.isValid():
            print("Compiling Shader")
            shader.setSource(VERTEX_SHADER, FRAGMENT_SHADER, 1)

        print("Setting Uniforms")
        shader.setUniform1f("col", 1.0)

No dice. Didn’t change things.

So where does this lead us? It looks like something is a bit skewy inside BGE to do with adding objects and custom shaders. Unfortunately the renderer is part of the BGE I never touched, so I don’t know what would be wrong there.

Each object gets a single color uniform,

You could use this to drive the differences between the objects.