Add customizable fur patterns
This commit is contained in:
parent
9729c278dc
commit
1940ae13e5
21 changed files with 705 additions and 49 deletions
Binary file not shown.
BIN
art/exported/characters/patterns/fox/body_arms.png
Normal file
BIN
art/exported/characters/patterns/fox/body_arms.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
40
art/exported/characters/patterns/fox/body_arms.png.import
Normal file
40
art/exported/characters/patterns/fox/body_arms.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://bdwqv502so784"
|
||||||
|
path="res://.godot/imported/body_arms.png-a4119ca84faf9d8bfa9bc0ab6f8ca133.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://art/exported/characters/patterns/fox/body_arms.png"
|
||||||
|
dest_files=["res://.godot/imported/body_arms.png-a4119ca84faf9d8bfa9bc0ab6f8ca133.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=true
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
art/exported/characters/patterns/fox/body_main.png
Normal file
BIN
art/exported/characters/patterns/fox/body_main.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
40
art/exported/characters/patterns/fox/body_main.png.import
Normal file
40
art/exported/characters/patterns/fox/body_main.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://ccbaq4d4nt3bm"
|
||||||
|
path="res://.godot/imported/body_main.png-c83ac58a68a1485819fba3e07de784cd.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://art/exported/characters/patterns/fox/body_main.png"
|
||||||
|
dest_files=["res://.godot/imported/body_main.png-c83ac58a68a1485819fba3e07de784cd.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=true
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
art/exported/characters/patterns/spots_bengal/body_arms.png
Normal file
BIN
art/exported/characters/patterns/spots_bengal/body_arms.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 87 KiB |
|
|
@ -0,0 +1,40 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://m2un0oahhss3"
|
||||||
|
path="res://.godot/imported/body_arms.png-a55eeb0787397c58508a7c65cc2d04f8.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://art/exported/characters/patterns/spots_bengal/body_arms.png"
|
||||||
|
dest_files=["res://.godot/imported/body_arms.png-a55eeb0787397c58508a7c65cc2d04f8.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=true
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
art/exported/characters/patterns/spots_bengal/body_main.png
Normal file
BIN
art/exported/characters/patterns/spots_bengal/body_main.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 174 KiB |
|
|
@ -0,0 +1,40 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://cybpx6ua1vaw"
|
||||||
|
path="res://.godot/imported/body_main.png-eff221076f4e25415158ecf38fdf03da.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://art/exported/characters/patterns/spots_bengal/body_main.png"
|
||||||
|
dest_files=["res://.godot/imported/body_main.png-eff221076f4e25415158ecf38fdf03da.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=true
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
Binary file not shown.
|
|
@ -56,6 +56,10 @@ static func signature_fields(data: Dictionary) -> Array:
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
str(appearance.get("fur_pattern", "")),
|
str(appearance.get("fur_pattern", "")),
|
||||||
|
str(appearance.get("fur_style", "")),
|
||||||
|
str(appearance.get("fur_color_2", "")),
|
||||||
|
str(appearance.get("fur_color_3", "")),
|
||||||
|
str(appearance.get("fur_color_4", "")),
|
||||||
str(appearance.get("ears", "")),
|
str(appearance.get("ears", "")),
|
||||||
str(appearance.get("eyes", "")),
|
str(appearance.get("eyes", "")),
|
||||||
str(appearance.get("nose", "")),
|
str(appearance.get("nose", "")),
|
||||||
|
|
|
||||||
|
|
@ -268,6 +268,10 @@ static func client_profile_fields(data: Dictionary) -> Array:
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
str(appearance.get("fur_pattern", "")),
|
str(appearance.get("fur_pattern", "")),
|
||||||
|
str(appearance.get("fur_style", "")),
|
||||||
|
str(appearance.get("fur_color_2", "")),
|
||||||
|
str(appearance.get("fur_color_3", "")),
|
||||||
|
str(appearance.get("fur_color_4", "")),
|
||||||
str(appearance.get("ears", "")),
|
str(appearance.get("ears", "")),
|
||||||
str(appearance.get("eyes", "")),
|
str(appearance.get("eyes", "")),
|
||||||
str(appearance.get("nose", "")),
|
str(appearance.get("nose", "")),
|
||||||
|
|
|
||||||
27
player/fur_pattern.gdshader
Normal file
27
player/fur_pattern.gdshader
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
shader_type spatial;
|
||||||
|
render_mode unshaded, cull_back;
|
||||||
|
|
||||||
|
// Pattern PNGs are data maps rather than colored artwork. Black selects the
|
||||||
|
// base color and the red, green, and blue channels select the three authored
|
||||||
|
// accent colors. Alpha is intentionally ignored so artists may leave unused
|
||||||
|
// UV space transparent.
|
||||||
|
uniform sampler2D pattern_texture : filter_linear_mipmap_anisotropic, repeat_disable;
|
||||||
|
uniform vec4 base_color : source_color = vec4(1.0);
|
||||||
|
uniform vec4 red_zone_color : source_color = vec4(0.9, 0.8, 0.6, 1.0);
|
||||||
|
uniform vec4 green_zone_color : source_color = vec4(0.4, 0.2, 0.1, 1.0);
|
||||||
|
uniform vec4 blue_zone_color : source_color = vec4(0.7, 0.2, 0.15, 1.0);
|
||||||
|
|
||||||
|
void fragment() {
|
||||||
|
vec3 zone_weights = max(texture(pattern_texture, UV).rgb, vec3(0.0));
|
||||||
|
float zone_total = zone_weights.r + zone_weights.g + zone_weights.b;
|
||||||
|
// Filtering between two authored zone colors can produce a sum near one.
|
||||||
|
// Normalize only an overfull sample, retaining smooth black-to-zone edges.
|
||||||
|
zone_weights /= max(zone_total, 1.0);
|
||||||
|
float base_weight = 1.0 - min(zone_total, 1.0);
|
||||||
|
ALBEDO = (
|
||||||
|
base_color.rgb * base_weight
|
||||||
|
+ red_zone_color.rgb * zone_weights.r
|
||||||
|
+ green_zone_color.rgb * zone_weights.g
|
||||||
|
+ blue_zone_color.rgb * zone_weights.b
|
||||||
|
);
|
||||||
|
}
|
||||||
1
player/fur_pattern.gdshader.uid
Normal file
1
player/fur_pattern.gdshader.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://eoeegcj00qy8
|
||||||
|
|
@ -2,7 +2,11 @@ class_name PlayerVisualPresenter
|
||||||
extends RefCounted
|
extends RefCounted
|
||||||
|
|
||||||
const PlayerScene: PackedScene = preload("res://player/player.tscn")
|
const PlayerScene: PackedScene = preload("res://player/player.tscn")
|
||||||
|
const FurPatternShader: Shader = preload("res://player/fur_pattern.gdshader")
|
||||||
const RUNTIME_MATERIAL_META: StringName = &"netfishing_runtime_material"
|
const RUNTIME_MATERIAL_META: StringName = &"netfishing_runtime_material"
|
||||||
|
const RUNTIME_FUR_PATTERN_MATERIAL_META: StringName = (
|
||||||
|
&"netfishing_runtime_fur_pattern_material"
|
||||||
|
)
|
||||||
const EAR_MESH_NAMES: Array[String] = [
|
const EAR_MESH_NAMES: Array[String] = [
|
||||||
"ears_antlers_round",
|
"ears_antlers_round",
|
||||||
"ears_bear",
|
"ears_bear",
|
||||||
|
|
@ -144,14 +148,17 @@ static func apply_appearance(
|
||||||
var fur_color: Color = CharacterCustomizationCatalog.option_color(
|
var fur_color: Color = CharacterCustomizationCatalog.option_color(
|
||||||
"fur_pattern", str(snapshot.get("fur_pattern", "white"))
|
"fur_pattern", str(snapshot.get("fur_pattern", "white"))
|
||||||
)
|
)
|
||||||
_set_mesh_color(skeleton, "body_arms", fur_color)
|
for fur_mesh_name: String in [
|
||||||
_set_mesh_color(skeleton, "body_main", fur_color)
|
"body_main",
|
||||||
_set_mesh_color(skeleton, "head_pointy", fur_color)
|
"body_arms",
|
||||||
_set_mesh_color(skeleton, "head_round", fur_color)
|
"head_pointy",
|
||||||
|
"head_round",
|
||||||
|
]:
|
||||||
|
_set_fur_mesh(skeleton, fur_mesh_name, snapshot, fur_color)
|
||||||
for ear_name: String in EAR_MESH_NAMES:
|
for ear_name: String in EAR_MESH_NAMES:
|
||||||
_set_mesh_color(skeleton, ear_name, fur_color)
|
_set_fur_mesh(skeleton, ear_name, snapshot, fur_color)
|
||||||
for tail_name: String in TAIL_MESH_NAMES:
|
for tail_name: String in TAIL_MESH_NAMES:
|
||||||
_set_mesh_color(skeleton, tail_name, fur_color)
|
_set_fur_mesh(skeleton, tail_name, snapshot, fur_color)
|
||||||
|
|
||||||
var eye_id: String = CharacterCustomizationCatalog.canonical_option_id(
|
var eye_id: String = CharacterCustomizationCatalog.canonical_option_id(
|
||||||
"eyes", str(snapshot.get("eyes", "simple_shine"))
|
"eyes", str(snapshot.get("eyes", "simple_shine"))
|
||||||
|
|
@ -202,17 +209,60 @@ static func _set_feature_texture(
|
||||||
material.albedo_texture = texture
|
material.albedo_texture = texture
|
||||||
|
|
||||||
|
|
||||||
static func _set_mesh_color(
|
static func _set_fur_mesh(
|
||||||
skeleton: Skeleton3D,
|
skeleton: Skeleton3D,
|
||||||
mesh_name: String,
|
mesh_name: String,
|
||||||
color: Color,
|
snapshot: Dictionary,
|
||||||
|
base_color: Color,
|
||||||
) -> void:
|
) -> void:
|
||||||
var mesh_instance: MeshInstance3D = skeleton.get_node_or_null(
|
var mesh_instance := skeleton.get_node_or_null(mesh_name) as MeshInstance3D
|
||||||
mesh_name
|
if mesh_instance == null:
|
||||||
) as MeshInstance3D
|
return
|
||||||
var material: StandardMaterial3D = _runtime_material(mesh_instance)
|
var style_id := CharacterCustomizationCatalog.canonical_option_id(
|
||||||
if material != null:
|
CharacterCustomizationCatalog.FUR_STYLE_ID,
|
||||||
material.albedo_color = color
|
str(snapshot.get(
|
||||||
|
CharacterCustomizationCatalog.FUR_STYLE_ID,
|
||||||
|
CharacterCustomizationCatalog.DEFAULT_FUR_STYLE,
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
var pattern_texture := CharacterCustomizationCatalog.fur_pattern_texture(
|
||||||
|
style_id,
|
||||||
|
mesh_name,
|
||||||
|
)
|
||||||
|
if pattern_texture == null:
|
||||||
|
var solid_material := _runtime_material(mesh_instance)
|
||||||
|
if solid_material != null:
|
||||||
|
solid_material.albedo_color = base_color
|
||||||
|
mesh_instance.material_override = solid_material
|
||||||
|
return
|
||||||
|
|
||||||
|
var pattern_material := _runtime_fur_pattern_material(mesh_instance)
|
||||||
|
pattern_material.set_shader_parameter("pattern_texture", pattern_texture)
|
||||||
|
pattern_material.set_shader_parameter("base_color", base_color)
|
||||||
|
pattern_material.set_shader_parameter(
|
||||||
|
"red_zone_color",
|
||||||
|
_fur_snapshot_color(snapshot, "fur_color_2", "cream"),
|
||||||
|
)
|
||||||
|
pattern_material.set_shader_parameter(
|
||||||
|
"green_zone_color",
|
||||||
|
_fur_snapshot_color(snapshot, "fur_color_3", "brown"),
|
||||||
|
)
|
||||||
|
pattern_material.set_shader_parameter(
|
||||||
|
"blue_zone_color",
|
||||||
|
_fur_snapshot_color(snapshot, "fur_color_4", "red"),
|
||||||
|
)
|
||||||
|
mesh_instance.material_override = pattern_material
|
||||||
|
|
||||||
|
|
||||||
|
static func _fur_snapshot_color(
|
||||||
|
snapshot: Dictionary,
|
||||||
|
category_id: String,
|
||||||
|
default_id: String,
|
||||||
|
) -> Color:
|
||||||
|
return CharacterCustomizationCatalog.option_color(
|
||||||
|
category_id,
|
||||||
|
str(snapshot.get(category_id, default_id)),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
static func _runtime_material(
|
static func _runtime_material(
|
||||||
|
|
@ -221,7 +271,9 @@ static func _runtime_material(
|
||||||
if mesh_instance == null or mesh_instance.mesh == null:
|
if mesh_instance == null or mesh_instance.mesh == null:
|
||||||
return null
|
return null
|
||||||
if mesh_instance.has_meta(RUNTIME_MATERIAL_META):
|
if mesh_instance.has_meta(RUNTIME_MATERIAL_META):
|
||||||
var runtime := mesh_instance.material_override as StandardMaterial3D
|
var runtime := mesh_instance.get_meta(
|
||||||
|
RUNTIME_MATERIAL_META
|
||||||
|
) as StandardMaterial3D
|
||||||
_configure_matte_material(runtime)
|
_configure_matte_material(runtime)
|
||||||
return runtime
|
return runtime
|
||||||
var source: StandardMaterial3D = mesh_instance.mesh.surface_get_material(
|
var source: StandardMaterial3D = mesh_instance.mesh.surface_get_material(
|
||||||
|
|
@ -232,7 +284,20 @@ static func _runtime_material(
|
||||||
var material: StandardMaterial3D = source.duplicate() as StandardMaterial3D
|
var material: StandardMaterial3D = source.duplicate() as StandardMaterial3D
|
||||||
_configure_matte_material(material)
|
_configure_matte_material(material)
|
||||||
mesh_instance.material_override = material
|
mesh_instance.material_override = material
|
||||||
mesh_instance.set_meta(RUNTIME_MATERIAL_META, true)
|
mesh_instance.set_meta(RUNTIME_MATERIAL_META, material)
|
||||||
|
return material
|
||||||
|
|
||||||
|
|
||||||
|
static func _runtime_fur_pattern_material(
|
||||||
|
mesh_instance: MeshInstance3D,
|
||||||
|
) -> ShaderMaterial:
|
||||||
|
if mesh_instance.has_meta(RUNTIME_FUR_PATTERN_MATERIAL_META):
|
||||||
|
return mesh_instance.get_meta(
|
||||||
|
RUNTIME_FUR_PATTERN_MATERIAL_META
|
||||||
|
) as ShaderMaterial
|
||||||
|
var material := ShaderMaterial.new()
|
||||||
|
material.shader = FurPatternShader
|
||||||
|
mesh_instance.set_meta(RUNTIME_FUR_PATTERN_MATERIAL_META, material)
|
||||||
return material
|
return material
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,28 @@ const CATEGORY_IDS: PackedStringArray = [
|
||||||
"tail",
|
"tail",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# CATEGORY_IDS controls the visible Profile navigation. The additional fur
|
||||||
|
# fields are edited together on the existing fur page, but remain explicit in
|
||||||
|
# saves and signed multiplayer appearance snapshots.
|
||||||
|
const SNAPSHOT_IDS: PackedStringArray = [
|
||||||
|
"species",
|
||||||
|
"scale",
|
||||||
|
"fur_pattern",
|
||||||
|
"fur_style",
|
||||||
|
"fur_color_2",
|
||||||
|
"fur_color_3",
|
||||||
|
"fur_color_4",
|
||||||
|
"ears",
|
||||||
|
"eyes",
|
||||||
|
"nose",
|
||||||
|
"mouth",
|
||||||
|
"tail",
|
||||||
|
]
|
||||||
|
|
||||||
const CATEGORY_LABELS: Dictionary = {
|
const CATEGORY_LABELS: Dictionary = {
|
||||||
"species": "head",
|
"species": "head",
|
||||||
"scale": "size",
|
"scale": "size",
|
||||||
"fur_pattern": "fur color",
|
"fur_pattern": "fur",
|
||||||
"ears": "ears",
|
"ears": "ears",
|
||||||
"eyes": "eyes",
|
"eyes": "eyes",
|
||||||
"nose": "nose",
|
"nose": "nose",
|
||||||
|
|
@ -28,6 +46,20 @@ const MIN_CHARACTER_SCALE: float = 0.5
|
||||||
const MAX_CHARACTER_SCALE: float = 1.5
|
const MAX_CHARACTER_SCALE: float = 1.5
|
||||||
const DEFAULT_CHARACTER_SCALE: float = 1.0
|
const DEFAULT_CHARACTER_SCALE: float = 1.0
|
||||||
const CHARACTER_SCALE_STEP: float = 0.05
|
const CHARACTER_SCALE_STEP: float = 0.05
|
||||||
|
const FUR_STYLE_ID: String = "fur_style"
|
||||||
|
const FUR_COLOR_IDS: PackedStringArray = [
|
||||||
|
"fur_pattern",
|
||||||
|
"fur_color_2",
|
||||||
|
"fur_color_3",
|
||||||
|
"fur_color_4",
|
||||||
|
]
|
||||||
|
const FUR_COLOR_LABELS: Dictionary = {
|
||||||
|
"fur_pattern": "primary",
|
||||||
|
"fur_color_2": "secondary",
|
||||||
|
"fur_color_3": "accent",
|
||||||
|
"fur_color_4": "detail",
|
||||||
|
}
|
||||||
|
const DEFAULT_FUR_STYLE: String = "solid"
|
||||||
|
|
||||||
const FEATURE_CATEGORIES: PackedStringArray = ["eyes", "nose", "mouth"]
|
const FEATURE_CATEGORIES: PackedStringArray = ["eyes", "nose", "mouth"]
|
||||||
const FEATURE_ASSET_ROOTS: Dictionary = {
|
const FEATURE_ASSET_ROOTS: Dictionary = {
|
||||||
|
|
@ -41,28 +73,79 @@ const FEATURE_LABEL_OVERRIDES: Dictionary = {
|
||||||
"dog_round": "round",
|
"dog_round": "round",
|
||||||
"three": "three",
|
"three": "three",
|
||||||
}
|
}
|
||||||
|
const FUR_COLOR_OPTIONS: Array = [
|
||||||
|
{"id": "white", "label": "white", "color": Color("f2f0e8")},
|
||||||
|
{"id": "ivory", "label": "ivory", "color": Color("f0e2c2")},
|
||||||
|
{"id": "cream", "label": "cream", "color": Color("e6d39b")},
|
||||||
|
{"id": "sand", "label": "sand", "color": Color("c6a66f")},
|
||||||
|
{"id": "tan", "label": "tan", "color": Color("ad7c55")},
|
||||||
|
{"id": "taupe", "label": "taupe", "color": Color("89756a")},
|
||||||
|
{"id": "silver", "label": "silver", "color": Color("b8c3c1")},
|
||||||
|
{"id": "gray", "label": "gray", "color": Color("819398")},
|
||||||
|
{"id": "slate", "label": "slate", "color": Color("58686c")},
|
||||||
|
{"id": "charcoal", "label": "charcoal", "color": Color("34444a")},
|
||||||
|
{"id": "black", "label": "black", "color": Color("192126")},
|
||||||
|
{"id": "dark_brown", "label": "dark brown", "color": Color("432a25")},
|
||||||
|
{"id": "chocolate", "label": "chocolate", "color": Color("603b2c")},
|
||||||
|
{"id": "brown", "label": "brown", "color": Color("7b4a32")},
|
||||||
|
{"id": "chestnut", "label": "chestnut", "color": Color("98513d")},
|
||||||
|
{"id": "ginger", "label": "ginger", "color": Color("d28742")},
|
||||||
|
{"id": "orange", "label": "orange", "color": Color("c86c36")},
|
||||||
|
{"id": "red", "label": "red", "color": Color("a9433f")},
|
||||||
|
{"id": "pink", "label": "pink", "color": Color("d8899e")},
|
||||||
|
{"id": "yellow", "label": "yellow", "color": Color("d8c545")},
|
||||||
|
{"id": "gold", "label": "gold", "color": Color("c29a3d")},
|
||||||
|
{"id": "sage", "label": "sage", "color": Color("8fa16b")},
|
||||||
|
{"id": "green", "label": "green", "color": Color("6f913c")},
|
||||||
|
{"id": "forest", "label": "forest", "color": Color("3f6945")},
|
||||||
|
{"id": "aqua", "label": "aqua", "color": Color("69aeb0")},
|
||||||
|
{"id": "teal", "label": "teal", "color": Color("3a8790")},
|
||||||
|
{"id": "blue", "label": "blue", "color": Color("4d76a8")},
|
||||||
|
{"id": "navy", "label": "navy", "color": Color("344b78")},
|
||||||
|
{"id": "lavender", "label": "lavender", "color": Color("9a7eaa")},
|
||||||
|
{"id": "purple", "label": "purple", "color": Color("76558f")},
|
||||||
|
]
|
||||||
const OPTIONS: Dictionary = {
|
const OPTIONS: Dictionary = {
|
||||||
"species": [
|
"species": [
|
||||||
{"id": "round", "label": "round"},
|
{"id": "round", "label": "round"},
|
||||||
{"id": "pointy", "label": "pointy"},
|
{"id": "pointy", "label": "pointy"},
|
||||||
],
|
],
|
||||||
# The serialized field remains `fur_pattern` for save and network
|
# `fur_pattern` remains the primary color key for compatibility with
|
||||||
# compatibility. It holds the initial solid fur-color selection until the
|
# existing appearance saves. The actual authored pattern has its own field.
|
||||||
# authored pattern layer is ready.
|
"fur_pattern": FUR_COLOR_OPTIONS,
|
||||||
"fur_pattern": [
|
"fur_color_2": FUR_COLOR_OPTIONS,
|
||||||
{"id": "white", "label": "white", "color": Color("f2f0e8")},
|
"fur_color_3": FUR_COLOR_OPTIONS,
|
||||||
{"id": "cream", "label": "cream", "color": Color("e6d39b")},
|
"fur_color_4": FUR_COLOR_OPTIONS,
|
||||||
{"id": "gray", "label": "gray", "color": Color("819398")},
|
"fur_style": [
|
||||||
{"id": "charcoal", "label": "charcoal", "color": Color("34444a")},
|
{"id": "solid", "label": "solid"},
|
||||||
{"id": "brown", "label": "brown", "color": Color("7b4a32")},
|
{
|
||||||
{"id": "orange", "label": "orange", "color": Color("c86c36")},
|
"id": "spots_bengal",
|
||||||
{"id": "red", "label": "red", "color": Color("a9433f")},
|
"label": "bengal spots",
|
||||||
{"id": "pink", "label": "pink", "color": Color("d8899e")},
|
"textures": {
|
||||||
{"id": "yellow", "label": "yellow", "color": Color("d8c545")},
|
"body_main": (
|
||||||
{"id": "green", "label": "green", "color": Color("6f913c")},
|
"res://art/exported/characters/patterns/"
|
||||||
{"id": "teal", "label": "teal", "color": Color("3a8790")},
|
+ "spots_bengal/body_main.png"
|
||||||
{"id": "blue", "label": "blue", "color": Color("4d76a8")},
|
),
|
||||||
{"id": "purple", "label": "purple", "color": Color("76558f")},
|
"body_arms": (
|
||||||
|
"res://art/exported/characters/patterns/"
|
||||||
|
+ "spots_bengal/body_arms.png"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fox",
|
||||||
|
"label": "fox",
|
||||||
|
"textures": {
|
||||||
|
"body_main": (
|
||||||
|
"res://art/exported/characters/patterns/"
|
||||||
|
+ "fox/body_main.png"
|
||||||
|
),
|
||||||
|
"body_arms": (
|
||||||
|
"res://art/exported/characters/patterns/"
|
||||||
|
+ "fox/body_arms.png"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
"ears": [
|
"ears": [
|
||||||
{"id": "none", "label": "none"},
|
{"id": "none", "label": "none"},
|
||||||
|
|
@ -96,6 +179,7 @@ const OPTIONS: Dictionary = {
|
||||||
const LEGACY_OPTION_ALIASES: Dictionary = {
|
const LEGACY_OPTION_ALIASES: Dictionary = {
|
||||||
"species": {"default": "round"},
|
"species": {"default": "round"},
|
||||||
"fur_pattern": {"solid": "white"},
|
"fur_pattern": {"solid": "white"},
|
||||||
|
"fur_style": {"bengal": "spots_bengal"},
|
||||||
"ears": {"default": "none"},
|
"ears": {"default": "none"},
|
||||||
"tail": {"default": "none"},
|
"tail": {"default": "none"},
|
||||||
"eyes": {"default": "simple_shine"},
|
"eyes": {"default": "simple_shine"},
|
||||||
|
|
@ -106,6 +190,7 @@ const LEGACY_OPTION_ALIASES: Dictionary = {
|
||||||
static var _feature_assets_ready: bool = false
|
static var _feature_assets_ready: bool = false
|
||||||
static var _feature_options: Dictionary = {}
|
static var _feature_options: Dictionary = {}
|
||||||
static var _feature_textures: Dictionary = {}
|
static var _feature_textures: Dictionary = {}
|
||||||
|
static var _fur_pattern_textures: Dictionary = {}
|
||||||
|
|
||||||
|
|
||||||
static func default_snapshot() -> Dictionary:
|
static func default_snapshot() -> Dictionary:
|
||||||
|
|
@ -114,6 +199,10 @@ static func default_snapshot() -> Dictionary:
|
||||||
"species": "round",
|
"species": "round",
|
||||||
"scale": DEFAULT_CHARACTER_SCALE,
|
"scale": DEFAULT_CHARACTER_SCALE,
|
||||||
"fur_pattern": "white",
|
"fur_pattern": "white",
|
||||||
|
"fur_style": DEFAULT_FUR_STYLE,
|
||||||
|
"fur_color_2": "cream",
|
||||||
|
"fur_color_3": "brown",
|
||||||
|
"fur_color_4": "red",
|
||||||
"ears": "none",
|
"ears": "none",
|
||||||
"eyes": _default_feature_option("eyes", "simple_shine"),
|
"eyes": _default_feature_option("eyes", "simple_shine"),
|
||||||
"nose": _default_feature_option("nose", "dog_round"),
|
"nose": _default_feature_option("nose", "dog_round"),
|
||||||
|
|
@ -343,13 +432,41 @@ static func option_color(category_id: String, option_id: String) -> Color:
|
||||||
return Color.WHITE
|
return Color.WHITE
|
||||||
|
|
||||||
|
|
||||||
|
static func fur_color_label(category_id: String) -> String:
|
||||||
|
return str(FUR_COLOR_LABELS.get(category_id, category_id))
|
||||||
|
|
||||||
|
|
||||||
|
static func fur_pattern_texture(
|
||||||
|
option_id: String,
|
||||||
|
component_id: String = "body_main",
|
||||||
|
) -> Texture2D:
|
||||||
|
var canonical_id := canonical_option_id(FUR_STYLE_ID, option_id)
|
||||||
|
if canonical_id == DEFAULT_FUR_STYLE:
|
||||||
|
return null
|
||||||
|
var cache_id := "%s:%s" % [canonical_id, component_id]
|
||||||
|
if _fur_pattern_textures.has(cache_id):
|
||||||
|
return _fur_pattern_textures[cache_id] as Texture2D
|
||||||
|
for option: Dictionary in options_for(FUR_STYLE_ID):
|
||||||
|
if str(option.get("id", "")) != canonical_id:
|
||||||
|
continue
|
||||||
|
var textures := option.get("textures", {}) as Dictionary
|
||||||
|
var resource_path := str(textures.get(component_id, ""))
|
||||||
|
if resource_path.is_empty():
|
||||||
|
return null
|
||||||
|
var texture := ResourceLoader.load(resource_path) as Texture2D
|
||||||
|
if texture != null:
|
||||||
|
_fur_pattern_textures[cache_id] = texture
|
||||||
|
return texture
|
||||||
|
return null
|
||||||
|
|
||||||
|
|
||||||
static func validate_snapshot(value: Variant) -> bool:
|
static func validate_snapshot(value: Variant) -> bool:
|
||||||
if typeof(value) != TYPE_DICTIONARY:
|
if typeof(value) != TYPE_DICTIONARY:
|
||||||
return false
|
return false
|
||||||
var snapshot: Dictionary = value
|
var snapshot: Dictionary = value
|
||||||
if snapshot.size() != CATEGORY_IDS.size():
|
if snapshot.size() != SNAPSHOT_IDS.size():
|
||||||
return false
|
return false
|
||||||
for category_id: String in CATEGORY_IDS:
|
for category_id: String in SNAPSHOT_IDS:
|
||||||
if category_id == SCALE_CATEGORY_ID:
|
if category_id == SCALE_CATEGORY_ID:
|
||||||
if not is_valid_character_scale(snapshot.get(category_id)):
|
if not is_valid_character_scale(snapshot.get(category_id)):
|
||||||
return false
|
return false
|
||||||
|
|
@ -367,7 +484,7 @@ static func sanitized_snapshot(value: Variant) -> Dictionary:
|
||||||
if typeof(value) != TYPE_DICTIONARY:
|
if typeof(value) != TYPE_DICTIONARY:
|
||||||
return result
|
return result
|
||||||
var snapshot: Dictionary = value
|
var snapshot: Dictionary = value
|
||||||
for category_id: String in CATEGORY_IDS:
|
for category_id: String in SNAPSHOT_IDS:
|
||||||
if category_id == SCALE_CATEGORY_ID:
|
if category_id == SCALE_CATEGORY_ID:
|
||||||
result[category_id] = character_scale(
|
result[category_id] = character_scale(
|
||||||
snapshot.get(category_id, DEFAULT_CHARACTER_SCALE)
|
snapshot.get(category_id, DEFAULT_CHARACTER_SCALE)
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ readonly -a QUICK_TESTS=(
|
||||||
"tests/fish_quality_validation.gd"
|
"tests/fish_quality_validation.gd"
|
||||||
"tests/fishing_audio_validation.gd"
|
"tests/fishing_audio_validation.gd"
|
||||||
"tests/fishing_surface_validation.gd"
|
"tests/fishing_surface_validation.gd"
|
||||||
|
"tests/fur_pattern_validation.gd"
|
||||||
"tests/logbook_validation.gd"
|
"tests/logbook_validation.gd"
|
||||||
"tests/player_experience_validation.gd"
|
"tests/player_experience_validation.gd"
|
||||||
"tests/shoreline_ambience_validation.gd"
|
"tests/shoreline_ambience_validation.gd"
|
||||||
|
|
|
||||||
135
tests/fur_pattern_validation.gd
Normal file
135
tests/fur_pattern_validation.gd
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
extends SceneTree
|
||||||
|
|
||||||
|
|
||||||
|
func _initialize() -> void:
|
||||||
|
call_deferred("_run")
|
||||||
|
|
||||||
|
|
||||||
|
func _run() -> void:
|
||||||
|
var legacy_snapshot := CharacterCustomizationCatalog.default_snapshot()
|
||||||
|
for added_id: String in [
|
||||||
|
"fur_style",
|
||||||
|
"fur_color_2",
|
||||||
|
"fur_color_3",
|
||||||
|
"fur_color_4",
|
||||||
|
]:
|
||||||
|
legacy_snapshot.erase(added_id)
|
||||||
|
var migrated := CharacterCustomizationCatalog.sanitized_snapshot(
|
||||||
|
legacy_snapshot
|
||||||
|
)
|
||||||
|
assert(CharacterCustomizationCatalog.validate_snapshot(migrated))
|
||||||
|
assert(migrated["fur_style"] == "solid")
|
||||||
|
var fur_colors := CharacterCustomizationCatalog.options_for("fur_pattern")
|
||||||
|
assert(fur_colors.size() == 30)
|
||||||
|
for natural_color_id: String in [
|
||||||
|
"ivory",
|
||||||
|
"sand",
|
||||||
|
"tan",
|
||||||
|
"taupe",
|
||||||
|
"black",
|
||||||
|
"dark_brown",
|
||||||
|
"chocolate",
|
||||||
|
"chestnut",
|
||||||
|
"ginger",
|
||||||
|
"sage",
|
||||||
|
"forest",
|
||||||
|
]:
|
||||||
|
assert(CharacterCustomizationCatalog.is_valid_option(
|
||||||
|
"fur_pattern",
|
||||||
|
natural_color_id,
|
||||||
|
))
|
||||||
|
|
||||||
|
var pattern_texture := CharacterCustomizationCatalog.fur_pattern_texture(
|
||||||
|
"spots_bengal"
|
||||||
|
)
|
||||||
|
assert(pattern_texture != null)
|
||||||
|
assert(pattern_texture.get_width() == 1024)
|
||||||
|
assert(pattern_texture.get_height() == 1024)
|
||||||
|
var arm_pattern_texture := CharacterCustomizationCatalog.fur_pattern_texture(
|
||||||
|
"spots_bengal",
|
||||||
|
"body_arms",
|
||||||
|
)
|
||||||
|
assert(arm_pattern_texture != null)
|
||||||
|
assert(arm_pattern_texture.get_width() == 1024)
|
||||||
|
assert(arm_pattern_texture.get_height() == 1024)
|
||||||
|
var fox_pattern_texture := CharacterCustomizationCatalog.fur_pattern_texture(
|
||||||
|
"fox",
|
||||||
|
"body_main",
|
||||||
|
)
|
||||||
|
assert(fox_pattern_texture != null)
|
||||||
|
assert(fox_pattern_texture.get_width() == 1024)
|
||||||
|
assert(fox_pattern_texture.get_height() == 1024)
|
||||||
|
var fox_arm_pattern_texture := (
|
||||||
|
CharacterCustomizationCatalog.fur_pattern_texture(
|
||||||
|
"fox", "body_arms"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert(fox_arm_pattern_texture != null)
|
||||||
|
assert(fox_arm_pattern_texture.get_width() == 1024)
|
||||||
|
assert(fox_arm_pattern_texture.get_height() == 1024)
|
||||||
|
assert(
|
||||||
|
CharacterCustomizationCatalog.fur_pattern_texture(
|
||||||
|
"spots_bengal",
|
||||||
|
"head_round",
|
||||||
|
) == null
|
||||||
|
)
|
||||||
|
|
||||||
|
var patterned := migrated.duplicate(true)
|
||||||
|
patterned["fur_pattern"] = "orange"
|
||||||
|
patterned["fur_style"] = "spots_bengal"
|
||||||
|
patterned["fur_color_2"] = "cream"
|
||||||
|
patterned["fur_color_3"] = "brown"
|
||||||
|
patterned["fur_color_4"] = "red"
|
||||||
|
assert(CharacterCustomizationCatalog.validate_snapshot(patterned))
|
||||||
|
|
||||||
|
var visuals := PlayerVisualPresenter.instantiate_visuals()
|
||||||
|
root.add_child(visuals)
|
||||||
|
PlayerVisualPresenter.apply_appearance(visuals, patterned)
|
||||||
|
var skeleton := visuals.find_child("Skeleton3D", true, false) as Skeleton3D
|
||||||
|
assert(skeleton != null)
|
||||||
|
var body := skeleton.get_node_or_null("body_main") as MeshInstance3D
|
||||||
|
assert(body != null)
|
||||||
|
var body_arrays := body.mesh.surface_get_arrays(0)
|
||||||
|
var body_uvs := body_arrays[Mesh.ARRAY_TEX_UV] as PackedVector2Array
|
||||||
|
assert(not body_uvs.is_empty())
|
||||||
|
var uv_min := Vector2(INF, INF)
|
||||||
|
var uv_max := Vector2(-INF, -INF)
|
||||||
|
for uv: Vector2 in body_uvs:
|
||||||
|
uv_min = uv_min.min(uv)
|
||||||
|
uv_max = uv_max.max(uv)
|
||||||
|
assert(uv_min.x < 0.05 and uv_min.y < 0.05)
|
||||||
|
assert(uv_max.x > 0.95 and uv_max.y > 0.95)
|
||||||
|
var material := body.material_override as ShaderMaterial
|
||||||
|
assert(material != null)
|
||||||
|
assert(material.shader == preload("res://player/fur_pattern.gdshader"))
|
||||||
|
assert(material.get_shader_parameter("pattern_texture") == pattern_texture)
|
||||||
|
assert(material.get_shader_parameter("base_color") == Color("c86c36"))
|
||||||
|
assert(material.get_shader_parameter("red_zone_color") == Color("e6d39b"))
|
||||||
|
assert(material.get_shader_parameter("green_zone_color") == Color("7b4a32"))
|
||||||
|
assert(material.get_shader_parameter("blue_zone_color") == Color("a9433f"))
|
||||||
|
var arms := skeleton.get_node_or_null("body_arms") as MeshInstance3D
|
||||||
|
assert(arms != null)
|
||||||
|
var arm_material := arms.material_override as ShaderMaterial
|
||||||
|
assert(arm_material != null)
|
||||||
|
assert(
|
||||||
|
arm_material.get_shader_parameter("pattern_texture")
|
||||||
|
== arm_pattern_texture
|
||||||
|
)
|
||||||
|
var round_head := skeleton.get_node_or_null("head_round") as MeshInstance3D
|
||||||
|
assert(round_head != null)
|
||||||
|
assert(round_head.material_override is StandardMaterial3D)
|
||||||
|
|
||||||
|
var recolored := patterned.duplicate(true)
|
||||||
|
recolored["fur_color_3"] = "teal"
|
||||||
|
assert(
|
||||||
|
NetworkProfileProtocol.signature_fields({"appearance": patterned})
|
||||||
|
!= NetworkProfileProtocol.signature_fields({"appearance": recolored})
|
||||||
|
)
|
||||||
|
|
||||||
|
PlayerVisualPresenter.apply_appearance(visuals, migrated)
|
||||||
|
assert(body.material_override is StandardMaterial3D)
|
||||||
|
|
||||||
|
print("Fur pattern validation: PASS")
|
||||||
|
visuals.queue_free()
|
||||||
|
await process_frame
|
||||||
|
quit()
|
||||||
1
tests/fur_pattern_validation.gd.uid
Normal file
1
tests/fur_pattern_validation.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://bwdmyn34biahx
|
||||||
|
|
@ -3,6 +3,8 @@ extends Control
|
||||||
|
|
||||||
const CHECK_DEBOUNCE_SECONDS: float = 0.4
|
const CHECK_DEBOUNCE_SECONDS: float = 0.4
|
||||||
const OPTION_GRID_COLUMNS: int = 6
|
const OPTION_GRID_COLUMNS: int = 6
|
||||||
|
const FUR_PALETTE_GRID_COLUMNS: int = 10
|
||||||
|
const FUR_CHANNEL_ICON_SIZE: int = 20
|
||||||
const FEATURE_DRAWER_ANIMATION_SECONDS: float = 0.16
|
const FEATURE_DRAWER_ANIMATION_SECONDS: float = 0.16
|
||||||
const ControllerMappingManagerType = preload(
|
const ControllerMappingManagerType = preload(
|
||||||
"res://settings/controller_mapping_manager.gd"
|
"res://settings/controller_mapping_manager.gd"
|
||||||
|
|
@ -48,11 +50,13 @@ var _experience_level: Label
|
||||||
var _experience_progress: ProgressBar
|
var _experience_progress: ProgressBar
|
||||||
var _experience_value: Label
|
var _experience_value: Label
|
||||||
var _feature_preview_cache: Dictionary = {}
|
var _feature_preview_cache: Dictionary = {}
|
||||||
|
var _fur_swatch_icon_cache: Dictionary = {}
|
||||||
var _expanded_feature_drawers: Dictionary = {}
|
var _expanded_feature_drawers: Dictionary = {}
|
||||||
var _feature_drawer_animation_key: String = ""
|
var _feature_drawer_animation_key: String = ""
|
||||||
var _scale_value_label: Label
|
var _scale_value_label: Label
|
||||||
var _voice_preview: AnimaleseVoiceType
|
var _voice_preview: AnimaleseVoiceType
|
||||||
var _voice_preview_tween: Tween
|
var _voice_preview_tween: Tween
|
||||||
|
var _active_fur_color_id: String = "fur_pattern"
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
|
@ -882,30 +886,167 @@ func _feature_preview_texture(
|
||||||
|
|
||||||
|
|
||||||
func _build_fur_color_options(options: Array) -> void:
|
func _build_fur_color_options(options: Array) -> void:
|
||||||
var grid := GridContainer.new()
|
var pattern_label := Label.new()
|
||||||
grid.columns = OPTION_GRID_COLUMNS
|
pattern_label.text = "pattern"
|
||||||
grid.add_theme_constant_override("h_separation", 10)
|
pattern_label.add_theme_color_override(
|
||||||
grid.add_theme_constant_override("v_separation", 10)
|
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY
|
||||||
_option_list.add_child(grid)
|
|
||||||
var selected_id: String = CharacterCustomizationCatalog.canonical_option_id(
|
|
||||||
"fur_pattern", str(_draft_appearance.get("fur_pattern", "white"))
|
|
||||||
)
|
)
|
||||||
|
_option_list.add_child(pattern_label)
|
||||||
|
var pattern_row := HBoxContainer.new()
|
||||||
|
pattern_row.add_theme_constant_override("separation", 8)
|
||||||
|
_option_list.add_child(pattern_row)
|
||||||
|
var selected_style_id := str(_draft_appearance.get(
|
||||||
|
CharacterCustomizationCatalog.FUR_STYLE_ID,
|
||||||
|
CharacterCustomizationCatalog.DEFAULT_FUR_STYLE,
|
||||||
|
))
|
||||||
|
for pattern_option: Dictionary in CharacterCustomizationCatalog.options_for(
|
||||||
|
CharacterCustomizationCatalog.FUR_STYLE_ID
|
||||||
|
):
|
||||||
|
var pattern_id := str(pattern_option.get("id", ""))
|
||||||
|
var pattern_button := Button.new()
|
||||||
|
pattern_button.text = str(pattern_option.get("label", pattern_id))
|
||||||
|
pattern_button.toggle_mode = true
|
||||||
|
pattern_button.button_pressed = selected_style_id == pattern_id
|
||||||
|
pattern_button.custom_minimum_size = Vector2(118.0, 32.0)
|
||||||
|
pattern_button.pressed.connect(_select_option.bind(
|
||||||
|
CharacterCustomizationCatalog.FUR_STYLE_ID,
|
||||||
|
pattern_id,
|
||||||
|
))
|
||||||
|
UtilityPageStyle.apply_compact_ocean_button(pattern_button)
|
||||||
|
pattern_row.add_child(pattern_button)
|
||||||
|
|
||||||
|
var channel_label := Label.new()
|
||||||
|
channel_label.text = "color channel"
|
||||||
|
channel_label.add_theme_color_override(
|
||||||
|
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY
|
||||||
|
)
|
||||||
|
_option_list.add_child(channel_label)
|
||||||
|
var channel_grid := GridContainer.new()
|
||||||
|
channel_grid.columns = 2
|
||||||
|
channel_grid.add_theme_constant_override("h_separation", 8)
|
||||||
|
channel_grid.add_theme_constant_override("v_separation", 8)
|
||||||
|
_option_list.add_child(channel_grid)
|
||||||
|
for color_category_id: String in CharacterCustomizationCatalog.FUR_COLOR_IDS:
|
||||||
|
var selected_color_id := str(_draft_appearance.get(
|
||||||
|
color_category_id,
|
||||||
|
"white",
|
||||||
|
))
|
||||||
|
var selected_color := CharacterCustomizationCatalog.option_color(
|
||||||
|
color_category_id,
|
||||||
|
selected_color_id,
|
||||||
|
)
|
||||||
|
var channel_button := Button.new()
|
||||||
|
channel_button.text = CharacterCustomizationCatalog.fur_color_label(
|
||||||
|
color_category_id
|
||||||
|
)
|
||||||
|
channel_button.icon = _fur_swatch_icon(selected_color)
|
||||||
|
channel_button.icon_alignment = HORIZONTAL_ALIGNMENT_LEFT
|
||||||
|
channel_button.alignment = HORIZONTAL_ALIGNMENT_LEFT
|
||||||
|
channel_button.add_theme_constant_override(
|
||||||
|
"icon_max_width", FUR_CHANNEL_ICON_SIZE
|
||||||
|
)
|
||||||
|
channel_button.add_theme_constant_override("h_separation", 8)
|
||||||
|
channel_button.tooltip_text = "Edit %s: %s" % [
|
||||||
|
CharacterCustomizationCatalog.fur_color_label(color_category_id),
|
||||||
|
_fur_option_label(options, selected_color_id),
|
||||||
|
]
|
||||||
|
channel_button.toggle_mode = true
|
||||||
|
channel_button.custom_minimum_size = Vector2(140.0, 34.0)
|
||||||
|
channel_button.button_pressed = (
|
||||||
|
_active_fur_color_id == color_category_id
|
||||||
|
)
|
||||||
|
channel_button.pressed.connect(
|
||||||
|
_select_fur_color_slot.bind(color_category_id)
|
||||||
|
)
|
||||||
|
UtilityPageStyle.apply_compact_ocean_button(channel_button)
|
||||||
|
channel_grid.add_child(channel_button)
|
||||||
|
|
||||||
|
_build_fur_palette(_active_fur_color_id, options)
|
||||||
|
|
||||||
|
|
||||||
|
func _build_fur_palette(category_id: String, options: Array) -> void:
|
||||||
|
var selected_id: String = CharacterCustomizationCatalog.canonical_option_id(
|
||||||
|
category_id,
|
||||||
|
str(_draft_appearance.get(category_id, "white")),
|
||||||
|
)
|
||||||
|
var label := Label.new()
|
||||||
|
label.text = "%s palette" % CharacterCustomizationCatalog.fur_color_label(
|
||||||
|
category_id
|
||||||
|
)
|
||||||
|
label.add_theme_font_size_override("font_size", 14)
|
||||||
|
label.add_theme_color_override(
|
||||||
|
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY
|
||||||
|
)
|
||||||
|
_option_list.add_child(label)
|
||||||
|
|
||||||
|
var grid := GridContainer.new()
|
||||||
|
grid.columns = FUR_PALETTE_GRID_COLUMNS
|
||||||
|
grid.add_theme_constant_override("h_separation", 6)
|
||||||
|
grid.add_theme_constant_override("v_separation", 6)
|
||||||
|
_option_list.add_child(grid)
|
||||||
for option: Dictionary in options:
|
for option: Dictionary in options:
|
||||||
var option_id: String = str(option.get("id", ""))
|
var option_id: String = str(option.get("id", ""))
|
||||||
var color: Color = CharacterCustomizationCatalog.option_color(
|
var color: Color = CharacterCustomizationCatalog.option_color(
|
||||||
"fur_pattern", option_id
|
category_id, option_id
|
||||||
)
|
)
|
||||||
var button := Button.new()
|
var button := Button.new()
|
||||||
button.text = ""
|
button.text = ""
|
||||||
button.tooltip_text = str(option.get("label", option_id))
|
button.tooltip_text = "%s: %s" % [
|
||||||
|
CharacterCustomizationCatalog.fur_color_label(category_id),
|
||||||
|
str(option.get("label", option_id)),
|
||||||
|
]
|
||||||
button.toggle_mode = true
|
button.toggle_mode = true
|
||||||
button.custom_minimum_size = Vector2(44.0, 44.0)
|
button.custom_minimum_size = Vector2(30.0, 30.0)
|
||||||
button.button_pressed = selected_id == option_id
|
button.button_pressed = selected_id == option_id
|
||||||
button.pressed.connect(_select_option.bind("fur_pattern", option_id))
|
button.pressed.connect(_select_option.bind(category_id, option_id))
|
||||||
_apply_fur_swatch_style(button, color, button.button_pressed)
|
_apply_fur_swatch_style(button, color, button.button_pressed)
|
||||||
grid.add_child(button)
|
grid.add_child(button)
|
||||||
|
|
||||||
|
|
||||||
|
func _fur_option_label(options: Array, option_id: String) -> String:
|
||||||
|
for option: Dictionary in options:
|
||||||
|
if str(option.get("id", "")) == option_id:
|
||||||
|
return str(option.get("label", option_id))
|
||||||
|
return option_id
|
||||||
|
|
||||||
|
|
||||||
|
func _fur_swatch_icon(color: Color) -> Texture2D:
|
||||||
|
var cache_id := color.to_html(true)
|
||||||
|
if _fur_swatch_icon_cache.has(cache_id):
|
||||||
|
return _fur_swatch_icon_cache[cache_id] as Texture2D
|
||||||
|
var image := Image.create(
|
||||||
|
FUR_CHANNEL_ICON_SIZE,
|
||||||
|
FUR_CHANNEL_ICON_SIZE,
|
||||||
|
false,
|
||||||
|
Image.FORMAT_RGBA8,
|
||||||
|
)
|
||||||
|
var center := Vector2.ONE * (float(FUR_CHANNEL_ICON_SIZE) * 0.5)
|
||||||
|
var radius := float(FUR_CHANNEL_ICON_SIZE) * 0.39
|
||||||
|
for y: int in range(FUR_CHANNEL_ICON_SIZE):
|
||||||
|
for x: int in range(FUR_CHANNEL_ICON_SIZE):
|
||||||
|
var sample_position := Vector2(float(x) + 0.5, float(y) + 0.5)
|
||||||
|
var alpha := clampf(
|
||||||
|
radius + 0.8 - sample_position.distance_to(center),
|
||||||
|
0.0,
|
||||||
|
1.0,
|
||||||
|
)
|
||||||
|
image.set_pixel(
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
Color(color.r, color.g, color.b, color.a * alpha),
|
||||||
|
)
|
||||||
|
var texture := ImageTexture.create_from_image(image)
|
||||||
|
_fur_swatch_icon_cache[cache_id] = texture
|
||||||
|
return texture
|
||||||
|
|
||||||
|
|
||||||
|
func _select_fur_color_slot(category_id: String) -> void:
|
||||||
|
if category_id not in CharacterCustomizationCatalog.FUR_COLOR_IDS:
|
||||||
|
return
|
||||||
|
_active_fur_color_id = category_id
|
||||||
|
_refresh_options()
|
||||||
|
|
||||||
|
|
||||||
func _apply_fur_swatch_style(
|
func _apply_fur_swatch_style(
|
||||||
button: Button,
|
button: Button,
|
||||||
color: Color,
|
color: Color,
|
||||||
|
|
|
||||||
|
|
@ -143,7 +143,7 @@ func _notification(what: int) -> void:
|
||||||
|
|
||||||
|
|
||||||
func _rotate(amount: float) -> void:
|
func _rotate(amount: float) -> void:
|
||||||
_camera_yaw = fposmod(_camera_yaw + amount, TAU)
|
_camera_yaw = fposmod(_camera_yaw - amount, TAU)
|
||||||
_apply_camera_orbit()
|
_apply_camera_orbit()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue