Spelutveckling

Ladda ner spelet Snake (Expandera först). Det flesta tips finns konkretiserade i den som exempel.

Oavsett vilket spel du tänker utveckla så finns det ett par saker som gör det enklare. Grundregel: Datorn är otroligt snabb men dum och glömsk. Allt måste skrivas ner ( i variabler) och alla instruktioner måste ges detaljerat och nogrannt. Tänk som en skapare av tecknad film. Ditt program ska tillverka minst 30 bilder per sekund. I de flesta fall är det bäst att rita varje bild på nytt.

Innan du börja koda

Här först ett par allmänna råd för spelutveckling

Ta fram penna och papper och skriv ner hur du tänker lägga upp spelet. I processing är det lämpligt att indela det i 4 kategorier:

Variabler - setup - draw - interaktion

  1. Vilka variabler behövs? Vad är det för typ av variabel? heltal, decimaltal, bokstav? Text? Tänk om det ska spelas på pappret, du i rollen som dator. Du är glömsk, behöver skriva ner ALLT.

  2. setup() : vad måste förbereddas innan den kan gå igång? Storlek så klart. Men vad är startvärde för dina variabler? är då slump inblandad?

  3. draw() : Det är den centrala loopen för spelet. här händer det flesta saker om och om igen. Oftast tre stora uppgifter:

    • rita alla objekt på skärmen med hjälp av variabler.
    • uppdatera värden i variablerna som tex rörelse
    • Kollision: Kolla om två saker är på samma plats eller för nära.

4a. Tangentbordshändelse: Om spelet görs med knappar så ska finnas blocket/metoden keyPressed(). Försök att undvika rita i denna metod. Bättre att ändra vissa variabler. Dessan variabler läses sedan/tas hänsyn till i draw-metoden.

4b. Mushändelser: Här gäller samma sak som för tangentbordshändelser.

Variabler

Fundera på vilka informationer du behöver lagra? Och vilka datatyper som är lämpliga? Är det enstaka informationer eller listor av data av samma typ?

Variabler: enstaka informationer

För saker som poäng, higshore, riktning, spelets hastighet behövs inga avancerade datatyper det fungera oftast med så kallade primitiva typer som

För det det inte blir krångligt med åtkomsten skapas (deklareras) alla variabler längst uppe i programfilen. Ett exempel hur de första rader i koden kan se ut:

import java.util.LinkedList; 
    //Variabler
    int antal = 8;
    boolean[] skaRitas = new boolean[50];  // ett fält med 50 platser för sanningsvärden;
    int highScore; 
    float tidsSteg = 0.2; 

Men det spelar ingen roll var man deklarera alla variabler så länge det är utanför en metod!. Är det många variabler så kan man läga alla deklarationer på en egen flik.

Variabler: fält, listor, flera av samma typ

2-dim fält

    //Variabler
  int antal = 8;
  boolean[][] felt = new boolean[antal][antal];  // ett fält med 8x8 platser;

  void setup() {

    size(800, 800);
    fill(255, 0, 0);
  }


  void draw() {

    background(255);
    for (int kolumn = 0; kolumn < antal; kolumn++) {
    for (int rad = 0; rad < antal; rad++) {
      if (felt[kolumn][rad]) 
      ellipse(kolumn * 100 + 50, rad * 100 + 50, 80, 80);
    }
    }
  }


  void mouseClicked() {

    int kolumn = mouseX / 100; 
    int rad = mouseY / 100; 

    if (felt[kolumn][rad])
    felt[kolumn][rad] = false; 
    else felt[kolumn][rad] = true;
  }

setup()

I denna metod läggs allt på plats innan spelet börja. Fönstrets storlek, fäger, textstorlek borde bestämmas här. Och de flesta variabler behöver ett startvärde. Den delen kan man lägga med fördel över till en egen metod. Då kan man anropa den igen om det är dags att starta om spelet (reset).

draw()

Det är huvuddelen av spelet. Metoden anropas 60 gånger per sekund. Det kan ändras med frameRate(FPS), men inte för långsam! Annars är det risk att tangentbords- och mushändelser bearbetas lika långsam. Ett trick för att undvika finns längre ner. Fundera innan du börja koda. Vilka steg ska göras regelbunden? Oftast börjar man draw() med background(); för att sudda bort allt. sedan byggs bilden på nytt: något spelplan, texter, bilder, oängtal mm. Sedan de olika spelfigurer. Efter kan man kolla om det är kollisioner eller annat. OBS! Var försiktig om du läsa färger på skärmen. Även om det inte syns för ditt öga är inte allt på skärmen hela tiden för datorn. Var noga NÄR du läsa färg på ett visst stlle på skärmen. Är allt som borde finnas redan ritat? Eller redan för mycket? Om du ska kolla kollision mellan två objekt kan det vara klokt att rita objekt 1, sedan kolla om det är kollision med objekt 2. Först efter der ritas objekt 2.

TIPS: skapa metoder för de olika steg i draw-blocket. till exempel: ritaSpelplan() ritaSkepp() kollaKollision() ritaFiender(). Se till att metoden utför en anvgränsad, secifik uppgift. Helst endast beräkna eller rita. Det hjälper att har koll på spelets struktur. Och det blir lättare att flytta koden eller använda samma kod på flera ställen.

skillnad data i minnet - bild på skärmen

Det här är ett exempel på skillnad mellan data i minnet och på skärmen. Vi tänker här i rader och spalter, 8 gånger 8 och sparar det på detta sätt: Är det en cirkel eller inte? per cell behövs bara ja/nej. Det är äkvivalent med true/false. Så fältet blir 2-dimensionellt, varje cell en boolean:

    int antal = 8;
    boolean[][] felt = new boolean[antal][antal];

Men skärmen har 800 x 800. Så det blir 100 pixlar per rad och kolumn. i draw-metoden läses/används data-fältet för att avgöra om något ska ritas: if (felt[kolumn][rad]) och de två variabler kolumn och rad, som går från 0 till 7, för att beräkna positionen ellipse(kolumn * 100 + 50, rad * 100 + 50, 80, 80); . Det här är översättningen från fält data till skärmpositioner. I I mouseClicked() är det tvärtom. musens position är skärmrelaterad och räknas om till fält-data. int kolumn = mouseX / 100; så om mouseX ligger mellan 0-99 så är det kolumn = 1, 100-199 kolumn = 2 osv. Samma för y-led/rad:

Spelets hastighet

Vanligtvis körs draw-blocket 60 gånger per sekund (60fps, om datorn hinner), som är ibland för snabbt. Styrningen är då alldeles för känsligt din rymdskepp rör sig för långt till höger eller vänster. Det kan ställas in via frameRate(50);. Men om man gör det för långsamt så blir styrning via mus och tangentbord lika trög. Datorn reagera med samma frekvens på tangentbords- och mushändelser. om spelet är inställt på 10 fps så 'läses' tangentbordet av 10 gånger per sekund. 0,1s är väldigt mycket för en reaktion.
Fundera på följande: Är dina variabler för rörelse som tex x, y, speed mm int variabler? Då är det förmodlingen bättre att byta till float. Då kan man även förflytta saker med tex x+= 0.6;. Då blir alla rörelser mindre hackigt. Ett annat sätt att komma runt det är att bibehålla 60fps, men hoppar regelbunden ur draw-blocket innan det händer något. Man inför en variabel för tid som ökar med 1 varje omgång. Var tredje omgång (det så klart ändras) hoppar den ur draw-blocket. draw är en metod som alla andra. Även om den har inget return-värde (void) så kan en metod avslutas med return :


void draw() {
tid++;
if( tid % 3 == 0 ) { return; } 


Interaktion

Processing har många sätt, delvis rätt detaljerade, att interagera med användaren: KeyPressed, MouseMove, MouseClicked och mfl. Använda dessa helst bara för att beräkna nya värden och ändra variabler. De variabler läses/används sedan i draw() för att rita. Det blir annars svårt att hitta fel när alla ritinstruktioner är fördelade över hela koden.

Kokbok

Studsa

Exempel hur en boll studsa vid underkanten. när den går en stud studsa den högre och högre. Kolla alternativet. Det är ett bättre sätt att studsa. lite mer 'fysikaliskt' rätt.

Utmaning: bygg in lite friktion så att den studsa allt mindre. Låt bollen röra sig även i x-led och att den studsa på väggen.

//Variabler
float x, y, dx, dy;

void setup() {
  size(800, 600);
  x = 300;  
  y = 100;  
  dx = 0;   
  dy = 0.1; //starthastighet neråt

  fill(255, 0, 0);
  stroke(255);
  strokeWeight(2.5);
}


void draw() {

  background(0);
  line(0, 125, 800, 125); // startlinje för bollen.
  ellipse(x, y, 50, 50);
  y = y + dy; // nytt läge
  dy = dy + 0.1; // hastigheten ökar (Gravitation)

  if (y > 575) {      
    dy *= -1;
  }

  // Alternativet
  //if (y > 575) { dy *= -1; } else {dy = dy + 0.1;}
}


2-dim fält: exempel

Utmaning: en rad eller kolumn ska försvinna när den är full.

    //Variabler
  int antal = 8;
  boolean[][] felt = new boolean[antal][antal];  // ett fält med 8x8 platser;

  void setup() {

    size(800, 800);
    fill(255, 0, 0);
  }


  void draw() {

    background(255);
    for (int kolumn = 0; kolumn < antal; kolumn++) {
    for (int rad = 0; rad < antal; rad++) {
      if (felt[kolumn][rad]) 
      ellipse(kolumn * 100 + 50, rad * 100 + 50, 80, 80);
    }
    }
  }


  void mouseClicked() {

    int kolumn = mouseX / 100; 
    int rad = mouseY / 100; 

    if (felt[kolumn][rad])
    felt[kolumn][rad] = false; 
    else felt[kolumn][rad] = true;
  }

Skjuta

Ett litet program som visa hur man skjuter. Styrning med tangenterna a eller d, skjuter med musklick. Laboration/Utmaning: Visa hur maånga kulor finns just nu listan. Se sedan till att listan med kulor rensas/förkortas när de är över fönsterkanten.

Dessutom kan du försöka skjuta något, det som saknas är fiende som kan skjutas. Rita en röd cirkel eller rektangel i övre delen av skärmen. Varje gång du rör/flytta en kula uppåt kollas om den är på något rött.

 import java.util.LinkedList;
//Variabler
LinkedList<Float> yBullets = new LinkedList<Float>();
LinkedList<Float> xBullets = new LinkedList<Float>();

float kanonX = 200, kanonY= 550;  // startpsoition för kanonen

final float BULLET_DY = -2.5;  // hastighet för kulorna. final gör det till en konstant, bearbetas lite snabbare. 

void setup(){
  size(800,600);
  ((java.awt.Canvas) surface.getNative()).requestFocus(); 

}


void draw(){
  background(0);
  rect(kanonX, kanonY, 40,10);

  for(int i = 0; i < xBullets.size(); i++) { // rita alla kulro i listan
    rect(xBullets.get(i), yBullets.get(i), 4,20);
    yBullets.set(i, yBullets.get(i) + BULLET_DY); // och ändra i y-led
  }

}

void keyPressed() {
  if(key == 'a')  kanonX -= 3; 
  if(key == 'd')  kanonX += 3; 
}
void mousePressed(){
  xBullets.add(kanonX +20); yBullets.add(kanonY);  // lägga till en kula i listan
}

slupmtal

Två metoder som levera slumptal mellan två värden i angivna steg. Första är heltal andra för decimaltal:

// Antar att import java.util.Random; 
// finns i början av programmet.

int slumpaHeltal(int lowest, int highest, int step) {
  Random rnd = new Random();
  int res= rnd.nextInt((highest-lowest)/step +1) * step + lowest;
  return res;
}




float slumpaHeltal(float lowest, float highest, float step) {
  Random rnd = new Random();
  float res= rnd.nextFloat() * (highest-lowest) + lowest;
  return res -= res % step ;
}

Flera knappar samtidigt

Vid styrning via tangentbord behovs ibland flera knappar samtidigt. Men variabeln key eller keyCode innehåller bara sista knapptryckningen. Om man till exempel skulle vilja använda ASDW eller piltangenterna för styrning men skulle vilja kunna gå diagonalt med tex S och D samtidigt kan vi inte bara lita på variabeln key eller keyCode. Vi behöver en hel förtäckning/lista om vissa tangenter är nedtryckta. I det här fallet skapas fyra boolean-variabler som sätts true ner en tengent trycks ner och false när den släpps. I draw ritas sedan upp till fyra cirklar:


boolean upPressed = false;
boolean downPressed = false;
boolean leftPressed = false;
boolean rightPressed = false;

float circleX = 100;
float circleY = 100;

void setup(){
  size(200,200);
}

void draw() {
  background(200);  

  if (upPressed) {
    ellipse(circleX, circleY-30, 20, 20);
  }

  if (downPressed) {
    ellipse(circleX, circleY+30, 20, 20);
  }

  if (leftPressed) {
    ellipse(circleX-30, circleY, 20, 20);
  }

  if (rightPressed) {
    ellipse(circleX + 30, circleY, 20, 20);
  }


}

void keyPressed() {
  if (keyCode == UP) {
    upPressed = true;
  }
  else if (keyCode == DOWN) {
    downPressed = true;
  }
  else if (keyCode == LEFT) {
    leftPressed = true;
  }
  else if (keyCode == RIGHT) {
    rightPressed = true;
  }
}

void keyReleased() {
  if (keyCode == UP) {
    upPressed = false;
  }
  else if (keyCode == DOWN) {
    downPressed = false;
  }
  else if (keyCode == LEFT) {
    leftPressed = false;
  }
  else if (keyCode == RIGHT) {
    rightPressed = false;
  }
}

Ett par mindre program för olika spelelement

laddaData

Så här kan man läsa data från en fil:

 import java.util.LinkedList;
//Variabler
LinkedList<Integer> xPos = new LinkedList<Integer>();
LinkedList<Integer> yPos = new LinkedList<Integer>();

void setup() {
  size(500, 500);
  fill(#FF0000);
  laddaData();
}


void draw() {
  background(255);
  for (int i = 0; i < xPos.size(); i++ ) {  // kunde även vara yPos.size().  vi antar att båda listor växer i samma takt.
    ellipse(xPos.get(i), yPos.get(i), 35, 35 );
  }
}


// OBS! Filen list.txt ska ligga i samma mapp. Innehåll
//  20, 40, 50, 60, 81
//  12,120,220,291,100


void laddaData() {  // Ladda två strängar
  String[] lines = loadStrings("list.txt");  // läsa alla rader från list.txt ( 2 st.)

  for (String s : lines[0].split(",") ) {   // splittra första raden vid komma 
    xPos.add(Integer.parseInt(s.trim()) );  // översätta en after en till ett tal och spara i xPos.
  }

  for (String s : lines[1].split(",") ) {  // splittra andra raden vid komma 
    yPos.add(Integer.parseInt(s.trim()) );  // översätta en after en till ett tal och spara i yPos.
  }
}

void mousePressed() {
  int posX = (mouseX+20) / 40*40;
  int posY = (mouseY+20) / 40*40;

  xPos.add(posX);    // lägg till i listan 
  yPos.add(posY);   // lägg till i listan
}