Nuclear Blueprint Build & Play Your Own Roguelite
JVDesignStudio - https://jvdesignstudio.co.uk/workshops/nuclear-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: Nuclear Blueprint Build & Play Your Own Roguelite

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

## STEP 01 ACTOR - Blueprint Actor Event Graph
- Connect Event BeginPlay to Print String. This is the first wire in any Blueprint it confirms the engine is alive.

## STEP 02 VARIABLES - Event BeginPlay',x:16,y:26,w:150,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'func', title:'Print String',sub:'Hello Unreal!',x:205,y:26,w:150,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(){G.phase='playing';sout('✅ Engine started the arena is live! Complete more steps to play.');} }, {title:'Variables Player Health & Speed
- Connect BeginPlay ? Set Health ? Set MoveSpeed. Type starting values in the orange fields these become your player stats.

## STEP 03 TICK - Event BeginPlay',x:8,y:36,w:145,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'vset',title:'Set Health',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:'HP 50-200 (e.g. 100)',val:''}}, {id:'n3',type:'vset',title:'Set MoveSpeed',x:316,y:14,w:130,lp:[{id:'l3e',type:'exec',label:''},{id:'l3v',type:'float',label:'Value'}],rp:[{id:'r3',type:'exec',label:''}],vf:{hint:'Speed 200-600 (e.g. 350)',val:''}}, ],[{f:'r1',t:'l2e'},{f:'r2',t:'l3e'}],[ {id:'n2',ok:v=>v&&v.trim()!==''&&!isNaN(parseInt(v.trim()))&&parseInt(v.trim())>0}, {id:'n3',ok:v=>v&&v.trim()!==''&&!isNaN(parseFloat(v.trim()))&&parseFloat(v.trim())>0}, ]), boxes:[{t:'box-c',tx:'Integer pins (blue) = whole numbers. Float pins (green) = decimals. The pin colour tells you the type instantly across the whole editor.'}], onApply(v){G.p.maxHp=parseInt(v['n2'])||100;G.p.hp=G.p.maxHp;G.p.speed=(parseFloat(v['n3'])||350)/110;G.f_player=true;updateHUD();sout('✅ HP='+v['n2']+' Speed='+v['n3']+
- Connect Event Tick ? Multiply (speed · delta) ? Add Movement Input. Delta Seconds keeps movement frame-rate independent.

## STEP 04 INPUT - Event Tick',sub:'~60x/sec',x:6,y:20,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:158,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. At 30fps · 0.033s. Speed · Delta = units per second not per frame. Without it, fast machines move faster than slow ones.'}], onApply(){sout('✅ Tick → Delta Seconds → Add 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 UE5.

## 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:178,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:215,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_TopDown. Add WASD bindings for IA_Move. Without adding this context in BeginPlay, nothing responds to keyboard input.'}], onApply(){G.f_player=true;sout('✅ Mapping Context added WASD active!');} }, {title:'IA_Move WASD Movement
- Connect IA_Move (Triggered) ? Break Vector2D ? two Add Movement Input nodes. X drives left/right, Y drives forward/back.

## STEP 06 MOUSE AIM - IA_Move (Triggered)',sub:'Enhanced Input',x:4,y:20,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:135,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:360,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:360,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 the World Direction on the right node to Get Actor Right Vector and on the forward node to Get Actor Forward Vector this keeps movement relative to camera angle.'}], onApply(){G.f_player=true;sout('✅ WASD wired! Move around the arena.');} }, {title:'Get Hit Under Cursor, Aim Point
- Connect Event Tick ? Get Hit Result Under Cursor ? Branch (bBlockingHit) ? Set Aim Location. This gives the 3D world position of the mouse cursor.

## 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',sub:'by Channel',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:345,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:155,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 prevents writing a garbage aim position.'}], onApply(){G.f_aim=true;sout('✅ Mouse aim active, move cursor over arena, the ship rotates!');} }, {title:'Find Look at Rotation Face Mouse
- Connect Find Look at Rotation ? RInterp To ? Set Actor Rotation. Type an interp speed for smooth rotation.

## STEP 08 🔫 BULLETS - Find Look at Rotation',x:4,y:16,w:165,lp:[{id:'l1s',type:'vec3',label:'Start'},{id:'l1t',type:'vec3',label:'Target'}],rp:[{id:'r1',type:'rot',label:'Return Value'}]}, {id:'n2',type:'func',title:'RInterp To',sub:'smooth',x:192,y:6,w:135,lp:[{id:'l2c',type:'rot',label:'Current'},{id:'l2t',type:'rot',label:'Target'},{id:'l2s',type:'float',label:'Speed'}],rp:[{id:'r2',type:'rot',label:''}],vf:{hint:'Speed 5-25 (e.g. 20)',val:''}}, {id:'n3',type:'func',title:'Set Actor Rotation',x:350,y:6,w:155,lp:[{id:'l3e',type:'exec',label:''},{id:'l3r',type:'rot',label:'New Rotation'}],rp:[{id:'r3',type:'exec',label:''}]}, ],[{f:'r1',t:'l2t'},{f:'r2',t:'l3r'}],[{id:'n2',ok:v=>parseFloat(v)>0&&parseFloat(v)<=30}]), boxes:[{t:'box-t',tx:'Speed 5 = lazy follow, 20 = snappy, 30 = instant. Pick based on how twitchy the aim should feel.'}], onApply(){sout('✅ Find Look at Rotation → RInterp To → Set Rotation facing mouse!');} }, // -- WEAPON DESIGNER STEPS -------------------------- {title:'Weapon Designer Bullet Count
- Design your starting weapon! Type how many bullets fire per shot. 1 = precise. 3 = burst. 8 = shotgun. This is what you spawn with better weapons come from room chests.

## STEP 09 🔫 SPREAD - On Weapon Init',x:8,y:34,w:145,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'weapon',title:'Set Bullet Count',sub:'Weapon Designer',x:180,y:12,w:165,lp:[{id:'l2e',type:'exec',label:''},{id:'l2v',type:'int',label:'Bullets per Shot'}],rp:[{id:'r2',type:'exec',label:''}],vf:{hint:'Bullets 1-8 (e.g. 3)',val:''}}, {id:'n3',type:'func',title:'Apply to Weapon Component',x:366,y:12,w:168,lp:[{id:'l3e',type:'exec',label:''}]}, ],[{f:'r1',t:'l2e'},{f:'r2',t:'l3e'}],[{id:'n2',ok:v=>parseInt(v)>=1&&parseInt(v)<=8}],true), boxes:[{t:'box-w',tx:'You\'ll find 12 different weapons in chests during rooms shotguns, snipers, rocket launchers, homing launchers, bouncers. Your starting weapon just gets you through the first room.'}], onApply(v){startWPN.bc=parseInt(v['n2'])||1;updateWpnCard();G.f_shoot=true;G.p.wpn0={...startWPN};sout('✅ Starting weapon: '+startWPN.bc+' bullet(s). Shoot with SPACE/LMB!');} }, {title:'Weapon Designer Spread Angle
- Type the spread angle in degrees. 0 = laser precision. 15 = slightly forgiving. 40 = full shotgun. Pairs directly with bullet count.

## STEP 10 🔫 FIRE RATE - On Weapon Init',x:8,y:34,w:145,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'weapon',title:'Set Spread Angle',sub:'Weapon Designer',x:180,y:12,w:165,lp:[{id:'l2e',type:'exec',label:''},{id:'l2v',type:'float',label:'Degrees 0-45'}],rp:[{id:'r2',type:'exec',label:''}],vf:{hint:'Spread 0-45· (e.g. 20)',val:''}}, {id:'n3',type:'func',title:'Recalculate Bullet Vectors',x:366,y:12,w:165,lp:[{id:'l3e',type:'exec',label:''}]}, ],[{f:'r1',t:'l2e'},{f:'r2',t:'l3e'}],[{id:'n2',ok:v=>parseFloat(v)>=0&&parseFloat(v)<=45}],true), boxes:[{t:'box-w',tx:'0· + 1 bullet = sniper-style. 30· + 6 bullets = full shotgun. 10· + 3 bullets = burst rifle. Your choice shapes how the first few rooms feel before you find chest weapons.'}], onApply(v){startWPN.sp=parseFloat(v['n2'])||0;updateWpnCard();G.p.wpn0={...startWPN};sout('? Spread: '+startWPN.sp+'· shooting is now live!');} }, {title:'Weapon Designer Fire Rate
- Type the seconds between shots. 0.05 = machine gun. 0.3 = automatic. 0.8 = heavy cannon. Slower fire rate = more ammo conservation in longer rooms.

## STEP 11 🔫 SPEED - On Weapon Init',x:8,y:34,w:145,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'weapon',title:'Set Fire Rate',sub:'Weapon Designer',x:180,y:12,w:165,lp:[{id:'l2e',type:'exec',label:''},{id:'l2v',type:'float',label:'Seconds between shots'}],rp:[{id:'r2',type:'exec',label:''}],vf:{hint:'Rate 0.05-1.0 (e.g. 0.2)',val:''}}, {id:'n3',type:'func',title:'Apply to Weapon Component',x:366,y:12,w:165,lp:[{id:'l3e',type:'exec',label:''}]}, ],[{f:'r1',t:'l2e'},{f:'r2',t:'l3e'}],[{id:'n2',ok:v=>parseFloat(v)>=0.05&&parseFloat(v)<=1.0}],true), boxes:[{t:'box-w',tx:'Rooms have 2-10 enemies. A slow rate forces careful aimed shots. A fast rate lets you spray but you\'ll struggle in open rooms with fast runners weaving.'}], onApply(v){startWPN.fr=parseFloat(v['n2'])||0.2;updateWpnCard();G.p.wpn0={...startWPN};sout('✅ Fire rate: '+startWPN.fr+'s between shots.');} }, {title:'Weapon Designer Bullet Speed
- Type the bullet speed. 200 = slow grenade arc. 1200 = normal. 2000 = instant-hit laser. Slower bullets are easier for fast enemies to dodge.

## STEP 12 🔫 DAMAGE - On Weapon Init',x:8,y:34,w:145,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'weapon',title:'Set Bullet Speed',sub:'Weapon Designer',x:180,y:12,w:165,lp:[{id:'l2e',type:'exec',label:''},{id:'l2v',type:'float',label:'UU/sec'}],rp:[{id:'r2',type:'exec',label:''}],vf:{hint:'Speed 200-2000 (e.g. 1200)',val:''}}, {id:'n3',type:'func',title:'Init Projectile Component',x:366,y:12,w:162,lp:[{id:'l3e',type:'exec',label:''}]}, ],[{f:'r1',t:'l2e'},{f:'r2',t:'l3e'}],[{id:'n2',ok:v=>parseFloat(v)>=200&&parseFloat(v)<=2000}],true), boxes:[{t:'box-w',tx:'Slow bullets (200-500) work great with the Homing modifier since they have time to curve. Fast bullets (1500+) are reliable but feel less satisfying to dodge enemies with.'}], onApply(v){startWPN.bs=(parseFloat(v['n2'])||1200)/100;updateWpnCard();G.p.wpn0={...startWPN};sout('✅ Bullet speed: '+v['n2']+' UU/s!');} }, {title:'Weapon Designer Bullet Damage
- Type damage per bullet. 1 = tickle. 5 = solid hit. 10 = one-shot most grunts. With more bullets per shot, use lower damage per bullet or enemies die in half a second.

## STEP 13 🔫 BOUNCE - On Weapon Init',x:8,y:34,w:145,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'weapon',title:'Set Bullet Damage',sub:'Weapon Designer',x:180,y:12,w:165,lp:[{id:'l2e',type:'exec',label:''},{id:'l2v',type:'int',label:'Damage per bullet'}],rp:[{id:'r2',type:'exec',label:''}],vf:{hint:'Damage 1-10 (e.g. 2)',val:''}}, {id:'n3',type:'func',title:'Apply Damage Modifier',x:366,y:12,w:162,lp:[{id:'l3e',type:'exec',label:''}]}, ],[{f:'r1',t:'l2e'},{f:'r2',t:'l3e'}],[{id:'n2',ok:v=>parseInt(v)>=1&&parseInt(v)<=10}],true), boxes:[{t:'box-w',tx:'DPS = (damage · bullets) · fire rate. A shotgun: 3·5 bullets at 0.4s = 37.5 DPS. A sniper: 10 dmg at 0.8s = 12.5 DPS but feels enormous. Both are viable, they play very differently.'}], onApply(v){startWPN.dmg=parseInt(v['n2'])||2;updateWpnCard();G.p.wpn0={...startWPN};sout('✅ Damage: '+startWPN.dmg+' per bullet. DPS: '+Math.round((startWPN.dmg*startWPN.bc)/startWPN.fr)+'!');} }, {title:'Weapon Modifier Bounce
- Type true or false to enable bouncing bullets. Bounced bullets ricochet off walls and room obstacles up to 3 times great in tight rooms.

## STEP 14 🔫 EXPLOSIVE - On Weapon Init',x:8,y:32,w:145,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'weapon',title:'Set Bounce',sub:'Weapon Modifier',x:180,y:10,w:155,lp:[{id:'l2e',type:'exec',label:''},{id:'l2b',type:'bool',label:'Bounce Enabled'}],rp:[{id:'r2',type:'exec',label:''}]}, {id:'n3',type:'vget',title:'Bool Literal',sub:'true = bouncing ON',x:8,y:106,w:148,rp:[{id:'r3',type:'bool',label:''}],vf:{hint:'Type: true OR false',val:''}}, ],[{f:'r1',t:'l2e'},{f:'r3',t:'l2b'}],[{id:'n3',ok:v=>v.toLowerCase()==='true'||v.toLowerCase()==='false'}],true), boxes:[{t:'box-w',tx:'Rooms have obstacles, corners, pillars, walls. A bouncing bullet can hit the same enemy from behind a pillar. Low bullet count + high speed + bounce = corner-peeking sniper.'}], onApply(v){startWPN.bounce=v['n3'].toLowerCase()==='true';updateWpnCard();G.p.wpn0={...startWPN};sout('✅ Bounce: '+(startWPN.bounce?'ON, bullets ricochet off walls!':'OFF'));} }, {title:'Weapon Modifier Explosive
- Type an explosion radius. 0 = off. 40 = small blast. 80 = room-clearing AOE. Explosive bullets deal splash damage to all enemies within the radius on impact.

## STEP 15 SPAWN BULLET - On Weapon Init',x:8,y:32,w:145,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'weapon',title:'Set Explosion Radius',sub:'Weapon Modifier',x:180,y:10,w:172,lp:[{id:'l2e',type:'exec',label:''},{id:'l2r',type:'float',label:'Radius (0=off)'}],rp:[{id:'r2',type:'exec',label:''}],vf:{hint:'Radius 0-80 (0=off, e.g. 40)',val:''}}, {id:'n3',type:'func',title:'Init Explosion Component',x:366,y:10,w:162,lp:[{id:'l3e',type:'exec',label:''}]}, ],[{f:'r1',t:'l2e'},{f:'r2',t:'l3e'}],[{id:'n2',ok:v=>parseFloat(v)>=0&&parseFloat(v)0;startWPN.exR=r;updateWpnCard();G.p.wpn0={...startWPN};sout('✅ Explosive: '+(startWPN.explosive?'ON radius '+r+'px':'OFF'));} }, {title:'Spawn Actor from Class Fire!
- Connect IA_Shoot ? Spawn Actor from Class. Wire Bullet Class Ref into Class and Get Actor Transform into Spawn Transform. Type "BP_Bullet" in the value field.

## STEP 16, ROOM WALLS - IA_Shoot (Triggered)',sub:'Enhanced Input',x:4,y:26,w:172,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'func',title:'Spawn Actor from Class',x:200,y:5,w:170,lp:[{id:'l2e',type:'exec',label:''},{id:'l2c',type:'cls',label:'Class'},{id:'l2t',type:'vec3',label:'Transform'}],rp:[{id:'r2e',type:'exec',label:''},{id:'r2r',type:'actor',label:'Return Value'}]}, {id:'n3',type:'vget',title:'Bullet Class Ref',sub:'(assign in Details)',x:4,y:115,w:165,rp:[{id:'r3',type:'cls',label:''}],vf:{hint:'Type: BP_Bullet',val:''}}, {id:'n4',type:'func',title:'Get Actor Transform',sub:'self = muzzle pos',x:4,y:195,w:165,rp:[{id:'r4',type:'vec3',label:'Return Value'}]}, ],[{f:'r1',t:'l2e'},{f:'r3',t:'l2c'},{f:'r4',t:'l2t'}],[ {id:'n3',ok:v=>v.trim().toLowerCase().includes('bullet')}, ]), boxes:[ {t:'box-c',tx:'Spawn Actor from Class creates a new Actor at runtime. The Class pin takes the Blueprint reference like a cookie cutter stamping out a new instance at the given location.'}, {t:'box-t',tx:'Drag the Return Value to set bullet velocity or damage right after spawning, before it flies off thats how the fire direction gets applied.'}, ], onApply(){G.f_shoot=true;G.p.wpn0={...startWPN};sout('✅ Weapon firing! SPACE or LMB, shoot with YOUR designed weapon!');} }, {title:'Room Walls, Obstacle Collision
- Connect BeginPlay ? Build Room Layout ? Spawn Obstacles. Wire the Room Template variable into Build Room. Type "1" for the first world template.

## STEP 17 GRUNT - Event BeginPlay',sub:'BP_Room',x:6,y:34,w:148,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'func',title:'Build Room Layout',x:178,y:12,w:162,lp:[{id:'l2e',type:'exec',label:''},{id:'l2t',type:'int',label:'Template Index'}],rp:[{id:'r2e',type:'exec',label:''},{id:'r2w',type:'object',label:'Wall Array'}]}, {id:'n3',type:'func',title:'Spawn Obstacles',x:362,y:12,w:158,lp:[{id:'l3e',type:'exec',label:''},{id:'l3w',type:'object',label:'Walls'}],rp:[{id:'r3',type:'exec',label:''}]}, {id:'n4',type:'vget',title:'Room Template Index',x:6,y:115,w:152,rp:[{id:'r4',type:'int',label:''}],vf:{hint:'World template (e.g. 1)',val:''}}, ],[{f:'r1',t:'l2e'},{f:'r2e',t:'l3e'},{f:'r4',t:'l2t'},{f:'r2w',t:'l3w'}],[ {id:'n4',ok:v=>parseInt(v)>=1&&parseInt(v)<=7}, ]), boxes:[ {t:'box-r',tx:'Rooms are chosen from 7 templates: corner pillars, central block, L-shape, side pillars, cross, corridor, scattered boxes. Boss rooms are always open. Each run randomises the layout per room.'}, {t:'box-c',tx:'Bullets collide with walls. Enemies pathfind around them. Some templates heavily favour explosive AOE; others favour sniping from behind cover.'}, ], onApply(){G.f_enemies=true;loadRoom(1,1);sout('✅ Room layout active, walls + enemies loaded. Clear the room!');} }, {title:'Enemy Class Grunt
- Wire BeginPlay ? Set Health ? Set Speed ? Set ContactDamage in BP_EnemyGrunt. Grunts are the basic enemy diamond shape, medium everything.

## STEP 18 RUNNER - Event BeginPlay',sub:'BP_EnemyGrunt',x:6,y:34,w:155,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'vset',title:'Set Health',x:183,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. 4)',val:''}}, {id:'n3',type:'vset',title:'Set Speed',x:322,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-3 (e.g. 1.5)',val:''}}, {id:'n4',type:'vset',title:'Set ContactDmg',x:460,y:12,w:76,lp:[{id:'l4e',type:'exec',label:''},{id:'l4v',type:'int',label:'Dmg'}],vf:{hint:'1-10(e.g.5)',val:''}}, ],[{f:'r1',t:'l2e'},{f:'r2',t:'l3e'},{f:'r3',t:'l4e'}],[ {id:'n2',ok:v=>parseInt(v)>=2&&parseInt(v)parseFloat(v)>=0.5&&parseFloat(v)parseInt(v)>=1&&parseInt(v)<=15}, ]), boxes:[{t:'box-c',tx:'Grunts (red diamonds) are the bread-and-butter enemy. They scale with world number by World 3 theyre significantly tougher. Tune HP against your starting weapons DPS.'}], onApply(){sout('✅ Grunt stats set they appear in every room from World 1!');} }, {title:'Enemy Class Runner
- Wire BeginPlay ? Set Health ? Set Speed in BP_EnemyRunner. Runners are fast and fragile low HP, very high speed, appear from World 1 Room 2.

## STEP 19 TANK - Event BeginPlay',sub:'BP_EnemyRunner',x:6,y:32,w:155,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'vset',title:'Set Health',x:183,y:10,w:118,lp:[{id:'l2e',type:'exec',label:''},{id:'l2v',type:'int',label:'HP (low)'}],rp:[{id:'r2',type:'exec',label:''}],vf:{hint:'HP 1-4 (e.g. 1)',val:''}}, {id:'n3',type:'vset',title:'Set Speed',x:322,y:10,w:118,lp:[{id:'l3e',type:'exec',label:''},{id:'l3v',type:'float',label:'Speed (high)'}],rp:[{id:'r3',type:'exec',label:''}],vf:{hint:'Spd 2.5-5 (e.g. 3)',val:''}}, {id:'n4',type:'vset',title:'Set EnemyType',sub:'runner',x:460,y:10,w:76,lp:[{id:'l4e',type:'exec',label:''}]}, ],[{f:'r1',t:'l2e'},{f:'r2',t:'l3e'},{f:'r3',t:'l4e'}],[ {id:'n2',ok:v=>parseInt(v)>=1&&parseInt(v)parseFloat(v)>=2&&parseFloat(v)<=5}, ]), boxes:[{t:'box-c',tx:'Runners (orange triangles) change the pressure of a room dramatically. 3 runners + 2 grunts feels chaotic. Balance them so missing a runner costs you, but theyre not impossibly fast.'}], onApply(){sout('✅ Runner set appear from Room 1-2 onward. Fast!');} }, {title:'Enemy Class Tank
- Wire BeginPlay ? Set Health ? Set Speed ? Set ContactDamage in BP_EnemyTank. Tanks are slow walls of HP appear from World 2.

## STEP 20 SHOOTER - Event BeginPlay',sub:'BP_EnemyTank',x:6,y:34,w:155,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'vset',title:'Set Health',x:183,y:12,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-35 (e.g. 15)',val:''}}, {id:'n3',type:'vset',title:'Set Speed',x:322,y:12,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.4-1.2 (e.g. 0.7)',val:''}}, {id:'n4',type:'vset',title:'Set ContactDmg',x:460,y:12,w:76,lp:[{id:'l4e',type:'exec',label:''},{id:'l4v',type:'int',label:'Dmg'}],vf:{hint:'10-20(e.g.12)',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)>=5&&parseInt(v)<=25}, ]), boxes:[{t:'box-c',tx:'Tanks (purple hexagons) force resource decisions, you need to commit many bullets to kill one. Explosive rounds or the Rocket Launcher found in chests makes tanks trivial.'}], onApply(){sout('✅ Tank set appear from World 2. High HP forces commitment!');} }, {title:'Enemy Class Shooter
- Wire BeginPlay ? Set Health ? Set FireRate ? Set BulletDamage in BP_EnemyShooter. Shooters fire back they fundamentally change how you use room cover.

## STEP 21 COLLISION - Event BeginPlay',sub:'BP_EnemyShooter',x:6,y:34,w:158,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'vset',title:'Set Health',x:186,y:12,w:115,lp:[{id:'l2e',type:'exec',label:''},{id:'l2v',type:'int',label:'HP'}],rp:[{id:'r2',type:'exec',label:''}],vf:{hint:'HP 3-10 (e.g. 4)',val:''}}, {id:'n3',type:'vset',title:'Set FireRate',x:322,y:12,w:120,lp:[{id:'l3e',type:'exec',label:''},{id:'l3v',type:'float',label:'Rate (secs)'}],rp:[{id:'r3',type:'exec',label:''}],vf:{hint:'Rate 0.8-3 (e.g. 1.5)',val:''}}, {id:'n4',type:'vset',title:'Set BulletDmg',x:456,y:12,w:82,lp:[{id:'l4e',type:'exec',label:''},{id:'l4v',type:'int',label:'Dmg'}],vf:{hint:'5-15 (e.g.8)',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)>=3&&parseInt(v)<=20}, ]), boxes:[{t:'box-c',tx:'Shooters (pink circles with white core) keep distance and punish. They appear from World 1 Room 4. Rooms with cover become crucial the cross template is deadly with two shooters.'}], onApply(){sout('✅ Shooter set appears from Room 1-4. Uses room cover against you!');} }, {title:'On Actor Begin Overlap Hit!
- Connect On Actor Begin Overlap ? Cast to BP_Enemy ? Apply Damage ? Destroy Actor. Wire Other Actor to Object, and Enemy reference to Damaged Actor. Type the damage amount.

## STEP 22 DOOR SYSTEM - On Actor Begin Overlap',sub:'BP_Bullet',x:4,y:16,w:188,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:384,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:'Destroy Actor',sub:'bullet',x:384,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 bullet hits a wall the Fail exec fires. Leave it unconnected unless you need special wall-hit behaviour (like a spark effect).'}], onApply(){G.f_collision=true;sout('✅ Collision live! Bullets damage enemies. Shoot to clear the room!');} }, {title:'Door System Room Complete
- Connect On Enemy Died ✅ Check All Enemies Dead ? Branch ? Open Door. When all enemies are gone, the exit door glows green and can be entered.

## STEP 23 TRANSITION - On Enemy Died',sub:'Custom Event',x:4,y:34,w:148,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'func',title:'Count Living Enemies',x:175,y:14,w:160,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:357,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:357,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 Exit Door',x:475,y:96,w:58,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 door is always in the right wall of each room. Its a glowing green portal when open. Walk into it to trigger the room transition to the next room.'}, {t:'box-t',tx:'Press Q in-game to swap between your two weapon slots. Slot 2 stays empty until you find a chest weapon then you carry two.'}, ], onApply(){G.f_doors=true;sout('✅ Door system active! Clear enemies → door glows green → walk into it!');} }, {title:'Room Transition, Load Next Room
- Connect On Door Entered ? Increment Room Index ? Branch (boss?) ? Load Room or Load Boss. Every 5th room is a boss room.

## STEP 24 CHESTS - On Door Entered',sub:'Custom Event',x:4,y:30,w:148,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'func',title:'Increment Room Index',x:174,y:10,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:10,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:90,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 Room',x:468,y:82,w:70,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: 5 rooms per world, 3 worlds = 15 rooms total + final boss. Room 5 of each world is a boss room. After the boss, a mutation screen appears before World 2 and 3.'}, {t:'box-c',tx:'The room transition plays a quick fade-to-black. The player spawns at the left wall of the new room. Enemies spawn at random positions avoiding the player\'s start zone.'}, ], onApply(){sout('✅ Room transition wired! Walk the door → next room loads seamlessly.');} }, {title:'Chest System Find Weapons
- Connect On Chest Overlap ? Pick Random Weapon ? Offer Swap ? Apply to Player Slot. Chests appear in 40% of rooms. Walk over one to get a new weapon.

## STEP 25 ⚡ MUTATION 1 - On Chest Overlap',sub:'BP_Chest',x:4,y:30,w:148,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'func',title:'Pick Random Weapon\nfrom Pool',x:174,y:8,w:165,lp:[{id:'l2e',type:'exec',label:''}],rp:[{id:'r2e',type:'exec',label:''},{id:'r2w',type:'object',label:'Weapon Data'}]}, {id:'n3',type:'func',title:'Offer Weapon Swap',sub:'prompt player',x:360,y:8,w:158,lp:[{id:'l3e',type:'exec',label:''},{id:'l3w',type:'object',label:'New Weapon'}],rp:[{id:'r3t',type:'exec',label:'Accepted'},{id:'r3f',type:'exec',label:'Declined'}]}, {id:'n4',type:'func',title:'Equip to Active Slot',x:360,y:100,w:158,lp:[{id:'l4e',type:'exec',label:''}]}, ],[{f:'r1',t:'l2e'},{f:'r2e',t:'l3e'},{f:'r2w',t:'l3w'},{f:'r3t',t:'l4e'}],[]), boxes:[ {t:'box-r',tx:'The 12 weapons in the pool: Rusty Pistol, Shotgun, Machine Gun, Sniper Rifle, Rocket Launcher, Bouncer, Homing Launcher, Plasma Cannon, Crossbow, Laser Rifle, Grenade Pistol, Dual SMGs.'}, {t:'box-t',tx:'You have 2 weapon slots. Press Q to swap. When both slots are filled, picking up a new weapon asks which slot to replace. The choice is immediate no going back.'}, ], onApply(){G.f_chests=true;sout('✅ Chest system active! Walk over the gold chest in rooms to find weapons.');} }, {title:'Mutation, Choose After Each World
- After clearing World 1, you choose a permanent upgrade. Pick one card it auto-fills the Blueprint. This mutation lasts the entire run.

## STEP 26 ⚡ MUTATION 2 - On World Complete',x:4,y:32,w:148,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'custom',title:'Apply Mutation',sub:'Weapon Mutation',x:174,y:10,w:162,lp:[{id:'l2e',type:'exec',label:''},{id:'l2m',type:'string',label:'Mutation ID'}],vf:{hint:'Auto-set by card below',val:''}}, ],[{f:'r1',t:'l2e'}],[{id:'n2',ok:v=>['damage_up','firerate_up','count_up'].includes(v)}],true), extraHTML:` ⚡DAMAGE UP+70% damage per bullet ?RAPID FIRE40% faster fire rate ⚡MORE BULLETS+2 bullets per shot `, boxes:[{t:'box-m',tx:'Damage Up synergises with slow-firing precise weapons. Rapid Fire synergises with single-bullet weapons. More Bullets synergises with wide-spread shotgun builds.'}], onApply(v){G.f_stepMutations=true;mut1=v['n2'];updateWpnCard();sout('✅ Mutation 1: '+mut1.replace('_
- After World 2, choose a survivor perk. These affect your character, not your weapon. One of these could save your life in World 3.

## STEP 27 DAMAGE UP - On World Complete',x:4,y:32,w:148,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'custom',title:'Apply Survivor Perk',sub:'Player Mutation',x:174,y:10,w:162,lp:[{id:'l2e',type:'exec',label:''},{id:'l2m',type:'string',label:'Perk ID'}],vf:{hint:'Auto-set by card below',val:''}}, ],[{f:'r1',t:'l2e'}],[{id:'n2',ok:v=>['speed_boost','shield','lifesteal'].includes(v)}],true), extraHTML:` ⚡SPEED BOOST45% faster movement 🛡️SHIELDAbsorb 1 hit every 10s ⚡LIFESTEAL+3 HP per enemy killed `, boxes:[{t:'box-m',tx:'Speed Boost lets you dodge enemy bullets, critical in Shooter-heavy rooms. Shield absorbs burst damage. Lifesteal rewards aggressive play, kill faster, heal faster.'}], onApply(v){mut2=v['n2'];if(mut2==='speed_boost')G.p.speedMult=1.45;if(mut2==='lifesteal')G.p.lifesteal=true;updateWpnCard();sout('✅ Mutation 2: '+mut2.replace('_
- Wire On Mutation Applied to Branch then Multiply Damage x 1.7 then Set Damage. This shows how the Damage Up perk modifies your weapon stat at runtime.

## STEP 28 RAPID FIRE - On Mutation Applied',x:4,y:30,w:162,rp:[{id:'r1e',type:'exec',label:''},{id:'r1s',type:'string',label:'Mutation ID'}]}, {id:'n2',type:'math',title:'== Equal',sub:'ID == damage_up',x:188,y:10,w:128,lp:[{id:'l2a',type:'string',label:'A'},{id:'l2b',type:'string',label:'B'}],rp:[{id:'r2',type:'bool',label:''}]}, {id:'n3',type:'branch',title:'Branch',x:338,y:10,w:100,lp:[{id:'l3e',type:'exec',label:''},{id:'l3c',type:'bool',label:'Cond'}],rp:[{id:'r3t',type:'exec',label:'True'}]}, {id:'n4',type:'math',title:'Multiply',sub:'Damage x 1.7',x:456,y:6,w:82,lp:[{id:'l4e',type:'exec',label:''},{id:'l4a',type:'float',label:'A'},{id:'l4b',type:'float',label:'B'}],rp:[{id:'r4',type:'exec',label:''}],vf:{hint:'Multiplier e.g. 1.7',val:''}}, ],[{f:'r1e',t:'l3e'},{f:'r1s',t:'l2a'},{f:'r2',t:'l3c'},{f:'r3t',t:'l4e'}],[ {id:'n4',ok:v=>parseFloat(v)>=1.1&&parseFloat(v)<=3.0}, ],true), boxes:[ {t:'box-c',tx:'The == node compares two strings. If the mutation ID matches damage_up the True branch fires and the multiplier is applied. The False branch is left unconnected so the perk does nothing if it doesnt match.'}, {t:'box-w',tx:'Damage Up multiplies every bullets base damage. A Sniper at 25 dmg x 1.7 = 42.5. Combined with explosive splash entire rooms clear in one shot.'}, ], onApply(v){const m=parseFloat(v['n4'])||1.7;if(mut1==='damage_up'){startWPN.dmg=Math.round((startWPN.dmg||1)*m);G.p.wpn0={...startWPN};}updateWpnCard();sout('Damage Up: x'+m);} }, {title:'Perk Rapid Fire Blueprint
- Wire On Mutation Applied to Branch then Multiply FireRate x 0.6 then Set FireRate. Lower fire rate value means faster shooting. Type the multiplier.

## STEP 29 MORE BULLETS - On Mutation Applied',x:4,y:30,w:162,rp:[{id:'r1e',type:'exec',label:''},{id:'r1s',type:'string',label:'Mutation ID'}]}, {id:'n2',type:'math',title:'== Equal',sub:'ID == firerate_up',x:188,y:10,w:132,lp:[{id:'l2a',type:'string',label:'A'},{id:'l2b',type:'string',label:'B'}],rp:[{id:'r2',type:'bool',label:''}]}, {id:'n3',type:'branch',title:'Branch',x:342,y:10,w:100,lp:[{id:'l3e',type:'exec',label:''},{id:'l3c',type:'bool',label:'Cond'}],rp:[{id:'r3t',type:'exec',label:'True'}]}, {id:'n4',type:'math',title:'Multiply',sub:'FireRate x 0.6',x:460,y:6,w:78,lp:[{id:'l4e',type:'exec',label:''},{id:'l4a',type:'float',label:'Rate'},{id:'l4b',type:'float',label:'Mult'}],rp:[{id:'r4',type:'exec',label:''}],vf:{hint:'Multiplier e.g. 0.6',val:''}}, ],[{f:'r1e',t:'l3e'},{f:'r1s',t:'l2a'},{f:'r2',t:'l3c'},{f:'r3t',t:'l4e'}],[ {id:'n4',ok:v=>parseFloat(v)>0&&parseFloat(v)<1.0}, ],true), boxes:[ {t:'box-c',tx:'Fire rate is the delay between shots in seconds. Multiplying by 0.6 means 40% less wait time. A value over 1.0 would slow firing down. The Branch ensures this only applies to the correct perk.'}, {t:'box-w',tx:'Rapid Fire on a Machine Gun 0.08s base makes it a wall of lead. On a Sniper it barely helps. Pick perks that match your weapon design.'}, ], onApply(v){const m=parseFloat(v['n4'])||0.6;if(mut1==='firerate_up'){startWPN.fr=Math.max(0.04,startWPN.fr*m);G.p.wpn0={...startWPN};}updateWpnCard();sout('Rapid Fire: x'+m);} }, {title:'Perk More Bullets Blueprint
- Wire On Mutation Applied to Branch then Integer Add (bullets + 2) then Set Bullet Count. Integer Add is how Blueprint increments a whole number variable.

## STEP 30 SPEED BOOST - On Mutation Applied',x:4,y:30,w:162,rp:[{id:'r1e',type:'exec',label:''},{id:'r1s',type:'string',label:'Mutation ID'}]}, {id:'n2',type:'math',title:'== Equal',sub:'ID == count_up',x:188,y:10,w:128,lp:[{id:'l2a',type:'string',label:'A'},{id:'l2b',type:'string',label:'B'}],rp:[{id:'r2',type:'bool',label:''}]}, {id:'n3',type:'branch',title:'Branch',x:338,y:10,w:100,lp:[{id:'l3e',type:'exec',label:''},{id:'l3c',type:'bool',label:'Cond'}],rp:[{id:'r3t',type:'exec',label:'True'}]}, {id:'n4',type:'math',title:'Add',sub:'Count + 2',x:456,y:6,w:82,lp:[{id:'l4e',type:'exec',label:''},{id:'l4a',type:'int',label:'Count'},{id:'l4b',type:'int',label:'Add'}],rp:[{id:'r4',type:'exec',label:''}],vf:{hint:'Bullets to add e.g. 2',val:''}}, ],[{f:'r1e',t:'l3e'},{f:'r1s',t:'l2a'},{f:'r2',t:'l3c'},{f:'r3t',t:'l4e'}],[ {id:'n4',ok:v=>parseInt(v)>=1&&parseInt(v)<=6}, ],true), boxes:[ {t:'box-c',tx:'Integer pins handle whole numbers. You cannot fire 2.5 bullets. The Add node takes BulletCount and outputs BulletCount+2. The result feeds into Set Bullet Count to update the weapon.'}, {t:'box-w',tx:'More Bullets widens every shot cone. A 3-bullet burst becomes 5. On a shotgun 8 bullets becomes 10 which is nearly impossible to dodge at close range.'}, ], onApply(v){const add=parseInt(v['n4'])||2;if(mut1==='count_up'){startWPN.bc=Math.min(startWPN.bc+add,12);G.p.wpn0={...startWPN};}updateWpnCard();sout('More Bullets: +'+add+' per shot');} }, {title:'Perk Speed Boost Blueprint
- Wire On Perk Applied to Branch then Multiply MoveSpeed x 1.45 then Set MoveSpeed. Speed Boost modifies the character variable, not the weapon.

## STEP 31 SHIELD - On Perk Applied',x:4,y:30,w:148,rp:[{id:'r1e',type:'exec',label:''},{id:'r1s',type:'string',label:'Perk ID'}]}, {id:'n2',type:'math',title:'== Equal',sub:'ID == speed_boost',x:174,y:10,w:138,lp:[{id:'l2a',type:'string',label:'A'},{id:'l2b',type:'string',label:'B'}],rp:[{id:'r2',type:'bool',label:''}]}, {id:'n3',type:'branch',title:'Branch',x:334,y:10,w:100,lp:[{id:'l3e',type:'exec',label:''},{id:'l3c',type:'bool',label:'Cond'}],rp:[{id:'r3t',type:'exec',label:'True'}]}, {id:'n4',type:'vset',title:'Set MoveSpeed',x:452,y:6,w:86,lp:[{id:'l4e',type:'exec',label:''},{id:'l4v',type:'float',label:'Value'}],vf:{hint:'Speed mult e.g. 1.45',val:''}}, ],[{f:'r1e',t:'l3e'},{f:'r1s',t:'l2a'},{f:'r2',t:'l3c'},{f:'r3t',t:'l4e'}],[ {id:'n4',ok:v=>parseFloat(v)>1.0&&parseFloat(v)<=3.0}, ]), boxes:[ {t:'box-c',tx:'Speed Boost modifies the Character Movement component directly. Unlike weapon perks this persists the whole run. 1.45x speed lets you outrun most enemy bullets.'}, {t:'box-t',tx:'Against Shooters in tight rooms extra speed means you can peek, shoot, and dodge before return fire connects. Best combined with rooms that have lots of cover.'}, ], onApply(v){const m=parseFloat(v['n4'])||1.45;if(mut2==='speed_boost')G.p.speedMult=m;sout('Speed Boost: x'+m+' movement');} }, {title:'Perk Shield Blueprint
- Wire On Perk Applied to Branch then Set Has Shield to true then Set Timer by Event for the recharge. Type the recharge time in seconds.

## STEP 32 LIFESTEAL - On Perk Applied',x:4,y:16,w:148,rp:[{id:'r1e',type:'exec',label:''},{id:'r1s',type:'string',label:'Perk ID'}]}, {id:'n2',type:'branch',title:'Branch',x:174,y:10,w:100,lp:[{id:'l2e',type:'exec',label:''},{id:'l2c',type:'bool',label:'Cond'}],rp:[{id:'r2t',type:'exec',label:'True'}]}, {id:'n3',type:'vset',title:'Set Has Shield',sub:'true',x:292,y:6,w:118,lp:[{id:'l3e',type:'exec',label:''},{id:'l3v',type:'bool',label:'Value'}],rp:[{id:'r3',type:'exec',label:''}]}, {id:'n4',type:'func',title:'Set Timer by Event',sub:'recharge',x:426,y:6,w:112,lp:[{id:'l4e',type:'exec',label:''},{id:'l4t',type:'float',label:'Time'}],vf:{hint:'Recharge secs e.g. 10',val:''}}, ],[{f:'r1e',t:'l2e'},{f:'r2t',t:'l3e'},{f:'r3',t:'l4e'}],[ {id:'n4',ok:v=>parseFloat(v)>=2&&parseFloat(v)<=30}, ]), boxes:[ {t:'box-c',tx:'Set Timer by Event fires a custom event after the given delay. When it fires it sets Has Shield back to true. On hit a separate node clears the bool and restarts the timer. This is the absorb-then-recharge loop.'}, {t:'box-t',tx:'10 seconds is the sweet spot. Too fast at 2s makes you nearly invincible. Too slow at 30s and it never recharges mid-run. The shield appears as a cyan ring around your ship.'}, ], onApply(v){const t=parseFloat(v['n4'])||10;if(mut2==='shield'){G.p.shield=true;G.p.shieldTimer=t*60;}sout('Shield wired! Recharge: '+t+'s');} }, {title:'Perk Lifesteal Blueprint
- Wire On Enemy Killed to Branch (Has Lifesteal?) then Heal Player. Type the HP gained per kill. The Branch prevents healing when the perk is not active.

## STEP 33 SCALING - On Enemy Killed',x:4,y:16,w:148,rp:[{id:'r1e',type:'exec',label:''},{id:'r1t',type:'string',label:'Enemy Type'}]}, {id:'n2',type:'vget',title:'Has Lifesteal',sub:'bool variable',x:4,y:110,w:130,rp:[{id:'r2',type:'bool',label:''}]}, {id:'n3',type:'branch',title:'Branch',x:174,y:10,w:100,lp:[{id:'l3e',type:'exec',label:''},{id:'l3c',type:'bool',label:'Cond'}],rp:[{id:'r3t',type:'exec',label:'True'}]}, {id:'n4',type:'func',title:'Heal Player',sub:'clamps to MaxHP',x:296,y:6,w:148,lp:[{id:'l4e',type:'exec',label:''},{id:'l4a',type:'float',label:'HealAmount'}],rp:[{id:'r4',type:'exec',label:''}],vf:{hint:'HP per kill e.g. 3',val:''}}, ],[{f:'r1e',t:'l3e'},{f:'r2',t:'l3c'},{f:'r3t',t:'l4e'}],[ {id:'n4',ok:v=>parseFloat(v)>=1&&parseFloat(v)<=15}, ]), boxes:[ {t:'box-c',tx:'Has Lifesteal is a bool variable set to true when the perk is chosen. The Branch checks it every kill, if false the True path never fires and no healing happens. This pattern is everywhere in Blueprint: check a condition, act only if true.'}, {t:'box-t',tx:'3 HP per kill is small but in a room of 8 enemies you gain 24 HP back. The Heal Player node clamps internally so you can never overheal past MaxHP.'}, ], onApply(v){const h=parseFloat(v['n4'])||3;if(mut2==='lifesteal'){G.p.lifesteal=true;G.p.lifestealAmt=h;}sout('Lifesteal: '+h+' HP per kill');} }, {title:'World Scaling Difficulty Curve
- Connect Event BeginPlay to Get World Index then Multiply by a scale factor then Apply to Enemy Stats. Type a scale factor to control how fast difficulty ramps up.

## STEP 34 BOSS - Event BeginPlay',sub:'BP_DifficultyManager',x:4,y:34,w:165,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'func',title:'Get World Index',x:192,y:12,w:140,lp:[{id:'l2e',type:'exec',label:''}],rp:[{id:'r2e',type:'exec',label:''},{id:'r2i',type:'int',label:'World 1-3'}]}, {id:'n3',type:'math',title:'Multiply',sub:'World x Scale',x:354,y:12,w:110,lp:[{id:'l3a',type:'int',label:'World'},{id:'l3b',type:'float',label:'Scale'}],rp:[{id:'r3',type:'float',label:'Modifier'}],vf:{hint:'Scale factor e.g. 1.4',val:''}}, {id:'n4',type:'func',title:'Apply Difficulty',x:354,y:100,w:180,lp:[{id:'l4e',type:'exec',label:''},{id:'l4m',type:'float',label:'Modifier'}]}, ],[{f:'r1',t:'l2e'},{f:'r2e',t:'l4e'},{f:'r2i',t:'l3a'},{f:'r3',t:'l4m'}],[ {id:'n3',ok:v=>parseFloat(v)>=1.0&&parseFloat(v)<=3.0}, ]), boxes:[ {t:'box-r',tx:'World 1 modifier 1.4. World 2 modifier 2.8. World 3 modifier 4.2. Each enemy HP and speed gets multiplied by this. A grunt with 4 HP becomes 5.6, then 11.2, then 16.8 across three worlds.'}, {t:'box-c',tx:'This is the core roguelite design loop: you get stronger via mutations and chest weapons while enemies get stronger via world scaling. The curve must be steep enough to feel different but fair enough to beat.'}, ], onApply(v){const f=parseFloat(v['n3'])||1.4;sout('Scaling wired! x'+f+' modifier, enemies ramp hard in World 3!');} }, {title:'Boss Room Spawn & Patterns
- Connect On Boss Room Load ? Spawn Boss Actor ? Start Pattern Timer. The boss cycles 3 patterns: Chase, Strafe, Charge. Wire them through a Sequence.

## STEP 35 HUD - On Boss Room Load',x:4,y:30,w:152,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'func',title:'Spawn Boss Actor',x:178,y:10,w:148,lp:[{id:'l2e',type:'exec',label:''},{id:'l2hp',type:'float',label:'HP Multiplier'}],rp:[{id:'r2e',type:'exec',label:''},{id:'r2b',type:'actor',label:'Boss Ref'}],vf:{hint:'Boss HP mult (e.g. 1.5)',val:''}}, {id:'n3',type:'macro',title:'Sequence',x:348,y:10,w:95,lp:[{id:'l3',type:'exec',label:'In'}],rp:[{id:'r3a',type:'exec',label:'Chase'},{id:'r3b',type:'exec',label:'Strafe'},{id:'r3c',type:'exec',label:'Charge'}]}, {id:'n4',type:'func',title:'Start Pattern Timer',x:348,y:108,w:185,lp:[{id:'l4',type:'exec',label:''}]}, ],[{f:'r1',t:'l2e'},{f:'r2e',t:'l3'},{f:'r3c',t:'l4'}],[{id:'n2',ok:v=>parseFloat(v)>=1&&parseFloat(v)<=5}]), boxes:[ {t:'box-c',tx:'The boss is always in an open room (no obstacles). It spawns at the top-centre. Pattern 1: chase player. Pattern 2: circle-strafe at range. Pattern 3: pick a direction and charge dodge sideways!'}, {t:'box-t',tx:'The boss fires a 5-bullet spread every second. After the boss dies, score 500 pts and advance to the next world. Boss HP scales with world number.'}, ], onApply(v){sout('✅ Boss patterns wired! HP mult: '+v['n2']+'x. Kill the boss to advance worlds!');} }, {title:'HUD, Health, Weapon & Minimap
- Connect Update HUD ? Set Health Percent ? Set Weapon Name ? Set Room Text. Wire the HUD Reference to each node. Type max HP.

## STEP 36 DEATH - Update HUD',sub:'Custom Event',x:4,y:34,w:128,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'vget',title:'HUD Reference',x:4,y:110,w:130,rp:[{id:'r2',type:'actor',label:''}]}, {id:'n3',type:'math',title:'· Divide',sub:'HP / MaxHP',x:154,y:22,w:135,lp:[{id:'l3a',type:'float',label:'HP'},{id:'l3b',type:'float',label:'Max'}],rp:[{id:'r3',type:'float',label:'Pct'}],vf:{hint:'Max HP (matches Step 2)',val:''}}, {id:'n4',type:'func',title:'Set Health Percent',x:310,y:6,w:158,lp:[{id:'l4e',type:'exec',label:''},{id:'l4t',type:'actor',label:'Target'},{id:'l4p',type:'float',label:'Pct'}],rp:[{id:'r4',type:'exec',label:''}]}, {id:'n5',type:'func',title:'Set Weapon Name',x:310,y:100,w:158,lp:[{id:'l5e',type:'exec',label:''},{id:'l5t',type:'actor',label:'Target'}]}, ],[{f:'r1',t:'l4e'},{f:'r4',t:'l5e'},{f:'r2',t:'l4t'},{f:'r2',t:'l5t'},{f:'r3',t:'l4p'}],[{id:'n3',ok:v=>parseFloat(v)>0}]), boxes:[ {t:'box-t',tx:'The in-game HUD also shows a mini World-Room display (W1-R3) and score. A small minimap in the top-left shows your current location. Fully visible when you apply this step.'}, ], onApply(){G.f_hud=true;updateHUD();sout('✅ HUD live, health, weapon slot, room number, score!');} }, {title:'Death Screen & High Score
- Connect On Player HP = 0 ? Delay ? Show Death Screen ? Enable Restart. Type the delay seconds.

## STEP 37 LAUNCH - On Player Death',x:4,y:32,w:148,rp:[{id:'r1',type:'exec',label:''}]}, {id:'n2',type:'func',title:'Delay',x:174,y:12,w:112,lp:[{id:'l2e',type:'exec',label:''},{id:'l2d',type:'float',label:'Duration'}],rp:[{id:'r2',type:'exec',label:'Completed'}],vf:{hint:'Delay secs (e.g. 1.5)',val:''}}, {id:'n3',type:'func',title:'Show Death Screen',x:306,y:6,w:155,lp:[{id:'l3e',type:'exec',label:''}],rp:[{id:'r3',type:'exec',label:''}]}, {id:'n4',type:'func',title:'Enable Restart Input',x:306,y:96,w:155,lp:[{id:'l4e',type:'exec',label:''}]}, ],[{f:'r1',t:'l2e'},{f:'r2',t:'l3e'},{f:'r3',t:'l4e'}],[{id:'n2',ok:v=>parseFloat(v)>=0.5&&parseFloat(v)<=5}]), boxes:[{t:'box-t',tx:'Press R or click the canvas to restart after death. You keep your weapon design but start at World 1 Room 1, true roguelite permadeath.'}], onApply(v){sout('✅ Death screen: shows after '+v['n2']+'s. R or click to restart!');} }, {title:'Name Your Weapon Launch!
- Connect Game Start ? Sequence ? Enable All Systems. Then type your weapon name and hit Launch, your full 15-room roguelite is ready!
