Javascript API's
Een Javascript api is heel iets anders dan een web API: het gaat eveneens om een set methodes die de programmeur kan gebruiken in zijn Javascript toepassing, maar dan op het gebied van webcam, bluetooth, fullscreen, speech recognition, 3D enz... Een uitgebreide lijst vind je op https://developer.mozilla.org/en-US/docs/Web/API
Dit hoofdstuk is “Games” gedoopt omdat het een verzameling bevat van allerlei leuke en eenvoudige technieken (waaronder een paar in CSS) die van pas kunnen komen in game-achtige toepassingen.
Local storage API
Zoals cookies, maar dan beter: localStorage bewaart teksten lokaal in de browser van de gebruiker met de eenvoudige methodes localStorage.getItem(key)
en localStorage.setItem(key, value)
.
tekst bewaren
Eenvoudig voorbeeld waar de gekozen waarde uit een lijst persisteert:
<p>selecteer een waarde uit de lijst, en refresh:</p>
<select id="selTest">
<option selected>keuze 1</option>
<option>keuze 2</option>
<option>keuze 3</option>
<option>keuze 4</option>
</select>
<p><em>→ merk op dat de keuze persisteert</em></p>
const selTest = document.querySelector('#selTest');
const savedValue = localStorage.getItem('selectValue');
if (savedValue != undefined) selTest.value = savedValue;
selTest.addEventListener('change', function() {
console.log(this.value)
localStorage.setItem('selectValue', this.value);
});
objecten bewaren
Met localStorage kan je enkel strings bewaren. Je kan objecten wel omzetten naar strings en terug met JSON.parse()
en JSON.stringify()
:
<p>selecteer een waarde uit de lijst, en refresh:</p>
<p><label>Naam: <input id="inpName" type="text" value="Rogier"></label></p>
<p><label>Leeftijd: <input id="inpAge" type="number" value="54"></label></p>
<p>
<button type="button" id="btnSave">opslaan</button>
<button type="button" id="btnHerstel">herstellen</button>
</p>
const inpName = document.querySelector('#inpName');
const inpAge = document.querySelector('#inpAge');
document.querySelector('#btnSave').addEventListener('click', function() {
const person = {
name: inpName.value,
age: inpAge.value,
};
localStorage.setItem('person', JSON.stringify(person));
});
document.querySelector('#btnHerstel').addEventListener('click', function() {
if (!localStorage.getItem('person')) return;
const person = JSON.parse(localStorage.getItem('person'));
inpName.value = person.name;
inpAge.value = person.age;
});
opgeslagen items bekijken
De localStorage items worden bewaard per site. Je kan ze altijd terugvinden in de inspector onder de Application tab:
Drag drop API
teksten verslepen
Drag drop van teksten is eenvoudig met het ondrop
event en dataTransfer.getData()
:
<p>
In alteration insipidity impression by travelling reasonable up motionless. Of
regard warmth by unable sudden garden ladies. No kept hung am size spot no. Likewise
led and dissuade rejoiced welcomed husbands boy. Do listening on he suspected
resembled. Water would still if to. Position boy required law moderate was may.
</p>
<textarea placeholder="...selecteer een stuk tekst, en sleep het hierin..."></textarea>
body {
width: 500px;
}
textarea {
border: 2px ridge;
height: 200px;
width: 100%;
}
const dropZone = document.querySelector('textarea');
dropZone.addEventListener('dragover', (e) => e.preventDefault() );
dropZone.addEventListener('dragenter', (e) => e.preventDefault() );
dropZone.addEventListener('drop', function(e) {
e.preventDefault();
txtPassed = e.dataTransfer.getData('text/plain');
this.innerHTML = txtPassed;
});
afbeeldingen verslepen
Voorbeeld met afbeeldingen (tweede afbeelding is niet draggable wegens draggable="false"
):
<div id="dragzone">
<img src="img/product1.jpg" alt="">
<img src="img/product2.jpg" alt="" draggable="false">
<img src="img/product3.jpg" alt="">
</div>
<div id="dropzone">
...drop item here...
</div>
#dropzone {
border: 2px ridge;
height: 200px;
width: 400px;
}
const dropZone = document.querySelector('#dropzone');
dropZone.addEventListener('dragover', (e) => e.preventDefault());
dropZone.addEventListener('dragenter', (e) => e.preventDefault());
dropZone.addEventListener('drop', function (e) {
imgPassed = e.dataTransfer.getData('text/uri-list');
this.innerHTML = "<img src='" + imgPassed + "'/>";
e.stopPropagation();
e.preventDefault();
});
afbeeldingen van desktop naar pagina slepen
Voorbeeld met afbeeldingen vanuit verkenner naar een webpagina slepen:
<div id="dropzone">Drop in images from your desktop</div>
<div id="thumbnails"></div>
#dropzone {
height: 150px;
border: 2px dashed #0687FF;
}
#thumbnails {
height: 125px;
margin-top: 10px;
}
#thumbnails img {
height: 100px;
margin: 10px
}
// drop area
const dropzone = document.querySelector('#dropzone');
const thumbs = document.querySelector('#thumbnails');
// dropzone events
dropzone.addEventListener('dragenter', (e) => { e.preventDefault(); });
dropzone.addEventListener('dragover', (e) => { e.preventDefault(); });
dropzone.addEventListener('dragleave', (e) => { e.preventDefault(); });
dropzone.addEventListener('drop', function (e) {
e.preventDefault();
// fetch dropped files
for (const file of e.dataTransfer.files) {
// images only
if (!file.type.match(/image.*/)) continue;
// create reader for dropped file
const reader = new FileReader();
reader.addEventListener('load', function (e) {
thumbs.innerHTML += `<img src="${e.target.result}" alt="${file.name}">`;
});
reader.addEventListener('error', () => { alert('an error occurred'); });
// feed dropped file to reader
reader.readAsDataURL(file);
}
});
Webcam API
De webcam gebruiken is heel eenvoudig: vraag een webcamstream op via navigator.mediaDevices.getUserMedia()
, en stel het in als bron van een <video>
element.
Voorbeeld 1: weergave webcam in een video element
<video id="webcam"></video>
<p><button type="button" id="start">start webcam</button></p>
<p id="message"></p>
#webcam {
border: 1px dotted #999;
height: 240px;
width: 320px;
}
const vidWebcam = document.querySelector('#webcam');
const btnStart = document.querySelector('#start');
const parMessage = document.querySelector('#message');
btnStart.addEventListener('click', async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
vidWebcam.srcObject = stream;
vidWebcam.autoplay = true;
} catch {
parMessage.innerHTML = 'weergave webcam mislukt';
}
});
Voorbeeld 2: photobooth
Een iets uitgebreider voorbeeld, met filters en de mogelijkheid snapshots te nemen:
<video id="webcamstream"></video>
<div id="filters">
<button type="button" data-filter="natural">natural</button>
<button type="button" data-filter="sepia">sepia</button>
<button type="button" data-filter="blur">blur</button>
<button type="button" data-filter="grayscale">grayscale</button>
<button type="button" data-filter="invert">invert</button>
<button type="button" data-filter="hue-rotate">hue-rotate</button>
</div>
<p><button type="button" id="btnsnap">take snapshot</button></p>
<div id="canvases">
<canvas width="133" height="100"></canvas>
<canvas width="133" height="100"></canvas>
<canvas width="133" height="100"></canvas>
<canvas width="133" height="100"></canvas>
</div>
/* general */
body {
margin: 20px auto;
width: 640px;
}
/* webcam */
#webcamstream {
height: 480px;
width: 640px;
}
/* snapshots */
canvas {
border: 1px solid #ccc;
margin: 10px 0;
}
canvas + canvas {
margin-left: 29px;
}
/* buttons */
button {
background-color: #428bca;
border: 1px solid #2a6496;
border-radius: 4px;
color: white;
cursor: pointer;
padding: 3px 6px;
text-transform: uppercase;
}
#dosnap {
position: absolute;
top: 10px;
right: 10px;
}
/* filters */
.sepia {
filter: sepia(1);
}
.blur {
filter: blur(5px);
}
.grayscale {
filter: grayscale(1);
}
.invert {
filter: invert(1);
}
.hue-rotate {
filter: hue-rotate(90deg);
}
// inits
const vidWebcam = document.querySelector('#webcamstream');
const btnSnap = document.querySelector('#btnsnap');
const snapshots = document.querySelectorAll('canvas');
const btnsFilter = document.querySelector('#filters');
let nr = 0;
// take snapshot
btnSnap.addEventListener('click', function () {
const context = snapshots[nr].getContext('2d');
context.filter = getComputedStyle(vidWebcam).getPropertyValue('filter');
context.drawImage(vidWebcam, 0, 0, 133, 100);
nr = (nr + 1) % 4;
});
// apply filter
btnsFilter.addEventListener('click', function (e) {
btnsFilter.querySelectorAll('button').forEach(btn => {
vidWebcam.classList.remove(btn.dataset.filter);
});
vidWebcam.classList.add(e.target.getAttribute('data-filter'));
});
// capture webcam
async function startWebcam() {
vidWebcam.autoplay = true;
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
vidWebcam.srcObject = stream;
};
startWebcam();
Nog meer online experimenten:
- de uitgebreide photobooth versie op https://webcamtoy.com/
- zandpartikels op https://lab.cheron.works/webgl-gpgpu-particles/
- webcam xylofoon op https://www.soundstep.com/blog/experiments/jsdetection/
Geolocation API
De positie van de gebruiker is eenvoudig te bepalen via navigator.geolocation
:
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function(pos) {
console.log(`je positie: ${pos.coords.longitude}, ${pos.coords.latitude}`);
});
}
Voorbeeld: positie met custom marker tonen op kaart (OpenLayers)
<!DOCTYPE html>
<html lang="en">
<head>
<title>Geolocation demo</title>
<meta charset="utf-8">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ol@v9.0.0/ol.css">
<style>
#map {
height: 300px;
width: 500px;
}
</style>
</head>
<body>
<p><button type="button" id="btn1">Show Position</button></p>
<div id="map"></div>
<script src="https://cdn.jsdelivr.net/npm/ol@v9.0.0/dist/ol.js"></script>
<script>
// show map with custom marker on current position
function showPosition(position) {
const longitude = position.coords.longitude
const latitude = position.coords.latitude
// create marker
const marker = new ol.Feature({
geometry: new ol.geom.Point(ol.proj.fromLonLat([longitude, latitude]))
});
// custom marker icon
marker.setStyle(new ol.style.Style({
image: new ol.style.Icon({
src: 'img/marker.png'
})
}));
// create layer with marker
const vectorLayer = new ol.layer.Vector({
source: new ol.source.Vector({
features: [marker]
})
});
// create map with layer
const map = new ol.Map({
target: 'map',
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
}),
vectorLayer
],
view: new ol.View({
center: ol.proj.fromLonLat([longitude, latitude]),
zoom: 12 // Zoom level
})
});
}
// button click event
document.querySelector('#btn1').addEventListener('click', function () {
if (!navigator.geolocation) return;
navigator.geolocation.getCurrentPosition(showPosition);
});
</script>
</body>
</html>
Device orientation API
Indien aanwezig op het toestel, kan je de gyroscoop, accelerometer, kompas enz... gebruiken in Javascript. Voorbeeld voor de kanteling van het toestel (werkt enkel op smartphones en sommige laptops als bepaalde Macbooks), dat gebruik maakt van het deviceorientation
event van window
:
<img src="img/chrome-layer-01.png" alt="" class="layer">
<img src="img/chrome-layer-02.png" alt="" class="layer">
<img src="img/chrome-layer-03.png" alt="" class="layer">
<img src="img/chrome-layer-04.png" alt="" class="layer">
<img src="img/chrome-layer-05.png" alt="" class="layer">
.layer {
position: absolute; /* stapel layers bovenop elkaar */
}
window.addEventListener('deviceorientation', function(e) {
const layers = document.querySelectorAll('.layer');
const offset = 50;
for (let i = 0; i < layers.length; i++) {
const layer = layers[i];
layer.style.left = `${Math.round(e.gamma * i) + offset}px`;
layer.style.top = `${Math.round(e.beta * i) + offset}px`;
}
});
- nog een mooi experiment op https://paperplanes.world/: gooi een papieren vliegtuig met je gsm
Notification API
Toon taskbar notificaties rechtsonder:
<input type="button" id="lnkNotifyMe" value="Notify Me">
function showNotification(title, msg, icon) {
const notification = new Notification(title, { body: msg, icon: icon } );
}
document.getElementById('lnkNotifyMe').addEventListener('click', function() {
if (Notification.permission == 'denied') return;
if (Notification.permission == 'granted') {
showNotification('Some notification', 'Hi there!', 'img/avatar.jpg');
return;
}
Notification.requestPermission(function (permission) {
if (permission === 'granted') {
showNotification('Some notification', 'Hi there!', 'img/avatar.jpg');
}
});
});
SpeechRecognition API
Basisvoorbeeld:
<p><button id="btnStart">Start listening</button></p>
<p><strong>You said:</strong> <em id="echo">nothing yet</em></p>
const btnStart = document.querySelector('#btnStart');
const emEcho = document.querySelector('#echo');
btnStart.addEventListener('click', function() {
const recognition = new (webkitSpeechRecognition || SpeechRecognition)();
recognition.lang = 'en-US';
recognition.start();
recognition.addEventListener('result', function(e) {
emEcho.innerHTML = e.results[0][0].transcript;
});
});
Audio en video
Je kan eenvoudig audio en video laden, afspelen, volume instellen enz... Meest voorkomende properties en methodes:
property | omschrijving |
---|---|
currentTime |
huidige speelpositie in seconden |
duration |
lengte van het bestand in seconden |
isPaused |
is het gepauzeerd of niet (true of false ) |
loop |
herhalen of niet (true of false ) |
src |
bron van het geluidsbestand (URL) |
volume |
volume (getal tussen 0 en 1) |
methode | omschrijving |
---|---|
play() |
speel af |
pause() |
pauzeer |
Voorbeeld 1: geluidseffecten
<p><button id="btnRight"><img src="img/code_right.png" height="20" alt=""> RIGHT</button></p>
<p><button id="btnWrong"><img src="img/code_wrong.png" height="20" alt=""> WRONG</button></p>
const sfxRight = new Audio('media/right.mp3');
const sfxWrong = new Audio('media/wrong.mp3');
document.querySelector('#btnRight').addEventListener('click', function() {
sfxRight.play();
});
document.querySelector('#btnWrong').addEventListener('click', function() {
sfxWrong.play();
});
Voorbeeld 2: muziekdoos
Tik een reeks van namen van noten in, en laat het muziekje afspelen:
<p>
<label>notes: <input type="text" value="A Cs D F A" id="inpSong"></label>
<input type="button" id="btnPlay" value="play">
</p>
<p id="msg"></p>
// inits
const msg = document.querySelector('#msg');
let notes;
// play note i
function playNextNote() {
if (!notes.length) {
msg.innerHTML = 'finished';
return;
}
note = notes.shift();
msg.innerHTML = `playing ${note}`;
const audio = new Audio(`media/notes/violin-${note}.mp3`);
audio.addEventListener('ended', playNextNote);
audio.play();
}
// add event listener to button
document.querySelector('#btnPlay').addEventListener('click', function() {
notes = document.querySelector('#inpSong').value.trim().split(' ');
playNextNote();
});
Voorbeeld 3: video kiezen en afspelen met weergave tijd
<p>
<select id="selVideo">
<option value="">selecteer video...</option>
<option>dinosaur.mp4</option>
<option>fish.mp4</option>
<option>toys.mp4</option>
</select>
</p>
<p><video id="theVideo" width="640" height="360"></video></p>
<p>tijd: <span id="elapsed">-</span></p>
video {
background-color: #ccc;
}
const vid = document.querySelector('#theVideo');
const elapsed = document.querySelector('#elapsed');
document.querySelector('#selVideo').addEventListener('change', function() {
if (this.value == '') return;
vid.src = `media/${this.value}`
vid.play();
});
setInterval(function() {
if (!video.src) return;
elapsed.innerHTML = `${vid.currentTime.toFixed(1)}/${vid.duration.toFixed(1)}s`;
}, 50);
Sound API
Dit is een zeer uitgebreide API waarmee je zowat alles kan wat met geluid te maken heeft:
- filteren: low pass, notch, peak...
- sound mixen: volume, gain, cross-fadings, mixing...
- rauwe data bewerken: lezen en schrijven (wat erop neerkomt dat eigenlijk alles mogelijk is)
- ...
Enkele mooie online voorbeelden:
- DinahMoe heeft mooie demo's, zoals Rick Ashtley of Tonecraft
- een eenvoudige equalizer
- Daftpunk Anatomy of a Mashup
- nog een audio visualizer
Voorbeeld: eenvoudige oscillator met versterker
Merk op hoe in de code als het ware toestellen aan elkaar gekoppeld worden:
<p><label>Frequency: <input type="range" min="200" value="1100" max="2000" id="rngFrequency"></label> </p>
<p><label>Volume: <input type="range" min="0" value="0.5" max="1" id="rngVolume" step="0.01"></label></p>
<p><button id="btnStop">stop</button></p>
// create audio context
const audioContext = new AudioContext();
// create oscillator
const oscillator = audioContext.createOscillator();
oscillator.frequency.value = 1000;
// create amp
const amp = audioContext.createGain();
amp.gain.value = 0.5;
// connect oscillator to amp, and amp to output
oscillator.connect(amp);
amp.connect(audioContext.destination);
// start oscillator
oscillator.start();
// bind events
document.querySelector('#rngFrequency').addEventListener('input', function() {
oscillator.frequency.value = this.value;
});
document.querySelector('#rngVolume').addEventListener('input', function() {
amp.gain.value = this.value;
});
document.querySelector('#btnStop').addEventListener('click', function() {
oscillator.stop();
this.disabled = true;
});
Nog meer API's: battery API, gamepad API, bluetooth API...
Er zijn nog veel meer API's te ontdekken — velen gevestigd, sommigen nog experimenteel. Een uitgebreide lijst vind je op https://developer.mozilla.org/en-US/docs/Web/API
Animaties
Wat is een animatie?
Een animatie is in essentie een snelle opeenvolging van stilstaande afbeeldingen of frames, die een illusie van een vloeiende beweging creëren:
Sprites
De meeste computerspellen bevatten bewegende objecten of sprites, die geanimeerd worden. Enkele definities:
- scene: de wereld waarin het spel wordt gespeeld
- sprite: een object in de scène dat kan worden geanimeerd (Super Mario, munt, kogel, bom...)
- spriteblad: een afbeelding die alle afbeeldingsversies van de sprite bevat
- gameloop: een stukje code dat x keer per seconde iteratief wordt uitgevoerd
- frame: een stilstaand beeld van een animatie
- FPS: frames per seconde
Spritesheets zijn afbeeldingen die alle mogelijke versies van één object bevatten. Een voorbeeld uit de klassieker Doom I:
Animaties met setInterval()
De methode setInterval(callback, ms)
voert een methode callback
uit met vaste tussenposen van ms
milliseconden. De timer kan gestopt worden met clearInterval()
. Eenvoudig voorbeeld:
Voorbeeld 1: klok
<p>de tijd is <span id="clock"></span></p>
<p><button id="btnStop">stop de tijd</button></p>
const btnStop = document.querySelector('#btnStop');
const clock = document.querySelector('#clock');
function padZeros(n) {
return ('0' + n).slice(-2);
}
function displayCurrentTime() {
const now = new Date();
const hours = padZeros(now.getHours());
const minutes = padZeros(now.getMinutes());
const seconds = padZeros(now.getSeconds());
clock.innerHTML = `${hours}:${minutes}:${seconds}`;
}
const timer = setInterval(displayCurrentTime, 100);
btnStop.addEventListener('click', () => clearInterval(timer));
Voorbeeld 2: stuiterende bal
Je zou setInterval()
ook kunnen gebruiken om animaties in je game te timen. Eenvoudig voorbeeld met een stuitende bal:
<div id="app"><img src="img/ball.png" id="ball" alt=""></div>
#app {
background-color: #eee;
height: 200px;
position: relative;
width: 400px;
}
#ball {
height: 50px;
left: 10px;
position: absolute;
top: 100px;
width: 50px;
}
let speedX = 10; let speedY = 5; let speedR = 10;
let x = 10; let y = 100; let r = 0;
const ball = document.querySelector('#ball');
function doLoop() {
x += speedX;
y += speedY;
r += speedR;
ball.style.left = `${x}px`;
ball.style.top = `${y}px`;
ball.style.transform = `rotate(${r}deg)`;
if (x < 0 || x > 350) { speedX *= -1; speedR *= -1; }
if (y < 0 || y > 150) { speedY *= -1; speedR *= -1; }
}
setInterval(doLoop, 20); // herhaal elke 20ms
De animatieframes worden exact om de 20ms berekend en getoond. Dit is ok bij heel eenvoudige games en animaties, maar bij complexere games kan het zijn dan de GPU niet meer kan volgen, en dat animaties schokkerig of onvolledig worden. Daarom gebruiken we voor games requestAnimationFrame()
(zie volgend onderdeel).
Animaties met requestAnimationFrame()
De requestAnimationFrame()
is speciaal bedoeld voor games: het wordt opgeroepen standaard 60 keer per seconde, maar niet eerder dan de GPU klaar is voor het volgende frame. Vergelijk volgende fragmenten:
function doLoop() {
// animatie logica hier
// ...
}
setInterval(doLoop, 20); // voer doLoop() elke 20ms uit
function doLoop() {
// animatie logica hier
// ...
// voer doLoop() pas weer uit als de GPU klaar is
requestAnimationFrame(doLoop);
}
doLoop(); // start de loop
Voorbeeld 1: stuiterende bal
Het stuiterende bal voorbeeld herschreven met requestAnimationFrame()
:
<div id="app"><img src="img/ball.png" id="ball" alt=""></div>
#app {
background-color: #eee;
height: 200px;
position: relative;
width: 400px;
}
#ball {
height: 50px;
left: 10px;
position: absolute;
top: 100px;
width: 50px;
}
let speedX = 10; let speedY = 5; let speedR = 10;
let x = 10; let y = 100; let r = 0;
const ball = document.querySelector('#ball');
function doLoop() {
x += speedX;
y += speedY;
r += speedR;
ball.style.left = `${x}px`;
ball.style.top = `${y}px`;
ball.style.transform = `rotate(${r}deg)`;
if (x < 0 || x > 350) { speedX *= -1; speedR *= -1; }
if (y < 0 || y > 150) { speedY *= -1; speedR *= -1; }
requestAnimationFrame(doLoop); // toon volgende frame wanneer CPU er klaar voor is
}
doLoop(); // start de loop
Voorbeeld 2: gunman game
Een compleet voorbeeld met animaties, besturing en geluidseffecten:
<div id="app">
<img id="gunman" alt="">
<div class="buttons">
<button id="btnStop">stop</button>
<button id="btnWalk">walk</button>
<button id="btnRun">run</button>
<button id="btnShoot">shoot</button>
<button id="btnDie">die</button>
<button id="btnTurn">turn</button>
</div>
</div>
body {
background-color: #333;
}
#app {
background-color: rgb(112, 61, 61);
background-image: url(img/panorama.jpg);
background-position: -750px 0;
width: 780px;
height: 500px;
margin: 0 auto;
position: relative;
}
.buttons {
display: flex;
left: 20px;
position: absolute;
top: 20px;
z-index: 10;
}
.buttons button {
margin: 0 5px;
}
#gunman {
background-image: url(img/sprite.png);
bottom: 100px;
display: block;
height: 150px;
left: 400px;
overflow: hidden;
position: absolute;
width: 150px;
}
// sprites list
const sprites = [];
// sound fx
const sndSplash = new Audio('media/splash.mp3');
const sndGun = new Audio('media/gun.mp3');
sndGun.loop = true;
// DOM elements
const app = document.querySelector('#app');
const btnStop = document.querySelector('#btnStop');
const btnWalk = document.querySelector('#btnWalk');
const btnRun = document.querySelector('#btnRun');
const btnShoot = document.querySelector('#btnShoot');
const btnDie = document.querySelector('#btnDie');
const btnTurn = document.querySelector('#btnTurn');
// background offset
let bgOffsetX = -750;
// gunman
const gunman = {
el: document.querySelector('#gunman'), // DOM element
w: 150, // sprite width
h: 150, // sprite height
fps: 5, // frames per second
isDead: false,
startTime: new Date(), // in milliseconds
direction: 1, // 1 = left, -1 = right
state: 'walk', // stop, walk, die, shoot
setState: function(state) { // sets sprite state
sndGun.pause();
this.startTime = new Date();
this.state = state;
this.fps = 15;
if (state == 'run') this.fps = 15;
if (state == 'walk') this.fps = 5;
if (state == 'stop') this.fps = 3;
if (state == 'die') sndSplash.play();
if (state == 'shoot') sndGun.play();
},
frames: { // series of images from sprite sheet
stop: [
[0, 0]
],
walk: [
[2, 0],
[3, 0],
[4, 0]
],
run: [
[2, 0],
[3, 0],
[4, 0]
],
die: [
[0, 1],
[1, 1],
[2, 1],
[3, 1],
[4, 1],
[5, 1],
[6, 1],
[7, 1],
[8, 1],
[9, 1]
],
shoot: [
[0, 0],
[0, 0],
[1, 0],
[1, 0]
]
}
};
// add gunman to sprites list
sprites.push(gunman);
// render a sprite
function renderSprite(sprite) {
// ignore dead sprites
if (sprite.isDead) return;
// detect death
const timePassed = new Date() - sprite.startTime;
const currFrames = sprite.frames[sprite.state];
let frameNr = parseInt(timePassed * sprite.fps / 1000);
if (sprite.state == 'die' && frameNr >= currFrames.length) {
sprite.isDead = true;
return;
}
// adjust frame
frameNr = frameNr % currFrames.length;
sprite.el.style.backgroundPositionX = `-${sprite.w * currFrames[frameNr][0]}px`;
sprite.el.style.backgroundPositionY = `-${sprite.h * currFrames[frameNr][1]}px`;
sprite.el.style.transform = `scaleX(${sprite.direction})`;
// adjust background
if (sprite.state == 'walk' || sprite.state == 'run') bgOffsetX += sprite.direction * sprite.fps / 5;
app.style.backgroundPositionX = `${bgOffsetX}px`;
}
function doLoop() {
for (const sprite of sprites) renderSprite(sprite);
requestAnimationFrame(doLoop);
}
btnStop.addEventListener('click', function() {
gunman.setState('stop');
});
btnWalk.addEventListener('click', function() {
gunman.setState('walk');
});
btnRun.addEventListener('click', function() {
gunman.setState('run');
});
btnShoot.addEventListener('click', function() {
sndGun.play();
gunman.setState('shoot');
});
btnDie.addEventListener('click', function() {
gunman.setState('die');
});
btnTurn.addEventListener('click', function() {
gunman.direction = gunman.direction * -1;
});
// start the game
doLoop();
3D in Javascript
3D begrippen
Voor 3D graphics en animaties hebben we het volgende nodig:
- een scene met alle objecten, lichten enz...
- enkele objecten (kleur, textuur, materiaal, vorm, positie, rotatie...)
- belichting (kleur, diafragma, positie, richting...)
- één of meerdere cameras (positie, doel...)
- waarschijnlijk ook geluidseffecten
Om die scene effectief weer te geven (renderen) moet je ook denken aan:
- welke objecten kunnen schaduw ontvangen?
- welke objecten kunnen schaduw geven?
- wat zijn de reflectie eigenschappen van objecten (glans, absorptie...)?
Tenslotte hebben we voor animaties ook één of andere game loop nodig die de frames weergeeft.
3D met Three.js library
Er zijn verschillende libraries die het werken met 3D in Javascript vereenvoudigen, maar een uitstekende is Three.js.
Voorbeeld 1: roterende donut eenvoudig
Codevoorbeeld van een roterende donut:
<!-- SCRIPTS -->
<script src="common/vendor/three.min.js"></script>
<script>
// init scene
const scene = new THREE.Scene();
// add ground
const groundGeometry = new THREE.PlaneGeometry(200, 300, 32);
const groundTexture = (new THREE.TextureLoader()).load('img/floor.jpg');
groundTexture.wrapS = THREE.RepeatWrapping;
groundTexture.wrapT = THREE.RepeatWrapping;
groundTexture.repeat.set(4, 4);
const groundMaterial = new THREE.MeshPhongMaterial({
shininess: 15,
specular: 0x888888,
shading: THREE.SmoothShading,
side: THREE.DoubleSide,
map: groundTexture
});
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = Math.PI / 360 * 110;
ground.castShadow = false;
ground.receiveShadow = true;
scene.add(ground);
// add shape
const shapeGeometry = new THREE.TorusGeometry(30, 10, 12, 24);
const shapeMaterial = new THREE.MeshPhongMaterial({
color: 0x156289,
side: THREE.DoubleSide,
shading: THREE.FlatShading,
shininess: 60,
specular: 0x156289
});
const shape = new THREE.Mesh(shapeGeometry, shapeMaterial);
shape.castShadow = true;
shape.receiveShadow = false;
shape.position.y = 70;
shape.rotation.y = Math.PI / 360 * 120;
scene.add(shape);
// add ambient light
const ambientlight = new THREE.AmbientLight(0x444444, 2.5);
scene.add(ambientlight);
// add spotlight
const spotlight = new THREE.SpotLight(0xFFFFFF, 0.7);
spotlight.position.set(150, 200, -75);
spotlight.shadow.camera.visible = true;
spotlight.castShadow = true;
spotlight.penumbra = 0.1;
spotlight.angle = 0.4;
scene.add(spotlight);
// add camera
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.01, 1000);
camera.position.y = 250;
camera.position.z = 250;
camera.lookAt(new THREE.Vector3(0, 50, 0));
// init renderer
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(new THREE.Color(0x000000));
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);
// render
function render() {
// keep looping
requestAnimationFrame(render);
shape.rotation.y += 0.03;
// render the scene
renderer.render(scene, camera);
}
render();
</script>
Voorbeeld 2: roterende donut geavanceerd
Geavanceerde versie van de roterende donut (demo hier, zip met code hier):
Voorbeeld 3: vier op een rij in 3D
Vier op een rij in 3D (demo hier, zip met code hier):
Voorbeeld 4: 3D OXO
OXO in 3D (demo hier, zip met code hier):