Browse Source

Custom sprite shader example (#71)

Add new example showing how to recolor parts of a sprite using a de-saturation method and recoloring a sprite outline also removing the outline in a shader.
Agustin 5 months ago
parent
commit
e8cb145cb3

BIN
material/custom_sprite/assets/Alchemy_vial.png


BIN
material/custom_sprite/assets/SourceSansPro-Semibold.ttf


+ 4 - 0
material/custom_sprite/assets/sprites.atlas

@@ -0,0 +1,4 @@
+images {
+  image: "/assets/Alchemy_vial.png"
+}
+extrude_borders: 2

+ 4 - 0
material/custom_sprite/assets/text48.font

@@ -0,0 +1,4 @@
+font: "/assets/SourceSansPro-Semibold.ttf"
+material: "/builtins/fonts/font.material"
+size: 22
+characters: " !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"

+ 11 - 0
material/custom_sprite/example.md

@@ -0,0 +1,11 @@
+---
+name: CustomSprite
+tags: material
+title: Custom Sprite
+brief: This example demonstrates a simple way to create and apply a custom sprite shader for changing colors and customizing an outline.
+scripts: set_color.script, recolor.fp, recolor.vp
+---
+
+If your game requires a sprite that can be recolored and reused, a custom shader will be needed. Your sprite's artwork can be composed in such a way that will help achieve things you may want to do. For example an outline around your sprite that can be turned off/on and color changed. When creating your artwork if your sprite's green-channel is shifted slightly below 1.0 and you add an outline around your sprite with full green color equal to 1.0 then in the shader you can manage the green channel values that match 1.0 and change the color or completely hide these values thus removing the outline altogether. Recoloring sprites to be used throughout a game is pretty common. One way to achieve re-coloring with a range of values instead of a single color is to de-saturate a part of the sprite you want to recolor. When you de-saturate an image it will even out the red, green and blue channel values to a grey-scale. You can then check in the shader for these grey-scale values and change the colors. To check for these values you can add 2 or 3 channels together as a float value and then with another float multiply a single channel by 2 or 3, we then compare these values when valid use a new color.
+
+In the example the custom sprite material has 2 vertex attributes each is a vector 4 of float values. The values are used for coloring the fluid and the outline from a script to the shader. The script has a function for creating a random color and also sets the color vertex properties

+ 110 - 0
material/custom_sprite/example/buttons.gui

@@ -0,0 +1,110 @@
+script: "/example/options.gui_script"
+fonts {
+  name: "default"
+  font: "/assets/text48.font"
+}
+nodes {
+  position {
+    x: 248.0
+    y: 230.0
+  }
+  size {
+    x: 100.0
+    y: 60.0
+  }
+  color {
+    x: 0.611
+    y: 0.487
+    z: 0.365
+  }
+  type: TYPE_BOX
+  id: "io"
+  inherit_alpha: true
+  size_mode: SIZE_MODE_AUTO
+}
+nodes {
+  position {
+    x: -2.0
+  }
+  size {
+    x: 40.0
+    y: 30.0
+  }
+  type: TYPE_TEXT
+  text: "Outline\n"
+  "(Off)"
+  font: "default"
+  id: "text"
+  line_break: true
+  parent: "io"
+  inherit_alpha: true
+}
+nodes {
+  position {
+    x: 360.0
+    y: 230.0
+  }
+  size {
+    x: 100.0
+    y: 60.0
+  }
+  color {
+    x: 0.611
+    y: 0.487
+    z: 0.365
+  }
+  type: TYPE_BOX
+  id: "OutlineColor"
+  inherit_alpha: true
+  size_mode: SIZE_MODE_AUTO
+}
+nodes {
+  position {
+    x: -2.0
+  }
+  size {
+    x: 40.0
+    y: 20.0
+  }
+  type: TYPE_TEXT
+  text: "Outline\n"
+  "Color"
+  font: "default"
+  id: "text1"
+  parent: "OutlineColor"
+  inherit_alpha: true
+}
+nodes {
+  position {
+    x: 470.0
+    y: 230.0
+  }
+  size {
+    x: 100.0
+    y: 60.0
+  }
+  color {
+    x: 0.611
+    y: 0.487
+    z: 0.365
+  }
+  type: TYPE_BOX
+  id: "Fluid"
+  inherit_alpha: true
+  size_mode: SIZE_MODE_AUTO
+}
+nodes {
+  size {
+    x: 40.0
+    y: 20.0
+  }
+  type: TYPE_TEXT
+  text: "Fluid\n"
+  "Color"
+  font: "default"
+  id: "text2"
+  parent: "Fluid"
+  inherit_alpha: true
+}
+material: "/builtins/materials/gui.material"
+adjust_reference: ADJUST_REFERENCE_PARENT

+ 33 - 0
material/custom_sprite/example/custom_sprite.collection

@@ -0,0 +1,33 @@
+name: "main"
+scale_along_z: 0
+embedded_instances {
+  id: "new"
+  data: "components {\n"
+  "  id: \"set_color\"\n"
+  "  component: \"/example/set_color.script\"\n"
+  "}\n"
+  "embedded_components {\n"
+  "  id: \"sprite\"\n"
+  "  type: \"sprite\"\n"
+  "  data: \"default_animation: \\\"Alchemy_vial\\\"\\n"
+  "material: \\\"/example/recolor.material\\\"\\n"
+  "textures {\\n"
+  "  sampler: \\\"texture_sampler\\\"\\n"
+  "  texture: \\\"/assets/sprites.atlas\\\"\\n"
+  "}\\n"
+  "\"\n"
+  "}\n"
+  ""
+  position {
+    x: 360.0
+    y: 360.0
+  }
+}
+embedded_instances {
+  id: "ui"
+  data: "components {\n"
+  "  id: \"buttons\"\n"
+  "  component: \"/example/buttons.gui\"\n"
+  "}\n"
+  ""
+}

+ 38 - 0
material/custom_sprite/example/options.gui_script

@@ -0,0 +1,38 @@
+local script_url = "/new#set_color"
+
+function init(self)
+	msg.post(".", "acquire_input_focus")
+
+	self.is_on = false
+	self.io_text = gui.get_node("text")
+	self.on_off = gui.get_node("io")
+	self.oColor = gui.get_node("OutlineColor")
+	self.fluid = gui.get_node("Fluid")
+
+end
+
+function on_input(self, action_id, action)
+	if action_id == hash("touch") and action.pressed then
+
+		if gui.pick_node(self.on_off, action.x, action.y) then
+
+			msg.post(script_url, "outline_io")
+			if self.is_on then
+				gui.set_text(self.io_text,"Outline (Off)")
+				self.is_on = not self.is_on
+			else
+				gui.set_text(self.io_text,"Outline (On)")
+				self.is_on = not self.is_on
+			end
+
+		elseif gui.pick_node(self.oColor, action.x, action.y) then
+
+			msg.post(script_url, "outline_color")
+
+		elseif gui.pick_node(self.fluid, action.x, action.y) then
+
+			msg.post(script_url, "fluid_color")
+
+		end
+	end
+end

+ 39 - 0
material/custom_sprite/example/recolor.fp

@@ -0,0 +1,39 @@
+#version 140
+
+uniform sampler2D texture_sampler;
+uniform f_uniform
+{
+    vec4 tint;
+};
+
+in vec2 var_texcoord0;
+// custom vertex attributes
+in vec4 new_color;
+in vec4 new_outline;
+
+out vec4 final_color;
+
+void main()
+{
+    lowp vec4 tint_pm = vec4(tint.xyz * tint.w, tint.w);
+    lowp vec4 sprite = texture(texture_sampler, var_texcoord0.xy);
+
+    // float values used for comparing
+    lowp float combine = (sprite.r + sprite.g);
+    lowp float greenmul = sprite.g * 2;
+
+    // when 2 channels added together equal the same as a single channel multipled then we have desaturated values
+    if(combine == greenmul){
+        sprite = vec4(sprite.rgb*new_color.rgb,sprite.a);
+    }
+
+    // when the green channel has a value of 1.0 and the w value is 1.0(on) then we color the outline
+    if(new_outline.w >= 1.0 && sprite.g >= 1.0){
+        sprite = vec4(new_outline.rgb,1.0);
+    }
+    else if (sprite.g >= 1.0){ //when the w value is not 1.0 we remove all values. turning the outline off
+        sprite = vec4(0.0, 0.0, 0.0, 0.0);
+    }
+    
+    final_color = vec4(sprite * tint);
+}

+ 44 - 0
material/custom_sprite/example/recolor.material

@@ -0,0 +1,44 @@
+name: "sprite"
+tags: "tile"
+vertex_program: "/example/recolor.vp"
+fragment_program: "/example/recolor.fp"
+vertex_constants {
+  name: "view_proj"
+  type: CONSTANT_TYPE_VIEWPROJ
+}
+fragment_constants {
+  name: "tint"
+  type: CONSTANT_TYPE_USER
+  value {
+    x: 1.0
+    y: 1.0
+    z: 1.0
+    w: 1.0
+  }
+}
+samplers {
+  name: "texture_sampler"
+  wrap_u: WRAP_MODE_CLAMP_TO_EDGE
+  wrap_v: WRAP_MODE_CLAMP_TO_EDGE
+  filter_min: FILTER_MODE_MIN_DEFAULT
+  filter_mag: FILTER_MODE_MAG_DEFAULT
+}
+attributes {
+  name: "newcolor"
+  semantic_type: SEMANTIC_TYPE_COLOR
+  double_values {
+    v: 0.3882
+    v: 0.6078
+    v: 1.0
+    v: 1.0
+  }
+}
+attributes {
+  name: "outline"
+  double_values {
+    v: 0.5
+    v: 0.5
+    v: 0.5
+    v: 0.0
+  }
+}

+ 25 - 0
material/custom_sprite/example/recolor.vp

@@ -0,0 +1,25 @@
+#version 140
+
+uniform v_inputs
+{
+    mat4 view_proj;
+};
+// positions are in world space
+in vec4 position;
+in vec2 texcoord0;
+// custom vertex attributes from material
+in vec4 newcolor;
+in vec4 outline;
+
+out vec2 var_texcoord0;
+// custom vertex attributes sent to fragment program
+out vec4 new_color;
+out vec4 new_outline;
+
+void main()
+{
+    gl_Position = view_proj * vec4(position.xyz, 1.0);
+    var_texcoord0 = texcoord0;
+    new_color = newcolor;
+    new_outline = outline;
+}

+ 41 - 0
material/custom_sprite/example/set_color.script

@@ -0,0 +1,41 @@
+local sprite_to_color = "/new#sprite" local brightness = 0.3
+
+local function random_color(self) -- create a new_color of random-ish float values (0.3 or 1.3)
+	local random_number_r = math.random(0, 1)+brightness
+	local random_number_b = math.random(0, 1)+brightness
+	local random_number_g = math.random(0, 1)+brightness
+	local new_color = vmath.vector4(random_number_r, random_number_g, random_number_b, self.outline_io)
+	return new_color
+end
+
+function init(self)
+	msg.post("@render:", "clear_color", { color = vmath.vector4(0.25960784,0.2315686274509804,0.229607843, 1.0) } )
+	
+	self.outline_io = 0.0 -- float is used when setting the w value of the material vertex attribute "outline" 0.0 = off 1.0 = on
+	
+	math.randomseed(socket.gettime()*10000)
+
+end
+
+function on_message(self, message_id, message)
+
+	if message_id == hash("outline_io") then
+
+		if self.outline_io <= 0.0 then
+			self.outline_io = 1.0 
+		else 
+			self.outline_io = 0.0 
+		end
+		go.set(sprite_to_color, "outline.w", self.outline_io)
+
+	elseif message_id == hash("outline_color") then
+
+		go.set(sprite_to_color, "outline", random_color(self)) -- set color for outline
+
+	elseif	message_id == hash("fluid_color") then
+
+		go.set(sprite_to_color, "newcolor", random_color(self)) -- set color for potion fluid
+
+	end
+
+end

+ 23 - 0
material/custom_sprite/game.project

@@ -0,0 +1,23 @@
+[bootstrap]
+main_collection = /example/custom_sprite.collectionc
+
+[script]
+shared_state = 1
+
+[display]
+width = 720
+height = 720
+
+[android]
+input_method = HiddenInputField
+
+[html5]
+scale_mode = stretch
+
+[project]
+title = custom_sprite_shader
+
+[graphics]
+default_texture_min_filter = nearest
+default_texture_mag_filter = nearest
+

+ 4 - 0
material/custom_sprite/input/game.input_binding

@@ -0,0 +1,4 @@
+mouse_trigger {
+  input: MOUSE_BUTTON_1
+  action: "touch"
+}