Game Programmer

Portfolio by Alexander Englesson

C++  ·  Gameplay Systems  ·  Engine Programming

Game Showcase
5 Projects
Group Projects
5 team games built in Unity and custom C++ engines — platformers, action RPGs, top-down adventures, and more.
View Projects →
Specialisation
In-Game UI Editor
A fully custom, drag-and-drop HUD system built in C++ — movable frames, action bars, spellbook, tooltips, and a live edit mode.
View Specialisation →
Tools
Editor Tools
Custom editor tooling built in C++ — particle emitter previews, VFX curve editors, scene document systems, and in-engine debug overlays.
View Tools →

About Me

Hi! I'm Alexander Englesson, a game programmer based in Stockholm, Sweden, currently studying Game Programming at The Game Assembly (graduating June 2026).

I specialise in C++ game development with hands-on experience building gameplay systems, engine features, HUD/UI, and VFX systems — working in teams alongside designers, artists, and technical artists.

I'm drawn to the systems side of game development — the architecture behind how things work, not just that they work. I like building tools and systems that other people on the team can rely on.

Languages: English  ·  Swedish  ·  Spanish

C++JavaUnity Gameplay ProgrammingEngine Programming HUD / UI SystemsVFX Systems Shader IntegrationPerformance Profiling

Education

The Game Assembly — Stockholm
Game Programming
Sep 2024 – Jun 2027 (ongoing)
C++ and game engine development, gameplay systems, VFX, HUD/UI, team projects with designers and artists.
Lund University — Faculty of Engineering
Electrical Engineering with Automation
Aug 2022 – Dec 2023
Mathematics, linear algebra, calculus, programming, and automation systems.

Experience

Skärgårdspadel Blidö — Stockholm Archipelago
Head Padel Coach & Camp Organiser
Summer 2021 – Present (seasonal)
Organised summer camps for 150–250 participants, coaching children and adults.

Contact

I'm currently studying at The Game Assembly and open to internships, collaborations, and game jams. Feel free to reach out!

Download CV
© 2026 Alexander Englesson  ·  Game Programmer  ·  Stockholm

Type
Engine / Tools
Team Size
Duration
Year

Overview

My Role & Responsibilities

Technical Highlights

Screenshots

© 2026 Alexander Englesson  ·  Game Programmer  ·  Stockholm
Specialisation

In-Game UI Editor

10+Draggable Frames
C++Language
TGEEngine
LiveEdit Mode
2026Year

Overview

I built a fully custom, player-configurable HUD system from scratch in C++ using the schools custom game engine (TGE). The system allows players to enter an edit mode and freely drag, resize, and toggle every UI element — player frame, target frame, party frames, buff/debuff frames, action bars, cast bar, and stance bar. Inspired by World of Warcraft's addon system, every frame is a movable object with real-time snap-to-grid and snap-to-element behaviour.

My Role & Responsibilities

I designed and implemented the entire HUD system solo — from the base Button/Frame architecture and drag-drop system, through to every individual UI widget. I built the edit mode panel with its checkbox settings, grid slider, and advanced options toggle. I also implemented the spellbook, action bar per-slot settings popups, party frame configuration, and the spell tooltip system.

HUD / UI SystemsDrag & DropEdit Mode Action BarsBuff / Debuff SystemCast Bar Party FramesSpell TooltipC++Custom Engine (TGE)

Technical Highlights

Every HUD element inherits from a shared Button base class that handles dragging, visibility, camera binding, and input registration — meaning adding a new movable frame takes minimal code. The edit mode uses a grid snap system with a configurable grid size (25–200px), and an element snap system that detects nearby frame edges and snaps to them automatically. The buff/debuff overlap resolver detects collisions between the buff and debuff frames and pushes them apart using the minimum displacement direction — preventing z-fighting and visual overlap without any manual positioning from the player.

Development Breakdown

Step 01 — Normal View
A Complete In-Game HUD

The normal gameplay view shows the full HUD in action — player and target unit frames with portraits, HP and mana bars, five party frames on the left, two action bars with spell icons and cooldown timers, buff icons with duration countdowns, and a cast bar that appears when casting. Everything is live and functional.

Normal View
Step 02 — Edit Mode
Live Grid, Draggable Frames & Debug Bounds

Pressing the edit mode toggle overlays a purple grid across the screen and draws debug bounding boxes around every HUD element. All frames become draggable — snap-to-grid keeps them aligned, and snap-to-elements lets frames lock to each other's edges. The HUD Edit Mode panel appears at the centre, letting the player check/uncheck individual frames and adjust the grid size with a live slider.

Edit Mode
Step 03 — Advanced Options
Two-Mode Settings Panel

The HUD Edit Panel has two modes — compact and advanced. In compact mode it shows the most common toggles. Enabling Advanced Options expands the panel and reveals extra sections: Frames (Pet Frame, Raid Frames) and Combat (Cast Bar, Stance Bar, Pet Bar). The panel itself is draggable in edit mode. All checkboxes wire directly to the HudEditSettings struct which propagates visibility changes to every frame instantly.

Advanced Options
Step 04 — Button Base Class
Shared Architecture for Every HUD Element

Every HUD element — player frame, target frame, action bar slots, cast bar — inherits from a single Button base class. It handles dragging, hover detection, child propagation, visibility, camera binding, and debug rendering. Adding a new movable frame takes almost no boilerplate code.

Button Base Class Code
Button.hC++
class Button : public InputObserver
{
public:
  // Drag & drop
  void SetDraggable(bool aValue);
  void MoveBy(const Vector2f& delta);
  bool IsDragging() const;

  // Visibility & parenting
  void SetVisible(bool v) { myIsVisible = v; }
  void AddChild(Button* child);
  Button* GetParent() const;

  // Camera & input
  virtual void SetCamera(shared_ptr<Camera> cam);
  virtual void RegisterInput() override;
  void DebugDrawBounds(const Color& color) const;

protected:
  void SetPosition2D(const Vector2f& newPos);
  void PropagateMoveToChildren(const Vector2f& delta);

  bool myIsDraggable = false;
  bool myIsDragging  = false;
  Vector2f myDragOffset = { 0.f, 0.f };
  Button* myParent = nullptr;
  vector<Button*> myChildren;
};
Step 05 — Child Propagation
Move Propagates to All Children

When a frame moves, SetPosition2D calculates the delta and calls PropagateMoveToChildren — which recursively repositions every child without touching their text layers. This means a player frame dragged in edit mode automatically carries its portrait, bars, and name text together as one unit.

Child Propagation
Button.cppC++
void Button::SetPosition2D(const Vector2f& aNewPos)
{
  const Vector2f delta = aNewPos - myPosition;
  myPosition = aNewPos;

  const float z = myTransform.GetPosition().z;
  mySprite.SetPosition({ myPosition.x, myPosition.y, z });
  myTransform.SetPosition({ myPosition.x, myPosition.y, z });

  for (auto& t : myTexts)
    t.SetPosition(t.GetPosition() + delta);

  PropagateMoveToChildren(delta);
}

void Button::PropagateMoveToChildren(const Vector2f& delta)
{
  for (Button* c : myChildren)
  {
    if (!c) continue;
    c->SetPosition2D_NoText(c->GetPosition() + delta);
  }
}
Step 06 — Drag & Drop
Grab-Point-Accurate Dragging

When a draggable frame receives a left-click while hovered, it enters drag mode and stores the cursor offset from the frame centre. Every subsequent hover event moves the frame to cursor minus offset — giving smooth, grab-point-accurate dragging regardless of where you click the frame.

Drag and Drop
Button.cpp — ReceiveInputC++
case InputEvent::Hover:
{
  Vector2f mousePos =
    myCamera->GetScreenToWorldCoordinatesVec2(mouseScreenPos);

  // If dragging: move frame to cursor position
  if (myIsDragging)
  {
    SetPosition2D(mousePos - myDragOffset);
    break;
  }
  // ... normal hover check ...
}

case InputEvent::MenuLClick:
{
  if (myIsDraggable && myIsHovered)
  {
    myIsDragging = true;
    // Store offset so frame doesn't snap to cursor centre
    myDragOffset = mousePos - myPosition;
  }
}
Step 07 — Grid Snap
Configurable Snap-to-Grid System

When snap-to-grid is enabled, every slot position is rounded to the nearest grid cell before being applied. The grid size is configurable via a live slider (25–200px, snaps to 5px increments). A single ApplySnap function handles all the rounding math — clean and reusable across every frame type.

Grid Snap
ActionBar.cppC++
// During layout rebuild — apply snap if enabled
Vector2f p = GetSlotPos(i);
if (myLayout.snapToGrid)
  p = ApplySnap(p);

slot->SetPosition(p);

// Snap function — rounds to nearest grid cell
Vector2f ActionBar::ApplySnap(const Vector2f& p) const
{
  const float g = std::max(1.f, myLayout.gridSize);
  return {
    std::round(p.x / g) * g,
    std::round(p.y / g) * g
  };
}

// Slider snaps to 5px increments (25–200px range)
s.gridSize = minG + t * (maxG - minG);
s.gridSize = std::round(s.gridSize / 5.f) * 5.f;
Step 08 — Action Bar Editor
Per-Bar Layout Control

Each action bar has its own edit popup — orientation (horizontal/vertical), number of rows, number of icons, icon size (with a live px slider), icon padding, and an "Always Show Buttons" toggle. Changes apply immediately to the bar's layout. Bars can be dragged freely in edit mode and snap to each other, allowing players to stack them or spread them anywhere on screen.

Action Bar Popup
Step 09 — Spellbook & Tooltips
Spell Assignment & Rich Tooltips

Clicking an empty action bar slot opens the spellbook popup showing all available spells as icon buttons. Clicking a spell assigns it to that slot and closes the spellbook. Hovering a filled slot shows a rich tooltip: spell name, mana cost, cast time (or "Instant cast"), and a damage description line. Spells have cooldowns, cast times, and trigger buffs/debuffs on the HUD when used.

Spell Tooltip

Feature Showcase

Normal View
Normal Gameplay View
Edit Mode
HUD Edit Mode
Advanced Options
Advanced Options
Normal Options
Normal Options Panel
Button Code
Button Base Class
Spellbook
Spellbook Popup
Spell Tooltip
Spell Tooltip
Action Bar Popup
Action Bar Edit Popup
Grid Snap
Snap-to-Grid System
Grab Point
Grab-Point Dragging
Move Propagation
Move Propagation
Child Propagation
Propagates to All Children
Player Frame
Player Frame Scale
Target Frame
Target Frame Scale
Party Frames
Party Frame Settings
Drag and Drop
Drag & Drop Handling
Grid Snap System
Grid Snap System
© 2026 Alexander Englesson  ·  Game Programmer  ·  Stockholm
Tools

Particle & VFX Editor

7Spawn Shapes
11Curve Channels
C++Language
PhysXCollision
LiveImGui Editor

Overview

I built a full particle and VFX authoring system in C++ integrated directly into our custom engine (TGE). The system covers everything from the data layer — a serialisable ParticleEmitterData struct with 7 spawn shapes, burst emission, sub-emitters, and world collision — through to a live ImGui editor where artists can edit over-lifetime curves for colour (R/G/B/A independently), size, velocity per axis, and rotation, and see results in-engine immediately. Particle parameters flow end-to-end from the editor into the runtime update loop and out through the pixel and vertex shaders.

My Role & Responsibilities

I designed and implemented the entire system — data architecture, serialisation, runtime particle update, ImGui editor UI, sub-emitter support, PhysX collision integration, and shader wiring. The goal was to make it production-usable: artists and designers could open the editor, tune an effect, and see it running in the scene without touching code or waiting for a rebuild.

C++ ImGui Particle Systems Curve Editor JSON Serialisation Sub-Emitters PhysX Raycasting Shader Integration VFX Workflow Custom Engine (TGE)

Technical Highlights

The core design challenge was keeping the editor and runtime in perfect sync. Every over-lifetime curve — colour channels R, G, B, A, size, velocity X/Y/Z, and rotation — is stored as a std::vector<CurveKey> inside ParticleEmitterData, serialised to JSON, and evaluated identically in both the editor preview and the runtime particle update. The entire data structure is wrapped in a CopyOnWriteWrapper so edits in the ImGui panel don't cause unnecessary copies until a value actually changes. World collision is handled by casting a PhysX ray from each particle's previous position to its current one every frame — on hit, a configurable decal TGO is spawned at the contact point and the particle can be set to die immediately or continue.

Development Breakdown

Step 01 — First Particle
One Particle, End-to-End

Before any systems existed, I proved the full pipeline end-to-end with a single hardcoded particle. The first non-obvious discovery: sprite size must be written directly into mySize on the instance data every frame — transform matrix scale has no effect on sprites. Getting this right on one particle meant everything built after it stood on a correct foundation.

ParticleEmitter.cpp — SpawnParticle(SubEmitter&) C++
// Size must go directly into instance data — NOT the transform
      float size = 1.f;
      if (!aEmitter.data.sizeOverLifetime.empty())
          size = aEmitter.data.sizeOverLifetime.front().value;

      auto spriteInst = std::make_shared<Tga::Sprite3DInstanceData>();
      spriteInst->mySize = { size, size }; // ← this is the only way
      spriteInst->myColor = { 1.f, 1.f, 1.f, 1.f };
      spriteInst->myTransform = t;

      // VFXInstance drives all over-lifetime curves each frame
      auto vfx = go->AddComponent<VFXInstance>();
      vfx->Init();
      vfx->SetType(VFXType::Sprite);
      vfx->SetShouldLoop(false);
      vfx->SetLifeTime(aEmitter.data.lifetime);
      vfx->SetTransform(t);
      vfx->SetSpawnedTime(Tga::Engine::GetInstance()->GetTotalTime());
Step 02 — Live Editor Preview
Particles Playing Inside the Editor

With one particle working, I integrated live preview into the TGE editor — first in the ObjectDefinitionDocument for single-asset preview, then in the SceneDocument so emitters play at their correct world-space positions. A critical bug I hit early: storing only the VFXInstance shared_ptr lets the owning GameObject go out of scope, leaving an internal pointer dangling and crashing on UpdateTransform(). Storing both together in a PreviewVFXEntry solved it.

PreviewParticleSystem.h — PreviewVFXEntry C++
// Storing ONLY VFXInstance is not enough —
      // the GO must stay alive or VFXInstance::myGameObject dangles
      struct PreviewVFXEntry
      {
          std::shared_ptr<GameObject>  go;      // keeps GO alive
          std::shared_ptr<VFXInstance> vfxInst; // drives curves
      };

      // SceneDocument — per-emitter world-space spawn
      const Tga::Vector3f emitterWorldPos =
          sceneObj->GetPosition();

      Tga::Vector3f spawnPos =
          emitterWorldPos + spawnResult.offset;

      // Each Tag::Particle object gets its own entry
      // in three maps: emitter state, particle list, VFX entries
      myPreviewParticles[id].push_back(pp);
      myPreviewEmitters[id]  = emitter;
      myPreviewVFXInstances[id] = entry;
Step 03 — Data Architecture
ParticleEmitterData — The Foundation of Everything

The entire system is driven by a single ParticleEmitterData struct — spawn shape, rate, burst schedules, gravity, colour ranges, direction spread, collision flags, and sub-emitters. The struct serialises fully to and from JSON, so emitter setups save, reload, and share between scenes without any code changes.

VFXinfo.h — ParticleEmitterData C++
struct ParticleEmitterData
      {
          std::string emitterName = "Emitter 0";
          std::vector<ParticleEmitterData> subEmitters; // child emitters

          Tga::StringId texturePath;
          float         lifetime    = 1.f;
          float         spawnRate   = 10.f;
          float         gravityScale = 0.f;
          bool          looping     = true;
          float         warmupTime  = 0.f; // pre-simulate on spawn

          // 7 spawn shapes — each with shape-specific params
          enum class SpawnShape : uint8_t
              { Point, Sphere, HollowSphere, Cone, Box, Circle, Edge };
          SpawnShape spawnShape = SpawnShape::Point;

          // Burst emission — multiple bursts, each with cycles
          struct Burst {
              float time; int count; int cycles; float interval;
          };
          std::vector<Burst> bursts;

          // Collision — PhysX raycast per particle per frame
          bool          collideWithWorld      = false;
          bool          spawnDecalOnCollision = false;
          bool          dieOnCollision        = true;
          Tga::StringId collisionTgoName;
      };
Step 04 — Curve System
11 Independent Over-Lifetime Channels

Rather than simple min/max scalars, I built 11 independent over-lifetime curve channels: colour R, G, B, A, size, velocity X/Y/Z, and rotation. Velocity curves are baked at spawn time against the particle's initial direction vector — so direction spread and spawn shape interact correctly without recomputing direction every frame.

ParticleEmitter.cpp — SpawnParticle(SubEmitter&) C++
// Velocity baked at spawn — dir is computed once from spawn shape
      auto BuildVelocityCurve = [&](
          const std::vector<Tga::CurveKey>& keys,
          float dirComponent) -> std::vector<Curve>
      {
          if (!keys.empty())
          {
              std::vector<Curve> out;
              for (auto& k : keys)
                  out.push_back({ k.time, k.value * dirComponent });
              return out;
          }
          return {};
      };

      // dir.x/y/z from CalculateParticleSpawn() — shape-aware
      auto velX = BuildVelocityCurve(data.velocityOverLifetimeX, dir.x);
      auto velY = BuildVelocityCurve(data.velocityOverLifetimeY, dir.y);
      auto velZ = BuildVelocityCurve(data.velocityOverLifetimeZ, dir.z);

      // Colour: 4 independent channels, each a CurveKey vector
      vfx->SetColorOverLifetimeR(BuildColorCurve(data.colorOverLifetimeR, ...));
      vfx->SetColorOverLifetimeG(BuildColorCurve(data.colorOverLifetimeG, ...));
      vfx->SetColorOverLifetimeB(BuildColorCurve(data.colorOverLifetimeB, ...));
      vfx->SetColorOverLifetimeA(BuildColorCurve(data.colorOverLifetimeA, ...));
Step 05 — World Collision & Decals
Per-Particle PhysX Raycasting with Decal Spawning

When collision is enabled, every live particle casts a PhysX ray from its previous position to its current one each frame. On hit, the particle can die immediately or continue — and if Spawn Decal on Collision is enabled, a configurable TGO asset spawns at the exact contact point. Collision TGO name and scale are both exposed in the ImGui editor and serialise with the emitter data.

ParticleEmitter.cpp — Update() C++
// Ray from previous position to current — catches fast-moving particles
      physx::PxVec3 origin = { prevPos.x, prevPos.y, prevPos.z };
      physx::PxVec3 delta  = { pos.x - prevPos.x,
                                pos.y - prevPos.y,
                                pos.z - prevPos.z };
      float dist = sqrtf(delta.x*delta.x + delta.y*delta.y + delta.z*delta.z);

      if (dist > 0.001f)
      {
          physx::PxVec3 dir = { delta.x/dist, delta.y/dist, delta.z/dist };
          physx::PxRaycastBuffer hit;

          if (globalCurrentPxScene->raycast(origin, dir, dist, hit))
          {
              if (emitter.data.spawnDecalOnCollision)
              {
                  Tga::Vector3f hitPos = { hit.block.position.x,
                                          hit.block.position.y,
                                          hit.block.position.z };
                  // Spawn configurable TGO at exact contact point
                  GameWorld::GetInstance().LoadAssetToScene(
                      emitter.data.collisionTgoName,
                      Tga::Matrix4x4f::CreateFromTranslation(hitPos),
                      true);
              }
              if (emitter.data.dieOnCollision)
                  vfx->ForceExpire();
          }
      }
© 2026 Alexander Englesson  ·  Game Programmer  ·  Stockholm