TWANG

Draw. Aim. TWANG.

A browser-based first-person 3D archery game built for the Cloud9 x JetBrains Hackathon — designed to be fast, competitive, and playable anywhere.


Inspiration

We wanted to build something that captures the pure thrill of esports competition but in a pick-up-and-play format perfect for event booth activations. Archery is intuitive — everyone understands the satisfaction of a perfect shot. But translating that into a browser game that loads fast, runs smooth on any hardware, and delivers that addictive "one more round" gameplay? That's the challenge we accepted.

The goal was simple: create a game fans can walk up to at a LCS or VCT event booth, immediately understand, compete on, and walk away saying "that was awesome." No downloads. No installs. Just open a browser and feel the TWANG.


What It Does

TWANG is a first-person 3D archery game with two distinct modes designed for different engagement scenarios:

Training Mode

Perfect your aim with configurable archery ranges:

  • Multiple target lanes at varied distances
  • Moving target support for skill progression
  • Arrow tracer visualization for learning trajectories
  • Player lock options for focused aim practice

Survival Mode (Competitive/Event)

The event booth showstopper — face waves of incoming mechanical enemies:

  • Progressive wave difficulty with 4 mech types
  • Power-up system (Flaming Arrows, Explosive Arrows)
  • Score-based competition for leaderboard rankings
  • Designed for quick sessions with instant gratification

Core Gameplay

Feature Description
Bow Mechanics Hold-to-charge power system with realistic draw physics
Physics Simulation Gravity-affected arrow trajectories with drag coefficients
Hit Detection Ray-mesh intersection + proximity fallback for reliable hits
Zone Scoring Bullseye (100), Inner (75), Middle (50), Outer (25), Edge (10)
Power-Ups Flaming arrows (1-shot kill), Explosive arrows (AoE damage)

How We Built It

The JetBrains + Junie Workflow

TWANG was developed entirely in JetBrains WebStorm with Junie AI Coding Agent as a core part of our development workflow. Here's how Junie accelerated our build:

Development Phase How Junie Helped
Project Scaffolding Generated the initial Vite + TypeScript + Babylon.js configuration with proper tsconfig and build settings
Boilerplate Reduction Created class skeletons for all game systems (PlayerController, EnemyManager, PowerUpManager) with proper TypeScript interfaces
Physics Implementation Helped derive the Euler integration formulas for arrow trajectory simulation with gravity and drag
Debug Iteration Rapid debugging cycles — describe the bug, Junie suggests fixes, test immediately in WebStorm's integrated terminal
Refactoring Extracted repeated patterns (hit detection, animation management) into clean, reusable methods
Documentation Generated JSDoc comments and this very DEVPOST write-up

The JetBrains IDE + Junie combination was particularly powerful for 3D game development because:

  1. Instant Type Feedback: WebStorm's TypeScript integration caught Babylon.js API misuse before runtime
  2. Junie Context Awareness: When asking Junie to implement features, it understood the existing codebase structure and followed established patterns
  3. Live Reload Workflow: Vite HMR + WebStorm's terminal meant changes were visible in-browser within milliseconds of Junie generating code
  4. Refactor Confidence: Junie could safely rename, extract, and restructure code while WebStorm ensured all references updated correctly

Technology Stack

Layer Technology Version Purpose
IDE JetBrains WebStorm 2024.3 Development environment
AI Assistant Junie - AI-powered coding acceleration
Language TypeScript 5.9.3 Type-safe game logic
3D Engine Babylon.js 8.42.0 WebGL rendering, physics, animations
Build Tool Vite 7.2.4 Fast HMR, optimized production builds
Audio Web Audio API Native Zero-latency sound effects
Assets glTF 2.0 - Efficient 3D model loading

Architecture Overview

+------------------------------------------------------------------+
|                         Game Loop (60fps)                        |
+-------------+-------------+-------------+-------------+----------+
|   Player    |     Bow     |    Arrow    |    Enemy    | Power-Up |
| Controller  |  Controller |  Controller |   Manager   |  Manager |
+-------------+-------------+-------------+-------------+----------+
|                       Scene Manager                              |
|     (Environment, Lighting, HDR Skybox, Ground Materials)        |
+------------------------------------------------------------------+
|                      Babylon.js Engine                           |
|            (WebGL 2.0 Renderer, Physics, Animation)              |
+------------------------------------------------------------------+
|                      Asset Preloader                             |
|          (Progressive loading, browser caching, tips)            |
+------------------------------------------------------------------+

Physics Implementation

Arrow flight uses Euler integration with real-time gravity and drag:

// Each frame:
arrow.velocity.y -= gravity * deltaTime;           // Gravity
arrow.velocity.scaleInPlace(1 - drag * deltaTime); // Air resistance
arrow.rootMesh.position.addInPlace(velocity.scale(deltaTime));
arrow.rootMesh.lookAt(position.add(velocity.normalize())); // Orient to velocity

Hit Detection System

We implemented a dual-layer hit detection system to handle the infamous thin-mesh problem:

  1. Primary: Ray-Mesh Intersection

    • Cast a 2-unit ray from arrow position along velocity vector
    • Uses Babylon.js built-in ray.intersectsMesh() for pixel-perfect accuracy
  2. Fallback: Proximity Detection

    • Distance-based check for edge cases
    • Catches glancing hits that ray might miss
// Ray casting for accurate hits
const ray = new Ray(arrow.position, arrow.velocity.normalize(), 2);
for (const mesh of scene.getMeshesByTags('target')) {
    const pick = ray.intersectsMesh(mesh);
    if (pick.hit) {
        // Calculate score from hit point distance to center
    }
}

Enemy AI System

Four mech types with distinct stats, all using simple but effective steering behavior:

Mech Health Speed Damage Size Shots to Kill
Leela 25 HP 8 5 0.4x 1
Stan 50 HP 7 8 0.5x 2
Mike 75 HP 6 12 0.6x 3
George 100 HP 5 15 0.7x 4

AI updates every frame:

  • Calculate direction to player
  • Rotate wrapper node (separate from animation skeleton)
  • Move toward player at mech-specific speed
  • Switch to melee attack when in range

Challenges We Ran Into

Building a 3D game that runs entirely in the browser sounds easy until you actually try it. Here's what bit us:

1. The Thin Mesh Hit Detection Problem

Problem: Arrows flying at 80+ units/second would phase right through flat target meshes — they'd move past the collision boundary between frames.

Solution: Replaced simple bounding-box checks with raycasting along the velocity vector. The ray extends 2 units ahead, catching intersections even at high speeds. Added proximity fallback for glancing shots.

// Before: Arrow at frame N is at position A, frame N+1 it's past the target
// After: Ray from A in direction V catches the intersection
const ray = new Ray(position, velocity.normalize(), 2);

Junie's Role: When we described the "arrows going through targets" bug, Junie immediately identified it as a classic tunneling problem and suggested the ray-casting approach with code examples.

2. Animation Overwriting Rotation

Problem: Babylon.js skeletal animations were fighting with our AI rotation code. Mechs would face random directions mid-chase.

Solution: Created a wrapper TransformNode architecture:

  • Wrapper node handles position and Y-rotation (AI controlled)
  • Model root parents to wrapper (animations play on skeleton, not affecting wrapper rotation)
enemy_wrapper (AI controls rotation)
  +-- model_root (scale + animation skeleton)
       +-- meshes...

Junie's Role: This was a tricky architectural problem. Junie helped us understand Babylon.js's transform hierarchy and suggested the wrapper pattern after we explained the symptoms.

3. Audio Latency with HTMLAudioElement

Problem: Using new Audio(src).play() introduced 50-150ms latency — unacceptable for a fast-paced archery game where arrow release needs to feel instant.

Solution: Migrated to Web Audio API with preloaded buffers:

// Pre-decode all sound effects on init
const buffer = await audioContext.decodeAudioData(arrayBuffer);
buffers.set('release', buffer);

// Instant playback via buffer source
const source = audioContext.createBufferSource();
source.buffer = buffers.get('release');
source.connect(gainNode);
source.start(0); // Zero latency!

4. 3D Model Loading Times

Problem: Babylon.js core + loaders is ~2MB minimized. Mech models are another 5-10MB. Initial load was brutal.

Solution: Multi-pronged approach:

Strategy Implementation
Code Splitting Vite manualChunks separates Babylon into own chunk
Progressive Loading Common assets load on page load, mode assets on demand
Asset Caching Set<string> tracks loaded URLs, prevents double-fetching
Loading Tips Rotate gameplay tips to mask perceived wait time
// vite.config.ts
manualChunks: {
    babylon: ['@babylonjs/core', '@babylonjs/loaders', '@babylonjs/materials'],
}

Junie's Role: Junie generated the entire AssetPreloader class with progress callbacks, caching logic, and the tip rotation system after we described what we needed.

5. Memory Leaks from Lodged Arrows and Dead Enemies

Problem: Arrows sticking in targets and dead enemies accumulating caused frame drops over long sessions.

Solution:

  • Lodged arrows auto-dispose after 10 seconds
  • Dead enemies dispose 3 seconds after death animation
  • Particle systems from explosions self-destruct after 1 second

6. First-Person Camera Inertia

Problem: Default Babylon.js FreeCamera has built-in inertia that felt sluggish and "floaty" for a precision archery game.

Solution: Cleared default inputs and implemented custom mouse look with zero inertia:

camera.inputs.clear(); // Remove defaults
camera.inertia = 0;    // No lag
// Custom mousemove handler with direct rotation updates

Accomplishments We're Proud Of

Sub-3-Second Load Time to Playable

After asset preloading optimizations, Training mode is ready to shoot in under 3 seconds on decent hardware. Survival takes ~5s due to mech models.

Pixel-Perfect Hit Detection

The ray-mesh intersection system means arrows hit exactly where they should. No "I totally hit that!" frustrations.

Zero-Latency Audio

Web Audio API implementation means the arrow release sound plays the instant you click — crucial for that satisfying feedback loop.

Smooth 60fps with 10+ Enemies

Optimized per-frame updates and mesh reuse keeps the game buttery even during intense wave fights.

Event-Ready UX

Big buttons, clear feedback, immediate engagement — designed for the booth environment where players have 30 seconds to "get it."

Rapid Development with Junie

What would normally take days of boilerplate writing and debugging was compressed into hours. Junie handled the repetitive scaffolding while we focused on game feel and polish.


What We Learned

Area Learning
3D in Browser WebGL 2.0 is capable but every KB counts. Asset optimization is non-negotiable.
Physics Euler integration works fine for arrows — no need for full physics engine
Audio Web Audio API > HTMLAudioElement for games. Buffers are your friend.
Animation Separate transform hierarchy for AI-controlled rotation vs skeletal animation
UX Loading screens with tips greatly improve perceived performance
AI-Assisted Dev Junie excels at boilerplate, patterns, and debugging — lets you focus on the creative work

TypeScript + Babylon.js Tips for Game Dev:

  1. Use Tags.AddTagsTo() for efficient collision group queries
  2. PreloadProgress callbacks make great loading UX
  3. onBeforeRenderObservable is the standard update loop entry point
  4. Manual parent/child transforms > physics constraints for arcade games

JetBrains + Junie Tips:

  1. Give Junie context about your existing architecture before asking for new features
  2. Use WebStorm's TypeScript integration to validate Junie-generated Babylon.js code immediately
  3. Iterate in small chunks — describe one problem, get one solution, test, repeat
  4. Let Junie handle the boilerplate so you can focus on game feel

What's Next for TWANG

Short Term (Post-Hackathon)

  • [ ] Leaderboard integration for competitive booth sessions
  • [ ] Difficulty presets (Casual, Normal, Hardcore)
  • [ ] Mobile touch controls (virtual joystick + tap-to-shoot)

Medium Term

  • [ ] Multiplayer mode (same screen split-screen or network)
  • [ ] More enemy types (flying drones, boss mechs)
  • [ ] Custom arena builder for event organizers

Long Term Vision

  • [ ] VR support via WebXR (already have first-person foundation!)
  • [ ] Cloud9 branded skins and cosmetics
  • [ ] Tournament mode with bracket integration

Perfect for Event Activations

TWANG is built specifically for the LCS/VCT Event Booth use case:

Requirement How TWANG Delivers
Fast Load <5s to gameplay, no downloads
Easy to Learn Point + click = shoot. Universal.
Quick Sessions Waves take 1-2 minutes. Perfect booth turnover.
Competitive Score-based. Natural leaderboard fit.
Spectator-Friendly First-person view is engaging to watch
Hardware Agnostic Any modern browser. Any OS. Any device.

Draw. Aim. TWANG.

Built With

Share this project:

Updates