This commit is contained in:
Spencer Killen 2025-03-29 23:25:13 -06:00
parent 9c30937a6c
commit 00c29e66fd
Signed by: sjkillen
GPG key ID: 3AF3117BA6FBB75B
44 changed files with 9546 additions and 0 deletions

8
Base.tres Normal file
View file

@ -0,0 +1,8 @@
[gd_resource type="StandardMaterial3D" format=3 uid="uid://c3a4yhqx17svd"]
[resource]
resource_name = "Base"
cull_mode = 2
vertex_color_use_as_albedo = true
albedo_color = Color(0.906332, 0.906332, 0.906332, 1)
roughness = 0.5

8
Eyes.tres Normal file
View file

@ -0,0 +1,8 @@
[gd_resource type="StandardMaterial3D" format=3 uid="uid://bmbuvvtluxtqa"]
[resource]
resource_name = "Eyes"
cull_mode = 2
vertex_color_use_as_albedo = true
albedo_color = Color(0, 0, 0, 1)
roughness = 0.5

51
Fur.tres Normal file
View file

@ -0,0 +1,51 @@
[gd_resource type="ShaderMaterial" load_steps=12 format=3 uid="uid://bf7f3xu32tv26"]
[ext_resource type="Shader" uid="uid://bgf62oe1pyffc" path="res://shell_fur.gdshader" id="1_4lo6c"]
[sub_resource type="Curve" id="Curve_4lo6c"]
_data = [Vector2(0, 0.460674), 0.0, 0.0, 0, 0, Vector2(0.706897, 0.47191), 0.0, 0.0, 0, 0, Vector2(0.894089, 0.494382), 0.0, 0.0, 0, 0]
point_count = 3
[sub_resource type="CurveTexture" id="CurveTexture_fxxdy"]
curve = SubResource("Curve_4lo6c")
[sub_resource type="Curve" id="Curve_38t5h"]
_data = [Vector2(0, 0), 0.0, 0.0, 0, 0, Vector2(0.684211, 0.438202), 0.0, 0.0, 0, 0, Vector2(1, 0.988764), 0.0, 0.0, 0, 0]
point_count = 3
[sub_resource type="CurveTexture" id="CurveTexture_di2cg"]
curve = SubResource("Curve_38t5h")
[sub_resource type="Curve" id="Curve_kh7y5"]
_data = [Vector2(0, 0), 0.0, 0.0, 0, 0, Vector2(1, 1), 0.0, 0.0, 0, 0]
point_count = 2
[sub_resource type="CurveTexture" id="CurveTexture_b6cys"]
curve = SubResource("Curve_kh7y5")
[sub_resource type="Curve" id="Curve_frk85"]
_data = [Vector2(0, 0.58427), 0.0, 0.0, 0, 0, Vector2(0.552632, 1), 0.0, 0.0, 0, 0, Vector2(1, 1), 0.0, 0.0, 0, 0]
point_count = 3
[sub_resource type="CurveTexture" id="CurveTexture_rdnk8"]
curve = SubResource("Curve_frk85")
[sub_resource type="Curve" id="Curve_tjf75"]
_data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(0.473684, 0.820225), -0.288202, -0.288202, 0, 0, Vector2(0.719298, 0.550562), -0.669561, -0.669561, 0, 0, Vector2(1, 0.202247), 0.0, 0.0, 0, 0]
point_count = 4
[sub_resource type="CurveTexture" id="CurveTexture_hawuf"]
curve = SubResource("Curve_tjf75")
[resource]
resource_name = "Fur"
render_priority = 0
shader = ExtResource("1_4lo6c")
shader_parameter/scale = 500.0
shader_parameter/clump_scale = 91.075
shader_parameter/grooming_factor = 2.033
shader_parameter/hair_curve_shape = SubResource("CurveTexture_b6cys")
shader_parameter/hair_stem_shape = SubResource("CurveTexture_hawuf")
shader_parameter/hair_clump_radius = SubResource("CurveTexture_di2cg")
shader_parameter/hair_clump_height = SubResource("CurveTexture_fxxdy")
shader_parameter/hair_depth_factor = SubResource("CurveTexture_rdnk8")

7
Mouth.tres Normal file
View file

@ -0,0 +1,7 @@
[gd_resource type="StandardMaterial3D" format=3 uid="uid://bbdd5508jhw37"]
[resource]
resource_name = "Mouth"
cull_mode = 2
albedo_color = Color(1, 0.62, 0.62, 1)
roughness = 0.5

129
PlayerInput.gd Normal file
View file

@ -0,0 +1,129 @@
extends Node
var allow_joins = true
var players: Dictionary[int, PlayerInputData]
const DEADZONE = 0.2
signal player_join(data: PlayerInputData)
func _ready() -> void:
Input.joy_connection_changed.connect(joy_connection_changed)
@warning_ignore("unused_parameter")
func joy_connection_changed(device: int, connected: bool):
print("Connected ", device)
func move_input(player: PlayerInputData, event: InputEvent, action: String):
var strength := 0.0
if event is InputEventJoypadMotion:
strength = abs(event.axis_value)
if strength <= DEADZONE:
strength = 0.0
if event.axis_value < 0.0:
action = action.replace("down", "up").replace("right", "left")
if action == "move_up" or action == "move_down":
if event.axis_value < 0.0:
player.raw_up = strength
player.raw_down = 0.0
else:
player.raw_down = strength
player.raw_up = 0.0
elif action == "move_right" or action == "move_left":
if event.axis_value < 0.0:
player.raw_left = strength
player.raw_right = 0.0
else:
player.raw_right = strength
player.raw_left = 0.0
elif event is InputEventKey:
strength = float(event.pressed)
if action == "move_up":
player.raw_up = strength
elif action == "move_right":
player.raw_right = strength
elif action == "move_down":
player.raw_down = strength
elif action == "move_left":
player.raw_left = strength
player.walk_dir = Vector3(
player.raw_right - player.raw_left,
0.0,
player.raw_down - player.raw_up,
)
func game_input(player: PlayerInputData, event: InputEvent):
if not event.is_action_type():
return
var action := get_action_name_all(event)
if action.contains("move"):
move_input(player, event, action)
if action == "jump":
player.raw_jump = event.is_pressed()
if action == "dig":
player.raw_dig = event.is_pressed()
player.jumping = player.raw_jump
player.digging = player.raw_dig
func join(who: int):
var new_player := PlayerInputData.new()
players[who] = new_player
player_join.emit(new_player)
func drop(who: int):
players[who].drop.emit()
players.erase(who)
func get_action_name_all(event: InputEvent) -> String:
return get_action_name(event).replace("_alt", "")
func get_action_name(event: InputEvent) -> String:
if event.is_echo():
return ""
if event.is_action("dig"):
return "dig"
if event.is_action("jump"):
return "jump"
if event.is_action("dig_alt"):
return "dig_alt"
if event.is_action("jump_alt"):
return "jump_alt"
if event.is_action("move_up"):
return "move_up"
if event.is_action("move_right"):
return "move_right"
if event.is_action("move_down"):
return "move_down"
if event.is_action("move_left"):
return "move_left"
if event.is_action("move_up_alt"):
return "move_up_alt"
if event.is_action("move_right_alt"):
return "move_right_alt"
if event.is_action("move_down_alt"):
return "move_down_alt"
if event.is_action("move_left_alt"):
return "move_left_alt"
return ""
func _input(event: InputEvent) -> void:
if not event.is_action_type():
return
var who = event.device
if get_action_name(event).contains("_alt"):
who = -2
elif event is InputEventKey:
who = -3
if players.has(who):
game_input(players[who], event)
elif allow_joins and get_action_name(event):
join(who)

1
PlayerInput.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://1xgx6a7b0tsx

16
PlayerInputData.gd Normal file
View file

@ -0,0 +1,16 @@
extends Resource
class_name PlayerInputData
@warning_ignore("unused_signal")
signal drop
var walk_dir: Vector3 = Vector3.ZERO
var digging: bool = false
var jumping: bool = false
var raw_dig := false
var raw_jump := false
var raw_up := 0.0
var raw_right := 0.0
var raw_down := 0.0
var raw_left := 0.0

1
PlayerInputData.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://dsqdjritmyalk

BIN
Pounce.res Normal file

Binary file not shown.

0
blends/.gdignore Normal file
View file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
blends/fox.blend Normal file

Binary file not shown.

BIN
blends/fox.blend1 Normal file

Binary file not shown.

102
blends/fox.py Normal file
View file

@ -0,0 +1,102 @@
import bpy
from bpy.types import Object
from pathlib import Path
from contextlib import contextmanager
if True:
blend_folder = Path(bpy.path.abspath("//"))
while blend_folder.resolve().stem not in ("", "blends"):
blend_folder /= ".."
EXPORT_PATH = str((blend_folder / "../fox.glb").resolve())
EXPORT_ANIMATIONS_PATH = str(
(blend_folder / "../fox_animations.glb").resolve())
import sys
sys.path.append(str(blend_folder.resolve()))
from shared_export import clear_selection, apply_modifier, sort_uv_layers, apply_bone_groups, select_collection, reverted, delete_uv_layer, apply_gn_attr
def prepare_modifiers(fox: Object, foxrig: Object):
geo = fox.modifiers["Fur"]
geo.show_viewport = True
apply_modifier(fox, geo)
apply_bone_groups(foxrig.data, fox)
def gltf_export(filename: str):
bpy.context.view_layer.update()
bpy.ops.export_scene.gltf(
filepath=filename,
export_format="GLB",
use_selection=True,
export_vertex_color="ACTIVE",
export_apply=False,
export_animation_mode="ACTIONS",
export_anim_slide_to_zero=True,
export_optimize_animation_size=False,
export_optimize_animation_keep_anim_armature=False,
export_def_bones=True,
export_animations=True,
)
@reverted
def export():
fox = bpy.data.objects["Fox"]
foxrig = bpy.data.objects["FoxRig"]
collection = bpy.data.collections["export"]
bpy.ops.object.mode_set(mode="OBJECT")
bpy.context.view_layer.update()
clear_selection(bpy.context)
select_collection(bpy.context, collection)
prepare_modifiers(fox, foxrig)
sort_uv_layers(fox.data)
# write_vertex_id_to_uv(fox)
gltf_export(EXPORT_PATH)
def vertex_id_mesh_info(helper):
return helper.VertexIDMeshInfo(
resource_path="res://fox.tscn",
node_path="FoxRig/Skeleton3D/Fox",
)
def write_vertex_id_to_uv(obj: Object):
import bmesh
mesh = obj.data
mesh.uv_layers.active = mesh.uv_layers[1]
bm = bmesh.new()
bm.from_mesh(mesh)
uv_layer = bm.loops.layers.uv.active
for face in bm.faces:
for loop in face.loops:
loop[uv_layer].uv.x = loop.vert.index
bm.to_mesh(obj.data)
@reverted
def export_animations():
mesh = bpy.data.objects["Fox"]
rig = bpy.data.objects["FoxRig"]
bpy.ops.object.mode_set(mode="OBJECT")
bpy.context.view_layer.update()
clear_selection(bpy.context)
rig.select_set(True)
mesh.select_set(True)
mesh.modifiers["Fur"].show_viewport = False
rig.data.pose_position = "POSE"
bpy.context.view_layer.update()
bpy.ops.export_scene.gltf(
filepath=EXPORT_ANIMATIONS_PATH,
export_format="GLB",
use_selection=True,
export_apply=False,
export_animation_mode="ACTIONS",
export_anim_slide_to_zero=True,
export_optimize_animation_size=False,
export_optimize_animation_keep_anim_armature=False,
export_def_bones=True,
)

132
blends/shared_export.py Normal file
View file

@ -0,0 +1,132 @@
from pathlib import Path
from sys import stderr
import bpy
from bpy.types import Object, Armature, Modifier, Mesh, Context, Collection
from functools import reduce, wraps
from operator import attrgetter, itemgetter, or_
def reverted(fn):
@wraps(fn)
def inside(*args, **kwargs):
bpy.ops.wm.save_as_mainfile()
try:
fn(*args, **kwargs)
finally:
bpy.ops.wm.revert_mainfile()
return inside
def read_export_path(argv) -> str:
i = next((i for i, arg in enumerate(argv) if arg == "--export-path"), None)
if i is None:
raise Exception(
"Error getting --export-path argument. This script must be executed from Makefile")
return str(Path(argv[i+1]).resolve())
def clear_selection(ctx: Context):
for obj in ctx.selected_objects:
obj.select_set(False)
def select_collection(ctx: Context, c: Collection):
for obj in c.objects:
obj.select_set(True)
def get_ik_limits(armature: Object) -> dict:
assert type(armature.data) is Armature
return reduce(or_, ({pbone.name: [
[pbone.ik_min_x, pbone.ik_max_x],
[pbone.ik_min_y, pbone.ik_max_y],
[pbone.ik_min_z, pbone.ik_max_z],
]} for pbone in armature.pose.bones
if pbone.use_ik_limit_x or pbone.use_ik_limit_y or pbone.use_ik_limit_z
), dict())
# https://blenderartists.org/t/how-to-apply-all-the-modifiers-with-python/1314483/2
def apply_modifier(obj: Object, modifier: Modifier):
with bpy.context.temp_override(object=obj, modifier=modifier):
bpy.context.view_layer.update()
bpy.ops.object.modifier_apply(modifier=modifier.name)
bpy.context.view_layer.update()
def sort_uv_layers(mesh: Mesh):
# UV layers use non-pythonic reference semantics, safest to always reference by their indices
assert len(
mesh.uv_layers) <= 7, "There must be fewer than 8 UV maps to sort (one less than the Blender maximum of 8)"
initial_layers = [[i, layer.name]
for i, layer in enumerate(mesh.uv_layers)]
initial_layers.sort(key=itemgetter(1))
for i, layer_name in tuple(initial_layers):
assert mesh.uv_layers[i].name == layer_name, mesh.uv_layers[i].name + \
" " + layer_name
mesh.uv_layers.active = mesh.uv_layers[i]
new_layer = mesh.uv_layers.new(name=f"new_{mesh.uv_layers[i].name}")
assert new_layer == mesh.uv_layers[-1]
mesh.uv_layers.active = mesh.uv_layers[0]
mesh.uv_layers.remove(mesh.uv_layers[i])
for pair in initial_layers:
if pair[0] > i:
pair[0] -= 1
for layer in mesh.uv_layers:
layer.name = layer.name.replace("new_", "")
mesh.uv_layers.active = mesh.uv_layers[0]
def delete_uv_layer(mesh: Mesh, name: str):
for layer in mesh.uv_layers:
if layer.name == name:
mesh.uv_layers.remove(layer)
return
raise Exception(f"failed to remove UV layer with name {name}")
def convert_attribute(obj, attribute_name: str):
"Don't convert this into a function that does multiple at once, indices are too fucky for that"
for i, attr in enumerate(obj.data.attributes):
if attr.name == attribute_name:
obj.data.attributes.active_index = i
bpy.context.view_layer.update()
bpy.ops.geometry.attribute_convert(
mode='VERTEX_GROUP', domain='POINT', data_type='FLOAT')
bpy.context.view_layer.update()
return
raise Exception("attribute not found", attribute_name)
def apply_bone_groups(rig: Armature, skinned: Object):
"""May not be needed in future versions of Blender
Converts geometry nodes attributes for bone groups back to vertex groups
Something fishy with indices and attributes, so algorithm is not efficient just to be safe
"""
with bpy.context.temp_override(object=skinned):
for bone_name in map(attrgetter("name"), rig.bones):
for i, attribute in enumerate(skinned.data.attributes):
if attribute.name == bone_name:
skinned.data.attributes.active_index = i
bpy.context.view_layer.update()
bpy.ops.geometry.attribute_convert(
mode='VERTEX_GROUP', domain='POINT', data_type='FLOAT')
bpy.context.view_layer.update()
break
else:
print("Failed to find attribute for", bone_name, file=stderr)
def apply_gn_attr(obj: Object, name: str):
"See above"
with bpy.context.temp_override(object=obj):
for i, attribute in enumerate(obj.data.attributes):
if attribute.name == name:
obj.data.attributes.active_index = i
bpy.context.view_layer.update()
bpy.ops.geometry.attribute_convert(
mode='VERTEX_GROUP', domain='POINT', data_type='FLOAT')
bpy.context.view_layer.update()
return
raise Exception(f"Failed to find attribute: {name}")

BIN
blends/vole.blend Normal file

Binary file not shown.

BIN
blends/vole.blend1 Normal file

Binary file not shown.

102
blends/vole.py Normal file
View file

@ -0,0 +1,102 @@
import bpy
from bpy.types import Object
from pathlib import Path
from contextlib import contextmanager
if True:
blend_folder = Path(bpy.path.abspath("//"))
while blend_folder.resolve().stem not in ("", "blends"):
blend_folder /= ".."
EXPORT_PATH = str((blend_folder / "../vole.glb").resolve())
EXPORT_ANIMATIONS_PATH = str(
(blend_folder / "../vole_animations.glb").resolve())
import sys
sys.path.append(str(blend_folder.resolve()))
from shared_export import clear_selection, apply_modifier, sort_uv_layers, apply_bone_groups, select_collection, reverted, delete_uv_layer, apply_gn_attr
def prepare_modifiers(vole: Object, volerig: Object):
geo = vole.modifiers["Fur"]
geo.show_viewport = True
apply_modifier(vole, geo)
apply_bone_groups(volerig.data, vole)
def gltf_export(filename: str):
bpy.context.view_layer.update()
bpy.ops.export_scene.gltf(
filepath=filename,
export_format="GLB",
use_selection=True,
export_vertex_color="ACTIVE",
export_apply=False,
export_animation_mode="ACTIONS",
export_anim_slide_to_zero=True,
export_optimize_animation_size=False,
export_optimize_animation_keep_anim_armature=False,
export_def_bones=True,
export_animations=True,
)
@reverted
def export():
vole = bpy.data.objects["Vole"]
volerig = bpy.data.objects["VoleRig"]
collection = bpy.data.collections["export"]
bpy.ops.object.mode_set(mode="OBJECT")
bpy.context.view_layer.update()
clear_selection(bpy.context)
select_collection(bpy.context, collection)
prepare_modifiers(vole, volerig)
sort_uv_layers(vole.data)
# write_vertex_id_to_uv(vole)
gltf_export(EXPORT_PATH)
def vertex_id_mesh_info(helper):
return helper.VertexIDMeshInfo(
resource_path="res://vole.tscn",
node_path="VoleRig/Skeleton3D/Vole",
)
def write_vertex_id_to_uv(obj: Object):
import bmesh
mesh = obj.data
mesh.uv_layers.active = mesh.uv_layers[1]
bm = bmesh.new()
bm.from_mesh(mesh)
uv_layer = bm.loops.layers.uv.active
for face in bm.faces:
for loop in face.loops:
loop[uv_layer].uv.x = loop.vert.index
bm.to_mesh(obj.data)
@reverted
def export_animations():
mesh = bpy.data.objects["Vole"]
rig = bpy.data.objects["VoleRig"]
bpy.ops.object.mode_set(mode="OBJECT")
bpy.context.view_layer.update()
clear_selection(bpy.context)
rig.select_set(True)
mesh.select_set(True)
mesh.modifiers["Fur"].show_viewport = False
rig.data.pose_position = "POSE"
bpy.context.view_layer.update()
bpy.ops.export_scene.gltf(
filepath=EXPORT_ANIMATIONS_PATH,
export_format="GLB",
use_selection=True,
export_apply=False,
export_animation_mode="ACTIONS",
export_anim_slide_to_zero=True,
export_optimize_animation_size=False,
export_optimize_animation_keep_anim_armature=False,
export_def_bones=True,
)

18
color.gdshaderinc Normal file
View file

@ -0,0 +1,18 @@
// https://godotshaders.com/shader/hsv-adjustment/
vec3 rgb2hsv(vec3 c) {
vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
float d = q.x - min(q.w, q.y);
float e = 1.0e-10;
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}
vec3 hsv2rgb(vec3 c) {
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}

1
color.gdshaderinc.uid Normal file
View file

@ -0,0 +1 @@
uid://dltay473a1mjr

19
fox.gd Normal file
View file

@ -0,0 +1,19 @@
extends Node3D
@onready var playback: AnimationNodeStateMachinePlayback = %AnimationTree.get("parameters/playback")
@export var pouncing := false
func idle():
playback.travel("Idle")
func walk():
playback.travel("Walk")
func pounce():
playback.travel("Pounce")
func is_busy():
return pouncing
func root_motion():
return %AnimationTree.get_root_motion_position()

1
fox.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://8683t0qihiwq

BIN
fox.glb Normal file

Binary file not shown.

6230
fox.glb.import Normal file

File diff suppressed because it is too large Load diff

57
fox.tscn Normal file
View file

@ -0,0 +1,57 @@
[gd_scene load_steps=13 format=3 uid="uid://dwm6v58p82b57"]
[ext_resource type="PackedScene" uid="uid://bhhevwh2jipt7" path="res://fox.glb" id="1_8x0u3"]
[ext_resource type="Script" uid="uid://8683t0qihiwq" path="res://fox.gd" id="2_kpck0"]
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_8x0u3"]
animation = &"Idle"
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_kpck0"]
animation = &"Pounce"
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_k0xb2"]
animation = &"Walk"
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_kpck0"]
advance_mode = 2
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_rxxu0"]
xfade_time = 0.1
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_wyljk"]
xfade_time = 0.1
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_somxj"]
xfade_time = 0.1
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_pcpmy"]
xfade_time = 0.1
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_k0xb2"]
xfade_time = 0.1
switch_mode = 2
advance_mode = 2
[sub_resource type="AnimationNodeStateMachine" id="AnimationNodeStateMachine_k0xb2"]
states/Idle/node = SubResource("AnimationNodeAnimation_8x0u3")
states/Idle/position = Vector2(370, 127)
states/Pounce/node = SubResource("AnimationNodeAnimation_kpck0")
states/Pounce/position = Vector2(529, 197)
states/Walk/node = SubResource("AnimationNodeAnimation_k0xb2")
states/Walk/position = Vector2(514, 58)
transitions = ["Start", "Idle", SubResource("AnimationNodeStateMachineTransition_kpck0"), "Idle", "Walk", SubResource("AnimationNodeStateMachineTransition_rxxu0"), "Walk", "Pounce", SubResource("AnimationNodeStateMachineTransition_wyljk"), "Walk", "Idle", SubResource("AnimationNodeStateMachineTransition_somxj"), "Idle", "Pounce", SubResource("AnimationNodeStateMachineTransition_pcpmy"), "Pounce", "Idle", SubResource("AnimationNodeStateMachineTransition_k0xb2")]
[node name="fox" instance=ExtResource("1_8x0u3")]
script = ExtResource("2_kpck0")
[node name="AnimationPlayer" parent="." index="1"]
root_motion_track = NodePath("FoxRig/Skeleton3D:root")
root_motion_local = false
[node name="AnimationTree" type="AnimationTree" parent="." index="2"]
unique_name_in_owner = true
root_node = NodePath("%AnimationTree/..")
root_motion_track = NodePath("FoxRig/Skeleton3D:root")
root_motion_local = false
tree_root = SubResource("AnimationNodeStateMachine_k0xb2")
anim_player = NodePath("../AnimationPlayer")

40
main.tscn Normal file
View file

@ -0,0 +1,40 @@
[gd_scene load_steps=6 format=3 uid="uid://cgpuhiledvecy"]
[ext_resource type="PackedScene" uid="uid://bmt76eb0ke5fw" path="res://player_spawn.tscn" id="1_ig7tw"]
[sub_resource type="Environment" id="Environment_ig7tw"]
[sub_resource type="BoxMesh" id="BoxMesh_7dm0k"]
size = Vector3(50, 0.5, 50)
[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_ig7tw"]
points = PackedVector3Array(25.3919, 0.940476, 25.3919, -25.3919, -0.646825, -25.3919, -25.3919, 0.940476, -25.3919, 25.3919, -0.646825, -25.3919, -25.3919, -0.646825, 25.3919, -25.3919, 0.940476, 25.3919, 25.3919, 0.940476, -25.3919, 25.3919, -0.646825, 25.3919)
[sub_resource type="BoxMesh" id="BoxMesh_ig7tw"]
[node name="Main" type="Node3D"]
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
environment = SubResource("Environment_ig7tw")
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="WorldEnvironment"]
transform = Transform3D(1, 0, 0, 0, 0.473074, 0.881023, 0, -0.881023, 0.473074, 0, 0, 0)
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.694463, 0)
mesh = SubResource("BoxMesh_7dm0k")
[node name="PlayerSpawn" parent="MeshInstance3D" instance=ExtResource("1_ig7tw")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.494699, 8.50994, 0.238789)
[node name="StaticBody3D2" type="StaticBody3D" parent="MeshInstance3D"]
[node name="CollisionShape3D" type="CollisionShape3D" parent="MeshInstance3D/StaticBody3D2"]
shape = SubResource("ConvexPolygonShape3D_ig7tw")
[node name="Camera3D" type="Camera3D" parent="."]
transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 31.348, 0)
fov = 50.0
[node name="MeshInstance3D2" type="MeshInstance3D" parent="."]
mesh = SubResource("BoxMesh_ig7tw")

249
noise.gdshaderinc Normal file
View file

@ -0,0 +1,249 @@
/* https://www.shadertoy.com/view/XsX3zB
*
* The MIT License
* Copyright © 2013 Nikita Miropolskiy
*
* ( license has been changed from CCA-NC-SA 3.0 to MIT
*
* but thanks for attributing your source code when deriving from this sample
* with a following link: https://www.shadertoy.com/view/XsX3zB )
*
*/
/* discontinuous pseudorandom uniformly distributed in [-0.5, +0.5]^3 */
vec3 random3(vec3 c) {
float j = 4096.0*sin(dot(c,vec3(17.0, 59.4, 15.0)));
vec3 r;
r.z = fract(512.0*j);
j *= .125;
r.x = fract(512.0*j);
j *= .125;
r.y = fract(512.0*j);
return r-0.5;
}
vec3 hash33(vec3 p){
float n = sin(dot(p, vec3(7, 157, 113)));
return fract(vec3(2097152, 262144, 32768)*n);
}
float worley(vec3 uv, float local_scale) {
vec3 prod = uv * local_scale;
vec3 index_uv = floor(prod);
vec3 fract_uv = fract(prod);
float minimum_dist = 0.6;
for (int y = -1; y <= 1; y++) {
for (int x = -1; x <= 1; x++) {
for (int z = -1; z <= 1; z++) {
vec3 neighbor = vec3(float(x),float(y), float(z));
vec3 point = hash33(index_uv + neighbor);
vec3 diff = neighbor + point - fract_uv;
float dist = length(diff);
minimum_dist = min(minimum_dist, dist);
}
}
}
return minimum_dist;
}
// FROM: https://www.shadertoy.com/view/XtBGDG
//http://webstaff.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf
//simplex pretty much 99% copied from there
//adjusted by getting "completely random" gradients instead of randoming from 12 preset ones
//and normalizing the gradient vector
float noise3D(vec3 p)
{
return fract(sin(dot(p ,vec3(12.9898,78.233,128.852))) * 43758.5453)*2.0-1.0;
}
float simplex3D(vec3 p)
{
float f3 = 1.0/3.0;
float s = (p.x+p.y+p.z)*f3;
int i = int(floor(p.x+s));
int j = int(floor(p.y+s));
int k = int(floor(p.z+s));
float g3 = 1.0/6.0;
float t = float((i+j+k))*g3;
float x0 = float(i)-t;
float y0 = float(j)-t;
float z0 = float(k)-t;
x0 = p.x-x0;
y0 = p.y-y0;
z0 = p.z-z0;
int i1,j1,k1;
int i2,j2,k2;
if(x0>=y0)
{
if(y0>=z0){ i1=1; j1=0; k1=0; i2=1; j2=1; k2=0; } // X Y Z order
else if(x0>=z0){ i1=1; j1=0; k1=0; i2=1; j2=0; k2=1; } // X Z Y order
else { i1=0; j1=0; k1=1; i2=1; j2=0; k2=1; } // Z X Z order
}
else
{
if(y0<z0) { i1=0; j1=0; k1=1; i2=0; j2=1; k2=1; } // Z Y X order
else if(x0<z0) { i1=0; j1=1; k1=0; i2=0; j2=1; k2=1; } // Y Z X order
else { i1=0; j1=1; k1=0; i2=1; j2=1; k2=0; } // Y X Z order
}
float x1 = x0 - float(i1) + g3;
float y1 = y0 - float(j1) + g3;
float z1 = z0 - float(k1) + g3;
float x2 = x0 - float(i2) + 2.0*g3;
float y2 = y0 - float(j2) + 2.0*g3;
float z2 = z0 - float(k2) + 2.0*g3;
float x3 = x0 - 1.0 + 3.0*g3;
float y3 = y0 - 1.0 + 3.0*g3;
float z3 = z0 - 1.0 + 3.0*g3;
vec3 ijk0 = vec3(float(i),float(j),float(k));
vec3 ijk1 = vec3(float(i)+float(i1),float(j)+float(j1),float(k)+float(k1));
vec3 ijk2 = vec3(float(i)+float(i2),float(j)+float(j2),float(k)+float(k2));
vec3 ijk3 = vec3(float(i)+1.,float(j)+1.,float(k)+1.);
vec3 gr0 = normalize(vec3(noise3D(ijk0),noise3D(ijk0*2.01),noise3D(ijk0*2.02)));
vec3 gr1 = normalize(vec3(noise3D(ijk1),noise3D(ijk1*2.01),noise3D(ijk1*2.02)));
vec3 gr2 = normalize(vec3(noise3D(ijk2),noise3D(ijk2*2.01),noise3D(ijk2*2.02)));
vec3 gr3 = normalize(vec3(noise3D(ijk3),noise3D(ijk3*2.01),noise3D(ijk3*2.02)));
float n0 = 0.0;
float n1 = 0.0;
float n2 = 0.0;
float n3 = 0.0;
float t0 = 0.5 - x0*x0 - y0*y0 - z0*z0;
if(t0>=0.0)
{
t0*=t0;
n0 = t0 * t0 * dot(gr0, vec3(x0, y0, z0));
}
float t1 = 0.5 - x1*x1 - y1*y1 - z1*z1;
if(t1>=0.0)
{
t1*=t1;
n1 = t1 * t1 * dot(gr1, vec3(x1, y1, z1));
}
float t2 = 0.5 - x2*x2 - y2*y2 - z2*z2;
if(t2>=0.0)
{
t2 *= t2;
n2 = t2 * t2 * dot(gr2, vec3(x2, y2, z2));
}
float t3 = 0.5 - x3*x3 - y3*y3 - z3*z3;
if(t3>=0.0)
{
t3 *= t3;
n3 = t3 * t3 * dot(gr3, vec3(x3, y3, z3));
}
return 96.0*(n0+n1+n2+n3);
}
float fbm(vec3 p)
{
float f;
f = 0.50000*simplex3D( p ); p = p*2.01;
f += 0.25000*simplex3D( p ); p = p*2.02; //from iq
f += 0.12500*simplex3D( p ); p = p*2.03;
f += 0.06250*simplex3D( p ); p = p*2.04;
f += 0.03125*simplex3D( p );
return f;
}
float noise(vec3 noise_uv, float noise_scale) {
return fbm(noise_uv * noise_scale);
}
/* https://www.shadertoy.com/view/XsX3zB
*
* The MIT License
* Copyright © 2013 Nikita Miropolskiy
*
* ( license has been changed from CCA-NC-SA 3.0 to MIT
*
* but thanks for attributing your source code when deriving from this sample
* with a following link: https://www.shadertoy.com/view/XsX3zB )
*
*/
/* discontinuous pseudorandom uniformly distributed in [-0.5, +0.5]^3 */
vec3 shell_random3(vec3 c) {
float j = 4096.0*sin(dot(c,vec3(17.0, 59.4, 15.0)));
vec3 r;
r.z = fract(512.0*j);
j *= .125;
r.x = fract(512.0*j);
j *= .125;
r.y = fract(512.0*j);
return r-0.5;
}
vec3 shell_hash33(vec3 p){
float n = sin(dot(p, vec3(7, 157, 113)));
return fract(vec3(2097152, 262144, 32768)*n);
}
float shell_worley(vec3 uv, float local_scale) {
vec3 prod = uv * local_scale;
vec3 index_uv = floor(prod);
vec3 fract_uv = fract(prod);
float minimum_dist = 1.0;
for (int y = -1; y <= 1; y++) {
for (int x = -1; x <= 1; x++) {
for (int z = -1; z <= 1; z++) {
vec3 neighbor = vec3(float(x),float(y), float(z));
vec3 point = shell_random3(index_uv + neighbor);
vec3 diff = neighbor + point - fract_uv;
float dist = length(diff);
minimum_dist = min(minimum_dist, dist);
}
}
}
return minimum_dist;
}
float shell_worley_color(vec3 uv, float local_scale, out vec3 vcolor) {
vec3 prod = uv * local_scale;
vec3 index_uv = floor(prod);
vec3 fract_uv = fract(prod);
float minimum_dist = 1.0;
// This is magic, fixed some weird noisy glitches
vcolor = vec3(-1.0);
for (int y = -1; y <= 1; y++) {
for (int x = -1; x <= 1; x++) {
for (int z = -1; z <= 1; z++) {
vec3 neighbor = vec3(float(x),float(y), float(z));
vec3 point = shell_random3(index_uv + neighbor);
vec3 diff = neighbor + point - fract_uv;
float dist = length(diff);
if (dist < minimum_dist) {
minimum_dist = dist;
vcolor = point;
}
}
}
}
return minimum_dist;
}

1
noise.gdshaderinc.uid Normal file
View file

@ -0,0 +1 @@
uid://dlwojkhehvmio

33
player.gd Normal file
View file

@ -0,0 +1,33 @@
extends CharacterBody3D
class_name Player
var input: PlayerInputData
const SPEED = 5.0
const JUMP_VELOCITY = 4.5
@onready var model = %fox
func _physics_process(delta: float) -> void:
if not model.is_busy():
var direction := input.walk_dir
if direction:
velocity.x = direction.x * SPEED
velocity.z = direction.z * SPEED
else:
velocity.x = move_toward(velocity.x, 0, SPEED)
velocity.z = move_toward(velocity.z, 0, SPEED)
if direction.length() > 0.01:
var target := global_transform.looking_at(global_position + input.walk_dir, Vector3(0.0, 1.0, 0.0)).basis.get_euler().y
var current_euler := global_basis.get_euler()
current_euler.y = lerp_angle(current_euler.y, target, delta * 20.0)
global_basis = Basis.from_euler(current_euler)
model.walk()
else:
model.idle()
if input.jumping:
model.pounce()
position += model.global_transform.basis * model.root_motion()
move_and_slide()

1
player.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://ccyxlwjirmyt0

19
player.tscn Normal file
View file

@ -0,0 +1,19 @@
[gd_scene load_steps=4 format=3 uid="uid://54sf77urgp3h"]
[ext_resource type="Script" uid="uid://ccyxlwjirmyt0" path="res://player.gd" id="1_4flbx"]
[ext_resource type="PackedScene" uid="uid://dwm6v58p82b57" path="res://fox.tscn" id="2_onrkg"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_i3pqv"]
radius = 1.96912
height = 5.13133
[node name="Player" type="CharacterBody3D"]
script = ExtResource("1_4flbx")
[node name="FoxShape" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, 0, 1.9351, 0.152762)
shape = SubResource("CapsuleShape3D_i3pqv")
[node name="fox" parent="." instance=ExtResource("2_onrkg")]
unique_name_in_owner = true
transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, 0, 0, 0)

10
player_spawn.gd Normal file
View file

@ -0,0 +1,10 @@
extends Marker3D
func _ready() -> void:
PlayerInput.player_join.connect(player_join)
func player_join(data: PlayerInputData):
var player: Player = preload("res://player.tscn").instantiate()
player.input = data
add_child(player)

1
player_spawn.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://ddce7di45eosf

6
player_spawn.tscn Normal file
View file

@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://bmt76eb0ke5fw"]
[ext_resource type="Script" uid="uid://ddce7di45eosf" path="res://player_spawn.gd" id="1_ckl4a"]
[node name="PlayerSpawn" type="Marker3D"]
script = ExtResource("1_ckl4a")

91
project.godot Normal file
View file

@ -0,0 +1,91 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=5
[application]
run/main_scene="uid://cgpuhiledvecy"
config/features=PackedStringArray("4.5")
[autoload]
PlayerInput="*res://PlayerInput.gd"
[input]
move_up={
"deadzone": 0.01,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":-1.0,"script":null)
]
}
move_right={
"deadzone": 0.01,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":1.0,"script":null)
]
}
move_down={
"deadzone": 0.01,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":1.0,"script":null)
]
}
move_left={
"deadzone": 0.01,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":-1.0,"script":null)
]
}
jump={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":7,"pressure":0.0,"pressed":true,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":0,"pressure":0.0,"pressed":true,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":1,"pressure":0.0,"pressed":true,"script":null)
]
}
dig={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":69,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":6,"pressure":0.0,"pressed":true,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":3,"pressure":0.0,"pressed":true,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":4,"pressure":0.0,"pressed":true,"script":null)
]
}
move_up_alt={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194320,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
move_right_alt={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194321,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
move_down_alt={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194322,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
move_left_alt={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194319,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
jump_alt={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194309,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
dig_alt={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194325,"key_label":0,"unicode":0,"location":2,"echo":false,"script":null)
]
}

68
shell_fur.gdshader Normal file
View file

@ -0,0 +1,68 @@
shader_type spatial;
// Can't figure out how to make back faces look good
// Flipping normals doesn't change their shading
render_mode cull_back, depth_prepass_alpha;
//#include "res://addons/vertexanimations/va_default.gdshaderinc"
#include "res://color.gdshaderinc"
#include "res://noise.gdshaderinc"
varying float fur_length;
varying float is_shell;
varying float shell_id_factor;
varying vec3 ungroomed_3d_uv;
varying vec3 groomed_normals;
uniform float scale = 700.0;
uniform float clump_scale = 90.1;
uniform float grooming_factor = 0.008;
uniform sampler2D hair_curve_shape : repeat_disable;
uniform sampler2D hair_stem_shape : repeat_disable;
uniform sampler2D hair_clump_radius : repeat_disable;
uniform sampler2D hair_clump_height : repeat_disable;
uniform sampler2D hair_base_color : source_color;
uniform sampler2D hair_depth_factor : repeat_disable;
uniform sampler2D normals : hint_normal;
void vertex() {
fur_length = CUSTOM0.w;
shell_id_factor = CUSTOM2.r;
ungroomed_3d_uv = CUSTOM0.xzy;
ungroomed_3d_uv.z *= -1.0;
groomed_normals = CUSTOM1.xzy;
groomed_normals.z *= -1.0;
is_shell = CUSTOM2.y;
//VERTEX = vertex_animation_default(VERTEX_ID, VERTEX);
}
void fragment() {
if (is_shell <= 0.0001) {
discard;
}
float curve_shape = texture(hair_curve_shape, vec2(1.0-shell_id_factor)).r;
float groom_weight = (((1.0-fur_length)*grooming_factor) + grooming_factor) * curve_shape;
vec3 groomed_3d_uv = ungroomed_3d_uv + (groomed_normals * groom_weight);
float clump_radius = 1.0-texture(hair_clump_radius, vec2(shell_worley(groomed_3d_uv, clump_scale))).r;
float clump_height = texture(hair_clump_height, vec2(clump_radius)).r;
float clump_shell_id = shell_id_factor / clump_height;
float stem_shape = texture(hair_stem_shape, vec2(clump_shell_id)).r;
float c = shell_worley(groomed_3d_uv, scale);
if (c > stem_shape) {
discard;
}
vec3 sample_hair_base_color = COLOR.rgb;
vec3 base = rgb2hsv(sample_hair_base_color);
float v = base.z * texture(hair_depth_factor, vec2(shell_id_factor)).r;
ALBEDO = hsv2rgb(vec3(base.x, base.y, v));
ROUGHNESS = 1.0;
SPECULAR = 0.0;
NORMAL_MAP = texture(normals, UV).xyz;
}

1
shell_fur.gdshader.uid Normal file
View file

@ -0,0 +1 @@
uid://bgf62oe1pyffc

19
vole.gd Normal file
View file

@ -0,0 +1,19 @@
extends Node3D
@onready var playback: AnimationNodeStateMachinePlayback = %AnimationTree.get("parameters/playback")
@export var pouncing := false
func idle():
playback.travel("Idle")
func walk():
playback.travel("Walk")
func pounce():
pass
func is_busy():
return pouncing
func root_motion():
return %AnimationTree.get_root_motion_position()

1
vole.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://dxdgep3hm0i8d

BIN
vole.glb Normal file

Binary file not shown.

2103
vole.glb.import Normal file

File diff suppressed because it is too large Load diff

20
vole.tscn Normal file
View file

@ -0,0 +1,20 @@
[gd_scene load_steps=5 format=3 uid="uid://dlpr8asp0m21a"]
[ext_resource type="PackedScene" uid="uid://cg7vjpjmg60ti" path="res://vole.glb" id="1_fw737"]
[ext_resource type="Script" uid="uid://dxdgep3hm0i8d" path="res://vole.gd" id="2_6xove"]
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_fw737"]
animation = &"IDLE"
[sub_resource type="AnimationNodeStateMachine" id="AnimationNodeStateMachine_fw737"]
states/IDLE/node = SubResource("AnimationNodeAnimation_fw737")
states/IDLE/position = Vector2(411, 153)
[node name="vole" instance=ExtResource("1_fw737")]
script = ExtResource("2_6xove")
[node name="AnimationTree" type="AnimationTree" parent="." index="2"]
root_motion_track = NodePath("VoleRig/Skeleton3D:root")
root_motion_local = false
tree_root = SubResource("AnimationNodeStateMachine_fw737")
anim_player = NodePath("../AnimationPlayer")