[TUT] Game development 03 - Screen Class

This site uses cookies. By continuing to browse this site, you are agreeing to our Cookie Policy.

  • Dit is een vervolg op: [TUT] Game development 02 - Beginnen met renderen

    Hallo ICTScripters,

    Het is alweer tijd voor de 3e tutorial game development. Momenteel hebben we de update() en render() methoden opgezet en deze gevuld met een BufferStrategy waarmee wij dingen op het scherm kunnen tonen.

    In deze tutorial gaan we eerst beginnen met het corrigeren van de update methoden en het berekenen van de FPS. Nadat we dit gedaan hebben gaan we beginnen aan de Screen klasse waarmee pixel voor pixel alles op het scherm getoond gaat worden.

    De update methoden
    Je zou denken dat de update methoden klaar en werkend is, maar schijn bedriegt. Iedereen heeft namelijk een andere computer met hele snelle of juist hele langzame processoren. Het gevolg hiervan is in de huidige situatie is dat de game client gemiddeld veel te veel updates uitvoert dan dat wij daadwerkelijk willen. We gaan daarom eerst de run methoden aanpassen.

    Source Code

    1. /**
    2. * Runs the thread of the game client.
    3. */
    4. @Override
    5. public void run() {
    6. // Huidige tijd in nano seconden.
    7. long lastTime = System.nanoTime();
    8. // Het aantal nano seconden per update. Een nano seconden bestaat uit het getal 1.000.000.000.
    9. // Wij gebruiken nano seconden omdat dit zoals je al ziet een stuk preciezer is!
    10. // Je wilt maximaal 60 updates per seconden, daarom delen wij dit door 60.
    11. // De "D" is een letter die gebruikt kan worden om een "double" te declareren dit kan vervangen worden door *.0
    12. final double ns = 1000000000D / 60D;
    13. double delta = 0;
    14. /*
    15. * This is the game loop. In the game loop the game logic will be
    16. * updated and the logics will be rendered to the screen.
    17. *
    18. * The game logic will be updated max. 60 times per second (UPS). The
    19. * game rendering will be unlimited (FPS).
    20. */
    21. while (running) {
    22. // Huidige tijd in nano seconden wanneer de loop wordt uitgevoerd.
    23. long now = System.nanoTime();
    24. // Hier wordt het verschil berekend tussen de begin en eindtijd tussen de "ticks" in en gedeeld
    25. // door het maximaal aantal updates per seconden.
    26. delta += (now - lastTime) / ns;
    27. // Hier wordt de tijd in nano seconden gereset
    28. lastTime = now;
    29. // Hier wordt gecheckt of het verschil hoger is als 1 en dan wordt de update uitgevoerd.
    30. while (delta >= 1) {
    31. update();
    32. // De delta wordt hier gereset.
    33. delta--;
    34. }
    35. render();
    36. }
    37. // Stop the game client if something went wrong.
    38. stop();
    39. }
    Display All


    Nu word elke update van de game logica maar maximaal 60x per seconden uitgevoerd voor elke computer. Nu willen we alleen nog graag het aantal updates- en frames per seconden meten. Dit doen we door een paar extra simpele berekeningen toe te voegen waarin we elke seconden deze waardes updaten.

    Source Code

    1. /**
    2. * Runs the thread of the game client.
    3. */
    4. @Override
    5. public void run() {
    6. long lastTime = System.nanoTime();
    7. // Huidige tijd in miliseconden wordt hier uitgevoerd.
    8. long timer = System.currentTimeMillis();
    9. final double ns = 1000000000D / 60D;
    10. double delta = 0;
    11. // Hier worden het aantal updates opgeslagen.
    12. int updates = 0;
    13. // Hier worden het de aantal frames opgeslagen.
    14. int frames = 0;
    15. /*
    16. * This is the game loop. In the game loop the game logic will be
    17. * updated and the logics will be rendered to the screen.
    18. *
    19. * The game logic will be updated max. 60 times per second (UPS). The
    20. * game rendering will be unlimited (FPS).
    21. */
    22. while (running) {
    23. long now = System.nanoTime();
    24. delta += (now - lastTime) / ns;
    25. lastTime = now;
    26. while (delta >= 1) {
    27. update();
    28. // Hier wordt het aantal updates opgeteld met 1.
    29. updates++;
    30. delta--;
    31. }
    32. render();
    33. // Hier wordt het aantal frames opgeteld met 1.
    34. frames++;
    35. // Hier wordt een simpele berekening gemaakt waarin we kijken of de huidige aantal
    36. // miliseconden minus de opgeslagen tijd hoger is dan 1000 miliseconden (1 seconden)
    37. if (System.currentTimeMillis() - timer > 1000) {
    38. // Hier wordt de titel aangepast met hierin de ups en fps waarden.
    39. frame.setTitle(title + " | " + updates + " ups, " + frames + " fps");
    40. // Hier wordt de timer gereset omdat het verschil hoger is dan 1000 miliseconden
    41. // moet er ook weer 1000 miliseconden bij worden opgeteld.
    42. timer += 1000;
    43. // Hier wordt het aantal updates gereset, anders blijft dit getal optellen.
    44. updates = 0;
    45. // Hier wordt het aantal frames gereset, anders blijft dit getal optellen.
    46. frames = 0;
    47. }
    48. }
    49. // Stop the game client if something went wrong.
    50. stop();
    51. }
    Display All


    Nu zul je zien wanneer het programma start het aantal frames per seconden en het aantal updates per seconden in het scherm te zien zijn.

    [Blocked Image: https://raw.githubusercontent.com/michaelbeers/Elysium/master/Afbeeldingen/03_01.jpg]

    Waarom zijn de aantal frames hoger dan het aantal updates?
    Het aantal frames is ten aller tijden zo hoog mogelijk om het renderen zo soepel mogelijk te laten verlopen. Voor het bloten oog wordt een frame rate lager dan 25 als schokkend ervaren dus hoe hoger hoe beter. Na maten je verder gaat met bouwen van de game zal dit uiteraard af gaan nemen.

    Verder met de tutorial...
    We gaan nu een begin maken met de Screen Class, de Screen Class is een object dat gaat dienen als scherm zodat we zo min mogelijk in de render() functie van de Game Class hoeven te zitten. Maak hiervoor dus een nieuwe klasse aan en plaats deze in een nieuwe package genaamd gfx.

    [Blocked Image: https://raw.githubusercontent.com/michaelbeers/Elysium/master/Afbeeldingen/03_02.jpg]

    Hierin gaan we 3 variabelen declareren namelijk de breedte, hoogte een een array met het aantal pixels.
    De array met pixels gaat ieder een individuele kleur bevatten. Dit doen wij om verder in het proces van de client zo min mogelijk te hoeven renderen. Waarom zou je dingen buiten het scherm renderen?

    In de Screen Class gaan we ook een Constructor declareren met de breedte en hoogte van het scherm. Het aantal pixels wordt berekent d.m.v. de breedte en hoogte zoals de simpele formule in de wiskunde om een oppervlakte te berekenen namelijk: w*h.

    Source Code

    1. package com.michaelbeers.elysium.gfx;
    2. public class Screen {
    3. public int width, height;
    4. public int[] pixels;
    5. /**
    6. * Constructs a new screen.
    7. *
    8. * @param width
    9. * @param height
    10. */
    11. public Screen(int width, int height) {
    12. this.width = width;
    13. this.height = height;
    14. pixels = new int[width * height];
    15. }
    16. }
    Display All


    Nu zijn we klaar om de eerste dingen om het scherm te gaan tonen d.m.v. een klein stukje voorbeeld code. Plaats daarom onder de methoden de volgende code:

    Source Code

    1. int time = 0;
    2. int counter = 0;
    3. public void render() {
    4. counter++;
    5. if (counter % 100 == 0) {
    6. time++;
    7. }
    8. for (int y = 0; y < height; y++) {
    9. for (int x = 0; x < width; x++) {
    10. // Hier wordt elke pixel ingevuld met de kleur ff00ff (magenta)
    11. // Omdat het vermedigvuldigen eerder wordt uitgevoerd dan het optellen creërd Java
    12. // Automatisch rijen: rij0 = (0*width), rij 1 = (1*width) enz.
    13. pixels[time + time * width] = 0xff00ff; // x + (y * width)
    14. }
    15. }
    16. }
    Display All


    Nu rest ons alleen nog de taak om de Game Class aan te passen. Declareer daarom de volgende variabelen en pas de constructor en render() methoden in de Game Class aan. Vergeet niet je imports te herstellen met CTRL+SHIFT+O ;)

    Source Code

    1. private Thread thread;
    2. private JFrame frame;
    3. private boolean running = false;
    4. private Screen screen;
    5. // Hier wordt een nieuwe gebufferde image aangemaakt met hierin een RGB image.
    6. private BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    7. // Hier worden de pixels van de gebufferden image opgeslagen, hoe dit exact werkt... geen idee
    8. // Dit is iets wat standaard door Java geschreven maar haat simpelweg de data van de afbeelding op
    9. // Als losse pixels.
    10. private int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
    Display All


    Source Code

    1. public Game() {
    2. Dimension size = new Dimension(width * scale, height * scale);
    3. setPreferredSize(size);
    4. // Initialize all the client components.
    5. this.screen = new Screen(width, height);
    6. this.frame = new JFrame();
    7. }


    Source Code

    1. /**
    2. * Renders everything from the game client to the screen.
    3. *
    4. * Puts thinks like the actual player, mobs or the level to the screen.
    5. */
    6. public void render() {
    7. /*
    8. * Gets the buffer strategy to render the screen. Whether it not exists
    9. * it will create a new buffer strategy with triple buffering.
    10. */
    11. BufferStrategy bs = getBufferStrategy();
    12. if (bs == null) {
    13. createBufferStrategy(3);
    14. return;
    15. }
    16. // Hier wordt de render methoden van de Screen Class aangemaakt.
    17. screen.render();
    18. // Hier worden de pixels van de Screen Class gesynchroniseert met de pixels
    19. // van de gebufferde image.
    20. for (int i = 0; i < pixels.length; i++) {
    21. pixels[i] = screen.pixels[i];
    22. }
    23. /*
    24. * Renders the graphics from the buffer strategy and show them on the
    25. * screen.
    26. */
    27. Graphics g = bs.getDrawGraphics();
    28. // Hier worden de losse pixels op het scherm geplaatst vanuit de gebufferde image.
    29. g.drawImage(image, 0, 0, getWidth(), getHeight(), null);
    30. g.dispose();
    31. bs.show();
    32. }
    Display All


    Als je alles goed hebt gedaan zal er nu een paarse lijn worden getekend en zal je een error krijgen wanneer de schuine lijn de onderkant van het scherm aanraakt.

    [Blocked Image: https://raw.githubusercontent.com/michaelbeers/Elysium/master/Afbeeldingen/03_03.jpg]

    Zoals het stukje voorbeeld code aangeeft wordt de X en Y van de pixel telkens met 1 stap verhoogd waarom blijven de pixels dan staan?
    Dit komt door de BufferStrategy, deze onthoud automatisch welke pixels er zijn ingevuld en welke niet, waarbij zwart telt als niet ingevuld. Daarom is het zaak dat we nog een methoden schrijven om het scherm in zijn geheel leeg te maken. Ga daarom terug naar de Screen Class en voeg hieraan de volgende methoden toe:

    Source Code

    1. public void clear() {
    2. // Een hele simpele loop die alle pixels zwart maakt (dus niet ingevuld)
    3. for (int i = 0; i < pixels.length; i++) {
    4. pixels[i] = 0;
    5. }
    6. }


    Wanneer je dit hebt gedaan is het alleen nog zaak om deze functie uit te voeren in de Game Class voor dat het scherm gerenderd wordt.

    Source Code

    1. screen.clear();
    2. screen.render();


    Je zal zien dat de paarse lijn nu veranderd wordt in 1 pixel die schuin naar beneden beweegt. Hiermee is dan ook een eind aan deze tutorial gekomen. Ik probeer zo snel mogelijk weer een vervolg te maken en ik hoop dat jullie hier weer veel aan hebben. Verder heb ik ook de Github repository aangepast en kunnen jullie eventueel het project downloaden mochten er zich problemen voordoen.

    Github EP 03 - Screen Class

    Met vriendelijke groet,

    Michael Beers

    Vervolg: [TUT] Game development 04 - SpriteSheets en Sprites
    Dit was mijn spreekbeurt, zijn er nog vragen?

    995 times read

Comments 1

  • K.Rens -

    Super tutorial, bedankt!

    Ik sta er wederom van versteld aan hoeveel dingen je dient te denken bij het ontwikkelen van zo iets.

    Knap, mijn respect groeit alleen maar!