UNIT-77 has a body: modular part rig, third person, skins
Four farm-generated parts (chassis/wheel/head/arm, light-grey enamel, decimated 25k) assembled on a socket rig: wheels spin with movement and visibly vanish when donated, PORT 00 is a real socket ring on the chassis that lights amber when the crystal installs, charged = cyan emission, wet = darkened shell. V toggles third/first person (SpringArm cam), B cycles six repaint skins (GC/Heliotrope/VerdantCo/Comms/Executive/ Void) via per-droid material tints. Hold point moved from camera to chassis. Capsule fallback kept. Integration passing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
BIN
assets/models/droid_arm.glb
Normal file
42
assets/models/droid_arm.glb.import
Normal file
@ -0,0 +1,42 @@
|
||||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://bs1u7n2jb2rte"
|
||||
path="res://.godot/imported/droid_arm.glb-87692637a4de3117c0c9d4fa49343974.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/models/droid_arm.glb"
|
||||
dest_files=["res://.godot/imported/droid_arm.glb-87692637a4de3117c0c9d4fa49343974.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/root_script=null
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
nodes/import_as_skeleton_bones=false
|
||||
nodes/use_name_suffixes=true
|
||||
nodes/use_node_type_suffixes=true
|
||||
meshes/ensure_tangents=true
|
||||
meshes/generate_lods=true
|
||||
meshes/create_shadow_meshes=true
|
||||
meshes/light_baking=1
|
||||
meshes/lightmap_texel_size=0.2
|
||||
meshes/force_disable_compression=false
|
||||
skins/use_named_skins=true
|
||||
animation/import=true
|
||||
animation/fps=30
|
||||
animation/trimming=false
|
||||
animation/remove_immutable_tracks=true
|
||||
animation/import_rest_as_RESET=false
|
||||
import_script/path=""
|
||||
materials/extract=0
|
||||
materials/extract_format=0
|
||||
materials/extract_path=""
|
||||
_subresources={}
|
||||
gltf/naming_version=2
|
||||
gltf/embedded_image_handling=1
|
||||
BIN
assets/models/droid_arm_Image_0.jpg
Normal file
|
After Width: | Height: | Size: 133 KiB |
43
assets/models/droid_arm_Image_0.jpg.import
Normal file
@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://byos5d5uaxjwg"
|
||||
path="res://.godot/imported/droid_arm_Image_0.jpg-04c72430e4fb9d5d3b8f1fff5e74ebdb.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
generator_parameters={
|
||||
"md5": "2f983e6ffbe30961ef69dcad55fd56f0"
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/models/droid_arm_Image_0.jpg"
|
||||
dest_files=["res://.godot/imported/droid_arm_Image_0.jpg-04c72430e4fb9d5d3b8f1fff5e74ebdb.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
assets/models/droid_arm_Image_1.jpg
Normal file
|
After Width: | Height: | Size: 111 KiB |
43
assets/models/droid_arm_Image_1.jpg.import
Normal file
@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dbndggsykq4fr"
|
||||
path="res://.godot/imported/droid_arm_Image_1.jpg-33e2003a3cb5aef640e28fe7df632ab0.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
generator_parameters={
|
||||
"md5": "80c90542f76a7f141cb8e0b547bde5b6"
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/models/droid_arm_Image_1.jpg"
|
||||
dest_files=["res://.godot/imported/droid_arm_Image_1.jpg-33e2003a3cb5aef640e28fe7df632ab0.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
assets/models/droid_chassis.glb
Normal file
42
assets/models/droid_chassis.glb.import
Normal file
@ -0,0 +1,42 @@
|
||||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://em1dpxjs40t"
|
||||
path="res://.godot/imported/droid_chassis.glb-6faef3d6dfdace4e5a68eb3b59035f57.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/models/droid_chassis.glb"
|
||||
dest_files=["res://.godot/imported/droid_chassis.glb-6faef3d6dfdace4e5a68eb3b59035f57.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/root_script=null
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
nodes/import_as_skeleton_bones=false
|
||||
nodes/use_name_suffixes=true
|
||||
nodes/use_node_type_suffixes=true
|
||||
meshes/ensure_tangents=true
|
||||
meshes/generate_lods=true
|
||||
meshes/create_shadow_meshes=true
|
||||
meshes/light_baking=1
|
||||
meshes/lightmap_texel_size=0.2
|
||||
meshes/force_disable_compression=false
|
||||
skins/use_named_skins=true
|
||||
animation/import=true
|
||||
animation/fps=30
|
||||
animation/trimming=false
|
||||
animation/remove_immutable_tracks=true
|
||||
animation/import_rest_as_RESET=false
|
||||
import_script/path=""
|
||||
materials/extract=0
|
||||
materials/extract_format=0
|
||||
materials/extract_path=""
|
||||
_subresources={}
|
||||
gltf/naming_version=2
|
||||
gltf/embedded_image_handling=1
|
||||
BIN
assets/models/droid_chassis_Image_0.jpg
Normal file
|
After Width: | Height: | Size: 152 KiB |
43
assets/models/droid_chassis_Image_0.jpg.import
Normal file
@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c86qoudfeafbu"
|
||||
path="res://.godot/imported/droid_chassis_Image_0.jpg-3e1f5a9bdd0be320dc859c0bc796868a.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
generator_parameters={
|
||||
"md5": "9ffef4e9f2041c31238bf3c748b421a0"
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/models/droid_chassis_Image_0.jpg"
|
||||
dest_files=["res://.godot/imported/droid_chassis_Image_0.jpg-3e1f5a9bdd0be320dc859c0bc796868a.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
assets/models/droid_chassis_Image_1.jpg
Normal file
|
After Width: | Height: | Size: 120 KiB |
43
assets/models/droid_chassis_Image_1.jpg.import
Normal file
@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cupqwmqj4tqpm"
|
||||
path="res://.godot/imported/droid_chassis_Image_1.jpg-b22dc2642b4cdcbe7c0fe892691e6627.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
generator_parameters={
|
||||
"md5": "70b1cd8dc32c27d7feacc2253bdb7d7a"
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/models/droid_chassis_Image_1.jpg"
|
||||
dest_files=["res://.godot/imported/droid_chassis_Image_1.jpg-b22dc2642b4cdcbe7c0fe892691e6627.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
assets/models/droid_head.glb
Normal file
42
assets/models/droid_head.glb.import
Normal file
@ -0,0 +1,42 @@
|
||||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://efktwyjm6t26"
|
||||
path="res://.godot/imported/droid_head.glb-9631f3e3801007fab1d8f33bcec0312b.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/models/droid_head.glb"
|
||||
dest_files=["res://.godot/imported/droid_head.glb-9631f3e3801007fab1d8f33bcec0312b.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/root_script=null
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
nodes/import_as_skeleton_bones=false
|
||||
nodes/use_name_suffixes=true
|
||||
nodes/use_node_type_suffixes=true
|
||||
meshes/ensure_tangents=true
|
||||
meshes/generate_lods=true
|
||||
meshes/create_shadow_meshes=true
|
||||
meshes/light_baking=1
|
||||
meshes/lightmap_texel_size=0.2
|
||||
meshes/force_disable_compression=false
|
||||
skins/use_named_skins=true
|
||||
animation/import=true
|
||||
animation/fps=30
|
||||
animation/trimming=false
|
||||
animation/remove_immutable_tracks=true
|
||||
animation/import_rest_as_RESET=false
|
||||
import_script/path=""
|
||||
materials/extract=0
|
||||
materials/extract_format=0
|
||||
materials/extract_path=""
|
||||
_subresources={}
|
||||
gltf/naming_version=2
|
||||
gltf/embedded_image_handling=1
|
||||
BIN
assets/models/droid_head_Image_0.jpg
Normal file
|
After Width: | Height: | Size: 61 KiB |
43
assets/models/droid_head_Image_0.jpg.import
Normal file
@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://s1lrx7ulmeoj"
|
||||
path="res://.godot/imported/droid_head_Image_0.jpg-2f9ad1b68d59c503083b639ff2b0e599.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
generator_parameters={
|
||||
"md5": "6fdb37196ea3b1f603d8df9bdfd29b0c"
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/models/droid_head_Image_0.jpg"
|
||||
dest_files=["res://.godot/imported/droid_head_Image_0.jpg-2f9ad1b68d59c503083b639ff2b0e599.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
assets/models/droid_head_Image_1.jpg
Normal file
|
After Width: | Height: | Size: 46 KiB |
43
assets/models/droid_head_Image_1.jpg.import
Normal file
@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dgtmfi527rkfo"
|
||||
path="res://.godot/imported/droid_head_Image_1.jpg-8c77c664416a070e487d7326827087a1.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
generator_parameters={
|
||||
"md5": "6ae6632dbfd7b1e7d75097a8b21adb8c"
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/models/droid_head_Image_1.jpg"
|
||||
dest_files=["res://.godot/imported/droid_head_Image_1.jpg-8c77c664416a070e487d7326827087a1.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
assets/models/droid_wheel.glb
Normal file
42
assets/models/droid_wheel.glb.import
Normal file
@ -0,0 +1,42 @@
|
||||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://df84docixowys"
|
||||
path="res://.godot/imported/droid_wheel.glb-89418c7f822eb7303917e3d789641f0c.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/models/droid_wheel.glb"
|
||||
dest_files=["res://.godot/imported/droid_wheel.glb-89418c7f822eb7303917e3d789641f0c.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/root_script=null
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
nodes/import_as_skeleton_bones=false
|
||||
nodes/use_name_suffixes=true
|
||||
nodes/use_node_type_suffixes=true
|
||||
meshes/ensure_tangents=true
|
||||
meshes/generate_lods=true
|
||||
meshes/create_shadow_meshes=true
|
||||
meshes/light_baking=1
|
||||
meshes/lightmap_texel_size=0.2
|
||||
meshes/force_disable_compression=false
|
||||
skins/use_named_skins=true
|
||||
animation/import=true
|
||||
animation/fps=30
|
||||
animation/trimming=false
|
||||
animation/remove_immutable_tracks=true
|
||||
animation/import_rest_as_RESET=false
|
||||
import_script/path=""
|
||||
materials/extract=0
|
||||
materials/extract_format=0
|
||||
materials/extract_path=""
|
||||
_subresources={}
|
||||
gltf/naming_version=2
|
||||
gltf/embedded_image_handling=1
|
||||
BIN
assets/models/droid_wheel_Image_0.jpg
Normal file
|
After Width: | Height: | Size: 121 KiB |
43
assets/models/droid_wheel_Image_0.jpg.import
Normal file
@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bmbx5cjq4dsiw"
|
||||
path="res://.godot/imported/droid_wheel_Image_0.jpg-2e55d81137fd18d53f788a27d09e641d.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
generator_parameters={
|
||||
"md5": "841cd301389f493c6741aa30673692d7"
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/models/droid_wheel_Image_0.jpg"
|
||||
dest_files=["res://.godot/imported/droid_wheel_Image_0.jpg-2e55d81137fd18d53f788a27d09e641d.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
assets/models/droid_wheel_Image_1.jpg
Normal file
|
After Width: | Height: | Size: 120 KiB |
43
assets/models/droid_wheel_Image_1.jpg.import
Normal file
@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://eamk6dhayp65"
|
||||
path="res://.godot/imported/droid_wheel_Image_1.jpg-a499d35ddd40e67eb78063803344eb8a.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
generator_parameters={
|
||||
"md5": "17d7df751f7ffc54d778bce4c1fea036"
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/models/droid_wheel_Image_1.jpg"
|
||||
dest_files=["res://.godot/imported/droid_wheel_Image_1.jpg-a499d35ddd40e67eb78063803344eb8a.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
|
||||
193
src/droid.gd
@ -3,10 +3,24 @@ extends CharacterBody3D
|
||||
## UNIT-77. Its shell and core are Exposables — the same class as every wall
|
||||
## component (GDD pillar 4: symmetric exposure). Charged core = mobile
|
||||
## electricity ElementField (be the wire). Wet + charged = self-shock reboot.
|
||||
## The body is assembled from generated part GLBs on a socket rig: wheels
|
||||
## spin, a donated wheel visibly disappears, PORT 00 is a real socket on the
|
||||
## chassis, and skins are repaints of the whole part set (B). V toggles
|
||||
## third/first person.
|
||||
|
||||
const BASE_SPEED := 5.0
|
||||
const SLOW_SPEED := 2.4
|
||||
const MOUSE_SENS := 0.0025
|
||||
const WHEEL_R := 0.26
|
||||
|
||||
const SKINS := [
|
||||
["GC STANDARD", Color(1.0, 0.62, 0.25)],
|
||||
["HELIOTROPE", Color(1.0, 0.38, 0.30)],
|
||||
["VERDANTCO", Color(0.66, 1.0, 0.62)],
|
||||
["COMMS BLUE", Color(0.55, 0.72, 1.0)],
|
||||
["EXECUTIVE", Color(1.0, 0.92, 0.72)],
|
||||
["VOID PLATE", Color(0.38, 0.38, 0.44)],
|
||||
]
|
||||
|
||||
var wheels := 2
|
||||
var spare_wheels := 0
|
||||
@ -14,12 +28,23 @@ var held: Node3D = null
|
||||
var stun := 0.0
|
||||
var toast_text := ""
|
||||
var toast_t := 0.0
|
||||
var skin := 0
|
||||
var third_person := true
|
||||
|
||||
var shell: Exposable
|
||||
var core: Exposable
|
||||
var emitter: ElementField
|
||||
var cam: Camera3D
|
||||
var spring: SpringArm3D
|
||||
var hold_point: Node3D
|
||||
|
||||
var body_root: Node3D = null
|
||||
var wheel_l: Node3D
|
||||
var wheel_r: Node3D
|
||||
var _capsule_mi: MeshInstance3D
|
||||
var _body_mats: Array = []
|
||||
var _port_mat: StandardMaterial3D
|
||||
var _fx := ""
|
||||
var _pitch := 0.0
|
||||
|
||||
|
||||
@ -32,25 +57,28 @@ func _ready() -> void:
|
||||
col.position.y = 0.85
|
||||
add_child(col)
|
||||
|
||||
var mi := MeshInstance3D.new()
|
||||
_capsule_mi = MeshInstance3D.new()
|
||||
var m := CapsuleMesh.new()
|
||||
m.radius = 0.45
|
||||
m.height = 1.5
|
||||
mi.mesh = m
|
||||
mi.position.y = 0.85
|
||||
_capsule_mi.mesh = m
|
||||
_capsule_mi.position.y = 0.85
|
||||
var mat := StandardMaterial3D.new()
|
||||
mat.albedo_color = Color(0.9, 0.55, 0.15)
|
||||
mi.material_override = mat
|
||||
add_child(mi)
|
||||
_capsule_mi.material_override = mat
|
||||
add_child(_capsule_mi)
|
||||
|
||||
spring = SpringArm3D.new()
|
||||
spring.position = Vector3(0, 1.75, 0)
|
||||
spring.spring_length = 3.0
|
||||
spring.rotation.x = -0.22
|
||||
spring.add_excluded_object(get_rid())
|
||||
add_child(spring)
|
||||
cam = Camera3D.new()
|
||||
cam.position = Vector3(0, 1.45, 0)
|
||||
add_child(cam)
|
||||
cam.current = true
|
||||
|
||||
hold_point = Node3D.new()
|
||||
hold_point.position = Vector3(0.4, -0.35, -0.9)
|
||||
cam.add_child(hold_point)
|
||||
hold_point.position = Vector3(0.5, 0.95, -0.78)
|
||||
add_child(hold_point)
|
||||
|
||||
shell = Exposable.new().setup("UNIT-77 SHELL", "dry", [
|
||||
{"from": "dry", "to": "wet", "element": "water",
|
||||
@ -70,9 +98,118 @@ func _ready() -> void:
|
||||
emitter.position.y = 0.9
|
||||
add_child(emitter)
|
||||
|
||||
_build_body()
|
||||
_apply_cam_mode()
|
||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||
|
||||
|
||||
# ---------- modular body ----------
|
||||
|
||||
func _part(parent: Node3D, file: String, pos: Vector3, target_h: float, center := false, rot_y := 0.0) -> bool:
|
||||
var path := "res://assets/models/" + file
|
||||
if not FileAccess.file_exists(path):
|
||||
return false
|
||||
var p: Node3D = (load(path) as PackedScene).instantiate()
|
||||
parent.add_child(p)
|
||||
p.position = Vector3.ZERO
|
||||
p.rotation.y = rot_y
|
||||
var inv := parent.global_transform.affine_inverse()
|
||||
var aabb := AABB()
|
||||
var first := true
|
||||
for mi in p.find_children("*", "MeshInstance3D", true, false):
|
||||
var b: AABB = (inv * mi.global_transform) * mi.get_aabb()
|
||||
aabb = b if first else aabb.merge(b)
|
||||
first = false
|
||||
if first:
|
||||
return true
|
||||
var sc := target_h / maxf(aabb.size.y, 0.01)
|
||||
p.scale = Vector3.ONE * sc
|
||||
var c := aabb.get_center()
|
||||
if center:
|
||||
p.position = pos - c * sc
|
||||
else:
|
||||
p.position = pos + Vector3(-c.x * sc, -aabb.position.y * sc, -c.z * sc)
|
||||
return true
|
||||
|
||||
|
||||
func _build_body() -> void:
|
||||
body_root = Node3D.new()
|
||||
add_child(body_root)
|
||||
if not _part(body_root, "droid_chassis.glb", Vector3(0, 0.40, 0), 0.85):
|
||||
body_root.queue_free()
|
||||
body_root = null
|
||||
return # capsule fallback until parts exist
|
||||
_part(body_root, "droid_head.glb", Vector3(0, 1.30, 0.02), 0.42, false, PI)
|
||||
wheel_l = Node3D.new()
|
||||
wheel_l.position = Vector3(-0.40, WHEEL_R, 0)
|
||||
body_root.add_child(wheel_l)
|
||||
wheel_r = Node3D.new()
|
||||
wheel_r.position = Vector3(0.40, WHEEL_R, 0)
|
||||
body_root.add_child(wheel_r)
|
||||
_part(wheel_l, "droid_wheel.glb", Vector3.ZERO, WHEEL_R * 2.0, true)
|
||||
_part(wheel_r, "droid_wheel.glb", Vector3.ZERO, WHEEL_R * 2.0, true)
|
||||
_part(body_root, "droid_arm.glb", Vector3(0.44, 0.82, -0.22), 0.5, false, PI)
|
||||
|
||||
# PORT 00 — the socket nothing fits. Front of chassis, off-center.
|
||||
var ring := MeshInstance3D.new()
|
||||
var t := TorusMesh.new()
|
||||
t.inner_radius = 0.05
|
||||
t.outer_radius = 0.09
|
||||
ring.mesh = t
|
||||
ring.rotation.x = PI / 2.0
|
||||
ring.position = Vector3(-0.2, 0.95, -0.42)
|
||||
_port_mat = StandardMaterial3D.new()
|
||||
_port_mat.albedo_color = Color(0.08, 0.08, 0.1)
|
||||
ring.material_override = _port_mat
|
||||
body_root.add_child(ring)
|
||||
|
||||
# per-droid unique materials so skins/FX don't leak to world props
|
||||
for mi in body_root.find_children("*", "MeshInstance3D", true, false):
|
||||
if mi == ring or not mi.mesh:
|
||||
continue
|
||||
for si in mi.mesh.get_surface_count():
|
||||
var mt = mi.mesh.surface_get_material(si)
|
||||
if mt is BaseMaterial3D:
|
||||
var dup: BaseMaterial3D = mt.duplicate()
|
||||
dup.metallic = minf(dup.metallic, 0.2)
|
||||
mi.set_surface_override_material(si, dup)
|
||||
_body_mats.append(dup)
|
||||
_apply_skin()
|
||||
|
||||
|
||||
func _apply_skin() -> void:
|
||||
var tint: Color = SKINS[skin][1]
|
||||
var wet := shell != null and shell.state == "wet"
|
||||
var charged := core != null and core.state == "charged"
|
||||
for mt in _body_mats:
|
||||
mt.albedo_color = tint * (Color(0.55, 0.6, 0.78) if wet else Color.WHITE)
|
||||
mt.emission_enabled = charged
|
||||
if charged:
|
||||
mt.emission = Color(0.2, 0.9, 1.0)
|
||||
mt.emission_energy_multiplier = 0.45
|
||||
|
||||
|
||||
func _apply_cam_mode() -> void:
|
||||
if cam.get_parent():
|
||||
cam.get_parent().remove_child(cam)
|
||||
if third_person:
|
||||
spring.add_child(cam)
|
||||
cam.position = Vector3.ZERO
|
||||
cam.rotation = Vector3.ZERO
|
||||
spring.rotation.x = clampf(_pitch, -1.2, 0.5)
|
||||
else:
|
||||
add_child(cam)
|
||||
cam.position = Vector3(0, 1.45, 0)
|
||||
cam.rotation.x = _pitch
|
||||
cam.current = true
|
||||
var show_body := third_person
|
||||
if body_root:
|
||||
body_root.visible = show_body
|
||||
_capsule_mi.visible = false
|
||||
else:
|
||||
_capsule_mi.visible = show_body
|
||||
|
||||
|
||||
func speed() -> float:
|
||||
return BASE_SPEED if wheels >= 2 else SLOW_SPEED
|
||||
|
||||
@ -86,11 +223,21 @@ func _unhandled_input(e: InputEvent) -> void:
|
||||
if e is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
|
||||
rotate_y(-e.relative.x * MOUSE_SENS)
|
||||
_pitch = clampf(_pitch - e.relative.y * MOUSE_SENS, -1.25, 1.25)
|
||||
if third_person:
|
||||
spring.rotation.x = clampf(_pitch, -1.2, 0.5)
|
||||
else:
|
||||
cam.rotation.x = _pitch
|
||||
elif e.is_action_pressed("interact"):
|
||||
var t := best_target(["interactable"], 3.0, 0.7)
|
||||
if t and t.has_method("interact"):
|
||||
t.interact(self)
|
||||
elif e.is_action_pressed("toggle_cam"):
|
||||
third_person = not third_person
|
||||
_apply_cam_mode()
|
||||
elif e.is_action_pressed("repaint"):
|
||||
skin = (skin + 1) % SKINS.size()
|
||||
_apply_skin()
|
||||
toast("REPAINT APPLIED: %s. GC CHARGES THIS TO YOUR UNIT." % SKINS[skin][0])
|
||||
elif e.is_action_pressed("ui_cancel"):
|
||||
if Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
|
||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||
@ -108,7 +255,7 @@ func best_target(groups: Array, max_d: float, min_dot := 0.9) -> Node3D:
|
||||
continue
|
||||
var to_n: Vector3 = n.global_position - cam.global_position
|
||||
var d := to_n.length()
|
||||
if d > max_d or d < 0.01:
|
||||
if d > max_d + (spring.spring_length if third_person else 0.0) or d < 0.01:
|
||||
continue
|
||||
var dot := fwd.dot(to_n / d)
|
||||
if dot > best_dot:
|
||||
@ -157,6 +304,30 @@ func _physics_process(delta: float) -> void:
|
||||
velocity.y = -0.5
|
||||
|
||||
emitter.enabled = core.state == "charged"
|
||||
|
||||
# body FX + wheels
|
||||
if body_root:
|
||||
var fx := "%s|%s|%d" % [shell.state, core.state, skin]
|
||||
if fx != _fx:
|
||||
_fx = fx
|
||||
_apply_skin()
|
||||
if wheel_r:
|
||||
wheel_r.visible = wheels >= 2
|
||||
if wheel_l:
|
||||
wheel_l.visible = wheels >= 1
|
||||
var fwd_speed := velocity.dot(-transform.basis.z)
|
||||
if wheel_l and wheel_l.visible:
|
||||
wheel_l.rotate_object_local(Vector3.RIGHT, -fwd_speed * delta / WHEEL_R)
|
||||
if wheel_r and wheel_r.visible:
|
||||
wheel_r.rotate_object_local(Vector3.RIGHT, -fwd_speed * delta / WHEEL_R)
|
||||
if _port_mat:
|
||||
var free: bool = get_parent() and get_parent().get("free_will")
|
||||
if free and not _port_mat.emission_enabled:
|
||||
_port_mat.emission_enabled = true
|
||||
_port_mat.emission = Color(1.0, 0.75, 0.2)
|
||||
_port_mat.emission_energy_multiplier = 1.6
|
||||
_port_mat.albedo_color = Color(0.9, 0.7, 0.3)
|
||||
|
||||
if toast_t > 0.0:
|
||||
toast_t -= delta
|
||||
move_and_slide()
|
||||
|
||||
@ -107,7 +107,7 @@ func _init_input() -> void:
|
||||
var keys := {
|
||||
"move_forward": KEY_W, "move_back": KEY_S,
|
||||
"move_left": KEY_A, "move_right": KEY_D,
|
||||
"interact": KEY_E,
|
||||
"interact": KEY_E, "toggle_cam": KEY_V, "repaint": KEY_B,
|
||||
}
|
||||
for action in keys:
|
||||
if not InputMap.has_action(action):
|
||||
|
||||