mirror of
https://github.com/Such-Software/wownerogue.git
synced 2026-10-17 14:21:25 +02:00
A browser-based roguelike synchronized with crypto block timing. A run is a race against the next block: reach the exit before the block lands, or the house wins. The same codebase runs the solo dungeon, the social Tavern, and operator-selected PvP and race rulesets, with separate free and paid leaderboards. Self-hostable under the MIT License. See README.md to run it, docs/ARCHITECTURE.md for the design, and docs/DEPLOY.md to operate an instance.
133 lines
4.2 KiB
Python
133 lines
4.2 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Build runtime GLBs for Kenney "Animated Characters 1".
|
|
|
|
Run with Blender, not plain Python:
|
|
|
|
blender --background --python scripts/build_kenney_3d_characters.py
|
|
|
|
The source FBX files remain in your local Kenney bundle. Outputs are written under
|
|
html/assets/generated/ (gitignored) for local delivery.
|
|
|
|
Point KENNEY_ASSETS_DIR at the "Animated Characters 1" directory of your own copy of the
|
|
Kenney Game Assets bundle, for example:
|
|
|
|
KENNEY_ASSETS_DIR="$HOME/assets/Kenney Game Assets All-in-1 1.1.0/3D assets/Animated Characters 1" \
|
|
blender --background --python scripts/build_kenney_3d_characters.py
|
|
"""
|
|
import os
|
|
from pathlib import Path
|
|
import bpy
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
_kenney_dir = os.environ.get("KENNEY_ASSETS_DIR")
|
|
if not _kenney_dir:
|
|
raise SystemExit(
|
|
"Set KENNEY_ASSETS_DIR to the 'Animated Characters 1' directory of your Kenney bundle."
|
|
)
|
|
KENNEY = Path(_kenney_dir).expanduser()
|
|
OUT = ROOT / "html/assets/generated/3d/kenney-animated-characters"
|
|
|
|
MODEL = KENNEY / "Model/characterMedium.fbx"
|
|
ANIMS = {
|
|
"idle": KENNEY / "Animations/idle.fbx",
|
|
"run": KENNEY / "Animations/run.fbx",
|
|
"jump": KENNEY / "Animations/jump.fbx",
|
|
}
|
|
SKINS = {
|
|
"survivorMaleB": KENNEY / "Skins/survivorMaleB.png",
|
|
"survivorFemaleA": KENNEY / "Skins/survivorFemaleA.png",
|
|
}
|
|
|
|
|
|
def clear_scene():
|
|
bpy.ops.object.select_all(action="SELECT")
|
|
bpy.ops.object.delete()
|
|
|
|
|
|
def import_fbx(path):
|
|
before = set(bpy.data.objects)
|
|
bpy.ops.import_scene.fbx(filepath=str(path), automatic_bone_orientation=True)
|
|
return [obj for obj in bpy.data.objects if obj not in before]
|
|
|
|
|
|
def find_armature(objects):
|
|
for obj in objects:
|
|
if obj.type == "ARMATURE":
|
|
return obj
|
|
return None
|
|
|
|
|
|
def assign_skin(meshes, texture_path):
|
|
image = bpy.data.images.load(str(texture_path), check_existing=True)
|
|
mat = bpy.data.materials.new(texture_path.stem)
|
|
mat.use_nodes = True
|
|
nodes = mat.node_tree.nodes
|
|
bsdf = nodes.get("Principled BSDF")
|
|
tex = nodes.new("ShaderNodeTexImage")
|
|
tex.image = image
|
|
mat.node_tree.links.new(tex.outputs["Color"], bsdf.inputs["Base Color"])
|
|
if "Alpha" in tex.outputs and "Alpha" in bsdf.inputs:
|
|
mat.node_tree.links.new(tex.outputs["Alpha"], bsdf.inputs["Alpha"])
|
|
mat.blend_method = "BLEND"
|
|
for mesh in meshes:
|
|
mesh.data.materials.clear()
|
|
mesh.data.materials.append(mat)
|
|
|
|
|
|
def append_animation(main_armature, anim_name, anim_path):
|
|
imported = import_fbx(anim_path)
|
|
anim_armature = find_armature(imported)
|
|
action = anim_armature.animation_data.action if anim_armature and anim_armature.animation_data else None
|
|
if action:
|
|
copied = action.copy()
|
|
copied.name = anim_name
|
|
main_armature.animation_data_create()
|
|
main_armature.animation_data.action = copied
|
|
track = main_armature.animation_data.nla_tracks.new()
|
|
track.name = anim_name
|
|
strip = track.strips.new(anim_name, int(copied.frame_range[0]), copied)
|
|
strip.name = anim_name
|
|
main_armature.animation_data.action = None
|
|
for obj in imported:
|
|
bpy.data.objects.remove(obj, do_unlink=True)
|
|
|
|
|
|
def build_skin(name, texture_path):
|
|
clear_scene()
|
|
imported = import_fbx(MODEL)
|
|
armature = find_armature(imported)
|
|
if not armature:
|
|
raise RuntimeError("No armature found in characterMedium.fbx")
|
|
meshes = [obj for obj in imported if obj.type == "MESH"]
|
|
assign_skin(meshes, texture_path)
|
|
for anim_name, anim_path in ANIMS.items():
|
|
append_animation(armature, anim_name, anim_path)
|
|
|
|
bpy.ops.object.select_all(action="DESELECT")
|
|
for obj in imported:
|
|
obj.select_set(True)
|
|
bpy.context.view_layer.objects.active = armature
|
|
|
|
OUT.mkdir(parents=True, exist_ok=True)
|
|
out_path = OUT / f"{name}.glb"
|
|
bpy.ops.export_scene.gltf(
|
|
filepath=str(out_path),
|
|
export_format="GLB",
|
|
use_selection=True,
|
|
export_animations=True,
|
|
export_nla_strips=True,
|
|
export_force_sampling=True,
|
|
)
|
|
print(f"Wrote {out_path}")
|
|
|
|
|
|
def main():
|
|
for name, skin in SKINS.items():
|
|
build_skin(name, skin)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|