Unreal 2D Platformer Blueprint, Build & Play
JVDesignStudio - https://jvdesignstudio.co.uk/workshops/unreal-2d-platformer.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: Unreal 2D Platformer Blueprint, Build & Play

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

## STEP 1 · SETUP - Project Setup
- Connect Event BeginPlay ? Init Game Mode. This is the entry point for your platformer, Unreal fires BeginPlay when the level loads.

## STEP 2 · CHARACTER - Event BeginPlay',sub:'BP_GameMode',x:8,y:28,w:158,rp:[{id:'r1',type:'exec',label:''}]},{id:'n2',type:'func',title:'Init Game Mode',x:196,y:14,w:145,lp:[{id:'l2',type:'exec',label:''}],rp:[{id:'r2',type:'exec',label:''}]}],[{f:'r1',t:'l2'}],[]), boxes:[{t:'box-c',tx:'In UE5, BP_GameMode controls all rules and flow. BeginPlay fires once per play session. Everything starts here, physics, inputs, HUD, and your character all boot from this single event node.'}], onApply:function(){sout('✅ Game Mode initialised. BeginPlay is wired!');document.getElementById('liveStatus').textContent='Setup ✓';}}, {title:'Character Blueprint
- Connect Spawn Default Pawn ? Possess. This creates your Character Blueprint actor and hands control to Player 0.

## STEP 3 · MOVEMENT - Spawn Default Pawn',sub:'BP_GameMode',x:8,y:20,w:162,rp:[{id:'r1',type:'exec',label:''},{id:'r1a',type:'object',label:'Return'}]},{id:'n2',type:'func',title:'Possess',x:200,y:8,w:125,lp:[{id:'l2e',type:'exec',label:''},{id:'l2p',type:'object',label:'In Pawn'}],rp:[{id:'r2',type:'exec',label:''}]}],[{f:'r1',t:'l2e'},{f:'r1a',t:'l2p'}],[]), boxes:[{t:'box-t',tx:'BP_PlatformerCharacter extends the built-in Character class, giving you CapsuleComponent for collision and CharacterMovementComponent for physics, completely free from the parent class.'}], onApply:function(){sout('✅ Character Blueprint spawned and possessed by Player 0!');}}, {title:'Move Left & Right
- Connect IA_Move (Triggered) ? Add Movement Input. Type a World Direction X value (e.g. 1.0).

## STEP 4 · JUMP - IA_Move (Triggered)',sub:'BP_PlatformerChar',x:8,y:26,w:168,rp:[{id:'r1',type:'exec',label:''},{id:'r1a',type:'float',label:'Action Value'}]},{id:'n2',type:'func',title:'Add Movement Input',x:208,y:8,w:162,lp:[{id:'l2e',type:'exec',label:''},{id:'l2d',type:'vector',label:'World Direction'},{id:'l2s',type:'float',label:'Scale Value'}],rp:[{id:'r2',type:'exec',label:''}],vf:{hint:'Direction X (e.g. 1.0)',val:''}}],[{f:'r1',t:'l2e'},{f:'r1a',t:'l2s'}],[{id:'n2',ok:function(v){return parseFloat(v)>0;}}]), boxes:[{t:'box-c',tx:'Add Movement Input respects CharacterMovementComponent, acceleration, friction and max walk speed are handled automatically. You supply a direction and scale; the component does the rest.'}], onApply:function(v){CHAR.speed=3.8;F.move=true;G.started=true;updateCharCard();sout('✅ Movement on! Arrow keys / WASD to move. Direction: '+v['n2']);}}, {title:'Jump
- Connect IA_Jump (Started) ? Jump. The built-in Jump() function handles ground checks and launch velocity automatically.

## STEP 5 · GRAVITY - IA_Jump',sub:'Started',x:8,y:28,w:155,rp:[{id:'r1',type:'exec',label:''}]},{id:'n2',type:'func',title:'Jump',sub:'Inherited from Character',x:192,y:12,w:148,lp:[{id:'l2',type:'exec',label:''}],rp:[{id:'r2',type:'exec',label:''}]}],[{f:'r1',t:'l2'}],[]), boxes:[{t:'box-t',tx:'Character?Jump() checks IsOnGround automatically. It reads JumpZVelocity from CharacterMovementComponent. Override the function in your child Blueprint to add custom jump behaviour.'}], onApply:function(){F.jump=true;sout('✅ Jump enabled! Press Space or tap JUMP. Velocity: '+Math.round(CHAR.jumpVel*62)+' UU/s');}}, {title:'Gravity Scale
- Connect Event BeginPlay ? Set Gravity Scale. Type a float, try 2.0 for snappy, or 1.2 for floaty.

## STEP 6 · COYOTE - Event BeginPlay',sub:'BP_PlatformerChar',x:8,y:28,w:155,rp:[{id:'r1',type:'exec',label:''}]},{id:'n2',type:'func',title:'Set Gravity Scale',x:192,y:8,w:152,lp:[{id:'l2e',type:'exec',label:''},{id:'l2g',type:'float',label:'Gravity Scale'}],rp:[{id:'r2',type:'exec',label:''}],vf:{hint:'Gravity (0.5-4.0)',val:''}}],[{f:'r1',t:'l2e'}],[{id:'n2',ok:function(v){var n=parseFloat(v);return n>=0.5&&n<=4;}}]), boxes:[{t:'box-c',tx:'UE default GravityScale is 1.0. Most 2D platformers use 2.0-3.0 for a satisfying snappy drop. Too high = falls like a brick. Too low = floaty balloon. Try different values and feel the difference!'}], onApply:function(v){var g=parseFloat(v['n2'])||2;CHAR.gravScale=Math.max(0.5,Math.min(4,g));F.gravity=true;updateCharCard();sout('? Gravity set to '+CHAR.gravScale+'✅. Jump to feel the difference!');}}, {title:'Coyote Time
- Connect On Walk Off Ledge ? Set Timer by Event ? on complete, Clear Jump Buffer. This grace window lets players jump just after walking off a ledge.

## STEP 7 · DBL JUMP - On Walk Off Ledge',x:8,y:28,w:155,rp:[{id:'r1',type:'exec',label:''}]},{id:'n2',type:'func',title:'Set Timer by Event',x:192,y:8,w:158,lp:[{id:'l2e',type:'exec',label:''},{id:'l2t',type:'float',label:'Time (s)'}],rp:[{id:'r2',type:'exec',label:'Expired'}],vf:{hint:'Grace secs (e.g. 0.12)',val:''}},{id:'n3',type:'func',title:'Clear Coyote Buffer',x:382,y:8,w:140,lp:[{id:'l3',type:'exec',label:''}]}],[{f:'r1',t:'l2e'},{f:'r2',t:'l3'}],[{id:'n2',ok:function(v){var n=parseFloat(v);return n>=0.05&&n<=0.3;}}]), boxes:[{t:'box-t',tx:'Named after Wile E. Coyote. Even 0.1s makes controls feel dramatically more forgiving. This technique has been standard in every commercial 2D platformer since the 1980s, players expect it.'}], onApply:function(v){F.coyote=true;sout('✅ Coyote time active! Walk off a ledge and jump late. Grace: '+(v['n2']||'0.12')+'s');}}, {title:'Double Jump
- Connect IA_Jump → Branch (Is Falling?) → True: Launch Character (upward).

## STEP 8 · CAMERA - IA_Jump',sub:'Started',x:8,y:36,w:155,rp:[{id:'r1',type:'exec',label:''}]},{id:'n2',type:'func',title:'Is Falling',sub:'CharMoveComp',x:8,y:96,w:125,rp:[{id:'r2b',type:'bool',label:'Return'}]},{id:'n3',type:'branch',title:'Branch',x:170,y:18,w:100,lp:[{id:'l3e',type:'exec',label:''},{id:'l3c',type:'bool',label:'Condition'}],rp:[{id:'r3t',type:'exec',label:'True'},{id:'r3f',type:'exec',label:'False'}]},{id:'n4',type:'func',title:'Launch Character',x:302,y:6,w:135,lp:[{id:'l4e',type:'exec',label:''},{id:'l4v',type:'vector',label:'Velocity'}],rp:[{id:'r4',type:'exec',label:''}]}],[{f:'r1',t:'l3e'},{f:'r2b',t:'l3c'},{f:'r3t',t:'l4e'}],[]), boxes:[{t:'box-c',tx:'Set JumpMaxCount to 2 on CharacterMovementComponent for an even cleaner solution. Override Z = True in Launch Character replaces vertical velocity completely, giving a consistent second jump height.'}], onApply:function(){F.doublejump=true;G.jumpsLeft=2;sout('✅ Double Jump! Press Space twice in the air.');}}, {title:'Camera Follow
- Connect Event Tick ? VInterp To ? Set World Location (SpringArm). The camera smoothly tracks the character every frame.

## STEP 9 · COINS - Event Tick',sub:'BP_PlatformerChar',x:8,y:28,w:142,rp:[{id:'r1',type:'exec',label:''},{id:'r1d',type:'float',label:'Delta'}]},{id:'n2',type:'func',title:'VInterp To',x:176,y:8,w:138,lp:[{id:'l2e',type:'exec',label:''},{id:'l2c',type:'vector',label:'Current'},{id:'l2t',type:'vector',label:'Target'}],rp:[{id:'r2e',type:'exec',label:''},{id:'r2v',type:'vector',label:'Result'}]},{id:'n3',type:'func',title:'Set World Location',sub:'SpringArm',x:344,y:8,w:152,lp:[{id:'l3e',type:'exec',label:''},{id:'l3v',type:'vector',label:'New Location'}]}],[{f:'r1',t:'l2e'},{f:'r2e',t:'l3e'},{f:'r2v',t:'l3v'}],[]), boxes:[{t:'box-t',tx:'Spring Arm + Camera component is the standard UE setup. Set Camera Lag Speed 6-10 for smooth tracking. The camera lags behind giving a natural feel. Constraint the boom to X axis only for a true side-scroller.'}], onApply:function(){F.camera=true;sout('✅ Camera follow on! The view now tracks your character.');}}, {title:'Coin Collectible
- Connect On Comp Begin Overlap (BP_Coin) ? Cast To BP_PlatformerChar ? Add Coin + Destroy.

## STEP 10 · HUD - On Comp Begin Overlap',sub:'BP_Coin',x:8,y:20,w:175,rp:[{id:'r1',type:'exec',label:''},{id:'r1o',type:'object',label:'Other Actor'}]},{id:'n2',type:'cast',title:'Cast To BP_PlatformerChar',x:212,y:8,w:175,lp:[{id:'l2e',type:'exec',label:''},{id:'l2o',type:'object',label:'Object'}],rp:[{id:'r2',type:'exec',label:'Success'}]},{id:'n3',type:'func',title:'Add Coin + Destroy',x:418,y:8,w:110,lp:[{id:'l3',type:'exec',label:''}]}],[{f:'r1',t:'l2e'},{f:'r1o',t:'l2o'},{f:'r2',t:'l3'}],[]), boxes:[{t:'box-c',tx:'Cast checks the overlapping actor IS a PlatformerCharacter before rewarding. This stops enemies or physics objects from accidentally triggering coin pickup. Always cast when dealing with Actor overlaps.'}], onApply:function(){F.coins=true;G.coins=0;sout('✅ Coins active! Collect the golden circles.');}}, {title:'Coin Counter HUD
- Connect On Coin Added (Event Dispatcher) ? Get Coin Count ? Set Text (WBP_HUD).

## STEP 11 · SPIKES - On Coin Added',sub:'Event Dispatcher',x:8,y:28,w:155,rp:[{id:'r1',type:'exec',label:''}]},{id:'n2',type:'vget',title:'Get Coin Count',x:8,y:90,w:130,rp:[{id:'r2',type:'int',label:'Count'}]},{id:'n3',type:'func',title:'Set Text',sub:'WBP_HUD',x:178,y:8,w:130,lp:[{id:'l3e',type:'exec',label:''},{id:'l3t',type:'string',label:'In Text'}],rp:[{id:'r3',type:'exec',label:''}]}],[{f:'r1',t:'l3e'},{f:'r2',t:'l3t'}],[]), boxes:[{t:'box-t',tx:'In Widget Blueprint, use a Binding on a Text block to auto-update without calling Set Text manually. Event-driven (as here) is more performant, bindings run every frame which can be wasteful.'}], onApply:function(){F.coinhud=true;updateHUD();sout('✅ Coin HUD live! Watch the Coins counter below as you collect.');}}, {title:'Spike Hazard
- Connect On Comp Begin Overlap (BP_Spike) ? Apply Damage then Spawn Emitter. Type the damage amount.

## STEP 12 · CHECKPOINT - On Comp Begin Overlap',sub:'BP_Spike',x:8,y:20,w:175,rp:[{id:'r1',type:'exec',label:''},{id:'r1o',type:'object',label:'Other Actor'}]},{id:'n2',type:'func',title:'Apply Damage',x:212,y:8,w:130,lp:[{id:'l2e',type:'exec',label:''},{id:'l2d',type:'float',label:'Damage'}],rp:[{id:'r2',type:'exec',label:''}],vf:{hint:'Damage amount (e.g. 1)',val:''}},{id:'n3',type:'func',title:'Spawn Emitter',sub:'NS_DeathFX',x:374,y:8,w:120,lp:[{id:'l3',type:'exec',label:''}]}],[{f:'r1',t:'l2e'},{f:'r2',t:'l3'}],[{id:'n2',ok:function(v){return parseFloat(v)>0;}}]), boxes:[{t:'box-c',tx:'Apply Damage routes through UE\'s damage pipeline. The Character\'s Take Damage event fires, you subtract from Health and check for death. This centralises all damage logic in one place, clean architecture.'}], onApply:function(v){F.spikes=true;sout('✅ Spikes armed! Touch the grey triangles to take damage. Dmg: '+(v['n2']||'1'));}}, {title:'Checkpoint & Respawn
- Connect On Overlap (Checkpoint) ? Set Respawn Location. On death, Set Actor Location to the saved point.

## STEP 13 · MOV PLAT - On Comp Begin Overlap',sub:'BP_Checkpoint',x:8,y:20,w:175,rp:[{id:'r1',type:'exec',label:''}]},{id:'n2',type:'func',title:'Set Respawn Point',x:212,y:8,w:148,lp:[{id:'l2e',type:'exec',label:''},{id:'l2l',type:'vector',label:'Location'}],rp:[{id:'r2',type:'exec',label:''}]},{id:'n3',type:'func',title:'Play Checkpoint FX',x:390,y:8,w:132,lp:[{id:'l3',type:'exec',label:''}]}],[{f:'r1',t:'l2e'},{f:'r2',t:'l3'}],[]), boxes:[{t:'box-t',tx:'Store the respawn Vector in a variable on GameMode or PlayerState so it persists across deaths. On death, teleport with Set Actor Location and call Re-Enable Input, otherwise the player stays frozen.'}], onApply:function(){F.checkpoint=true;G.respawn={x:2*T,y:9*T};sout('✅ Checkpoints active! Respawn point saved on contact.');}}, {title:'Moving Platform
- Connect Event Tick ? Timeline: Platform ? Lerp Vector ? Set Actor Location. The platform oscillates between two points.

## STEP 14 · SPRING - Event Tick',sub:'BP_MovingPlatform',x:8,y:28,w:140,rp:[{id:'r1',type:'exec',label:''}]},{id:'n2',type:'func',title:'Timeline: Platform',x:176,y:8,w:130,lp:[{id:'l2',type:'exec',label:'Play'}],rp:[{id:'r2e',type:'exec',label:'Update'},{id:'r2v',type:'float',label:'Alpha'}]},{id:'n3',type:'math',title:'Lerp (Vector)',x:336,y:8,w:116,lp:[{id:'l3e',type:'exec',label:''},{id:'l3a',type:'float',label:'Alpha'}],rp:[{id:'r3e',type:'exec',label:''},{id:'r3v',type:'vector',label:'Result'}]},{id:'n4',type:'func',title:'Set Actor Location',x:482,y:8,w:50,lp:[{id:'l4e',type:'exec',label:''},{id:'l4v',type:'vector',label:''}]}],[{f:'r1',t:'l2'},{f:'r2e',t:'l3e'},{f:'r2v',t:'l3a'},{f:'r3e',t:'l4e'},{f:'r3v',t:'l4v'}],[]), boxes:[{t:'box-c',tx:'Timelines are perfect for looping platform movement. Create a float curve (0?1?0), set it to loop, and Lerp between StartPoint and EndPoint vectors. The platform automatically reverses direction.'}], onApply:function(){F.movingplat=true;sout('✅ Moving platforms active! Two patrol platforms now in the level.');}}, {title:'Spring / Bounce Pad
- Connect On Overlap (BP_SpringPad) ? Launch Character with high Z velocity. Override Z must be True.

## STEP 15 · FLAG - On Comp Begin Overlap',sub:'BP_SpringPad',x:8,y:20,w:175,rp:[{id:'r1',type:'exec',label:''},{id:'r1o',type:'object',label:'Other Actor'}]},{id:'n2',type:'func',title:'Launch Character',x:212,y:8,w:142,lp:[{id:'l2e',type:'exec',label:''},{id:'l2v',type:'vector',label:'Launch Velocity'},{id:'l2b',type:'bool',label:'Override Z'}],rp:[{id:'r2',type:'exec',label:''}]}],[{f:'r1',t:'l2e'}],[]), boxes:[{t:'box-t',tx:'Override Z = True replaces any current vertical velocity completely, giving a reliable consistent bounce. Keep Override XY = False to preserve horizontal momentum. Z of 1500-2000 UU/s feels great.'}], onApply:function(){F.spring=true;sout('✅ Spring pads active! The green coil launches you skyward.');}}, {title:'Level End Flag
- Connect On Overlap (BP_LevelEnd) ? Cast To BP_PlatformerChar ? Open Level By Name.

## STEP 16 · LIVES - On Comp Begin Overlap',sub:'BP_LevelEnd',x:8,y:20,w:175,rp:[{id:'r1',type:'exec',label:''},{id:'r1o',type:'object',label:'Other Actor'}]},{id:'n2',type:'cast',title:'Cast To BP_PlatformerChar',x:212,y:8,w:172,lp:[{id:'l2e',type:'exec',label:''},{id:'l2o',type:'object',label:'Object'}],rp:[{id:'r2',type:'exec',label:'Success'}]},{id:'n3',type:'func',title:'Open Level By Name',x:414,y:8,w:118,lp:[{id:'l3',type:'exec',label:''}]}],[{f:'r1',t:'l2e'},{f:'r1o',t:'l2o'},{f:'r2',t:'l3'}],[]), boxes:[{t:'box-c',tx:'Open Level fires async level streaming. For a win screen load a dedicated WinScreen map. Use Game Instance variables to carry the score between levels, Game Instance persists across map loads.'}], onApply:function(){F.flag=true;sout('✅ Flag active! Reach the orange flag on the right to win.');}}, {title:'Lives System
- Connect On Player Death → Decrement Lives → Branch (Lives > 0) → Respawn or Game Over.

## STEP 17 · BOOST - On Player Death',x:8,y:28,w:148,rp:[{id:'r1',type:'exec',label:''}]},{id:'n2',type:'func',title:'Decrement Lives',x:184,y:8,w:132,lp:[{id:'l2',type:'exec',label:''}],rp:[{id:'r2',type:'exec',label:''},{id:'r2i',type:'int',label:'Lives Left'}]},{id:'n3',type:'branch',title:'Branch',x:344,y:6,w:90,lp:[{id:'l3e',type:'exec',label:''},{id:'l3c',type:'bool',label:'Lives > 0'}],rp:[{id:'r3t',type:'exec',label:'True'},{id:'r3f',type:'exec',label:'False'}]},{id:'n4',type:'func',title:'Respawn',x:464,y:2,w:68,lp:[{id:'l4',type:'exec',label:''}]},{id:'n5',type:'func',title:'Game Over',x:464,y:50,w:68,lp:[{id:'l5',type:'exec',label:''}]}],[{f:'r1',t:'l2'},{f:'r2',t:'l3e'},{f:'r3t',t:'l4'},{f:'r3f',t:'l5'}],[]), boxes:[{t:'box-t',tx:'Store Lives in a Game Instance variable so it persists across map reloads. Branch checks lives remain: True ? respawn at checkpoint; False ? Game Over screen, then reset lives when returning to main menu.'}], onApply:function(){F.lives=true;G.hp=CHAR.lives;updateHUD();sout('✅ Lives active! ♥ shown below. Touch spikes to lose a life.');}}, {title:'Speed Boost Pickup
- Connect On Overlap (BP_SpeedBoost) ? Set Max Walk Speed (×1.6) ? Set Timer to reset.

## STEP 18 · FX - On Overlap',sub:'BP_SpeedBoost',x:8,y:20,w:152,rp:[{id:'r1',type:'exec',label:''}]},{id:'n2',type:'func',title:'Set Max Walk Speed',x:188,y:8,w:155,lp:[{id:'l2e',type:'exec',label:''},{id:'l2s',type:'float',label:'Speed'}],rp:[{id:'r2',type:'exec',label:''}],vf:{hint:'Boosted speed (e.g. 640)',val:''}},{id:'n3',type:'func',title:'Set Timer By Event',x:374,y:8,w:148,lp:[{id:'l3e',type:'exec',label:''},{id:'l3t',type:'float',label:'Duration'}]}],[{f:'r1',t:'l2e'},{f:'r2',t:'l3e'}],[{id:'n2',ok:function(v){return parseFloat(v)>=200;}}]), boxes:[{t:'box-c',tx:'1.5-2× normal speed feels impactful without breaking level design. Use Set Timer By Function Name to auto-revert after 3-5 seconds. Play a Niagara burst on pickup, purple or lightning blue looks great.'}], onApply:function(v){F.speedboost=true;sout('✅ Speed Boost ready! Purple trail on activation. Speed: '+(v['n2']||'640')+' UU/s');}}, {title:'Particle FX
- Connect On Jumped ? Spawn Niagara (NS_JumpDust). Connect On Landed ? Spawn Niagara (NS_LandImpact).

## STEP 19 · ANIM - On Jumped',sub:'BP_PlatformerChar',x:8,y:28,w:150,rp:[{id:'r1',type:'exec',label:''}]},{id:'n2',type:'func',title:'Spawn Niagara System',sub:'NS_JumpDust',x:184,y:8,w:155,lp:[{id:'l2e',type:'exec',label:''},{id:'l2l',type:'vector',label:'Location'}],rp:[{id:'r2',type:'exec',label:''}]},{id:'n3',type:'event',title:'On Landed',sub:'BP_PlatformerChar',x:8,y:94,w:150,rp:[{id:'r3',type:'exec',label:''}]},{id:'n4',type:'func',title:'Spawn Niagara System',sub:'NS_LandImpact',x:184,y:90,w:155,lp:[{id:'l4e',type:'exec',label:''}]}],[{f:'r1',t:'l2e'},{f:'r3',t:'l4e'}],[]), boxes:[{t:'box-t',tx:'Niagara replaces Cascade in UE5. Good juice rule: every player action needs visual + audio feedback. Add camera shake on land, squash/stretch on jump start, coin burst on collect, transforms feel from flat to polished.'}], onApply:function(){F.particles=true;sout('✅ Particle FX on! Blue sparks on jump, gold bursts on coin collect.');}}, {title:'Animation State Machine
- Connect Anim Graph Update ? check Get Velocity (Vector Length > 0) ? Set Anim State. Idle is the default.

## STEP 20 🚀 LAUNCH - Anim Graph Update',sub:'ABP_Platformer',x:8,y:28,w:155,rp:[{id:'r1',type:'exec',label:''}]},{id:'n2',type:'func',title:'Get Velocity',sub:'Owner',x:8,y:86,w:115,rp:[{id:'r2v',type:'vector',label:'Velocity'}]},{id:'n3',type:'math',title:'VectorLength > 0',x:148,y:75,w:128,lp:[{id:'l3v',type:'vector',label:'Vector'}],rp:[{id:'r3b',type:'bool',label:'Is Moving'}]},{id:'n4',type:'anim',title:'Set Anim State',sub:'Idle / Run / Jump',x:200,y:10,w:130,lp:[{id:'l4e',type:'exec',label:''},{id:'l4b',type:'bool',label:'Is Moving'}],rp:[{id:'r4',type:'exec',label:''}]}],[{f:'r1',t:'l4e'},{f:'r2v',t:'l3v'},{f:'r3b',t:'l4b'}],[]), boxes:[{t:'box-c',tx:'Animation Blueprint (ABP) has its own separate graph. The Anim Graph runs every frame. State Machines handle Idle?Run?Jump transitions based on booleans/floats set from the character Blueprint each tick.'}], onApply:function(){sout('✅ Animation State Machine wired! Idle, Run, Jump states connected.');}}, {title:'Name Your Character & Launch!
- Connect BeginPlay ? Sequence ? enable all four systems. Type your character name and Launch!
