Castle Siege Blueprint, Build & Play Your Own Brawler
JVDesignStudio - https://jvdesignstudio.co.uk/workshops/castle-siege-blueprint.html

Offline reference pack for this workshop.
This is a companion copy of the build steps, so you can follow along without
the website. Open the workshop online for the interactive screenshots, videos
and the copy buttons.

What you build: Castle Siege Blueprint, Build & Play Your Own Brawler

--------------------------------------------------------------------
STEP-BY-STEP BUILD
--------------------------------------------------------------------

## STEP 01 · ACTOR - Blueprint Actor Event Graph
- Connect Event BeginPlay to Print String. This is the first node in every Blueprint, it confirms the engine has started and the castle siege can begin.

## STEP 02 · VARIABLES - Event BeginPlay',x:16,y:28,w:152,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'func',title:'Print String',sub:'Castle Siege Starting!',x:200,y:28,w:162,lp:[{id:'l1',type:'exec',label:''}],rp:[{id:'r2',type:'exec',label:''}]}, ],[{f:'r1',t:'l1'}],[]), boxes:[{t:'box-c',tx:'The white arrow pins are exec wires, they control the order nodes run. BeginPlay fires once when the game starts, then flows right to every connected node.'}], onApply(){sout('✅ Engine started! Complete more steps to storm the castle.');} }, {title:'Knight Variables: Stamina & Speed
- Connect BeginPlay → Set Stamina → Set MoveSpeed. Type your knight's starting values in the gold fields, these define how tough and fast your warrior is.

## STEP 03 · TICK - Event BeginPlay',x:8,y:36,w:145,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'vset',title:'Set Stamina',x:174,y:14,w:122,lp:[{id:'l2e',type:'exec',label:''},{id:'l2v',type:'int',label:'Value'}],rp:[{id:'r2',type:'exec',label:''}],vf:{hint:'Stamina 60-200 (e.g.100)',val:''}}, {id:'n3',type:'vset',title:'Set MoveSpeed',x:316,y:14,w:132,lp:[{id:'l3e',type:'exec',label:''},{id:'l3v',type:'float',label:'Value'}],rp:[{id:'r3',type:'exec',label:''}],vf:{hint:'Speed 200-500 (e.g.300)',val:''}}, ],[{f:'r1',t:'l2e'},{f:'r2',t:'l3e'}],[ {id:'n2',ok:v=>v&&!isNaN(parseInt(v))&&parseInt(v)>=30&&parseInt(v)v&&!isNaN(parseFloat(v))&&parseFloat(v)>=100&&parseFloat(v)<=600}, ]), boxes:[{t:'box-c',tx:'Integer pins (blue) = whole numbers. Float pins (green) = decimals. Higher stamina = more hits taken. Higher speed = faster movement, but dodge timing matters more.'}], onApply(v){G.p.maxHp=parseInt(v['n2'])||100;G.p.hp=G.p.maxHp;G.p.speed=(parseFloat(v['n3'])||300)/110;G.p.maxStam=100;G.p.stamBar=100;G.f_player=true;updateKnightCard();updateHUD();sout('✅ Stamina='+v['n2']+' Speed='+v['n3']+
- Connect Event Tick → Multiply (speed × delta) → Add Movement Input. Delta Seconds makes your knight move the same speed regardless of frame rate.

## STEP 04 · INPUT - Event Tick',sub:'~60x/sec',x:6,y:22,w:152,rp:[{id:'r1e',type:'exec',label:''},{id:'r1d',type:'float',label:'Delta Seconds'}]}, {id:'n2',type:'math',title:'× Multiply',x:182,y:14,w:128,lp:[{id:'l2a',type:'float',label:'Speed'},{id:'l2b',type:'float',label:'Delta'}],rp:[{id:'r2',type:'float',label:'Result'}]}, {id:'n3',type:'func',title:'Add Movement Input',x:334,y:6,w:160,lp:[{id:'l3e',type:'exec',label:''},{id:'l3s',type:'float',label:'Scale'}],rp:[{id:'r3',type:'exec',label:''}]}, ],[{f:'r1e',t:'l3e'},{f:'r1d',t:'l2b'},{f:'r2',t:'l3s'}],[]), boxes:[{t:'box-c',tx:'At 60fps Delta ≈ 0.016s. Speed × Delta = units per second, not per frame. This is crucial, without it, movement on a 144Hz monitor is twice as fast as on 60Hz.'}], onApply(){sout('✅ Tick → Delta Seconds → Movement wired!');} }, {title:'Enhanced Input, Mapping Context
- Connect BeginPlay → Get Player Controller → Get Enhanced Input Subsystem → Add Mapping Context. Without this chain, no input works at all in Unreal.

## STEP 05 · MOVEMENT - Event BeginPlay',x:6,y:36,w:148,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'func',title:'Get Player Controller',x:176,y:18,w:165,lp:[{id:'l2e',type:'exec',label:''}],rp:[{id:'r2e',type:'exec',label:''},{id:'r2p',type:'object',label:'Controller'}]}, {id:'n3',type:'func',title:'Get Enhanced Input\nSubsystem',x:6,y:116,w:182,lp:[{id:'l3p',type:'object',label:'Player Ctrl'}],rp:[{id:'r3',type:'object',label:'Subsystem'}]}, {id:'n4',type:'func',title:'Add Mapping Context',x:214,y:108,w:168,lp:[{id:'l4e',type:'exec',label:''},{id:'l4s',type:'object',label:'Target'},{id:'l4m',type:'object',label:'Mapping Context'}],rp:[{id:'r4',type:'exec',label:''}]}, ],[{f:'r1',t:'l2e'},{f:'r2e',t:'l4e'},{f:'r2p',t:'l3p'},{f:'r3',t:'l4s'}],[]), boxes:[{t:'box-u',tx:'Content Browser → Input → Input Mapping Context. Name it IMC_Knight. Add WASD bindings for IA_Move. Without adding this in BeginPlay, nothing responds to keyboard at all.'}], onApply(){G.f_player=true;sout('✅ Mapping Context added, WASD active!');} }, {title:'WASD Movement, Break Vector2D
- Connect IA_Move (Triggered) → Break Vector2D → two Add Movement Input nodes. X drives left/right, Y drives forward/back.

## STEP 06 · AIM - IA_Move (Triggered)',sub:'Enhanced Input',x:4,y:22,w:172,rp:[{id:'r1e',type:'exec',label:''},{id:'r1v',type:'vec3',label:'Action Value'}]}, {id:'n2',type:'func',title:'Break Vector2D',x:200,y:6,w:138,lp:[{id:'l2v',type:'vec3',label:''}],rp:[{id:'r2x',type:'float',label:'X (right)'},{id:'r2y',type:'float',label:'Y (fwd)'}]}, {id:'n3',type:'func',title:'Add Mvmt Input',sub:'Right',x:362,y:4,w:155,lp:[{id:'l3e',type:'exec',label:''},{id:'l3s',type:'float',label:'Scale'}],rp:[{id:'r3',type:'exec',label:''}]}, {id:'n4',type:'func',title:'Add Mvmt Input',sub:'Forward',x:362,y:84,w:155,lp:[{id:'l4e',type:'exec',label:''},{id:'l4s',type:'float',label:'Scale'}]}, ],[{f:'r1e',t:'l3e'},{f:'r1v',t:'l2v'},{f:'r2x',t:'l3s'},{f:'r2y',t:'l4s'}],[]), boxes:[{t:'box-t',tx:'Set World Direction on the right node to Get Actor Right Vector and on the forward node to Get Actor Forward Vector. This keeps movement camera-relative.'}], onApply(){G.f_player=true;sout('✅ WASD wired! Move your knight around the arena.');} }, // ── SECTION 2: AIM & ATTACK ── {title:'Get Hit Under Cursor, Aim Point
- Connect Event Tick → Get Hit Result Under Cursor → Branch (bBlockingHit) → Set Aim Location. This gives the 3D position of the mouse on the ground plane.

## STEP 07 · ROTATION - Event Tick',x:4,y:34,w:135,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'func',title:'Get Hit Result\nUnder Cursor',x:158,y:8,w:165,lp:[{id:'l2e',type:'exec',label:''}],rp:[{id:'r2e',type:'exec',label:''},{id:'r2h',type:'bool',label:'bBlockingHit'},{id:'r2l',type:'vec3',label:'Hit Location'}]}, {id:'n3',type:'branch',title:'Branch',x:344,y:10,w:100,lp:[{id:'l3e',type:'exec',label:''},{id:'l3c',type:'bool',label:'Cond'}],rp:[{id:'r3t',type:'exec',label:'True'},{id:'r3f',type:'exec',label:'False'}]}, {id:'n4',type:'vset',title:'Set Aim Location',x:158,y:128,w:158,lp:[{id:'l4e',type:'exec',label:''},{id:'l4v',type:'vec3',label:'Value'}]}, ],[{f:'r1',t:'l2e'},{f:'r2e',t:'l3e'},{f:'r2h',t:'l3c'},{f:'r3t',t:'l4e'},{f:'r2l',t:'l4v'}],[]), boxes:[{t:'box-c',tx:'Branch is Blueprint\'s if/else. bBlockingHit is false when the cursor is off-screen, the Branch stops a garbage aim position being stored.'}], onApply(){G.f_aim=true;sout('✅ Mouse aim active! Your knight faces the cursor.');} }, {title:'Find Look at Rotation, Face Mouse
- Connect Find Look at Rotation (using Aim Location) → RInterp To → Set Actor Rotation. Type a smooth rotation speed.

## STEP 08 · WEAPON - Get Aim Location',x:6,y:46,w:145,rp:[{id:'r1',type:'vec3',label:''}]}, {id:'n2',type:'func',title:'Find Look at\nRotation',x:174,y:12,w:148,lp:[{id:'l2s',type:'vec3',label:'Start'},{id:'l2t',type:'vec3',label:'Target'}],rp:[{id:'r2',type:'rot',label:'Return'}]}, {id:'n3',type:'func',title:'RInterp To',x:344,y:6,w:125,lp:[{id:'l3c',type:'rot',label:'Current'},{id:'l3t',type:'rot',label:'Target'},{id:'l3s',type:'float',label:'Speed'}],rp:[{id:'r3',type:'rot',label:'Result'}],vf:{hint:'Rot speed 8-20 (e.g.12)',val:''}}, {id:'n4',type:'func',title:'Set Actor Rotation',x:492,y:6,w:46,lp:[{id:'l4',type:'rot',label:''}]}, ],[{f:'r1',t:'l2t'},{f:'r2',t:'l3t'},{f:'r3',t:'l4'}],[ {id:'n3',ok:v=>parseFloat(v)>=4&&parseFloat(v)<=30}, ]), boxes:[{t:'box-t',tx:'RInterp To smooths rotation over time rather than snapping instantly. Low speed = slow lazy turn, high speed = snappy. A value around 12 feels like a responsive but physical warrior.'}], onApply(){G.f_aim=true;sout('✅ Rotation smooth! Knight now turns towards your cursor.');} }, // ── SECTION 3: MELEE SYSTEM ── {title:'Design Your Starting Weapon
- Wire your weapon stats: BeginPlay → Set WeaponName → Set SwingDamage → Set WeaponReach. Type values in the gold fields, this is the sword, axe or mace you begin the siege with.

## STEP 09 · WEAPON - Event BeginPlay',x:6,y:36,w:150,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'weapon',title:'Set WeaponName',x:176,y:14,w:136,lp:[{id:'l2e',type:'exec',label:''},{id:'l2v',type:'string',label:'Name'}],rp:[{id:'r2',type:'exec',label:''}],vf:{hint:'Name (e.g. Longsword)',val:''}}, {id:'n3',type:'weapon',title:'Set SwingDamage',x:332,y:14,w:132,lp:[{id:'l3e',type:'exec',label:''},{id:'l3v',type:'int',label:'Damage'}],rp:[{id:'r3',type:'exec',label:''}],vf:{hint:'Dmg 3-20 (e.g. 8)',val:''}}, {id:'n4',type:'weapon',title:'Set WeaponReach',x:482,y:14,w:58,lp:[{id:'l4e',type:'exec',label:''}],vf:{hint:'Reach 20-60 (e.g.36)',val:''}}, ],[{f:'r1',t:'l2e'},{f:'r2',t:'l3e'},{f:'r3',t:'l4e'}],[ {id:'n2',ok:v=>v&&v.trim().length>=2}, {id:'n3',ok:v=>parseInt(v)>=1&&parseInt(v)parseInt(v)>=15&&parseInt(v)<=65}, ],true), boxes:[{t:'box-w',tx:'Long reach weapons can hit enemies through castle pillars and around corners, powerful but the arc is narrow. Short reach weapons sweep wider arcs and are better in tight corridors.'}], onApply(v){ startWPN.name=v['n2']||'Blade'; startWPN.dmg=parseInt(v['n3'])||8; startWPN.reach=parseInt(v['n4'])||36; G.p.wpn0={...startWPN}; updateKnightCard();updateHUD(); sout('✅ Weapon: '+startWPN.name+' ('+startWPN.dmg+' dmg, '+startWPN.reach+'px reach) equipped!'); } }, {title:'Weapon Swing Speed & Sweep Angle
- Wire Set SwingSpeed → Set SweepAngle. Speed is cooldown in seconds, lower = faster swings. Sweep angle is the arc the blade cuts.

## STEP 10 · STAMINA - Set SwingSpeed',x:6,y:18,w:150,lp:[{id:'l1e',type:'exec',label:''},{id:'l1v',type:'float',label:'Cooldown(s)'}],rp:[{id:'r1',type:'exec',label:''}],vf:{hint:'Cooldown 0.2-1.2 (e.g.0.45)',val:''}}, {id:'n2',type:'weapon',title:'Set SweepAngle',x:178,y:18,w:150,lp:[{id:'l2e',type:'exec',label:''},{id:'l2v',type:'int',label:'Degrees'}],rp:[{id:'r2',type:'exec',label:''}],vf:{hint:'Sweep 20-120 (e.g.70)',val:''}}, {id:'n3',type:'weapon',title:'Set WeaponColor',sub:'visual only',x:350,y:18,w:140,lp:[{id:'l3e',type:'exec',label:''}]}, ],[{f:'r1',t:'l2e'},{f:'r2',t:'l3e'}],[ {id:'n1',ok:v=>parseFloat(v)>=0.15&&parseFloat(v)parseInt(v)>=15&&parseInt(v)<=130}, ]), boxes:[ {t:'box-w',tx:'A wide sweep angle (90°+) is great for groups of grunts in open courtyards. A narrow angle (20°-30°) means precision strikes, good for high-damage slow weapons like great axes.'}, {t:'box-t',tx:'Swing speed + damage creates a build tradeoff: fast low-damage vs slow high-damage. Think about which rooms your knight will struggle in.'}, ], onApply(v){ startWPN.spd=parseFloat(v['n1'])||0.45; startWPN.sweep=parseInt(v['n2'])||70; G.p.wpn0={...startWPN}; G.f_attack=true; updateKnightCard();updateHUD(); sout('✅ Swing speed='+v['n1']+'s sweep='+v['n2']+'°, Space/Click to swing!'); } }, // ── SECTION 4: STAMINA ── {title:'Stamina System, Action Cost
- Connect IA_Attack → Subtract Stamina → Branch (≥ 0) → True: Perform Swing, False: Play Exhausted Animation. Stamina gates how often you can attack and sprint.

## STEP 11 · STAMINA - IA_Attack (Triggered)',sub:'Enhanced Input',x:4,y:34,w:175,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'math',title:'- Subtract',sub:'Stamina cost',x:200,y:14,w:118,lp:[{id:'l2e',type:'exec',label:''},{id:'l2v',type:'float',label:'Cost'}],rp:[{id:'r2e',type:'exec',label:''},{id:'r2v',type:'float',label:'New Stam'}],vf:{hint:'Cost 5-20 (e.g. 8)',val:''}}, {id:'n3',type:'math',title:'>= Check',sub:'Stam >= 0',x:340,y:14,w:118,lp:[{id:'l3a',type:'float',label:'A'},{id:'l3b',type:'float',label:'B (0)'}],rp:[{id:'r3',type:'bool',label:'OK?'}]}, {id:'n4',type:'branch',title:'Branch',x:340,y:96,w:100,lp:[{id:'l4e',type:'exec',label:''},{id:'l4c',type:'bool',label:'Cond'}],rp:[{id:'r4t',type:'exec',label:'Swing'},{id:'r4f',type:'exec',label:'Exhausted'}]}, ],[{f:'r1',t:'l2e'},{f:'r2e',t:'l4e'},{f:'r2v',t:'l3a'},{f:'r3',t:'l4c'}],[ {id:'n2',ok:v=>parseFloat(v)>=2&&parseFloat(v)<=25}, ]), boxes:[ {t:'box-c',tx:'Stamina recovers automatically when standing still. Running also costs stamina. A knight who charges in swinging wildly will be exhausted and vulnerable when brutes close in.'}, ], onApply(){G.f_stam=true;sout('✅ Stamina system active! Watch the bar below your knight when you sprint and swing.');} }, {title:'Stamina Regen, Recovery Rate
- Connect Event Tick → Get Stamina → Less Than MaxStam → Branch True → Add Regen → Set Stamina. Type the regen amount per tick.

## STEP 12 · ENEMIES - Event Tick',x:4,y:34,w:120,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'vget',title:'Get Stamina',x:144,y:20,w:120,rp:[{id:'r2',type:'float',label:''}]}, {id:'n3',type:'math',title:'parseFloat(v)>=0.2&&parseFloat(v)<=3}, ]), boxes:[{t:'box-t',tx:'Fast regen (1.5+) favours aggressive play, you can swing freely as long as you rest between rooms. Slow regen (0.5) creates tense resource decisions mid-fight.'}], onApply(v){sout('✅ Stamina regen set to '+v['n5']+'/tick!');} }, // ── SECTION 5: ENEMIES ── {title:'Enemy Class: Guard Grunt
- Wire BeginPlay → Set Health → Set Speed → Set ContactDamage in BP_Guard. Grunts are the most common enemy, the rabble defending every zone.

## STEP 13 · ENEMIES - Event BeginPlay',sub:'BP_Guard',x:6,y:34,w:155,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'vset',title:'Set Health',x:182,y:12,w:118,lp:[{id:'l2e',type:'exec',label:''},{id:'l2v',type:'int',label:'HP'}],rp:[{id:'r2',type:'exec',label:''}],vf:{hint:'HP 3-15 (e.g. 5)',val:''}}, {id:'n3',type:'vset',title:'Set Speed',x:320,y:12,w:118,lp:[{id:'l3e',type:'exec',label:''},{id:'l3v',type:'float',label:'Speed'}],rp:[{id:'r3',type:'exec',label:''}],vf:{hint:'Spd 1.0-2.5 (e.g.1.3)',val:''}}, {id:'n4',type:'vset',title:'Set ContactDmg',x:458,y:12,w:78,lp:[{id:'l4e',type:'exec',label:''},{id:'l4v',type:'int',label:'Dmg'}],vf:{hint:'Dmg 3-12',val:''}}, ],[{f:'r1',t:'l2e'},{f:'r2',t:'l3e'},{f:'r3',t:'l4e'}],[ {id:'n2',ok:v=>parseInt(v)>=1&&parseInt(v)parseFloat(v)>=0.5&&parseFloat(v)parseInt(v)>=2&&parseInt(v)<=20}, ]), boxes:[{t:'box-c',tx:'Guards (red circles) appear in every zone. Their HP matters: weak guards die easily but swarm you in numbers. Strong guards force you to commit stamina, dangerous in later zones.'}], onApply(){G.f_enemies=true;sout('✅ Guards are spawning! They rush your position from the right.');} }, {title:'Enemy Class: Castle Brute
- Wire BeginPlay → Set Health → Set Speed → Set ContactDamage in BP_Brute. Brutes are heavily armoured, slow moving walls of HP that appear from Zone 2.

## STEP 14 · ENEMIES - Event BeginPlay',sub:'BP_Brute',x:6,y:34,w:155,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'vset',title:'Set Health',x:182,y:14,w:118,lp:[{id:'l2e',type:'exec',label:''},{id:'l2v',type:'int',label:'HP (high)'}],rp:[{id:'r2',type:'exec',label:''}],vf:{hint:'HP 12-40 (e.g.18)',val:''}}, {id:'n3',type:'vset',title:'Set Speed',x:320,y:14,w:118,lp:[{id:'l3e',type:'exec',label:''},{id:'l3v',type:'float',label:'Speed (low)'}],rp:[{id:'r3',type:'exec',label:''}],vf:{hint:'Spd 0.5-1.2 (e.g.0.8)',val:''}}, {id:'n4',type:'vset',title:'Set ContactDmg',x:458,y:14,w:78,lp:[{id:'l4e',type:'exec',label:''}],vf:{hint:'Dmg 10-25',val:''}}, ],[{f:'r1',t:'l2e'},{f:'r2',t:'l3e'},{f:'r3',t:'l4e'}],[ {id:'n2',ok:v=>parseInt(v)>=5&&parseInt(v)parseFloat(v)>=0.3&&parseFloat(v)parseInt(v)>=6&&parseInt(v)<=30}, ]), boxes:[{t:'box-c',tx:'Brutes (brown hexagons) force resource decisions, you need multiple swings to kill one. Weapons with high damage-per-hit (war axe, greatsword) are effective. Low-damage daggers will exhaust you.'}], onApply(){sout('✅ Brutes are set! They appear from Zone 2, hard to kill, hard hits.');} }, {title:'Enemy Class: Castle Archer
- Wire BeginPlay → Set Health → Set FireRate → Set ArrowDamage in BP_Archer. Archers keep their distance and fire from range, they change how you use cover.

## STEP 15 · COLLISION - Event BeginPlay',sub:'BP_Archer',x:6,y:34,w:155,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'vset',title:'Set Health',x:182,y:12,w:118,lp:[{id:'l2e',type:'exec',label:''},{id:'l2v',type:'int',label:'HP'}],rp:[{id:'r2',type:'exec',label:''}],vf:{hint:'HP 2-8 (e.g. 3)',val:''}}, {id:'n3',type:'vset',title:'Set FireRate',x:320,y:12,w:118,lp:[{id:'l3e',type:'exec',label:''},{id:'l3v',type:'float',label:'Rate (secs)'}],rp:[{id:'r3',type:'exec',label:''}],vf:{hint:'Rate 1.0-3 (e.g.1.6)',val:''}}, {id:'n4',type:'vset',title:'Set ArrowDmg',x:458,y:12,w:78,lp:[{id:'l4e',type:'exec',label:''},{id:'l4v',type:'int',label:'Dmg'}],vf:{hint:'Dmg 4-14(e.g.6)',val:''}}, ],[{f:'r1',t:'l2e'},{f:'r2',t:'l3e'},{f:'r3',t:'l4e'}],[ {id:'n2',ok:v=>parseInt(v)>=1&&parseInt(v)parseFloat(v)>=0.6&&parseFloat(v)parseInt(v)>=2&&parseInt(v)<=18}, ]), boxes:[{t:'box-r',tx:'Archers (green triangles) appear from Zone 1-3 onward. They retreat when you close in. Rooms with the cross or L-shaped walls make archers deadly, they can hide and shoot from cover positions.'}], onApply(){G.f_enemies=true;sout('✅ Archers set! They fire arrows from range, use pillars as cover!');} }, // ── SECTION 6: COLLISION ── {title:'On Actor Begin Overlap, Sword Hit!
- Connect On Actor Begin Overlap → Cast to BP_Enemy → Apply Damage → Spawn Blood FX → Destroy if Dead. Wire Other Actor to Object and Enemy ref to Damaged Actor.

## STEP 16 · COLLISION - On Actor Begin Overlap',sub:'BP_SwordArc',x:4,y:16,w:190,rp:[{id:'r1e',type:'exec',label:''},{id:'r1a',type:'actor',label:'Other Actor'}]}, {id:'n2',type:'cast',title:'Cast to BP_Enemy',x:214,y:4,w:148,lp:[{id:'l2e',type:'exec',label:''},{id:'l2o',type:'actor',label:'Object'}],rp:[{id:'r2s',type:'exec',label:'Success'},{id:'r2en',type:'actor',label:'As Enemy'}]}, {id:'n3',type:'func',title:'Apply Damage',x:382,y:4,w:148,lp:[{id:'l3e',type:'exec',label:''},{id:'l3a',type:'actor',label:'Damaged Actor'},{id:'l3d',type:'float',label:'Damage'}],rp:[{id:'r3',type:'exec',label:''}],vf:{hint:'Damage (e.g. 1)',val:''}}, {id:'n4',type:'func',title:'Spawn Blood FX',x:382,y:108,w:148,lp:[{id:'l4e',type:'exec',label:''}]}, ],[{f:'r1e',t:'l2e'},{f:'r1a',t:'l2o'},{f:'r2s',t:'l3e'},{f:'r2en',t:'l3a'},{f:'r3',t:'l4e'}],[ {id:'n3',ok:v=>parseFloat(v)>0}, ]), boxes:[{t:'box-c',tx:'Cast to BP_Enemy fails when the sword arc hits a wall, the Fail exec pin fires. Leave it unconnected unless you want a wall-hit spark effect (a great extra touch!).'}], onApply(){G.f_collision=true;sout('✅ Collision live! Your sword damages enemies. Space/Click to swing!');} }, {title:'Enemy Takes Damage, HP Check & Death
- In BP_Enemy: Connect ApplyDamage Event → Subtract HP → Branch (HP ≤ 0) → True: Play Death FX → Destroy Actor and Broadcast OnEnemyDied.

## STEP 17 · ROOMS - AnyDamage Event',sub:'BP_Enemy',x:4,y:36,w:160,rp:[{id:'r1e',type:'exec',label:''},{id:'r1d',type:'float',label:'Damage'}]}, {id:'n2',type:'math',title:'- Subtract',sub:'HP - Damage',x:186,y:14,w:118,lp:[{id:'l2e',type:'exec',label:''},{id:'l2v',type:'float',label:'Damage'}],rp:[{id:'r2e',type:'exec',label:''},{id:'r2v',type:'float',label:'New HP'}]}, {id:'n3',type:'math',title:'<= LessEqual',sub:'NewHP <= 0',x:325,y:14,w:114,lp:[{id:'l3a',type:'float',label:'A'},{id:'l3b',type:'float',label:'B(0)'}],rp:[{id:'r3',type:'bool',label:'Dead?'}]}, {id:'n4',type:'branch',title:'Branch',x:325,y:96,w:100,lp:[{id:'l4e',type:'exec',label:''},{id:'l4c',type:'bool',label:'Cond'}],rp:[{id:'r4t',type:'exec',label:'Dead'},{id:'r4f',type:'exec',label:'Alive'}]}, {id:'n5',type:'custom',title:'Broadcast\nOnEnemyDied',x:447,y:96,w:88,lp:[{id:'l5',type:'exec',label:''}]}, ],[{f:'r1e',t:'l2e'},{f:'r1d',t:'l2v'},{f:'r2e',t:'l4e'},{f:'r2v',t:'l3a'},{f:'r3',t:'l4c'},{f:'r4t',t:'l5'}],[]), boxes:[{t:'box-t',tx:'Broadcasting OnEnemyDied is the signal to the Room Manager that it should check if all enemies are dead and maybe open the exit gate. Events decouple systems cleanly.'}], onApply(){G.f_collision=true;sout('✅ Death system wired! Enemies die and drop score!');} }, // ── SECTION 7: ROOMS ── {title:'Zone System, Room Manager
- Connect On Enemy Died → Count Living Enemies → Branch (== 0) → Open Gate. When all guards are gone, the iron gate glows gold and can be walked through.

## STEP 18 · ROOMS - OnEnemyDied',sub:'Custom Event',x:4,y:34,w:145,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'func',title:'Count Living\nEnemies',x:172,y:14,w:155,lp:[{id:'l2e',type:'exec',label:''}],rp:[{id:'r2e',type:'exec',label:''},{id:'r2c',type:'int',label:'Count'}]}, {id:'n3',type:'math',title:'== (Equal)',sub:'Count == 0',x:348,y:14,w:112,lp:[{id:'l3a',type:'int',label:'A'},{id:'l3b',type:'int',label:'B (0)'}],rp:[{id:'r3',type:'bool',label:''}]}, {id:'n4',type:'branch',title:'Branch',x:348,y:96,w:100,lp:[{id:'l4e',type:'exec',label:''},{id:'l4c',type:'bool',label:'Cond'}],rp:[{id:'r4t',type:'exec',label:'True'}]}, {id:'n5',type:'func',title:'Open Gate',x:468,y:96,w:68,lp:[{id:'l5',type:'exec',label:''}]}, ],[{f:'r1',t:'l2e'},{f:'r2e',t:'l4e'},{f:'r2c',t:'l3a'},{f:'r3',t:'l4c'},{f:'r4t',t:'l5'}],[]), boxes:[ {t:'box-r',tx:'The exit gate is always in the right wall of each zone. It glows gold when open. Walk into it to trigger the zone transition.'}, {t:'box-t',tx:'Zone layout: 5 zones per world, 3 worlds = 15 zones total + final boss. After each world, a perk screen appears.'}, ], onApply(){G.f_doors=true;sout('✅ Gate system active! Clear enemies → gate glows gold → walk through!');} }, {title:'Zone Transition, Load Next Zone
- Connect On Gate Entered → Increment Zone Index → Branch (boss zone?) → Load Zone or Load Boss. Every 5th zone is the Baron's quarters.

## STEP 19 · CHESTS - OnGateEntered',sub:'Custom Event',x:4,y:34,w:148,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'func',title:'Increment Zone Index',x:174,y:12,w:155,lp:[{id:'l2e',type:'exec',label:''}],rp:[{id:'r2e',type:'exec',label:''},{id:'r2i',type:'int',label:'New Index'}]}, {id:'n3',type:'math',title:'Modulo %',sub:'Index % 5 == 0',x:350,y:12,w:112,lp:[{id:'l3a',type:'int',label:'A'},{id:'l3b',type:'int',label:'B(5)'}],rp:[{id:'r3',type:'bool',label:'Boss?'}]}, {id:'n4',type:'branch',title:'Branch',x:350,y:92,w:100,lp:[{id:'l4e',type:'exec',label:''},{id:'l4c',type:'bool',label:'Cond'}],rp:[{id:'r4t',type:'exec',label:'Boss'},{id:'r4f',type:'exec',label:'Normal'}]}, {id:'n5',type:'func',title:'Load Zone',x:470,y:84,w:66,lp:[{id:'l5',type:'exec',label:''}]}, ],[{f:'r1',t:'l2e'},{f:'r2e',t:'l4e'},{f:'r2i',t:'l3a'},{f:'r3',t:'l4c'},{f:'r4f',t:'l5'}],[]), boxes:[ {t:'box-r',tx:'World structure: Courtyard → Gatehouse → Barracks → Ramparts → Boss zone. After defeating each Baron, the next wing of the castle opens with tougher guards.'}, {t:'box-c',tx:'The transition plays a brief fade. Your knight spawns on the left wall of each new zone. Enemies spawn in random positions avoiding your entry point.'}, ], onApply(){sout('✅ Zone transitions wired!');} }, // ── SECTION 8: CHEST WEAPONS ── {title:'Weapon Chest, Loot System
- Connect On Chest Overlap → Get Random Weapon From Pool → Offer Swap → Set Player Weapon. Walking into a chest gives you a new weapon to replace or carry.

## STEP 20 · CHESTS - On Chest Overlap',x:4,y:34,w:155,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'func',title:'Get Random Weapon\nFrom Pool',x:180,y:12,w:168,lp:[{id:'l2e',type:'exec',label:''}],rp:[{id:'r2e',type:'exec',label:''},{id:'r2w',type:'object',label:'Weapon Data'}]}, {id:'n3',type:'func',title:'Show Weapon Offer\nUI Widget',x:370,y:12,w:166,lp:[{id:'l3e',type:'exec',label:''},{id:'l3w',type:'object',label:'Weapon'}],rp:[{id:'r3e',type:'exec',label:''},{id:'r3b',type:'bool',label:'Accepted'}]}, {id:'n4',type:'func',title:'Set Player Weapon',x:370,y:116,w:166,lp:[{id:'l4e',type:'exec',label:''},{id:'l4w',type:'object',label:'Weapon'}]}, ],[{f:'r1',t:'l2e'},{f:'r2e',t:'l3e'},{f:'r2w',t:'l3w'},{f:'r3e',t:'l4e'},{f:'r2w',t:'l4w'}],[]), boxes:[ {t:'box-c',tx:'The weapon pool has 12 weapons: Longsword, War Axe, Mace, Rapier, Halberd, Flail, Greatsword, Dual Daggers, Morning Star, Spear, Battle Axe, and Arming Sword.'}, {t:'box-t',tx:'Chest chance scales by room type, Treasury rooms always have chests. Open zones have ~40% chance. The weapon you find is random but never one you already have.'}, ], onApply(){G.f_chests=true;sout('✅ Chest system active! Walk into a chest to loot a weapon.');} }, {title:'Weapon Slot System, Carry Two
- Connect IA_SwapWeapon → Toggle Active Slot → Set Active Weapon. Press Q to swap between your starting weapon and a looted weapon.

## STEP 21 · HUD - IA_SwapWeapon',sub:'Q key pressed',x:4,y:28,w:155,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'func',title:'Toggle Active Slot',x:180,y:8,w:148,lp:[{id:'l2e',type:'exec',label:''}],rp:[{id:'r2e',type:'exec',label:''},{id:'r2s',type:'int',label:'New Slot 0/1'}]}, {id:'n3',type:'vset',title:'Set Active Weapon',x:348,y:8,w:155,lp:[{id:'l3e',type:'exec',label:''},{id:'l3s',type:'int',label:'Slot Index'}]}, ],[{f:'r1',t:'l2e'},{f:'r2e',t:'l3e'},{f:'r2s',t:'l3s'}],[]), boxes:[ {t:'box-t',tx:'Two weapon slots lets you build a combo, e.g. Rapier (long pierce reach) + Greatsword (wide sweep). Use the quick weapon for pokes, swap for crowd clears.'}, ], onApply(){sout('✅ Weapon swap active! Press Q to toggle between slot 1 and slot 2.');} }, // ── SECTION 9: HUD ── {title:'Player HUD, Stamina Bar & Status
- Connect BeginPlay → Create Widget (WBP_HUD) → Add to Viewport. Wire the Stamina float to the Progress Bar fill. The HUD shows stamina, HP, score and current weapon.

## STEP 22 · PERKS - Event BeginPlay',x:6,y:28,w:148,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'func',title:'Create Widget',sub:'WBP_HUD',x:176,y:10,w:138,lp:[{id:'l2e',type:'exec',label:''}],rp:[{id:'r2e',type:'exec',label:''},{id:'r2w',type:'object',label:'Widget Ref'}]}, {id:'n3',type:'func',title:'Add to Viewport',x:335,y:10,w:148,lp:[{id:'l3e',type:'exec',label:''},{id:'l3w',type:'object',label:'Target'}],rp:[{id:'r3',type:'exec',label:''}]}, {id:'n4',type:'vset',title:'Set HUD Ref',x:503,y:10,w:33,lp:[{id:'l4e',type:'exec',label:''}]}, ],[{f:'r1',t:'l2e'},{f:'r2e',t:'l3e'},{f:'r2w',t:'l3w'},{f:'r3',t:'l4e'}],[]), boxes:[ {t:'box-u',tx:'In UMG Editor: add a Progress Bar for stamina, a Text Block for HP, and an Image for the current weapon icon. Bind them to your BP_Knight variables using \'Bind\' in the Details panel.'}, {t:'box-t',tx:'A good stamina bar colour scheme: gold when full, orange below 50%, red below 20%. Colour-coding gives the player instant reading at a glance.'}, ], onApply(){G.f_hud=true;updateHUD();sout('✅ HUD active! Stamina, HP, score and weapon displayed.');} }, // ── SECTION 10: PERKS ── {title:'World Perk Screen, Choose a Buff
- After each world a Perk screen appears. Wire Show Perk Screen → Wait for Player Choice → Apply Perk Effect → Continue to Next World. Perks are permanent buffs.

## STEP 23 · PERKS - On World Complete',x:4,y:28,w:155,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'func',title:'Show Perk Screen\nUI Widget',x:180,y:8,w:160,lp:[{id:'l2e',type:'exec',label:''}],rp:[{id:'r2e',type:'exec',label:''},{id:'r2p',type:'string',label:'Chosen Perk'}]}, {id:'n3',type:'func',title:'Apply Perk Effect',x:362,y:8,w:148,lp:[{id:'l3e',type:'exec',label:''},{id:'l3p',type:'string',label:'Perk ID'}],rp:[{id:'r3',type:'exec',label:''}]}, {id:'n4',type:'func',title:'Load Next World',x:530,y:8,w:8,lp:[{id:'l4e',type:'exec',label:''}]}, ],[{f:'r1',t:'l2e'},{f:'r2e',t:'l3e'},{f:'r2p',t:'l3p'},{f:'r3',t:'l4e'}],[]), boxes:[{t:'box-m',tx:'Perks available: Berserk (1.6× damage when below 30% HP), Shield Bash (start with a one-hit shield), Second Wind (passive stamina and HP regen), Swift Feet (1.35× movement speed).'}], onApply(){sout('✅ Perk system wired!');} }, {title:'Perk, Choose Your Knight Trait
- Select your World 2 perk card below. This permanently upgrades your knight for the rest of the siege.

## STEP 24 · PERKS - Perk, World 3 Battle Perk
- Select your World 3 perk. These change your entire playstyle, pick to complement your World 2 choice.

## STEP 25 · BOSS - Boss, The Baron\'s Blueprint
- In BP_Baron: Connect Event BeginPlay → Set Phase → Set Health → Start Phase 1 Pattern. The Baron has 2 phases, he enrages below 50% HP.

## STEP 26 · BOSS - Event BeginPlay',sub:'BP_Baron',x:6,y:28,w:152,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'vset',title:'Set Phase',sub:'= 1',x:180,y:8,w:110,lp:[{id:'l2e',type:'exec',label:''}],rp:[{id:'r2',type:'exec',label:''}]}, {id:'n3',type:'vset',title:'Set Health',x:310,y:8,w:118,lp:[{id:'l3e',type:'exec',label:''},{id:'l3v',type:'int',label:'Boss HP'}],rp:[{id:'r3',type:'exec',label:''}],vf:{hint:'HP 120-300 (e.g.180)',val:''}}, {id:'n4',type:'func',title:'Start Phase 1\nAttack Pattern',x:450,y:8,w:86,lp:[{id:'l4e',type:'exec',label:''}]}, ],[{f:'r1',t:'l2e'},{f:'r2',t:'l3e'},{f:'r3',t:'l4e'}],[ {id:'n3',ok:v=>parseInt(v)>=80&&parseInt(v)<=400}, ]), boxes:[ {t:'box-c',tx:'Phase 1: The Baron charges in wide arcs and summons archers. Phase 2 (below 50%): Baron enrages, speed increases, attacks get faster, he summons a brute escort.'}, {t:'box-r',tx:'The Throne Room is the largest open room, no cover. You will want high HP or Swift/Second Wind to dodge the Baron\'s charge attacks.'}, ], onApply(){sout('✅ Baron configured! He waits in the Throne Room at zone 5.');} }, {title:'Boss Phase Transition, Enrage
- Connect AnyDamage → Get HP → Less Than (maxHP/2) → Branch → True: Set Phase 2 → Increase Speed → Spawn Escort. The Baron transforms at half health.

## STEP 27 · POLISH - AnyDamage Event',sub:'BP_Baron',x:4,y:34,w:152,rp:[{id:'r1e',type:'exec',label:''},{id:'r1d',type:'float',label:'Damage'}]}, {id:'n2',type:'vget',title:'Get HP',x:178,y:26,w:88,rp:[{id:'r2',type:'float',label:''}]}, {id:'n3',type:'math',title:'< Less Than',sub:'HP < Max/2',x:286,y:14,w:112,lp:[{id:'l3a',type:'float',label:'A'},{id:'l3b',type:'float',label:'B'}],rp:[{id:'r3',type:'bool',label:''}]}, {id:'n4',type:'branch',title:'Branch',x:286,y:94,w:100,lp:[{id:'l4e',type:'exec',label:''},{id:'l4c',type:'bool',label:'Cond'}],rp:[{id:'r4t',type:'exec',label:'Enrage'},{id:'r4f',type:'exec',label:'Normal'}]}, {id:'n5',type:'func',title:'Set Phase 2\n+ Spawn Escort',x:406,y:94,w:130,lp:[{id:'l5',type:'exec',label:''}]}, ],[{f:'r1e',t:'l4e'},{f:'r2',t:'l3a'},{f:'r3',t:'l4c'},{f:'r4t',t:'l5'}],[]), boxes:[{t:'box-t',tx:'Tip: Add a visual flash when the Baron enters Phase 2, a red particle burst or screen shake. It makes the moment feel dramatic and warns the player of the increased danger.'}], onApply(){sout('✅ Enrage phase wired! The Baron rages at 50% HP!');} }, // ── SECTION 12: POLISH ── {title:'Camera Shake on Heavy Hit
- Connect Apply Damage (success) → Play Camera Shake. Wire a larger shake on killing blows. Good screen shake makes every sword swing feel powerful.

## STEP 28 · POLISH - On Apply Damage\n(Success)',x:4,y:28,w:165,rp:[{id:'r1e',type:'exec',label:''},{id:'r1d',type:'float',label:'Damage Done'}]}, {id:'n2',type:'math',title:'>= Check',sub:'Dmg >= KillThreshold',x:190,y:12,w:130,lp:[{id:'l2a',type:'float',label:'Dmg'},{id:'l2b',type:'float',label:'Threshold'}],rp:[{id:'r2',type:'bool',label:'Kill?'}]}, {id:'n3',type:'branch',title:'Branch',x:190,y:94,w:100,lp:[{id:'l3e',type:'exec',label:''},{id:'l3c',type:'bool',label:'Cond'}],rp:[{id:'r3t',type:'exec',label:'Heavy'},{id:'r3f',type:'exec',label:'Light'}]}, {id:'n4',type:'func',title:'Play Camera Shake',sub:'Heavy (0.6 scale)',x:312,y:80,w:168,lp:[{id:'l4e',type:'exec',label:''}]}, {id:'n5',type:'func',title:'Play Camera Shake',sub:'Light (0.2 scale)',x:312,y:140,w:168,lp:[{id:'l5e',type:'exec',label:''}]}, ],[{f:'r1e',t:'l3e'},{f:'r1d',t:'l2a'},{f:'r2',t:'l3c'},{f:'r3t',t:'l4e'},{f:'r3f',t:'l5e'}],[]), boxes:[{t:'box-t',tx:'Two shake intensities: a light tremor for regular hits and a heavy rumble for killing blows. This gives instant feedback, the player feels the difference between chipping and ending a fight.'}], onApply(){shake(5,8);sout('✅ Camera shake active! Check how it feels when you kill a brute.');} }, {title:'Particle FX, Blood & Impact Sparks
- Connect Apply Damage → Spawn Niagara System (NS_BloodSplat) at Enemy Location. Wire the hit direction for directional blood spray.

## STEP 29 · SCORING - On Hit Success',x:4,y:28,w:148,rp:[{id:'r1e',type:'exec',label:''},{id:'r1l',type:'vec3',label:'Hit Location'},{id:'r1n',type:'vec3',label:'Hit Normal'}]}, {id:'n2',type:'func',title:'Spawn Niagara\nSystem at Location',x:174,y:8,w:168,lp:[{id:'l2e',type:'exec',label:''},{id:'l2s',type:'object',label:'Niagara Asset'},{id:'l2l',type:'vec3',label:'Location'},{id:'l2n',type:'vec3',label:'Rotation'}],rp:[{id:'r2',type:'exec',label:''}]}, {id:'n3',type:'func',title:'Spawn Blood Decal',x:362,y:8,w:160,lp:[{id:'l3e',type:'exec',label:''},{id:'l3l',type:'vec3',label:'Location'}]}, ],[{f:'r1e',t:'l2e'},{f:'r1l',t:'l2l'},{f:'r1n',t:'l2n'},{f:'r2',t:'l3e'},{f:'r1l',t:'l3l'}],[]), boxes:[{t:'box-c',tx:'Blood decals are static sprites that persist on the floor for ~30 seconds. Niagara particles are dynamic, the blood spray launches in the direction away from the hit normal for realistic direction.'}], onApply(){spawnParts(CW/2,CH/2,'#cc3333
- Connect On Enemy Died → Get Enemy Score Value → Add to Total Score → Update HUD Score Widget. Different enemies are worth different points.

## STEP 30 · LAUNCH - OnEnemyDied',sub:'Custom Event',x:4,y:34,w:145,rp:[{id:'r1e',type:'exec',label:''},{id:'r1e2',type:'object',label:'Enemy Ref'}]}, {id:'n2',type:'func',title:'Get Score Value',x:172,y:14,w:148,lp:[{id:'l2e',type:'exec',label:''},{id:'l2r',type:'object',label:'Enemy'}],rp:[{id:'r2e',type:'exec',label:''},{id:'r2v',type:'int',label:'Points'}]}, {id:'n3',type:'math',title:'+ Add',sub:'Score + Points',x:342,y:14,w:112,lp:[{id:'l3a',type:'int',label:'Score'},{id:'l3b',type:'int',label:'Points'}],rp:[{id:'r3',type:'int',label:'New Score'}]}, {id:'n4',type:'func',title:'Set Score',x:476,y:14,w:60,lp:[{id:'l4e',type:'exec',label:''},{id:'l4v',type:'int',label:'Value'}]}, ],[{f:'r1e',t:'l2e'},{f:'r1e2',t:'l2r'},{f:'r2e',t:'l4e'},{f:'r2v',t:'l3b'},{f:'r3',t:'l4v'}],[]), boxes:[{t:'box-r',tx:'Score values: Guard = 50pts, Brute = 150pts, Archer = 75pts, Baron = 500pts. Zone clear bonus: +200pts. No-damage zone: +500pts bonus. These create replayability, can you beat your high score?'}], onApply(){sout('✅ Score system wired! Kills give points. Check the HUD score.');} }, // ── FINAL STEP ── {title:'Launch the Siege! ⚔
- All systems are active. Your knight is forged, your blade is ready, the castle gates are ahead. Hit Launch Siege to charge, or press Quick Play in the header for an instant random run.
