Lir 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 .

Jeg kan godt mærke, at jeg er inspireret af projektet for tiden, så jeg udvikler faktisk noget hurtigere, end jeg får skrevet disse artikler *hint hint*.

Stjerner

Sidst fik vi introduceret planeter, tyngdekraft og ikke mindst evnen til at springe i luften. Da Sylvester så spillet, sagde han: "Du er sej far, men der mangler stjerner", og det har han ret i. Det er ret kedeligt med den sorte skærm og yderligere, så mister man orienteringen, hvis man får fløjet udenfor synsvidde af planeterne, så det var oplagt at få nogle stjerner i baggrunden.

Heldigvis kommer PIXI til hjælp igen, med et objekt kaldet TilingSprite som er en sprite, der blot gentager den samme baggrund igen og igen. Den kan man så passende initiere til samme størrelse som skærmen, og med den rette texture har man nu en fin stjernehimmel som baggrund. Bemærk at vi placerer stjernehimlen direkte på vores stage , og ikke i den container vi byggede for, at kunne scrolle i sidste kapitel. Hvis vi havde gjort dette, ville stjernehimlen blot scrolle væk, når vi fløj ud af skærmen, og da vores bane principielt er uendelig stor, ville vi aldrig kunne håndtere en sprite der var stor nok.

 var star_field_texture: PIXI.Texture.from('gfx/star_field.png'),
var star_field = new PIXI.TilingSprite
    (star_field_texture, $(window).innerWidth(), $(window).innerHeight());
stage.addChild(star_field);  

Det ser bestemt pænere ud end den sorte baggrund, men vi mangler noget bevægelse og som nævnt før, så kan vi ikke blot fylde hele vores container. Vi bliver derfor nødt til at finde på noget andet.

Heldigvis kan man manipulere med texturen på en TilingSprite ved f.eks. at forskyde denne, så hvis vi blot forskyder texturen samtidigt med vi scroller skærmen, så bør vi skabe illusionen af, at stjernehimlen scroller med.

 // ... resten af koden som scroller
// Vi flytter ikke skærmen, men vi flytter på containeren, der ligger "bag" skærmen. 
// Derfor bruger vi negative værdier.
container.setTransform(-this.scroll_position_x, -this.scroll_position_y);

// Dette er den nye kode, som flytter på stjernehimlen.
star_field.tilePosition.x = -this.scroll_position_x;
star_field.tilePosition.y = -this.scroll_position_y;  

Det giver klart et mere dynamisk resultat, og nu kan man også bruge stjernerne til at orientere sig om, hvad retning rumskibet bevæger sig i, når planeterne ikke er synlige. Nu ligner det dog, at planeterne er limet fast til vores stjernebaggrund, eller at stjernerne ligger i samme plan som planeterne, og det ser også lidt fjollet ud.

Det er der dog en lynkur på, for vi behøver jo ikke at scrolle stjernerne i præcis samme hastighed som skærmen. Vi kan f.eks. dividere med 1½.

 // Dette er den nye kode, som flytter på stjernehimlen.
star_field.tilePosition.x = -this.scroll_position_x/1.5;
star_field.tilePosition.y = -this.scroll_position_y/1.5;  

Se det giver en meget bedre effekt. Dette er faktisk kaldet parallax, dvs. når noget som er længere væk bevæger sig langsommere.

...og så var det lige kreativiteten ramte, for kan vi ikke lave dette nummer i dobbelt? Principielt kan jeg tilføje to baggrunde med stjerner, og så bevæge dem i forskellig hastighed.

 var star_field_texture: PIXI.Texture.from('gfx/star_field.png'),
var star_field_1 = new PIXI.TilingSprite
    (star_field_texture, $(window).innerWidth(), $(window).innerHeight());
var star_field_2 = new PIXI.TilingSprite
    (star_field_texture, $(window).innerWidth(), $(window).innerHeight());
stage.addChild(star_field_1);
stage.addChild(star_field_2);

function scroll() {
    // ...

    // Dette er den nye kode, som flytter på stjernehimlen.
    star_field_1.tilePosition.x = -this.scroll_position_x/1.5;
    star_field_1.tilePosition.y = -this.scroll_position_y/1.5;
    star_field_2.tilePosition.x = -this.scroll_position_x/2.0;
    star_field_2.tilePosition.y = -this.scroll_position_y/2.0;
}  

BUM. Så sidder den lige i skabet.

Mere animation

Lidt lir giver jo lyst til mere lir, og noget jeg synes mangler, er at gøre rumskibet lidt mere livligt. Vi mangler noget udstødning fra motorerne, så der er sammenhæng imellem hvad man trykker på, på tastaturet og hvad man ser på skærmen. Jeg gav mig selv i kast med et tegneprogram og tegnede seks billeder der skulle forestille raket-udstødning:

Herefter brugte jeg samme teknik som med eksplosionen til at lave en animeret raket-udstødningssprite, og herefter skulle de tilføjes til rumskibet.

Og her er det igen meget nyttigt med PIXI's hierarki, for her kan jeg blot tilføje alle disse animations-sprites som undersprites til selve rumskibet, for når jeg gør dette, vil de altid være orienteret og placeret i forhold til rumskibet, så når jeg flytter og drejer på rumskibet, så følger mine udstødningssprites automatisk med. Så her er mine to hovedmotorer:

 main_engine_1_sprite = new PIXI.AnimatedSprite(engineTextures);
    main_engine_1_sprite.anchor.set(0.5,0);
    main_engine_1_sprite.scale.set(0.2);
    main_engine_1_sprite.x -= 11;
    main_engine_1_sprite.y += 45;
    main_engine_1_sprite.gotoAndPlay(0);
    main_engine_1_sprite.visible = false;
    Spaceship.sprite.addChild(this.main_engine_1_sprite);
        
    main_engine_2_sprite = new PIXI.AnimatedSprite(engineTextures);
    main_engine_2_sprite.anchor.set(0.5,0);
    main_engine_2_sprite.scale.set(0.2);
    main_engine_2_sprite.x += 11;
    main_engine_2_sprite.y += 45;
    main_engine_2_sprite.gotoAndPlay(0);
    main_engine_2_sprite.visible = false;
    Spaceship.sprite.addChild(this.main_engine_2_sprite);  

Bemærk jeg add'er dem til rumskibet, og bemærk også, at jeg starter med at gøre dem usynlige. Min plan er nemlig, at når man så trykker på en bevægelsestast, så kan jeg vise den tilsvarende motorsprite, og når man slipper tasten igen, skjuler jeg spriten igen.

 $(window).keydown(function(event) {
    switch (event.keyCode) {
        case 38: // Op
            Spaceship.movement = 'accelerate';
            Spaceship.main_engine_1_sprite.visible = true;
            Spaceship.main_engine_2_sprite.visible = true;
            break;
    }
})

$(window).keyup(function(event) {
    switch (event.keyCode) {
        case 38: // Op
            Spaceship.movement = 'none';
            Spaceship.main_engine_1_sprite.visible = false;
            Spaceship.main_engine_2_sprite.visible = false;
            break;
    }
})  

Og mere skal der egentligt ikke til. Så er der udstødning fra rumskibets motorer.

Et mål

Nu hvor det virkeligt begynder at ligne noget, så bør spillet også have et mål. Lige nu kan man blot flyve ind imellem nogle planeter og undgå at styrte ned.

Min ide er, at vi spreder nogle kasser med "rum-last" ud over banen, og spilleren så skal samle disse op ved at flyve hen over dem. Det er der ikke de store ben i. På samme måde som jeg har placeret planeter på banen, placerer jeg blot en række kasser.

 var box = PIXI.Sprite.from('gfx/Box green.png');
box.x = 100;
box.y = 200;
box.type = 'box';
box.diameter = 20;
level_container.addChild(box);
collision_objects.push(box);  

Som du kan se, smider jeg den endda ind i mit array med kollisionsobjekter, da jeg så også blot kan genbruge kollisionslogikken, som jeg allerede har anvendt til at holde styr på hvornår vi fløj ind i en planet. Det eneste problem ved dette, er at rumskibet så eksploderer, når det kolliderer med kasserne, og det er jo ikke ønskværdigt. Derfor bliver jeg nødt til, at justere min kollisionsrutine, så den kan skelne imellem om jeg kolliderer med en planet eller en boks. Bemærk i øvrigt, at jeg af den årsag, har tilføjet en egenskab kaldet type ovenfor, således at jeg kan aflæse om mit objekt er en boks.

Dvs. min nye kollisionsfunktion bliver som følger:

 function() {
    collision_objects.forEach(function(collision_object) {
    var dist = distance(Spaceship.sprite.x, Spaceship.sprite.y, collision_object.sprite.x, collision_object.sprite.y);
    // Check om vi kolliderer.
    if (dist < collision_object.diameter/2+25) {
        // Check om det er en kasse
        if (collision_object.type == 'box') {
            // Vi fjerner blot kassen for at vise, at den er samlet op.
            collision_object.destroy();
            break;
        }
        // Skjul rumskibet.
        Spaceship.sprite.visible = false;
        // Flyt eksplosionen hen hvor rumskibet var.
        Explosion.sprite.x = Spaceship.sprite.x;
        Explosion.sprite.y = Spaceship.sprite.y;
        // Vis eksplosionen og afspil animationen fra billed 0.
        Explosion.sprite.visible = true;
        Explosion.sprite.gotoAndPlay(0);
    }
};  

Så er det blot at tilføje en masse kasser og så er der et mål i spillet.

Points

Som det sidste i denne omgang, så mangler der lidt konkurrence i spillet. Man skal kunne slå hinanden, og det kræver naturligvis, at man skal kunne score nogle points - og vise nogle points. Og det åbner jo et helt nyt emne, nemlig at vise tekst.

Og her falder PIXI heller ikke bagefter. En af de fede ting er faktisk, at man kan bruge f.eks. Googles enorme font-bibliotek. Blot fonten er loadet på siden, så kan man også bruge den i PIXI.

Jeg styrtede derfor over til Google Fonts hvor jeg fandt fonten Gajraj One , som jeg synes passer perfekt til mit spil.

Man definerer først en stil til sin font, hvor man både kan definere skygger, gradienter, linjer omkring bogstaverne og alt hvad man ellers kan forestille sig, og så laver man et tekst- objekt, der på de fleste måder faktisk er at sammenligne med en sprite.

 const style = new PIXI.TextStyle({
    fontFamily: 'Gajraj One',
    fontSize: 36,
    fontWeight: 'bold',
    fill: ['#ff0000', '#ff4433'],
    stroke: 'yellow',
    strokeThickness: 5,
    lineJoin: 'round',
});
        
score_text = new PIXI.Text('Points', style);

// Positioner den og tilføj den til scenen
score_text.x = $(window).innerWidth()-250;
score_text.y = 20;
stage.addChild(score_text);  

I min kollisionsdetektions-funktion, hvor jeg fjerner boxen, når man flyver ind i den, er det egentligt blot, at holde øje med hvor mange points man har, og så opdatere teksten i tekst-objektet med tallet, og så har man et pointtæller.

 // Variabel til at holde styr på points.
var points = 0;
function() {
    collision_objects.forEach(function(collision_object) {
    var dist = distance(Spaceship.sprite.x, Spaceship.sprite.y, collision_object.sprite.x, collision_object.sprite.y);
    // Check om vi kolliderer.
    if (dist < collision_object.diameter/2+25) {
        // Check om det er en kasse
        if (collision_object.type == 'box') {
            // Vi fjerner kassen for at vise, at den er samlet op.
            collision_object.destroy();
            // ...og tildeler nogle points, og opdaterer points-tælleren.
            points += 100;
            score_text.text = points;
            break;
        }
        // Skjul rumskibet.
        Spaceship.sprite.visible = false;
        // Flyt eksplosionen hen hvor rumskibet var.
        Explosion.sprite.x = Spaceship.sprite.x;
        Explosion.sprite.y = Spaceship.sprite.y;
        // Vis eksplosionen og afspil animationen fra billed 0.
        Explosion.sprite.visible = true;
        Explosion.sprite.gotoAndPlay(0);
    }
};  

Version 3

Jeg har lige tilføjet et par detaljer til i version 3, såsom en "Game over"-tekst, og muligheden for efterfølgende at trykke spacebar for at starte forfra, fremfor at reloade siden. Og det er det i denne omgang, men nu synes jeg også at det for alvor begynder at tage form.

Og så skal der spilles: Du kan prøve version 3 her (hvis du sidder ved en computer og har et tastatur) , og hvis du ønsker hele kildeteksten, er du velkommen til downloade den her .

Du kan nu læse næste kapitel i Lyd på rumskibet