🎨 Graafika, Animatsioonid & Heli
Sprite sheet'id, piksel-kunst, helidisain, muusika — kõik mis annab mängule elu ja atmosfääri.
🖼️ Samm 1 — Sprite Sheet'ide Loomine
Sprite sheet on pilt mis sisaldab mitu "kaadrit" animatsioonist reas.
class SpriteSheet {
constructor(image, frameWidth, frameHeight) {
this.image = image;
this.frameWidth = frameWidth;
this.frameHeight = frameHeight;
this.columns = Math.floor(image.width / frameWidth);
this.rows = Math.floor(image.height / frameHeight);
this.totalFrames = this.columns * this.rows;
}
drawFrame(ctx, frameIndex, x, y, scale = 1) {
const col = frameIndex % this.columns;
const row = Math.floor(frameIndex / this.columns);
ctx.drawImage(
this.image,
col * this.frameWidth, // Source X
row * this.frameHeight, // Source Y
this.frameWidth, // Source W
this.frameHeight, // Source H
x, y, // Destination X, Y
this.frameWidth * scale, // Destination W
this.frameHeight * scale // Destination H
);
}
}
class Animation {
constructor(spriteSheet, frames, frameRate = 10, loop = true) {
this.spriteSheet = spriteSheet;
this.frames = frames; // [0, 1, 2, 3] frame indeksid
this.frameRate = frameRate;
this.loop = loop;
this.currentFrame = 0;
this.timer = 0;
this.finished = false;
}
update(deltaTime) {
if (this.finished) return;
this.timer += deltaTime;
const frameDuration = 1000 / this.frameRate;
if (this.timer >= frameDuration) {
this.timer -= frameDuration;
this.currentFrame++;
if (this.currentFrame >= this.frames.length) {
if (this.loop) {
this.currentFrame = 0;
} else {
this.currentFrame = this.frames.length - 1;
this.finished = true;
}
}
}
}
draw(ctx, x, y, scale = 1) {
const frameIndex = this.frames[this.currentFrame];
this.spriteSheet.drawFrame(ctx, frameIndex, x, y, scale);
}
reset() {
this.currentFrame = 0;
this.timer = 0;
this.finished = false;
}
}
// Kasutamine:
// const sheet = new SpriteSheet(playerImage, 64, 64);
// const idleAnim = new Animation(sheet, [0, 1, 2, 3], 8);
// const runAnim = new Animation(sheet, [4, 5, 6, 7, 8, 9, 10, 11], 12);
🏃 Samm 2 — Animatsioonide Haldur
class Animator {
constructor() {
this.animations = new Map();
this.currentAnim = null;
this.currentKey = '';
this.flipX = false;
}
add(key, animation) {
this.animations.set(key, animation);
if (!this.currentAnim) this.play(key);
}
play(key) {
if (this.currentKey === key) return;
this.currentKey = key;
this.currentAnim = this.animations.get(key);
if (this.currentAnim) this.currentAnim.reset();
}
update(deltaTime) {
if (this.currentAnim) this.currentAnim.update(deltaTime);
}
draw(ctx, x, y, scale = 1) {
if (!this.currentAnim) return;
ctx.save();
if (this.flipX) {
ctx.translate(x + this.currentAnim.spriteSheet.frameWidth * scale, y);
ctx.scale(-1, 1);
this.currentAnim.draw(ctx, 0, 0, scale);
} else {
this.currentAnim.draw(ctx, x, y, scale);
}
ctx.restore();
}
}
// Kasutamine:
// const animator = new Animator();
// animator.add('idle', idleAnim);
// animator.add('run', runAnim);
// animator.add('jump', jumpAnim);
//
// if (velocity.x !== 0) animator.play('run');
// else animator.play('idle');
// animator.flipX = velocity.x < 0;
🎵 Samm 3 — Web Audio API
Web Audio API annab täieliku kontrolli heli üle — helitugevus, pitch, 3D positsioneerimine, efektid.
class AudioManager {
constructor() {
this.ctx = new (window.AudioContext || window.webkitAudioContext)();
this.sounds = new Map();
this.musicGain = this.ctx.createGain();
this.sfxGain = this.ctx.createGain();
this.masterGain = this.ctx.createGain();
// Helipuu: sounds → sfxGain → masterGain → output
this.sfxGain.connect(this.masterGain);
this.musicGain.connect(this.masterGain);
this.masterGain.connect(this.ctx.destination);
this.musicGain.gain.value = 0.3;
this.sfxGain.gain.value = 0.7;
this.currentMusic = null;
}
async load(key, url) {
const response = await fetch(url);
const arrayBuffer = await response.arrayBuffer();
const audioBuffer = await this.ctx.decodeAudioData(arrayBuffer);
this.sounds.set(key, audioBuffer);
}
play(key, options = {}) {
const buffer = this.sounds.get(key);
if (!buffer) return;
const source = this.ctx.createBufferSource();
source.buffer = buffer;
// Pitch muutmine
if (options.pitch) {
source.playbackRate.value = options.pitch;
}
// Juhuslik pitch variatsioon (et ei kõlaks monotoonselt)
if (options.pitchVariation) {
const variation = 1 + (Math.random() - 0.5) * options.pitchVariation;
source.playbackRate.value *= variation;
}
source.connect(this.sfxGain);
source.start(0);
return source;
}
playMusic(key, loop = true) {
if (this.currentMusic) this.currentMusic.stop();
const buffer = this.sounds.get(key);
if (!buffer) return;
const source = this.ctx.createBufferSource();
source.buffer = buffer;
source.loop = loop;
source.connect(this.musicGain);
source.start(0);
this.currentMusic = source;
}
// Sujuv helitugevuse muutmine
fadeMusic(targetVolume, duration = 1) {
this.musicGain.gain.linearRampToValueAtTime(
targetVolume, this.ctx.currentTime + duration
);
}
// Vajalik kasutaja interaktsiooni järel
resume() {
if (this.ctx.state === 'suspended') {
this.ctx.resume();
}
}
}
// Kasutamine:
// const audio = new AudioManager();
// await audio.load('shoot', '/assets/audio/shoot.wav');
// await audio.load('bgMusic', '/assets/audio/music.mp3');
// audio.play('shoot', { pitchVariation: 0.2 });
// audio.playMusic('bgMusic');
Brauseri piirang: Heli ei saa mängida enne kasutaja interaktsiooni (klikk/klahv). Kutsu audio.resume() esimese interaktsiooni järel!
🎶 Samm 4 — Protseduurilised Heliefektid
class SFXGenerator {
constructor(audioCtx) {
this.ctx = audioCtx;
}
// Laseriheli
laser() {
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(1000, this.ctx.currentTime);
osc.frequency.exponentialRampToValueAtTime(100, this.ctx.currentTime + 0.2);
gain.gain.setValueAtTime(0.3, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.2);
osc.connect(gain).connect(this.ctx.destination);
osc.start();
osc.stop(this.ctx.currentTime + 0.2);
}
// Plahvatusheli
explosion() {
const bufferSize = this.ctx.sampleRate * 0.5;
const buffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
data[i] = (Math.random() * 2 - 1) * (1 - i / bufferSize);
}
const source = this.ctx.createBufferSource();
source.buffer = buffer;
const filter = this.ctx.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.setValueAtTime(2000, this.ctx.currentTime);
filter.frequency.exponentialRampToValueAtTime(50, this.ctx.currentTime + 0.5);
const gain = this.ctx.createGain();
gain.gain.setValueAtTime(0.5, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.5);
source.connect(filter).connect(gain).connect(this.ctx.destination);
source.start();
}
// Mündi kogumine
coin() {
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(587, this.ctx.currentTime); // D5
osc.frequency.setValueAtTime(880, this.ctx.currentTime + 0.08); // A5
gain.gain.setValueAtTime(0.3, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.3);
osc.connect(gain).connect(this.ctx.destination);
osc.start();
osc.stop(this.ctx.currentTime + 0.3);
}
}
// const sfx = new SFXGenerator(audio.ctx);
// sfx.laser();
// sfx.explosion();
// sfx.coin();
🖌️ Samm 5 — Piksel-kunsti Põhitõed
Piksel-kunsti reeglid:
- Kasuta piiratud värvipalli (8-16 värvi)
- Tööta väikses suuruses (16×16, 32×32, 64×64)
- Ära kasuta anti-aliasing'ut — iga piksel on tahtlik
- Loo esmalt siluett, siis detailid
- Piirjooned: tumedad (ei pea olema must)
- Valgus peab tulema ühest suunast
// Pixel-perfect Canvas seadistus
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
// KRIITILINE — lülita interpolatsioon välja!
ctx.imageSmoothingEnabled = false;
// CSS-is ka:
// canvas { image-rendering: pixelated; image-rendering: crisp-edges; }
// Tööta väikesel Canvasil ja suurenda CSS-iga
canvas.width = 320; // Mängu loogiline suurus
canvas.height = 240;
canvas.style.width = '960px'; // 3x suurendus
canvas.style.height = '720px';
📝 Harjutused
-
Sprite Sheet animatsioon
Loo SpriteSheet ja Animation klass. Lae tasuta spritesheet (kenney.nl), loo animatsioonid idle, run ja attack. Juhib klaviatuuriga.
-
Animatsioonide haldur
Loo Animator klass mis haldab mitu animatsiooni. Lisa flip (peegeldamine) ja automaatne animatsiooni valik (idle/run/jump) vastavalt olekule.
-
Helisüsteemi ehitamine
Ehita AudioManager Web Audio API-ga. Lae kolm heliefekti + üks taustamuusika. Lisa helitugevuse kontroll, mute nupp ja fade efektid.
-
Protseduurilised heliefektid
Genereeri Web Audio oscillator'itega: laserilask, plahvatus, mündi kogumine, hüppamine, menu hover. Loo SFXGenerator klass.
-
Piksel-kunsti mängija
Joonista Piskel'is 32×32 mängija tegelane: idle (4 kaadrit), run (6 kaadrit). Ekspordi PNG spritesheet. Integreeri mängu.
-
Täielik audiovisuaalne demo
Kombineeri kõik: animeeritud mängija + heliefektid + taustamuusika + partiklid. Loo väike interaktiivne demo kus kõik elemendid töötavad koos.