We start in 2D because the scope is small enough to finish, and finishing a tiny complete game teaches more than half-building a big one. Unreal’s 2D toolkit is Paper2D (built-in). For animation state machines, the community plugin PaperZD is excellent and widely used; we’ll keep to core Paper2D so you have no external dependencies, and mention PaperZD as the upgrade path.
This is the first part where you actually build. By the end you’ll have L_Hollow_2D — a one-screen side-scroller inside the Wisp project — where BP_Wisp2D (a PaperCharacter) runs and jumps across a floor, collects motes that increment a count, falls into a pit and respawns, and reaches a beacon that flashes “Zone Lit.” Every step below has exact asset names, settings, node sequences, and a Test it check so you can reproduce the result without guessing.
Reality check: Unreal is a 3D engine doing 2D by putting sprites on flat planes in a 3D world with an orthographic camera. It’s overkill for 2D compared to Godot/GameMaker — but doing it here teaches you the engine, and “2D in a 3D engine” is exactly how stylized 2.5D games (Ori, Hollow Knight-likes) are built.
3.0 Where we are in the Wisp project
You created the Wisp project back in Part 0 from the Third Person (Blueprint) template with Starter Content, and you enabled Paper2D and Enhanced Input there. That means three things matter before you touch anything:
- Paper2D is already enabled. You do not need to re-enable it — verify in
Edit → Plugins(search “Paper2D”, the box is checked) and move on. PaperZD is not installed and we won’t need it. - Enhanced Input is the default input system, so the
IA_/IMC_asset types and the Enhanced Input Local Player Subsystem node are available with no setup. BP_ThirdPersonCharacteralready exists from the template. We leave it alone — Part 5 rebuilds the 3D Wisp from it. For 2D we build a separate character (BP_Wisp2D, aPaperCharacter) and a separate map, so the 2D and 3D prototypes coexist in one project.
Create the map first so everything has a home. File → New Level → Empty Level, then File → Save Current Level As… → navigate to Content/_Wisp/Maps/ → name it L_Hollow_2D. An Empty Level has no floor, no light, and no atmosphere — that’s fine for a flat 2D scene; we’ll add our own floor and an orthographic camera. Add a single Directional Light from the Place Actors panel and set Mobility = Static so sprites aren’t pitch black (sprites use the default lit material and still take some lighting). Save.
Folder discipline pays off later. As you create each asset below, save it into the matching Content/_Wisp/ subfolder: textures/sprites/flipbooks under Characters/, the input assets and Blueprints under Blueprints/, the widget under UI/, the pickup sound under Audio/. Part 10 will thank you.
3.1 Core 2D asset types (the vocabulary)
Four asset types make up every Paper2D game. Know what each one is before you make them:
- Sprite (
UPaperSprite) — a 2D image, i.e. a rectangular region of a texture, that you can place in the world or drive from a component. - Texture (the imported PNG) — the source pixels. For crisp 2D you set its Texture Group to 2D Pixels (unfiltered) and its Filter to Nearest, so it doesn’t get blurred by mip-mapping/bilinear filtering.
- Flipbook (
UPaperFlipbook) — an ordered list of sprites played at a frame rate; this is a 2D animation (Idle, Run, Jump). - Tile Set + Tile Map — a palette of tiles and a painted grid, like the Tiled editor inside Unreal. Optional for us; we’ll offer a no-art floor first and a Tile Map alternative second.
3.2 Lab A — getting sprites with zero art skill
You do not need to draw anything. Pick one of the two paths below. Path 1 (solid-color squares from a Starter Content texture) needs nothing external and is the fastest way to a playable build; Path 2 (a free CC0 spritesheet) gives you real animation frames. Either way, the import-settings step is identical and mandatory.
Path 1 — solid-color square sprites (no download). Starter Content ships a 1×1-ish flat texture you can tint, but the simplest reliable source is a small uniform texture you create from the editor, then color it via a material. Concretely:
- In the Content Browser,
Add (+) → Texture (or import any small PNG you already have). If you have nothing, use Starter Content’sT_Default_Material_Greyor any 16×16-or-larger texture underContent/StarterContent/Textures/— copy it intoContent/_Wisp/Characters/first (drag → Copy Here) so you never edit Starter Content in place. - Right-click the copied texture → Create Sprite. This makes one
UPaperSpritecovering the whole texture. Rename itSPR_Wisp_Idle. - To make it a clean colored square regardless of the source pixels, open the sprite, and in Details set the Default Material to a tinted material. Easiest: create
M_Wisp(a Material with Material Domain = Surface, Shading Model = Unlit). A plainConstant3Vectorwired into Emissive Color is a fixed color that a Material Instance can never recolor — an instance can only override parameters. So drop aConstant3Vector, right-click it → Convert to Parameter, name itGlowColor, and wire that into Emissive Color. Now makeMI_Wisp2D_Cyan/MI_Wisp2D_Magentainstances that each setGlowColorto a different color. AssignM_Wisp(or one of the instances) as the sprite’s material. You now have a glowing colored square — perfect for a wisp.
With Path 1 you’ll have a single static sprite per actor (no animation frames). That is enough to finish the milestone; section 3.5 shows how to fake Idle/Run/Jump by tint or scale if you want, and section 3.4 still drives real movement.
Path 2 — a free CC0 spritesheet (real frames). Download a public-domain character sheet (e.g. from Kenney.nl or OpenGameArt, both offer CC0 packs — verify the license says CC0/public domain). You want a single PNG with frames laid out in a uniform grid (say 8 frames across, each 32×32 or 64×64).
- Drag the PNG into
Content/_Wisp/Characters/. Rename itT_Wisp_Sheet. - This sheet now has the frames you’ll slice in Lab B.
Mandatory import settings (both paths). Pixel art must not be blurred or compressed. Do this for every 2D texture:
- Right-click the texture in the Content Browser → Sprite Actions → Apply Paper2D Texture Settings. This one click sets the texture up for 2D: Filter = Nearest, Texture Group = 2D Pixels (Unfiltered), mip generation off, and compression suited to sprites.
- Open the texture and confirm in Details: Filter = Nearest, Texture Group = 2D Pixels (Unfiltered), Mip Gen Settings = NoMipmaps. For crisp UI-style art, Compression Settings = UserInterface2D (RGBA) keeps colors exact (larger on disk; fine for a tiny game).
- Save the texture.
Why this matters: without “2D Pixels (Unfiltered)” + Nearest, Unreal bilinear-filters and mip-maps your texture, so a sharp 32×32 sprite turns into a smeared blob when the camera scales it. The single biggest “my pixel art looks wrong” bug is a skipped Apply Paper2D Texture Settings.
Test it: drag SPR_Wisp_Idle (Path 1) or the texture (Path 2) into the viewport. You should see a crisp, hard-edged sprite, not a blurry one. Delete the test actor before continuing.
3.3 Lab B — extract sprites and build flipbooks
Skip this section entirely if you took Path 1 and are happy with a static square — go to 3.4. If you have a spritesheet (Path 2), slice it and build the three animations.
- Extract. Right-click
T_Wisp_Sheet→ Sprite Actions → Extract Sprites. For a uniform grid choose Extract Sprites: Grid and enter the cell size (e.g.Cell Width = 64,Cell Height = 64); Unreal previews the slice rectangles. Click Extract. You now have oneUPaperSpriteper frame, namedT_Wisp_Sheet_Sprite_0,_1, … - Tidy names (optional but worth it): rename the idle frames
SPR_Wisp_Idle_00.., run framesSPR_Wisp_Run_00.., jump frame(s)SPR_Wisp_Jump_00. Move them into aCharacters/Wisp/subfolder. - Create Idle flipbook. Select the idle frames in order, right-click → Create Flipbook. Name it
FB_Wisp_Idle. Open it in the Flipbook Editor and set Frames Per Second = 6 (slow breathing loop). Confirm the frame order in the timeline; drag to reorder if needed. - Create Run flipbook. Select run frames → Create Flipbook →
FB_Wisp_Run, FPS = 12. - Create Jump flipbook. Select the jump frame(s) → Create Flipbook →
FB_Wisp_Jump, FPS = 8 (or 1 frame; a single-frame flipbook is valid and reads fine as a jump pose).
Test it: double-click FB_Wisp_Run — the preview should play a looping run cycle at a sane speed. If it’s a strobing mess, your FPS is too high or frames are out of order.
3.4 Lab C — build BP_Wisp2D from PaperCharacter
PaperCharacter is ACharacter with a UPaperFlipbookComponent (named Sprite) in place of the skeletal mesh — you still get the full CharacterMovementComponent, capsule, and jump for free. That’s why a side-scroller is so little work in Unreal.
- In
Content/_Wisp/Blueprints/:Add (+) → Blueprint Class. In the All Classes search box type PaperCharacter, select it, and name the new BlueprintBP_Wisp2D. These are the throwaway 2D-prototype Blueprints; the canonicalBP_Wisp/BP_Mote/BP_Beaconare the 3D versions you build in Part 5, so the 2D and 3D versions coexist in one project without a name clash. - Open
BP_Wisp2D. Select the inherited Sprite component (thePaperFlipbookComponent) — this single component drives the character’s look in both paths. For Path 2, set Source Flipbook = FB_Wisp_Idle. For Path 1, the inherited Sprite component still expects a flipbook, not a static sprite, so wrap your one image in a one-frame flipbook: right-clickSPR_Wisp_Idle→ Create Flipbook, name itFB_Wisp2D_Idle, then set the Sprite component’s Source Flipbook = FB_Wisp2D_Idle. Do not add a separatePaperSpriteComponent— the movement and animation logic in 3.5 flips the inherited Sprite component, and a separate static sprite would never turn. - Orient the sprite to face the camera. The side-scroll camera sits on +Y looking toward −Y, so the flat sprite already faces it: select the Sprite component and set its Rotation = (0, 0, 0). Verify visually after you place the camera; only if the sprite renders back-to-front, set Yaw = 180.
- Constrain movement to a plane (the side-scroller trick). Select the CharacterMovement component. In Details → Planar Movement set Constrain to Plane = true and Plane Constraint Normal = (X 0, Y 1, Z 0). Tick Snap to Plane at Start = true. This locks the Wisp to the X-Z plane (Y stays constant), so it can never drift toward/away from the camera. Movement happens along world X; jumping along world Z.
- Tune movement feel (CharacterMovement → details): Max Walk Speed = 600, Jump Z Velocity = 900, Gravity Scale = 2.0 (snappier platformer gravity), Air Control = 0.8, Ground Friction = 8. Under Character Movement (General) leave default; these values give responsive arcade jumps rather than floaty ones.
- Capsule size. Select the CapsuleComponent (root) and set Capsule Half Height / Capsule Radius to roughly match the sprite (e.g. 50 / 25 for a 64×64 sprite). The capsule, not the sprite, is what collides.
- Compile and Save.
Axis convention: we move along world X (left/right), jump along world Z (up), and the camera looks down world −Y (the “depth” we lock out). Keep this triple in your head — every transform, plane normal, and input direction in this part is consistent with it.
3.5 Lab D — Enhanced Input (IA, IMC, and the BeginPlay chain)
Enhanced Input replaced the legacy “Action/Axis Mappings.” You create Input Actions (verbs) and an Input Mapping Context (keys → verbs, with modifiers), then add the context to the player at runtime.
- In
Content/_Wisp/Blueprints/Input/:Add (+) → Input → Input Action. Name itIA_Move. Open it, set Value Type = Axis1D (float). - Create another Input Action
IA_Jump. Leave Value Type = Digital (bool). - Create an Input Mapping Context (
Add (+) → Input → Input Mapping Context):IMC_Player. Open it and add the mappings:- IA_Move → key D (no modifier → produces +1).
- IA_Move → key A, add a Negate modifier (under the mapping’s Modifiers array, add Negate) → produces −1. (Optionally also map Right Arrow / Left Arrow + Negate, and the gamepad Left Thumbstick X-Axis.)
- IA_Jump → key Space Bar (and optionally Gamepad Face Button Bottom).
Register the context on BeginPlay. Open BP_Wisp2D → Event Graph. Off Event BeginPlay build this exact chain:
Event BeginPlay
-> Get Controller
-> Cast To PlayerController
(As Player Controller)
-> Get Local Player // off the PlayerController
-> Get Enhanced Input Local Player Subsystem
(target = the Local Player above)
-> Add Mapping Context
Mapping Context = IMC_Player
Priority = 0
In practice the cleanest node to use is Enhanced Input Local Player Subsystem (a “Get … Subsystem” node that takes a Player Controller or Local Player as target), then drag off it to Add Mapping Context. If the cast fails (e.g. on a server-spawned pawn) the chain simply no-ops, which is correct for a single-player slice.
Wire the Move action. Right-click the graph → search IA_Move → add the EnhancedInputAction IA_Move event. Off its Triggered pin:
EnhancedInputAction IA_Move (Triggered)
Action Value (float) ---+
|
-> Add Movement Input
Target = self
World Direction = (X 1, Y 0, Z 0)
Scale Value = Action Value (the float)
// face the way we move:
-> Branch (Action Value > 0?)
True -> Set World Scale 3D on Sprite = (1, 1, 1)
False -> Set World Scale 3D on Sprite = (-1, 1, 1) // mirror on X
Mirroring the Sprite’s X scale flips the character to face left/right without a separate left-facing flipbook. (Alternatively rotate the actor yaw by 180°, but scaling the sprite avoids rotating the capsule.)
Wire the Jump action. Add the EnhancedInputAction IA_Jump event:
EnhancedInputAction IA_Jump (Started) -> Jump (ACharacter::Jump)
EnhancedInputAction IA_Jump (Completed) -> Stop Jumping (ACharacter::StopJumping)
Compile and Save. The C++ binding for the same behavior (use this if you’re driving BP_Wisp2D from a C++ parent class instead of pure Blueprint) is unchanged from the engine pattern:
void AWispCharacter::SetupPlayerInputComponent(UInputComponent* Input)
{
auto* EIC = CastChecked<UEnhancedInputComponent>(Input);
EIC->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AWispCharacter::Move);
EIC->BindAction(JumpAction, ETriggerEvent::Started, this, &ACharacter::Jump);
EIC->BindAction(JumpAction, ETriggerEvent::Completed, this, &ACharacter::StopJumping);
}
void AWispCharacter::Move(const FInputActionValue& Value)
{
AddMovementInput(FVector(1.f, 0.f, 0.f), Value.Get<float>());
}
(The C++ side still needs the BeginPlay AddMappingContext call — same chain as the Blueprint, via ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>().)
3.6 Lab E — the flipbook state machine (set only on change)
Skip if you took the static-sprite Path 1. Otherwise, pick the flipbook each frame by the Wisp’s motion state — but only call Set Flipbook when the chosen flipbook actually differs from the current one, so you don’t reset the animation to frame 0 every tick.
Add a variable DesiredFlipbook (type Paper Flipbook (object reference)). On Event Tick in BP_Wisp2D:
Event Tick
-> Get Movement Component -> Is Falling?
True -> DesiredFlipbook = FB_Wisp_Jump
False -> (continue)
Get Velocity -> Vector Length (or |X|)
Branch ( |Velocity.X| > 10.0 )
True -> DesiredFlipbook = FB_Wisp_Run
False -> DesiredFlipbook = FB_Wisp_Idle
// only switch when it changed:
-> Get Sprite -> Get Flipbook (current)
-> Branch ( current != DesiredFlipbook )
True -> Sprite -> Set Flipbook (DesiredFlipbook)
This hand-rolled machine is exactly what PaperZD automates with a visual state graph; adopt PaperZD once you have more than three states. If you’re on Path 1 (one static sprite) you can fake feedback by setting the Sprite’s material to MI_Wisp2D_Cyan while moving and MI_Wisp2D_Magenta while idle — same “only on change” pattern.
Test it (after 3.7/3.9 so you can possess): run, and watch the Wisp swap to Run while moving, Jump while airborne, and Idle when still. If it strobes back to frame 0 constantly, your “only on change” branch is missing or inverted.
3.7 Lab F — a floor to stand on
Two ways; do the quick one to get playable, upgrade later if you like.
Quick floor (box collision, no art).
- From Place Actors, drag a Cube (Starter Content / Engine basic shape) into the level. Set its Transform Scale = (40, 2, 1) so it’s a long thin platform along X, and position it at the bottom of the play area (e.g.
Location = (0, 0, −100)). Set Mobility = Static. - Confirm its collision is solid: Details → Collision → Collision Presets = BlockAll. The capsule lands on it.
- Add one or two more cubes as raised ledges to make jumping meaningful. Keep them all at the same Y as the Wisp so the plane constraint lines up.
Tile Map floor (optional, “proper” path).
- Import or copy a tileset PNG, apply Apply Paper2D Texture Settings, then right-click it → Create Tile Set (
TS_Hollow). In the Tile Set Editor, set Tile Size to your grid (e.g. 32×32), select a solid tile, and add collision geometry to it (a box that fills the tile) using the Tile Set Editor’s collision tools. Add (+) → Paper2D → Tile Map(TM_Hollow). Open the Tile Map Editor, chooseTS_Hollowas the active tile set, and paint a floor row and some platforms. Tiles painted from a collision-enabled source tile become solid.- Drag
TM_HollowintoL_Hollow_2D. Set its Y = 0 to share the Wisp’s plane. The tile map’sPaperTileMapComponentgenerates collision automatically from the collision tiles.
Test it: not yet possessable, but in the viewport the Wisp’s capsule (if you drop one in) should rest on the floor, not fall through.
3.8 Lab G — the orthographic side-scroll camera
We want a fixed-orientation camera looking down world −Y at the X-Z plane, with no rotation lag (side-scrollers feel wrong if the camera swivels). Put it on the Wisp via a Spring Arm so it follows.
- Open
BP_Wisp2D. Add a Spring Arm component to the root. Add a Camera component as a child of the Spring Arm. - Select the Spring Arm:
- Target Arm Length = 800 (camera distance back along the arm).
- Rotation (Relative) = (Pitch 0, Yaw −90, Roll 0) so the arm points along world −Y (the camera ends up in front of the X-Z plane looking at it). Verify the sprite isn’t mirrored; if it is, flip the Sprite component yaw by 180°.
- Do Collision Test = false (no walls to dodge in 2D).
- Enable Camera Lag = false, Enable Camera Rotation Lag = false — crisp, no drift. (Position lag is a taste choice; leave it off for the milestone.)
- Tick Inherit Pitch = false, Inherit Yaw = false, Inherit Roll = false so the camera never rotates with the pawn.
- Select the Camera component: set Projection Mode = Orthographic. Set Ortho Width = 1920 (how many world units the view spans horizontally — smaller = more zoomed in). Leave Auto Calculate Ortho Planes on, or set Ortho Near/Far Clip manually if sprites get clipped.
- Compile, Save.
Orthographic projection removes perspective foreshortening, so the level looks flat and 2D — exactly what you want. If your sprites vanish, it’s almost always the ortho clip planes: turn on Auto Calculate Ortho Planes or widen Ortho Far Clip.
3.9 Lab H — the GameMode and possession
Something has to say “BP_Wisp2D is the player.” Two equivalent ways:
- GameMode default pawn (preferred — works on respawn/restart).
Add (+) → Blueprint Class → Game Mode Base, name itBP_Hollow2DGameMode(save inBlueprints/). Open it, set Default Pawn Class = BP_Wisp2D. Then inL_Hollow_2D:Window → World Settings → GameMode Override = BP_Hollow2DGameMode. Place a Player Start actor where the Wisp should appear (on top of the floor, e.g.Location (0, 0, 0)) — the GameMode spawns the default pawn there. - Quick override (one-off testing). Drag one
BP_Wisp2Dinto the level, select it, set Details → Pawn → Auto Possess Player = Player 0. Good for a fast check; the GameMode route is what the milestone uses because respawn needs a clean spawn.
Also give BP_Hollow2DGameMode a place to hold score: add an integer variable MoteCount = 0 (we’ll increment it from motes). For a single-player slice, storing it on the GameMode is fine; PlayerState is the “correct” home if you later go multiplayer.
Test it: press Play (PIE). The Wisp spawns at the Player Start, you can run with A/D, jump with Space, land on the floor, and the camera follows flat and steady. If you can’t move, re-check the Add Mapping Context chain (3.5) and that GameMode Override/Auto Possess is set.
3.10 Lab I — motes, a death plane, and the beacon goal
BP_Mote2D (the collectible).
Add (+) → Blueprint Class → Actor→BP_Mote2D(save inBlueprints/).- Add a PaperSprite component, set Source Sprite to a small bright sprite (reuse
SPR_Wisp_Idletinted cyan viaMI_Wisp2D_Cyan, or a star frame from your sheet). Scale it down (~0.5). - Add a Sphere Collision component sized to the sprite. Set Collision Presets = OverlapAllDynamic (or a custom preset that overlaps Pawn). Make the sphere the root or attach the sprite to it.
- In the Event Graph, add On Component Begin Overlap (Sphere):
On Component Begin Overlap (Sphere)
Other Actor --> Cast To BP_Wisp2D
(valid?) ->
Get Game Mode -> Cast To BP_Hollow2DGameMode
-> Set MoteCount = (MoteCount + 1)
-> Play Sound at Location (a Starter Content cue, e.g. a UI/chime; pick any)
-> Destroy Actor (self)
Place 5 BP_Mote2D actors along the level (some up on ledges so jumping matters), all at the Wisp’s Y = 0.
Death plane (fall → respawn).
- From Place Actors, drag a Trigger Box (it’s a
ABoxTriggerwith a box that overlaps by default). Make it wide and place it well below the floor (e.g.Location (0, 0, −800),Scale (60, 10, 1)). - Record a start transform: simplest is to read the Player Start location, or store a Vector variable
StartLocation = (0, 0, 0)on the GameMode (or on the Wisp’s BeginPlay, cache Get Actor Location). - Select the Trigger Box and, in its level Blueprint or via a small
BP_DeathPlaneactor, on On Actor Begin Overlap:
On Actor Begin Overlap (Trigger Box)
Other Actor --> Cast To BP_Wisp2D
(valid?) ->
Set Actor Location (target = the Wisp,
New Location = StartLocation,
Sweep = false, Teleport = true)
-> Get Character Movement -> Stop Movement Immediately
// clears momentum so the Wisp doesn't keep falling after the teleport
(Wrapping this in a tiny BP_DeathPlane actor instead of the Level Blueprint keeps it reusable and respawn-safe.)
BP_Beacon2D (the goal → “Zone Lit”).
- First make the widget.
Add (+) → User Interface → Widget Blueprint→WBP_ZoneLit(save inUI/). Add a centered Text block reading ZONE LIT in the cyan/magenta style; optionally a semi-transparent full-screen border behind it. Add (+) → Blueprint Class → Actor→BP_Beacon2D. Add a PaperSprite (or a Starter Content mesh like a pillar/cone) tinted dark, and a Box Collision set to Overlap with Pawn. Place it at the far right end of the level on the floor, at Y = 0.- On On Component Begin Overlap (Box):
On Component Begin Overlap (Box)
Other Actor --> Cast To BP_Wisp2D
(valid?) ->
// light up the beacon: swap material to MI_Wisp2D_Cyan (emissive) or
// toggle a Point Light child component On
Set Material / Point Light -> Set Visibility (true)
-> Create Widget (Class = WBP_ZoneLit) -> Add to Viewport
-> (optional) Get Game Mode -> set a bool BeaconLit = true
-> (optional) Set Game Paused (true) // freeze on the win beat
For the 2D milestone, lighting the beacon = showing “Zone Lit.” In Part 5 the same beacon gains a real Point Light that Lumen reacts to; here a material swap or a toggled light component is enough.
3.11 Test it — the full checklist
Press Play and verify every item. If one fails, the fix is in the matching lab above.
| Press / do | You should see | If not, check |
|---|---|---|
| Press Play (PIE) | Wisp spawns at Player Start, resting on the floor | GameMode Override + Default Pawn (3.9); capsule vs floor collision (3.7) |
| Hold D / A | Wisp runs right / left, sprite flips to face that way | IMC_Player Negate on A; Add Mapping Context chain (3.5) |
| Press Space | Wisp jumps; Jump flipbook while airborne | IA_Jump Started/Completed; Is Falling branch (3.6) |
| Move sideways | Camera follows flat, no rotation, no drift | Spring Arm lag off + Inherit rotation off (3.8) |
| Touch a mote | Sound plays, mote disappears, MoteCount +1 | Sphere overlap preset; Cast To BP_Wisp2D (3.10) |
| Collect all 5 | MoteCount = 5 (print it with Print String to confirm) | Each mote increments the same GameMode instance |
| Fall in the pit | Wisp teleports back to the start, keeps playing | Death plane overlap + Set Actor Location (3.10) |
| Reach the beacon | Beacon lights up, “ZONE LIT” appears on screen | Box overlap + Create Widget → Add to Viewport (3.10) |
3.12 Package a quick build to share
Full packaging, build configs, and source control are Part 10 — but you can ship a rough build now so the milestone is actually shareable (most people never get this far). Quick path:
- Set the default map so the build boots into your level:
Edit → Project Settings → Maps & Modes→ Game Default Map = L_Hollow_2D (and Editor Startup Map too if you like). Platforms (the toolbar dropdown) → Windows → Package Project. Choose an output folder outside the project (e.g.Builds/HollowProto/). Leave the config at Development for now (Shipping comes in Part 10).- Wait for the cook/package to finish, then run
WindowsNoEditor/Wisp.exe(or the platform’s equivalent) from the output folder. Zip that folder and send it.
Commit first. Before packaging, make a git commit (“Part 3: playable 2D Hollow prototype”). Every milestone = a commit is the habit the capstone relies on, and it’s required before you let an AI agent touch the project in Part 8. Large binary cooks should stay out of git — that’s what the Builds/ folder and the Part 10 .gitignore are for.
Build the 2D Hollow prototype in the Wisp project. In L_Hollow_2D: BP_Wisp2D (a PaperCharacter, plane-constrained to X-Z, normal (0,1,0)) runs and jumps across a floor (box collision or a collision-enabled Tile Map) under an orthographic side-scroll camera. It collects 5 motes (BP_Mote2D: sphere overlap → increment MoteCount on BP_Hollow2DGameMode → sound → destroy), survives a death pit that respawns it at the start transform, and reaches a beacon (BP_Beacon2D) that lights up and shows the WBP_ZoneLit “Zone Lit” widget. Drive it all with IA_Move + IA_Jump + IMC_Player and the BeginPlay Add Mapping Context chain. Run the full Test it checklist, then package a quick Development build and send it to a friend. This is the same core loop — explore, collect, reach the beacon — you’ll rebuild in 3D in Part 5, just flattened. Most people who “want to make games” never get a packaged build into a friend’s hands; you just did.