day 2
This commit is contained in:
parent
9c30937a6c
commit
00c29e66fd
44 changed files with 9546 additions and 0 deletions
8
Base.tres
Normal file
8
Base.tres
Normal 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
8
Eyes.tres
Normal 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
51
Fur.tres
Normal 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
7
Mouth.tres
Normal 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
129
PlayerInput.gd
Normal 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
1
PlayerInput.gd.uid
Normal file
|
@ -0,0 +1 @@
|
||||||
|
uid://1xgx6a7b0tsx
|
16
PlayerInputData.gd
Normal file
16
PlayerInputData.gd
Normal 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
1
PlayerInputData.gd.uid
Normal file
|
@ -0,0 +1 @@
|
||||||
|
uid://dsqdjritmyalk
|
BIN
Pounce.res
Normal file
BIN
Pounce.res
Normal file
Binary file not shown.
0
blends/.gdignore
Normal file
0
blends/.gdignore
Normal file
BIN
blends/__pycache__/fox.cpython-311.pyc
Normal file
BIN
blends/__pycache__/fox.cpython-311.pyc
Normal file
Binary file not shown.
BIN
blends/__pycache__/shared_export.cpython-311.pyc
Normal file
BIN
blends/__pycache__/shared_export.cpython-311.pyc
Normal file
Binary file not shown.
BIN
blends/__pycache__/vole.cpython-311.pyc
Normal file
BIN
blends/__pycache__/vole.cpython-311.pyc
Normal file
Binary file not shown.
BIN
blends/fox.blend
Normal file
BIN
blends/fox.blend
Normal file
Binary file not shown.
BIN
blends/fox.blend1
Normal file
BIN
blends/fox.blend1
Normal file
Binary file not shown.
102
blends/fox.py
Normal file
102
blends/fox.py
Normal 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
132
blends/shared_export.py
Normal 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
BIN
blends/vole.blend
Normal file
Binary file not shown.
BIN
blends/vole.blend1
Normal file
BIN
blends/vole.blend1
Normal file
Binary file not shown.
102
blends/vole.py
Normal file
102
blends/vole.py
Normal 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
18
color.gdshaderinc
Normal 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
1
color.gdshaderinc.uid
Normal file
|
@ -0,0 +1 @@
|
||||||
|
uid://dltay473a1mjr
|
19
fox.gd
Normal file
19
fox.gd
Normal 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
1
fox.gd.uid
Normal file
|
@ -0,0 +1 @@
|
||||||
|
uid://8683t0qihiwq
|
BIN
fox.glb
Normal file
BIN
fox.glb
Normal file
Binary file not shown.
6230
fox.glb.import
Normal file
6230
fox.glb.import
Normal file
File diff suppressed because it is too large
Load diff
57
fox.tscn
Normal file
57
fox.tscn
Normal 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
40
main.tscn
Normal 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
249
noise.gdshaderinc
Normal 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
1
noise.gdshaderinc.uid
Normal file
|
@ -0,0 +1 @@
|
||||||
|
uid://dlwojkhehvmio
|
33
player.gd
Normal file
33
player.gd
Normal 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
1
player.gd.uid
Normal file
|
@ -0,0 +1 @@
|
||||||
|
uid://ccyxlwjirmyt0
|
19
player.tscn
Normal file
19
player.tscn
Normal 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
10
player_spawn.gd
Normal 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
1
player_spawn.gd.uid
Normal file
|
@ -0,0 +1 @@
|
||||||
|
uid://ddce7di45eosf
|
6
player_spawn.tscn
Normal file
6
player_spawn.tscn
Normal 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
91
project.godot
Normal 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
68
shell_fur.gdshader
Normal 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
1
shell_fur.gdshader.uid
Normal file
|
@ -0,0 +1 @@
|
||||||
|
uid://bgf62oe1pyffc
|
19
vole.gd
Normal file
19
vole.gd
Normal 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
1
vole.gd.uid
Normal file
|
@ -0,0 +1 @@
|
||||||
|
uid://dxdgep3hm0i8d
|
BIN
vole.glb
Normal file
BIN
vole.glb
Normal file
Binary file not shown.
2103
vole.glb.import
Normal file
2103
vole.glb.import
Normal file
File diff suppressed because it is too large
Load diff
20
vole.tscn
Normal file
20
vole.tscn
Normal 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")
|
Loading…
Add table
Reference in a new issue