Lyd på rumskibet

Emner på denne side: Computer, Programmering, Spil

Hvis du ikke har været med fra starten, så start med at læse, at jeg har bygget et rumskib .

Efter alle de grafiske forbedringer jeg fik lavet sidst, så er der noget nyt som glimrer med sit fravær, og det er LYD.

Lyd

Umiddelbart er der ingen lydfunktioner bygget ind i PIXI, men man skal heldigvis ikke kigge langt, for der eksisterer et plugin, der helt passende hedder PIXI.sound.

Det er så let som at inkludere den tilhørende javascriptfil, f.eks. fra unpkg.com:

 <script src="https://unpkg.com/@pixi/sound/dist/pixi-sound.js"></script>  

Typisk fungerer biblioteket ved, at man loader en lyd under et givent kaldenavn og så kan man bruge den fremadrettet.

 PIXI.sound.add('explosion', {
        url: 'explosion.mp3',
        preload: true,
    });  

I ovenstående har jeg fundet mig en god eksplosionslyd på Freesound.org , som er et kæmpe arkiv over gratis lydklip, hvor filen hedder explosion.mp3, og den kan jeg blot indlæse som ovenfor. Jeg angiver, at mit kaldenavn til lyden er explosion og at jeg i øvrigt gerne vil have at lyden bliver preloadet.

Herefter kan jeg let afspille lyden.

 PIXI.sound.play('explosion');  

Så for at få lyd på når rumskibet styrter ned i en planet og eksploderer, er det blot ovenstående linjer der skal til. Det eneste der er værd at nævne er, at der tilsvarende er en stop -kommando, hvis man ønsker at stoppe lyden i utide, hvilket jeg fandt relevant, når det kom til motorlyd på rumskibet.

Der er selvfølgelig en større dybde i PIXI.sound, såsom muligheder for lydfiltre etc., men til mit lille spil er det ikke relevant.

Variation

I version 3 kunne man flyve rundt blandt de samme planeter og opsamle de samme kasser, der var placeret de samme steder. Jeg kunne godt tænke mig lidt variation, så det vil jeg adressere på flere måder.

Lad os starte med kasserne. Det er lidt kedeligt, at de altid er placeret samme sted, så frem for at jeg placerer alle kasserne, har jeg i stedet lavet en funktion, der automatisk kan placere kasser på banerne.

Frem for at placere kassen helt tilfældigt, har jeg i stedet valgt en algoritme, hvor jeg først vælger en planet og derefter vælger en retning og en afstand fra denne planet, hvor jeg placerer kassen. På den måde kan jeg også styre sværhedsgraden, ved at lægge kasserne tæt på planeterne med tilhørende risiko for at styrte ned eller lidt længere fra for at reducere risikoen. Hvis to planeter er placeret tæt, er der selvfølgelig en risiko for, at jeg får placeret en kasse tilknyttet en planet oveni en anden planet, så jeg slutter af med et kollisionscheck, for at sikre at min valgte placering ikke er oveni en anden planet. Det kan jeg jo blot gøre med mine etablerede kollisionsrutiner.

 // Vælg et interval for rækkevidde i pixels over planetens overflade
var min_box_height = 50;
var max_box_height = 150;

// Vælg en tilfældig planet
var planet = planet_objects[Math.floor(Math.random()*planet_objects.length)];
// Start et loop, så vi bliver ved indtil vi finder en gyldig position.
do {
    
    // Vælg en tilfældig retning
    var direction = Math.random()*Math.PI*2;
    
    // Vælg en rækkevidde fra planeten, ved at finde en tilfældig værdi imellem min og max,
    // og efterfølgende lægge planetens radius til, da vi starter fra planetens centrum.
    var range = Math.random()*(max_box_height-min_box_height)+min_box_height+planet.diameter/2;
    
    // Udregn hvor vores punkt havner.
    var x = planet.sprite.x + Math.cos(direction)*range;
    var y = planet.sprite.y + Math.sin(direction)*range;
    
    // Lav et kollisionscheck imod alt hvad vi kan kollidere med.
    var collision = false;
    collision_objects.every(function(element) {
    
        // Find afstanden
        var dist = distance(x,y, element.sprite.x, element.sprite.y);
    
        // Check om vi rammer under
        if (dist < element.diameter/2+20) {
    
            // Vi ramte noget, så indiker vi har en kollision og forlad loopet.
            collision = true;
            return false;
    
        };
        return true;
    }.bind(this));
    
    // Hvis vi ikke er kollideret med noget, kan vi bryde loopet, da vi har en gyldig position.
    if (! collision) {
        break;
    }
    
    // Vi looper igen for at forsøge en ny position.
} while(true);  

Næste variation er, at jeg vil have flere baner. Banerne vil jeg dog gerne designe eksplicit, selv om man også her sagtens ville kunne lave et tilfældighedsprincip på linje med ovenstående. Men hvordan kommer man til en ny bane? En umiddelbar tanke kunne være, når man havde samlet en vis mængde kasser, eller opnået et vist pointtal, men jeg forestiller mig, at dette er et spil, hvor man skal konkurrere i, at opnå så mange points som muligt, så derfor ville et baneskift ved et bestemt antal points, altid give den samme spil-oplevelse. Derfor beslutter jeg i stedet, at indføre at man har X sekunder på hver bane og så skal se hvor mange points man kan samle, inden man sendes videre til næste bane.

Dette kræver dog en tidstæller, men den er hurtigt implementeret præcis på samme måde som pointtælleren fra sidste kapitel. Eneste forskel er, at i stedet for at opdatere når man får points, så bruger vi tickeren til hele tiden at opdatere den med den forbrugte tid. Og for at gøre det lidt ekstra hektisk, så tæller jeg ikke kun sekunder, men også antallet af frames, således at den pisker derudaf.

 var seconds = 60;
var frames = 0;

var countdown_text = new PIXI.Text('60:00');

function countdown (delta) {
    
    // Træk antallet af 1/60-dele fra frame-tælleren.
    frames -= delta;
    while (frames < 0) {
    
        // Når vi har talt 60 frames ned, så trækker vi et sekund fra.
        frames += 60;
        seconds -= 1;
    
    }
    
    // Hvis sekunder går under 0, så er timeren slut og så skal vi skifte bane.
    if (seconds < 0) {
    
        countdown_text.text = '0:00';
        // SKIFT BANE
    
    } else {
    
        // Opdater teksten med antal sekunder og frames.
        var display_frames = Math.floor(frames);
        countdown_text.text = seconds+':'+(display_frames > 9 ? display_frames : '0'+display_frames);
    
    }
}  

Jeg lavede egentlig tælleren på en lidt anden måde til at starte med, men fik nogle meget lange frame-værdier. Her fandt jeg faktisk ud af, at når tickeren kalder en funktion, så er delta faktisk sjældent lig 1, men derimod et meget langt decimal-tal (der ligger tæt på 1), da dette callback åbenbart er upræcist i forhold til 1/60 af et sekund og tit falder lidt før eller lidt efter. Jeg prøvede først at runde selve frames ned, men fandt så at upræcisionen alligevel var så stor, at min timer faktisk endte med, at gå så meget for langsomt, at man tydeligt opdagede, at der gik mere end 60 sekunder. Derfor runder jeg kun den værdi af, som vi viser.

Baneskiftet i sig selv er let nok. Her indfører jeg blot en variabel, der indikerer hvad bane man er på, og så genbruger jeg blot koden der renser scenen og tegner planeterne, hvor jeg så checker hvad bane man er på og herefter tegner planeterne som jeg ønsker.

Version 4

I version 4 er der tre baner. Den første bane har man 30 sekunder til, og de to sidste baner har man hver 60 sekunder til. Herefter strander man på en bane 4 man ikke rigtig kan gøre noget på eller komme videre fra.

Min næste milestone er, at lancere en version som er komplet. Dvs. der er et antal baner, og man kan enten gennemføre alle banerne eller dø i forsøget. Der bør også være en mekanisme der holder øje med highscore og en titelskærm, men indtil da...

Kan du komme til bane 4? Du kan prøve version 4 her (hvis du sidder ved en computer og har et tastatur) OG HUSK LYD, og hvis du ønsker hele kildeteksten, er du velkommen til downloade den her .

Du kan nu læse sidste kapitel i Gravity