Oatelaus/bladium

★ 0Forks 0JavaScriptGitHub ↗Compare

README

⚔️ Bladium — 2D Beyblade Battle Game

An interactive, browser-based 2D Beyblade battle game built with vanilla HTML5 Canvas, CSS, and JavaScript. Customize your spinning top, power-up your launch, and battle an AI rival in a physics-driven stadium.


📁 Project Structure

bladium/
├── index.html              # Entry point — all UI screens, canvas, button markup
├── css/
│   └── style.css           # All styles, animations, overlays, HUD, responsive layout
└── js/
    ├── parts.js            # Part definitions (15 parts: 5 top / 5 mid / 5 bot)
    ├── beyblade.js         # Beyblade class — physics, rendering, collision, stat modifiers
    ├── particles.js        # Particle & ParticleSystem classes for VFX
    └── game.js             # Main controller — game loop, state machine, AI, input, rendering

No dependencies, no build step. Open index.html in any browser to play.


🎮 Game Flow (State Machine)

MENU  ──[START]──▶  CUSTOMIZE  ──[READY]──▶  LAUNCH  ──[LAUNCH!]──▶  BATTLE  ──[end]──▶  RESULT
  ▲                    ▲                                                                    │
  │                    └────────────────────────[RECUSTOMIZE]────────────────────────────────┘
  └──────────────────────────────────[REMATCH]──────────────────────────────────────────────┘
State Screen What happens
MENU #menuScreen Title, START button
CUSTOMIZE #customizeScreen Swap top/mid/bot parts, live preview, stat bars
LAUNCH #launchScreen Tap/click to fill power meter, LAUNCH button
BATTLE Canvas + #battleHud Beys fight in stadium, mouse-driven, HUD visible
RESULT #resultScreen Winner, collision stats, rematch/recustomize buttons

Managed by showScreen(state) in game.js — hides all overlays, shows the target one.


🧩 Part System (js/parts.js)

Three slot types, each with 5 parts of varying rarity (common → epic):

Slot Role Stat focus Array
Top (Attack Ring) Primary ATK contributor ATK TOP_PARTS
Middle (Forge Disc) Primary DEF contributor DEF MIDDLE_PARTS
Bottom (Performance Tip) Primary SPD contributor SPD BOTTOM_PARTS

Each part object:

{ id: 'flame', name: 'Flame Ring', atk: 3, def: 1, spd: 1,
  color: '#ff4444', accentColor: '#ff8866', rarity: 'common' }

Key functions:

  • calculateStats(top, mid, bot) → { atk, def, spd, stamina } — sums contributions, stamina derived from DEF + SPD
  • getPart(type, index) / getPartIndex(type, partId) — accessor helpers

Selection state in game.js: selectedTopIdx, selectedMidIdx, selectedBotIdx.


🌀 Beyblade Class (js/beyblade.js)

class Beyblade(x, y, topPart, midPart, botPart, isPlayer)

Core Properties

Property Type Description
x, y float Position in canvas space
vx, vy float Velocity vector
targetX, targetY float Desired position (set via setTarget())
radius number Collision/draw radius (default 28)
spinSpeed 0–100 Current spin percentage — decays over time, determines movement capability
angle radians Current rotation angle for visual spinning
baseStats {atk,def,spd,stamina} Calculated from parts, never changes
currentStats {atk,def,spd,stamina} Base stats (can be modified temporarily)
statModifiers {atk,def,spd} Temporary ± modifiers from collision RNG
statModifierTimers {atk,def,spd} Remaining frames for each modifier

Key Methods

Method Purpose
getEffectiveAtk() currentStats.atk + statModifiers.atk, clamped ≥ 0
getEffectiveDef() Same pattern for DEF
getEffectiveSpd() Same pattern for SPD
getSpeed() Magnitude of velocity vector
isAlive() spinSpeed > 5
launch(power) Sets spinSpeed from power (clamped 20–100), gives initial velocity. Overcharge (>85) adds wobble
setTarget(tx, ty) Sets target position for movement
update(dt, stadiumRadius, cx, cy) Full physics tick — spin decay, movement toward target, friction, speed clamp, stadium boundary bounce, trail
collide(other) → result | null Checks overlap, calculates power/defense with RNG, applies knockback + spin damage + stat changes, returns collision data
applyStatMod(stat, amount, duration) Adds temporary stat modifier for N frames
applyRngStatChange() → {stat, amount, isBuff} | null 25% buff / 20% debuff on random stat for ~3s
draw(ctx) Full beyblade render — trail, three spinning layers, center dot, flash, modifier indicators
drawTopPart(ctx) 8-spike star ring using topPart.color
drawMiddlePart(ctx) Radial gradient disc using midPart colors
drawBottomPart(ctx) Small center circle with cross using botPart.color

Movement Physics

  • Bey accelerates toward targetX/Y with force proportional to SPD × spinSpeed%
  • Max speed clamped to SPD × 0.055 × spinSpeed%
  • Friction of 0.98 applied each frame
  • Stadium boundary bounce (elastic reflection, 0.7 damping, −2 spin)

Collision Resolution Formula

thisPower  = ATK_eff × (spinSpeed/100) × (moveSpeed + 1)
otherPower = ATK_eff × (spinSpeed/100) × (moveSpeed + 1)
thisDef    = DEF_eff × (spinSpeed/100)
rng        = random(0.75, 1.25)

thisImpact  = (thisPower - otherDef) × rng
otherImpact = (otherPower - thisDef) × rng

winner = thisImpact > otherImpact ? this : other
loserSpinLoss  = intensity × 0.08
winnerSpinLoss = intensity × 0.02

✨ Particle System (js/particles.js)

Class Purpose
Particle Single particle with position, velocity, gravity, life, color, size
ParticleSystem Manages array of particles

ParticleSystem methods:

  • burst(x, y, count, colors, intensity) — radial explosion
  • spark(x, y, count, color) — fast small sparks
  • ring(x, y, radius, count, color) — expanding ring
  • update() / draw(ctx) / clear()

Used for: launch taps (sparks), collision impacts (burst + ring).


🎯 Game Controller (js/game.js)

Global Variables

Variable Type Purpose
gameState GameState enum Current screen
selectedTopIdx/MidIdx/BotIdx int Part selection indices
launchPower 0–100 Current charge level
launchCharging bool Whether still accepting taps
playerBey, rivalBey Beyblade Active beyblade instances
particles ParticleSystem Global particle manager
stadiumCenterX/Y, stadiumRadius float Computed from canvas size
mouseX/Y, mouseActive float, bool Cursor tracking for steering
screenShake float Decaying shake intensity
aiBehavior string `'patrol'

Core Functions

Function Purpose
resizeCanvas() Sets canvas to window size, recalculates stadium
showScreen(state) Hides all overlays, shows target, resets relevant state
updateCustomizeUI() Refreshes part names, stat bars, totals from selection indices
drawPreviewBey() Renders a spinning preview on #previewCanvas using temp Beyblade
startPreviewAnimation() RAF loop for preview — auto-cancels on leaving CUSTOMIZE
resetLaunchState() Resets power meter to 0
handleLaunchTap() +7 power per tap, clamps to 100, spawns spark particles
updateLaunchUI() Updates power bar fill, marker, text, button state
startBattle() Creates both beys, applies launch, resets battle state
updateBattleHud() Syncs spin bars, labels, timer from bey state
updateAI() AI behavior decision tree + target position update
showToast(msg, isBuff) Shows floating stat-change notification
endBattle() Determines winner, populates result screen
drawStadium() Renders the circular arena
renderBattle() Full frame — background, stadium, beys, particles, screen shake
gameLoop(timestamp) Main RAF loop — handles all state updates and rendering

AI Behavior Decision Tree

Every N frames:
  if rival.spinSpeed < 30        → defensive (flee)
  else if close to player + 50%  → charge (rush)
  else if roll < 0.35            → aggressive (pursue)
  else if roll < 0.70            → patrol (circle)
  else                           → defensive (keep distance)

Input Handling

Event Action
mousemove / touchmove Track cursor for steering + launch tap location
mouseleave / touchend Set mouseActive = false
click / touchstart Trigger handleLaunchTap() during LAUNCH state
Keyboard Space Tap to charge during LAUNCH
Keyboard L Instant launch if power ≥ 20

Launch Mechanics

  • Each tap adds 7 power (max 100)
  • Power decays at 0.15/frame after 400ms of inactivity
  • Green zone (60–85%): +10 bonus to initial spin speed
  • Overcharge (>85%): +5 bonus but 2 seconds of wobble (erratic movement)
  • Launch button disabled until power ≥ 20

🎨 Visual Design

Element Technique
Beyblade layers 3 concentric rotating canvases with spokes, gradients, star-spike attack ring
Spin trails Fading position history drawn as translucent circles
Collision flash White ring overlay fading over 10 frames
Screen shake Random ctx.translate offset decaying at 0.9/frame
Particles Burst + expanding ring on collision; sparks on launch tap
Stat modifiers Floating colored text orbiting the bey (+2 ATK, -1 DEF, etc.)
Power meter Gradient fill bar with zone labels (Weak/Good/Perfect/Over) and animated marker
HUD Semi-transparent panels with gradient spin bars, timer in gold

📐 Coordinate System & Sizing

  • Canvas fills viewport: canvas.width/height = window.innerWidth/Height
  • Stadium radius: min(canvas.width, canvas.height) × 0.38
  • Stadium center: (canvas.width/2, canvas.height/2)
  • Beyblade radius: 28px (preview: 70px)
  • All coordinates in canvas pixel space
  • Screen shake offsets translation matrix temporarily

🔧 How to Modify

Adding new parts

Edit js/parts.js — add object to TOP_PARTS, MIDDLE_PARTS, or BOTTOM_PARTS array with id, name, atk, def, spd, color, accentColor, rarity.

Tweaking game balance

Key constants in js/game.js:

  • TAP_POWER_GAIN (7) — power per tap
  • LAUNCH_DECAY_RATE (0.15) — power loss per frame
  • launchBonus in startBattle() — perfect/overcharge bonuses

Key constants in js/beyblade.js:

  • spinDecayRate (0.015) — base spin loss per frame
  • friction (0.98) — velocity damping
  • Collision: loserSpinLoss = intensity × 0.08, winnerSpinLoss = intensity × 0.02
  • Collision: RNG range 0.75 + random() × 0.5 (±25%)
  • applyRngStatChange(): 25% buff chance, 20% debuff chance

Changing AI difficulty

Edit startBattle() → rivalPower formula, or updateAI() behavior probabilities/timers.

Adding new screens/states

  1. Add state to GameState enum
  2. Add overlay HTML in index.html
  3. Add case in showScreen()
  4. Handle in gameLoop() if needed

🌐 Browser Compatibility

  • Works on all modern browsers (Chrome, Firefox, Safari, Edge)
  • Touch support for mobile devices
  • No server required — open index.html directly
  • Google Fonts loaded via CDN (Orbitron + Rajdhani)
  • Uses CSS backdrop-filter (gracefully degrades)

Contributors

Oatelaus

Issues