Make a scene reusable

Godot Version

4.4.1

Question

I want to make a scene reusable by setting its properties unique to that scene.
For example I want an enemy to have different color (color picker) and a different speed (int) I can’t do that because, if I set it, it makes it the same as the last instance that’s in the level.

Have you tried setting the values in code when you load the enemy scene in another scene?

enemy1.color = <Color value 1>
enemy1.speed = speed1
enemy2.color = <Color value 2>
enemy2.speed = speed2
...

you can make an inherited scene if you don’t want to set the properties with code

Yes, I put it in the func _ready. Is that wrong?

I’m sorry I’m not very familiar with that, can you unpack that?

I set the values in _ready(). I would expect that to work for you. Could you post your code please?
(Doing it in a pre-formatted block makes it much easier to read.)

If you right click on a scene in the file manager, you can make an inherited scene. Inherited scenes share the code and properties of the other scene, but changes to the inherited scene do not affect the original scene.

I will provide code, once I get home.

2 Likes

I have it narrowed down to the shader I use on the scene.
I use a color swap shader that has palette swap functionality by changing the output palette.
I use an enum and an array to figure out which palette to load(in the Game manager).I swap colors in the func _ready of the scene. I simplified the following to just the issue. So I put all the code I use to replicate the issue.

Shader:

shader_type canvas_item;

const uint max_palette_array_size = uint(8);
uniform uint palette_array_size = uint(0);
uniform sampler2D input_palette_texture: filter_nearest;
uniform sampler2D output_palette_texture: filter_nearest;
uniform vec4 input_palette_array[max_palette_array_size]: source_color;
uniform vec4 output_palette_array[max_palette_array_size]: source_color;

int compare_floats(float a, float b) {
	float difference = a - b;
	
	if(abs(difference) < 0.0001) {
		return 0;
	} else if(difference > 0.0) {
		return 1;
	} else {
		return -1;
	}
}
bool color_is_approx_equal(vec4 a, vec4 b) {
	if (
	    a.r >= b.r - 0.001 && a.r <= b.r + 0.001 &&
	    a.g >= b.g - 0.001 && a.g <= b.g + 0.001 &&
	    a.b >= b.b - 0.001 && a.b <= b.b + 0.001 && 
	    a.a >= b.a - 0.001 && a.a <= b.a + 0.001
	) {
		return true;
	}
	
	return false;
}
bool color_is_transparent(vec4 color) {
	return compare_floats(color.a, 0.0) == 0;
}
vec2 find_color_in_palette_texture(vec4 color, sampler2D palette) {
	ivec2 palette_size = textureSize(palette, 0);
	
	for(uint y = uint(0); y < uint(palette_size.y); y++) {
		for(uint x = uint(0); x < uint(palette_size.x); x++) {
			vec2 palette_coord = vec2(
				float(x) / float(palette_size.x),
				float(y) / float(palette_size.y)
			);
			
			vec4 palette_color = texture(palette, palette_coord);
			
			if(color == palette_color) {
				return palette_coord;
			}
			if(
				color_is_transparent(color) &&
				color_is_transparent(palette_color)
			) {
				return palette_coord;
			}
		}
	}
	
	return vec2(-1.0);
}
vec4 get_color_from_palette_texture(vec2 coord, sampler2D palette) {
	return texture(palette, coord);
}

void swap_colors_from_palette_textures(
	vec4 input_color,
	inout vec4 output_color
) {
	vec2 palette_coord = find_color_in_palette_texture(
		input_color, input_palette_texture
	);
	
	if(palette_coord == vec2(-1.0)) {
		return;
	}
	
	output_color = get_color_from_palette_texture(
		palette_coord,
		output_palette_texture
	);
}

int find_color_in_palette_array(vec4 color, vec4[max_palette_array_size] palette) {
	int safe_palette_array_size = int(clamp(
		palette_array_size,
		0,
		max_palette_array_size
	));
	
	for(int i = 0; i < safe_palette_array_size; i++) {
		vec4 palette_color = palette[i];
		
		if(color_is_approx_equal(color, palette_color)) {
			return i;
		}
		if(color_is_transparent(color) && color_is_transparent(palette_color)) {
			return i;
		}
	}
	
	return -1;
}

vec4 get_color_from_palette_array(
	uint coord,
	vec4[max_palette_array_size] palette
) {
	return palette[coord];
}

void swap_colors_from_palette_arrays(
	vec4 input_color,
	inout vec4 output_color
) {
	int palette_coord = find_color_in_palette_array(
		input_color,
		input_palette_array
	);
	
	if(palette_coord == -1) {
		return;
	}
	
	output_color = get_color_from_palette_array(
		uint(palette_coord),
		output_palette_array
	);
}

void fragment() {
	vec4 input_color = texture(TEXTURE, UV);
	vec4 output_color = texture(TEXTURE, UV);
	
	swap_colors_from_palette_textures(input_color, output_color);
	swap_colors_from_palette_arrays(input_color, output_color);
	
	COLOR = output_color;
}

Game Manager

extends Node2D

class_name GameManager
enum StateColor {YELLOW, RED, BLUE, GREEN}
const StateColorValue:=["YELLOW", "RED", "BLUE", "GREEN"]
const ShaderColorPalette:=[preload("res://shader/yellow_palette.png") ,preload("res://shader/red_palette.png") ,preload("res://shader/blue_palette.png"),preload("res://shader/green_palette.png")]

THE ACTUAL SCENE:

extends Area2D
@onready var animatedsprite2d= $AnimatedSprite2D

@export var PlayerColorState:GameManager.StateColor=GameManager.StateColor.YELLOW

func _ready():
	self.animatedsprite2d.material.set("shader_parameter/output_palette_texture",GameManager.ShaderColorPalette[PlayerColorState])
	

Is the shader shared between all instances of that scene I use in my level?
I’m guessing this is the reason?

Possible solutions I see are:

  1. Create a new scene with an animation sprite 2d that has different animations for different colors (ok-ish solution?)
  2. Each color has a separate scene (not a good solution)
  3. Whatever someone else says on here?

I have definitely experienced shader parameters being shared between instances.

By sheer coincidence, I just posted about that yesterday. Rather than type it all out again, I will point you to my answer on that post.

Oh, awesome, that makes sense. Thanks!
I’ll try it and see.

One other question, would it be possible to update the look of the scene in the 2D view in Godot. So if I select green in the drop down, it updates the scene to green in the level in the 2D view?

Using the approach I suggested, no. The shader isn’t defined before run time, so there is nothing to assign a parameter to.

You could potentially update the scene, without affecting the other objects that share the shader, by making the scene local. You do that by right clicking on the scene and choosing the Make Local menu item.

Fair warning, I do not know if that will work. I suspect it will, because I believe the underlying issue is because the ShaderMaterial is being shared between instances. Take that with a large grain of salt - I have not verified my suspicions against the Godot code base.

1 Like

It looks like making it local does not “unshare” the shader.

1 Like

Thanks alot for your help! Your solution worked!

But say you were going to do that for a different scene. I want it to update to the color, how would I do that?

It sounds like you are reusing the same Material Resource. If you have a shared scene, they will still use the same Material Resource. You need to make the material resource unique otherwise anytime you set a property on the Material, it will affect all that use it.

IIRC, you do this by going to your Material in the Inspector, and setting the Resource to be Local to Scene

1 Like

I actually tried this and it works really well.
The only issue I have is that if I change the “output_palette” custom shader parameter from code, it does not update the color when I tell it to.