Skip to content

Blender Combat Demo

App: apps/combat_blender_demo/

Demonstrates Blender-authored combat (engine PLM-198 / ADR 0069) end to end: CombatVolumes.blend carries a Fighter armature with a sword hitbox and a body hurtbox authored in Blender — bone-parented meshes placed in the Plume_Hitboxes / Plume_Hurtboxes collections (Passive + Animated rigid bodies). No hitbox geometry lives in code.

The demo instantiates the Fighter twice, tells the engine who owns which boxes (owner is game-side), and lunges one into the other. The engine turns each Blender volume into a bone-attached Jolt sensor, Jolt reports the overlap, and Combat.onHit fires. Health is the game’s — here the demo just tallies strikes.

Terminal window
% ./plume3d combat_blender_demo

The attacker closes in and lands strikes; a GUI overlay shows the running hit count.

  • On init, loads CombatVolumes.blend and instantiates the Fighter twice (attacker + target).
  • Reads each character’s Blender-authored volumes off the tag maps — character.hitboxes and character.hurtboxes — and sets owner on every box (a hitbox never strikes its own owner). This is the whole “who is whose enemy” wiring; the shapes and bone attachment came from Blender.
  • Binds each character’s volumes to an AnimController (character.attachController("") + hb.bindController(ctrl)) so they ride their bones at the authored offset — the sword sensor sits on the hand where the Blender mesh does, not at the armature origin. A rest-pose controller (empty graph) is all a static combatant needs; an animated attack drives the same bones and both the sensor and the visible mesh bone-follow the swing (the sword tracks the hand automatically — no game code).
  • Registers Combat.onHit(scene) { |hit| … } and tallies each strike. What a hit costs would be the game’s business — there is no HP type in the engine.
  • In update, lunges the attacker along +X until the authored sword volume overlaps the target’s body volume, at which point the sensor fires onHit.
import "engine" for Scene, Resource, Combat
var scene = Scene.new()
var res = Resource.loadBlend("CombatVolumes.blend")
var a = res.instantiate("Fighter", scene) // attacker — carries Blender-authored volumes
var b = res.instantiate("Fighter", scene) // target
// The GAME assigns identity; geometry + bone attachment came from Blender.
for (hb in a.hitboxes.values) { hb.owner = 1 }
for (hu in a.hurtboxes.values) { hu.owner = 1 }
for (hb in b.hitboxes.values) { hb.owner = 2 }
for (hu in b.hurtboxes.values) { hu.owner = 2 }
// Bind a controller so the Blender-authored volumes ride their bones at the authored offset.
var ctrl = a.attachController("")
for (hb in a.hitboxes.values) { hb.bindController(ctrl) }
for (hu in a.hurtboxes.values) { hu.bindController(ctrl) }
Combat.onHit(scene) { |hit|
// engine detected the overlap; the game owns the consequence (damage/HP/death live here)
}

The Plume3D Blender add-on ships a Combat Volumes panel: select the mesh, click Mark Hitbox / Mark Hurtbox, and it sets a Passive + Animated rigid body and moves the object into Plume_Hitboxes / Plume_Hurtboxes. The object name becomes the tag (sword → hitboxes["sword"]). Bone-parent the mesh to the bone it should ride. That’s the whole authoring contract — see Combat → Authoring volumes in Blender.

Terminal window
% ./plume3d --test apps/combat_blender_demo

Asserts the Blender-authored volumes surface on the instantiated character: hitboxes has the sword key, hurtboxes has the body key, and setting owner on them does not crash. The hit mechanics (sensor overlap, dedup, lifecycle) are covered by tests/unit/test_combat_hitbox.cpp and test_combat_wren.cpp.