调色板替换

精灵存的是调色板索引而不是颜色,所以轮换调色板就能重绘整幅画面。

上方的编辑器正在运行这个项目。改一行代码,它就会重新加载。

用到的 Godot 类

Sprite2DImageTextureShaderMaterial

代码

scripts/main.gd
extends Node

const PALETTE: Array = ['#2b3a67', '#8da5f3', '#8eef97', '#f3d48d', '#fc7f7f']
const ART: Array = [
	'00011111000',
	'00122222100',
	'01233333210',
	'12344444321',
	'12344444321',
	'01233333210',
	'00122222100',
	'00011111000',
]


func _ready() -> void:
	var background: ColorRect = ColorRect.new()
	background.color = Color('#12141a')
	add_child(background)
	background.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)

	var material: ShaderMaterial = ShaderMaterial.new()
	material.shader = Get.shader('palette')
	material.set_shader_parameter('palette_texture', _make_palette())
	material.set_shader_parameter('palette_size', PALETTE.size())

	var sprite: Sprite2D = Sprite2D.new()
	sprite.texture = _make_art()
	sprite.material = material
	sprite.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
	sprite.scale = Vector2(28, 28)
	sprite.position = get_viewport().get_visible_rect().size * 0.5
	add_child(sprite)

	var label: Label = Label.new()
	label.text = 'the sprite stores palette indices, the shader picks the colors'
	label.position = Vector2(40, 40)
	label.add_theme_color_override('font_color', Color('#999999'))
	add_child(label)


func _make_art() -> ImageTexture:
	var image: Image = Image.create(ART[0].length(), ART.size(), false, Image.FORMAT_RGBA8)

	for y in ART.size():
		for x in ART[0].length():
			var index: int = int(ART[y][x])
			var value: float = float(index) / PALETTE.size()
			image.set_pixel(x, y, Color(value, value, value, 1.0))

	return ImageTexture.create_from_image(image)


func _make_palette() -> ImageTexture:
	var image: Image = Image.create(PALETTE.size(), 1, false, Image.FORMAT_RGBA8)

	for i in PALETTE.size():
		image.set_pixel(i, 0, Color(PALETTE[i]))

	return ImageTexture.create_from_image(image)
shaders/palette.gdshader
shader_type canvas_item;

uniform sampler2D palette_texture: filter_nearest;
uniform int palette_size = 5;
uniform float cycle_speed = 0.6;

void fragment() {
	float index = texture(TEXTURE, UV).r;

	if (index < 0.01) {
		discard;
	}

	// the pixel stores the index and not the color, so cycling the palette repaints the whole art
	float slot = floor(index * float(palette_size) - 0.5);
	float shifted = mod(slot + floor(TIME * cycle_speed), float(palette_size));

	COLOR = texture(palette_texture, vec2((shifted + 0.5) / float(palette_size), 0.5));
}

更多着色器示例