Snake Game Verbessern

P

PlzNichtSchlagen

Mitglied
Hey ich habe schonmal etwas hier gepostet, nun würde ich gerne fragen ob man dieses Spiel noch irgendwie verbessern könnte.
Edit: Ausserdem habe ich probleme das die Tasteneingaben ein wenig spät kommen das heisst wenn ich "UP" drücke kann ich nicht direkt "RIGHT" drücken. Weiss weiner wie man das besser machen könnte? Und ebenfalls passiert es immer das aus irgendwelchen Gründen die Schlange manchmal in sich reinfährt und stirbt. Was habe ich falsch gemacht?


MAIN KLASSE:

Javascript:
package game;

import java.awt.Dimension;
  

import javax.swing.JFrame;

public class Main {
  

public static void main(String[] args) {
    JFrame frame = new JFrame("Snake Komplexe Leistung");
    frame.setContentPane (new GamePanel());
    frame.pack();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setResizable(false);
    frame.setPreferredSize(new Dimension(GamePanel.WIDTH, GamePanel.HEIGHT));
    frame.setVisible(true);
    frame.setLocationRelativeTo(null);
}
}


GAMEPANEL KLASSE:

Javascript:
package game;

import java.awt.Color;



import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferedImage;
import java.util.ArrayList;

import javax.swing.JPanel;

@SuppressWarnings("serial")
public class GamePanel extends JPanel implements Runnable, KeyListener {
  
    public static final int WIDTH = 400;
    public static final int HEIGHT = 400;
   //Render
    private Graphics2D g2d;
    private BufferedImage image;

  
    //Spiel Loop
    private Thread thread;
    private boolean running;
    private long targetTime;
  
    //Spiel Zeugs
    private final int SIZE = 10;
    private Entity kopf,apfel;
    private ArrayList<Entity> schlange;
    private int score;
    private int level;
    private boolean gameover;
    //Bewegung
    private int dx,dy;
  
    //Key input
    private boolean up,down,right,left,start;
  
  
    public GamePanel () {
        setPreferredSize(new Dimension(WIDTH, HEIGHT));
        setFocusable(true);
        requestFocus();
        addKeyListener(this);
      
    }
  
    @Override
    public void addNotify() {
        super.addNotify();
        thread = new Thread(this);
        thread.start();
    }
    private void setFPS(int fps) {
        targetTime = 1000 / fps;
    }
  
    @Override
    public void keyTyped(KeyEvent e) {
      

    }

    @Override
    public void keyPressed(KeyEvent e) {
        int k = e.getKeyCode();
      
        if (k == KeyEvent.VK_UP) up = true;
        if (k == KeyEvent.VK_DOWN) down = true;
        if (k == KeyEvent.VK_LEFT) left = true;
        if (k == KeyEvent.VK_RIGHT) right = true;
        if (k == KeyEvent.VK_ENTER) start = true;
      

    }

    @Override
    public void keyReleased(KeyEvent e) {
        int k = e.getKeyCode();
      
        if (k == KeyEvent.VK_UP) up = false;
        if (k == KeyEvent.VK_DOWN) down = false;
        if (k == KeyEvent.VK_LEFT) left = false;
        if (k == KeyEvent.VK_RIGHT) right = false;
        if (k == KeyEvent.VK_ENTER) start = false;

    }

    @Override
    public void run() {
        if(running) return;
        init();
        long startTime;
        long elapsed;
        long wait;
        while(running) {
            startTime = System.nanoTime();
          
            update();
            requestRender();
          
          
            elapsed = System.nanoTime() - startTime;
            wait = targetTime - elapsed  / 1000000;
            if(wait > 0) {
                try {
                    Thread.sleep(wait);
                }catch(Exception e) {
                    e.printStackTrace();
                }
            }
          
        }
    }
    private void init() {
        image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
        g2d = image.createGraphics();
        running = true;
        setUplevel();
      
      
    }
    private void setUplevel() {
        schlange = new ArrayList<Entity>();
        kopf = new Entity(SIZE);
        kopf.setPosition(WIDTH / 2, HEIGHT/ 2);
        schlange.add(kopf);
      
        for(int i = 1;i < 3;i++) {
            Entity e = new Entity(SIZE);
            e.setPosition(kopf.getX() + (i * SIZE) , kopf.getY());
            schlange.add(e);
              }
        apfel = new Entity(SIZE);
        setApple();
        score = 0;
        gameover = false;
        level = 1;
        dx = dy = 0;
        setFPS(level * 10);
      
      
      
    }
    public void setApple() {
        int x = (int) (Math.random() *(WIDTH - SIZE));
        int y = (int) (Math.random() *(HEIGHT - SIZE));
        x = x - (x % SIZE);
        y = y - (y % SIZE);
        apfel.setPosition(x, y);
      
    }


@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    if (image != null) {
        g.drawImage(image, 0, 0, null);
    }
}


    private void requestRender() {
        render(g2d);
        repaint();




      
    }

    private void update() {
        if(gameover) {
            if(start) {
                setUplevel();
            }
          
            return;
        }
         if(up && dy == 0){
             dy = -SIZE;
             dx = 0;
    }
         if(down && dy == 0){
             dy = SIZE;
             dx = 0;
         }
         if(left && dx == 0){
             dy = 0;
             dx = -SIZE;
         }
         if(right && dx == 0 && dy != 0){
             dy = 0;
             dx = SIZE;
         }
      
         if(dx != 0 || dy != 0) {
         for (int i = schlange.size() - 1;i > 0;i--) {
          
             schlange.get(i).setPosition(
                     schlange.get(i - 1).getX(),
                     schlange.get(i - 1).getY()
                                      
                     );
          
          
         }
        kopf.move(dx, dy);
         }
      
         for(Entity e : schlange) {
             if(e.isCollsion(kopf)) {
                 gameover = true;
                 break;
              
              
             }
          
          
          
         }
      
         if(apfel.isCollsion(kopf)) {
             score++;
             setApple();
          
             Entity e = new Entity(SIZE);
             e.setPosition(-100,-100);
             schlange.add(e);
             if(score % 10 == 0) {
                 level++;
                 if(level > 10) level = 10;
                 setFPS(level * 10);
              
             }
          
          
          
         }
      
      
      
         if (kopf.getX() < 0) kopf.setX(WIDTH - 10);
         if (kopf.getY() < 0) kopf.setY(HEIGHT - 10);
         if (kopf.getX() > WIDTH - 10) kopf.setX(0);
         if (kopf.getY() > HEIGHT - 10) kopf.setY(0);
      
      
      
  
      
    }
    public void render(Graphics2D g2d) {
    g2d.clearRect(0, 0, WIDTH, HEIGHT); 
  
    g2d.setColor(Color.GREEN);
    for(Entity e : schlange) {
        e.render(g2d);
      
      
    }
    g2d.setColor(Color.RED);
    apfel.render(g2d);
    if(gameover) {
        g2d.drawString("GameOver!", 150, 200);
      
    }
  
    g2d.setColor(Color.WHITE);
    g2d.drawString("Score : " + score + "Level : " + level, 10, 10);
    if(dx == 0 && dy == 0) {
        g2d.drawString("Ready!", 150, 200);
    }
  
  
  
      
    }



}

ENTITY KLASSE:

Javascript:
package game;

import java.awt.Graphics2D;
import java.awt.Rectangle;

public class Entity {
    private int x,y,size;
    public Entity(int size) {
        this.size = size;
      
    }
  
    public int getX(){
      
        return x;
      
    }
  
    public int getY(){
      
        return y;
      
    }
  
    public void setX(int x){
      
        this.x = x;
      
    }
  
    public void setY(int y){     
        this.y = y;
      
    }
  
    public void setPosition(int x,int y)
  
    {
        this.x = x;
        this.y = y;
    }
  
    public void move(int dx,int dy){
        x += dx;
        y += dy;
      
      
      
      
      
    }
  
    public Rectangle getBound(){
         return new Rectangle(x, y, size, size);
    }
  
    public boolean isCollsion(Entity o){
        if(o == this) return false;
        return getBound().intersects(o.getBound());
      
    }
    public void render(Graphics2D g2d) {
        g2d.fillRect(x + 1, y + 1, size - 2, size - 2);
      
    }
}
 
Zuletzt bearbeitet:
Blender3D

Blender3D

Top Contributor
Hey ich habe schonmal etwas hier gepostet, nun würde ich gerne fragen ob man dieses Spiel noch irgendwie verbessern könnte.
Habe den Code einmal etwas verfeinert.
Java:
import java.awt.Dimension;

import javax.swing.JFrame;

import game.GamePanel;

public class start {
    public static void main(String[] args) {
        JFrame frame = new JFrame("Snake Komplexe Leistung");
        GamePanel game = new GamePanel(20);
        frame.setContentPane(game);
        frame.pack();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setResizable(false);
        frame.setPreferredSize(new Dimension(GamePanel.WIDTH, GamePanel.HEIGHT));
        frame.addKeyListener(game.getKeyBoard());
        frame.pack();
        frame.setVisible(true);
        frame.setLocationRelativeTo(null);
        game.start();
    }
}
Java:
package game;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import javax.swing.JPanel;
import game.Snake.DIRECTION;

@SuppressWarnings("serial")
public class GamePanel extends JPanel implements Runnable {

    public static final int WIDTH = 600;
    public static final int HEIGHT = 600;
    private static final int MESSAGE_X = 150;
    private static final int MESSAGE_Y = 200;
    // Render
    private Graphics2D g2d;
    private BufferedImage image;

    // Spiel Loop
    private Thread thread;
    private boolean running;
    private long targetTime;

    // Spiel Zeugs
    private final int SIZE;
    private Entity apple;

    private int score;
    private int level;
    private boolean gameover;
    private boolean start = false;
    private Snake snake;

    public GamePanel(int size) {
        SIZE = size;
        setPreferredSize(new Dimension(WIDTH, HEIGHT));

    }

    public KeyAdapter getKeyBoard() {
        return new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                int k = e.getKeyCode();

                if (k == KeyEvent.VK_UP)
                    snake.setDirection(DIRECTION.UP);
                if (k == KeyEvent.VK_DOWN)
                    snake.setDirection(DIRECTION.DOWN);
                if (k == KeyEvent.VK_LEFT)
                    snake.setDirection(DIRECTION.LEFT);
                if (k == KeyEvent.VK_RIGHT)
                    snake.setDirection(DIRECTION.RIGHT);
                if (k == KeyEvent.VK_ENTER)
                    start = true;
            }
        };
    }

    private void init() {
        image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
        g2d = image.createGraphics();
        setFont(g2d.getFont().deriveFont(Font.BOLD, SIZE));
        running = true;
        setupLevel();

    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (image != null)
            g.drawImage(image, 0, 0, null);
    }

    private void render() {
        g2d.clearRect(0, 0, WIDTH, HEIGHT);
        snake.render(g2d);
        g2d.setColor(Color.RED);
        apple.render(g2d);
        showMessages();
    }

    @Override
    public void run() {
        if (running)
            return;
        init();
        long startTime;
        long elapsed;
        long wait;
        while (running) {
            startTime = System.nanoTime();
            update();
            render();
            repaint();
            elapsed = System.nanoTime() - startTime;
            wait = targetTime - elapsed / 1000000;
            if (wait > 0) {
                try {
                    Thread.sleep(wait);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public void setApple() {
        int x = (int) (Math.random() * (WIDTH - SIZE));
        int y = (int) (Math.random() * (HEIGHT - SIZE));
        x = x - (x % SIZE);
        y = y - (y % SIZE);
        apple.setPosition(x, y);

    }

    private void setFPS(int fps) {
        targetTime = 1000 / fps;
    }

    private void setupLevel() {
        snake = new Snake(SIZE, WIDTH / 2, HEIGHT / 2);
        apple = new Entity(SIZE);
        setApple();
        score = 0;
        gameover = false;
        level = 1;
        setFPS(level * 10);
        start = false;
    }

    private void showMessages() {
        g2d.setFont(g2d.getFont().deriveFont(Font.BOLD, SIZE));
        if (gameover)
            g2d.drawString("GAME OVER!", MESSAGE_X, MESSAGE_Y);
        g2d.setColor(Color.WHITE);
        g2d.drawString("Score : " + score + "      Level : " + level, SIZE / 2, SIZE);
        if (!snake.isMoving())
            g2d.drawString("READY!", MESSAGE_X, MESSAGE_Y);
    }

    public void start() {
        running = false;
        if (thread != null)
            while (thread.isAlive())
                ;
        thread = new Thread(this);
        thread.start();
    }

    private void update() {
        if (gameover) {
            if (start)
                setupLevel();
            return;
        }
        snake.move();
        if (snake.isCollision()) {
            gameover = true;
            return;
        }

        if (snake.isCollision(apple)) {
            score++;
            setApple();
            snake.grow();
            if (score % 10 == 0) {
                level++;
                if (level > 10)
                    level = 10;
                setFPS(level * 10);
            }
        }
        snake.adaptBounds(WIDTH - SIZE, HEIGHT - SIZE);
    }

}

Java:
package game;

import java.awt.Graphics2D;
import java.awt.Rectangle;

public class Entity {
    private int x;
    private int y;
    public final int size;
    private Rectangle bound = null;

    public Entity(int size) {
        this.size = size;
        bound = new Rectangle(0, 0, size, size);
    }

    public Rectangle getBound() {
        bound.x = x;
        bound.y = y;
        return bound;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public boolean isCollsion(Entity o) {
        if (o == this)
            return false;
        return getBound().intersects(o.getBound());
    }

    public void move(int dx, int dy) {
        x += dx;
        y += dy;
    }

    public void render(Graphics2D g2d) {
        g2d.fillRect(x + 1, y + 1, size - 2, size - 2);
    }

    public void setPosition(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public void setX(int x) {
        this.x = x;
    }

    public void setY(int y) {
        this.y = y;
    }

}
Java:
package game;

import java.awt.Color;
import java.awt.Graphics2D;
import java.util.ArrayList;

public class Snake {
    private ArrayList<Entity> snake = new ArrayList<Entity>();
    private int dx = 0;
    private int dy = 0;

    public enum DIRECTION {
        UP, DOWN, LEFT, RIGHT
    }

    public Snake(int size, int x, int y) {
        Entity head = new Entity(size);
        snake.add(head);
        head.setPosition(x, y);
        for (int i = 1; i < 3; i++) {
            Entity e = new Entity(size);
            e.setPosition(head.getX() + (i * size), head.getY());
            snake.add(e);
        }
    }

    public void adaptBounds(int width, int height) {
        Entity head = getHead();
        if (head.getX() < 0)
            head.setX(width - 10);
        if (head.getY() < 0)
            head.setY(height - 10);
        if (head.getX() > width - 10)
            head.setX(0);
        if (head.getY() > height - 10)
            head.setY(0);
    }

    private Entity getHead() {
        return snake.get(0);
    }

    public void grow() {
        Entity e = new Entity(getHead().size);
        e.setPosition(-100, -100);
        snake.add(e);
    }

    public boolean isCollision() {
        Entity head = getHead();
        for (Entity e : snake) {
            if (e.isCollsion(head))
                return true;
        }
        return false;
    }

    public boolean isCollision(Entity apple) {
        return getHead().isCollsion(apple);
    }

    public boolean isMoving() {
        return !(dx == 0 && dy == 0);
    }

    public void move() {
        if (dx == 0 && dy == 0)
            return;
        for (int i = snake.size() - 1; i > 0; i--)
            snake.get(i).setPosition(snake.get(i - 1).getX(), snake.get(i - 1).getY());
        getHead().move(dx, dy);
    }

    public void render(Graphics2D g) {
        g.setColor(Color.GREEN);
        for (Entity e : snake)
            e.render(g);
    }

    public void setDirection(DIRECTION dir) {
        int speed = getHead().size;
        if (dir == DIRECTION.UP && dy == 0) {
            dy = -speed;
            dx = 0;
        }
        if (dir == DIRECTION.DOWN && dy == 0) {
            dy = speed;
            dx = 0;
        }
        if (dir == DIRECTION.LEFT && dx == 0) {
            dy = 0;
            dx = -speed;
        }
        if (dir == DIRECTION.RIGHT && dx == 0 && dy != 0) {
            dy = 0;
            dx = speed;
        }
    }
}
 
MoxxiManagarm

MoxxiManagarm

Top Contributor
Ich finde deine Logik für die Bewegung der Schlange nicht optimal. Stelle dir doch mal vor du hast eine Reihe von gleichgroßen Würfeln vor dir auf den Tisch. Du willst diese Reihe nach links verschieben. Nimmst du dann wirklich jeden Würfel und rückst ihn eins nach links? Ich würde einfach den Würfel ganz rechts wegnehmen und ihn nach links packen. Das ist eine konstante Laufzeit. Bei deiner Logik benötigst du immer länger für die Bewegung der Schlange je länger sie ist. Eventuell hat das auch schon was mit deinem Problemchen zu tun.

Im Fall von Snake würde ich also den Tail entfernen und ihn je nach aktueller Bewegungsrichtung an den Head "ankleben". Der "angeklebte" Stein ist dann der neue Head und der vorherige Head ist dann einfach Body. Eventuell kannst du dann noch überlegen ob du die ArrayList nicht ggf. durch eine FIFO Datenstruktur ersetzen willst.

Außerdem empfehle ich dir auf den Thread zu verzichten und stattdessen den Swing Timer einzusetzen https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/swing/Timer.html
 
Zuletzt bearbeitet:
Blender3D

Blender3D

Top Contributor
P

PlzNichtSchlagen

Mitglied
Achso ok, dankeschön für die schnellen Antworten. Ich bin neu hier und habe mich erstmal an etwas simpleres probiert. Aber vielen dank für die Antworten ich werde sie bei dem nächsten programm beachten.
 
Blender3D

Blender3D

Top Contributor
Was meinst du mit Auflösung an der Stelle?
Java:
timer = new Timer(1, this);
Erzeugt einen Timer der theoretisch 1000 mal pro Sekunde einen ActionListener aufruft.
Das bedeutetet die zeitliche Auflösung wäre im besten Fall 1 ms.
So wie der TE das gemacht hat ist das schon der richtige Weg. Da gibt es natürlich auch noch Potential zur Verbesserung z.B aktives Rendern anstatt repaint(), Die verlorenen Frames mitzählen um sie auszuglichen ..
Aber für das Snake Spiel ist das im Moment nicht so wichtig.


Eine Verbesserung in meinem Codevorschlag war auch z.B.

Java:
 public Rectangle getBound() {
        bound.x = x;
        bound.y = y;
        return bound;
    }
versus
Java:
public Rectangle getBound(){
         return new Rectangle(x, y, size, size);
    }
Warum?
der Code des TE erzeugt jedes mal ein neues Rectangle pro Entity. In einer permanenten Schleife wie im Game Loop führt das zu einer unnötigen Belastung des Garbagescollectors.
 
P

PlzNichtSchlagen

Mitglied
Java:
timer = new Timer(1, this);
Erzeugt einen Timer der theoretisch 1000 mal pro Sekunde einen ActionListener aufruft.
Das bedeutetet die zeitliche Auflösung wäre im besten Fall 1 ms.
So wie der TE das gemacht hat ist das schon der richtige Weg. Da gibt es natürlich auch noch Potential zur Verbesserung z.B aktives Rendern anstatt repaint(), Die verlorenen Frames mitzählen um sie auszuglichen ..
Aber für das Snake Spiel ist das im Moment nicht so wichtig.


Eine Verbesserung in meinem Codevorschlag war auch z.B.

Java:
 public Rectangle getBound() {
        bound.x = x;
        bound.y = y;
        return bound;
    }
versus
Java:
public Rectangle getBound(){
         return new Rectangle(x, y, size, size);
    }
Warum?
der Code des TE erzeugt jedes mal ein neues Rectangle pro Entity. In einer permanenten Schleife wie im Game Loop führt das zu einer unnötigen Belastung des Garbagescollectors.

Also vorerst möchte ich sagen das dieser Code natürlich besser ist, aber warum "spawnen" bzw erscheinen die Äpfel manchmal am äussersten Rand sodass man sie nur ganz wenig bis kaum erkennen kann. Also sie spawnwn ausserhalb der Auflösung. Weiterhin wenn die Schlange nach links fährt und ich gleichzeitig hoch und rechts drücke dann ist es direkt GameOver gibt es da nicht irgendwie eine Blockade das er nicht in die selbe "Line" in sich hineinfährt?
 
MoxxiManagarm

MoxxiManagarm

Top Contributor
Ganz ehrlich, das Gleichgewicht zwischen Bewegung und Richtungswechsel empfand ich persönlich auch immer als größte Herausforderung bei Snake. Ich glaube das kannst du eigentlich nur für dich debuggen und für dich entscheiden. Eventuell musst du Richtungswechsel decouncen, Timer neustarten oder Ähnliches.
 
P

PlzNichtSchlagen

Mitglied
Ganz ehrlich, das Gleichgewicht zwischen Bewegung und Richtungswechsel empfand ich persönlich auch immer als größte Herausforderung bei Snake. Ich glaube das kannst du eigentlich nur für dich debuggen und für dich entscheiden. Eventuell musst du Richtungswechsel decouncen, Timer neustarten oder Ähnliches.

Ok, ich kenne mich da nicht so aus. Wie würdest du es lösen (ich will es nicht kopieren ich will mir eher ein Beispiel ansehen). Und wie würde man deiner Meinung nach das Problem mit den Äpfel lösen?

Edit: würde das so gehen?
Javascript:
    public void setApple() {
        int x = (int) (Math.random() * (500 - SIZE));
        int y = (int) (Math.random() * (500 - SIZE));
        x = x - (x % SIZE);
        y = y - (y % SIZE);
        apfel.setPosition(x, y);
 
Zuletzt bearbeitet:
MoxxiManagarm

MoxxiManagarm

Top Contributor
ich will mir eher ein Beispiel ansehen
Da gibt es doch bestimmt unzählige Beispiele im Internet.

Wie würdest du es lösen [...] Und wie würde man deiner Meinung nach das Problem mit den Äpfel lösen?
Ich würde grundsätzlich noch mehr in Rastern denken und entwickeln - Stichwort: MVC. Ein Apfel und alle Körperteile der Schlange haben dann Koordinaten (Model). Speed ist dann der Zeitabstand in welchem ein Bewegungsschritt der Schlange passiert. Auf welchem Pixel und wie groß diese Teile dann gezeichnet werden übernimmt die GUI (View). Eine Positionsänderung ist dann immer nur um 1 bzw. -1 auf der jeweiligen Achse. Eine Kollision lässt sich dann einfach ermitteln auf Basis dieser Koordinaten (Überschreiben von Entity.equals() und dann Einsatz von Snake.snake.contains()). Aktuell übernimmt die Collision Detection deine GUI. Stell dir vor du würdest morgen dein Spiel von Swing auf JavaFX umstellen wollen. Mit MVC hättest du relativ einfach Spiel, aktuell no way.

Ein Apfel auf einer Koordinate wird dann auch genau auf einem Kachel des Rasters gespawnt.
 
Zuletzt bearbeitet:
MoxxiManagarm

MoxxiManagarm

Top Contributor
Vielleicht hilft dir nochmal ein kleines Anschaungsbeispiel wie ich es meine.

Sagen wir du hast ein 3x3 großes Snake Raster. Ich habe keine Swing GUI, sondern nur Zeichen
ooo
ooo
ooo

Deine Schlange spawnt mit 2 Teilen und die Bewegungsrichtung ist default links. a ist hier der Head
ooo
oab
ooo

Außerdem spawnst du einen Apfel x, per Zufall an eine Koordinate, die nicht im Objekt Schlange vorhanden ist.
xoo
oab
ooo

Beim nächsten Timer-Tick (weshalb ich auch Swing Timer meinte) wird entsprechend der aktuell ausgewählten Bewegungsrichtung die theoretisch nächste Koordinate (k) ermittelt.

xoo
kab
ooo

Bevor du k in die Schlange einfügst überprüfst du folgendes:
- liegt k außerhalb des Rasters, hat als eine Koordinate -1 bzw. 3? (du bist mit der Wand kollidiert) --> Spiel verloren
- gleicht k einem Element e der Schlange (k.x == e.x && k.y == e.y)? (Schlange ist mit sich selbst kollidiert) --> Spiel verloren
- gleicht k dem Apfel? (Schlange hat Apfel gefressen) --> k wird in die Schlange eingefügt ohne den Schwanz zu verlieren. Neuer Apfel wird gespawnt
- nicht von all dem? k wird in die Schlange eingefügt und der Schwanz (oben b) wird entfernt

k ist nach oberen Raster dann der neue Kopf und b ist verschwunden

xoo
kao
ooo

Würdest du nun nicht die Richtung wechseln würdest du die Wand knutschen. Angenommen du hast nun die Richtung nach oben ausgewählt. Dann ist xka nun deine Schlange und ein neuer Apfel s wird irgendwo an stelle eines o gespawnt

xoo
kao
oos

usw.

Das Spiel ist gewonnen, wenn der Apfel nicht mehr gespawnt werden kann, es gibt also kein o mehr.

Du könntest z.B. verhindern, dass innerhalb eines Zeitschrittes die Richtung mehrmals geändert werden darf
Außerdem darf dabei nicht die genau gleiche bzw. entgegengesetzte Richtung gewählt werden (also nur oben und unten, falls aktuell links)

Ob nun die oben erwähnten Schritte von einer Konsole, von einer Swing app oder einer sonstigen Visualisierung dargestellt werden, ist eigentlich total schnuppe.
 
Blender3D

Blender3D

Top Contributor
Also vorerst möchte ich sagen das dieser Code natürlich besser ist, aber warum "spawnen" bzw erscheinen die Äpfel manchmal am äussersten Rand sodass man sie nur ganz wenig bis kaum erkennen kann. Also sie spawnwn ausserhalb der Auflösung. Weiterhin wenn die Schlange nach links fährt und ich gleichzeitig hoch und rechts drücke dann ist es direkt GameOver gibt es da nicht irgendwie eine Blockade das er nicht in die selbe "Line" in sich hineinfährt?
Habe den Code noch einmal refactored. Das mit dem Rand sollte behoben sein und die Blockade auch.
Ich habe für @MoxxiManagarm noch ein Beispiel mit animiertem Text eingefügt wie man den Swing Timer zustätzlich nützen kann.

Wichtig alle Klassen habe sich etwas geändert also alle neu rein kopieren!
Java:
import javax.swing.JFrame;
import game.GamePanel;

public class start {
    public static void main(String[] args) {
        JFrame frame = new JFrame("Snake Komplexe Leistung");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        GamePanel game = new GamePanel(25);
        frame.setContentPane(game);
        frame.setResizable(false);
        frame.addKeyListener(game.getKeyBoard());
        frame.pack();
        frame.setVisible(true);
        frame.setLocationRelativeTo(null);
        game.start();
    }
}
Java:
package game;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.util.Random;

import javax.swing.JPanel;
import game.Snake.DIRECTION;

@SuppressWarnings("serial")
public class GamePanel extends JPanel implements Runnable {
    private static Random rnd = new Random(System.currentTimeMillis());
    public static final int WIDTH = 600;
    public static final int HEIGHT = 600;
    private static final int MESSAGE_X = WIDTH / 2 - 100;
    private static final int MESSAGE_Y = HEIGHT - 20;
    private static final int MAX_LEVEL = 10;
    private final int SIZE;
    // Render
    private Graphics2D g2d;
    private BufferedImage image;

    // Spiel Loop
    private Thread thread;
    private boolean running;
    private long targetTime;
    private final static int TXT_GAMEOVER = 0;
    private final static int TXT_STARTED = 1;

    private GameEffect[] text = { new FallingText("GAME OVER", WIDTH / 2 - 70, 0, HEIGHT, 70, true),
            new SnakeText("GOOD LUCK!", 0, HEIGHT / 2, WIDTH, 13) };

    // Spiel Zeugs

    private int score;
    private int level;
    private boolean gameover = true;

    private Snake snake;
    private Entity apple;

    public GamePanel(int size) {
        SIZE = size;
        setPreferredSize(new Dimension(WIDTH, HEIGHT));
    }

    public KeyAdapter getKeyBoard() {
        return new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                int key = e.getKeyCode();
                if (key == KeyEvent.VK_UP)
                    snake.setDirection(DIRECTION.UP);
                if (key == KeyEvent.VK_DOWN)
                    snake.setDirection(DIRECTION.DOWN);
                if (key == KeyEvent.VK_LEFT)
                    snake.setDirection(DIRECTION.LEFT);
                if (key == KeyEvent.VK_RIGHT)
                    snake.setDirection(DIRECTION.RIGHT);
                if (key == KeyEvent.VK_ENTER && gameover) {
                    text[TXT_STARTED].start(20);
                    resetLevel();
                    gameover = false;
                }
            }
        };
    }

    private void init() {
        image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
        g2d = image.createGraphics();
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        setFont(g2d.getFont().deriveFont(Font.BOLD, SIZE));
        running = true;
        resetLevel();
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (image != null)
            g.drawImage(image, 0, 0, null);
    }

    private void render() {
        g2d.clearRect(0, 0, WIDTH, HEIGHT);
        g2d.setColor(Color.BLACK);
        g2d.fillRect(0, 0, WIDTH, HEIGHT);
        snake.render(g2d);
        g2d.setColor(Color.RED);
        apple.render(g2d);
        showMessages();
        for (GameEffect txt : text) {
            if (!txt.isOver())
                txt.draw(g2d);
        }
    }

    @Override
    public void run() {
        init();
        long startTime;
        long elapsed;
        long wait;
        while (running) {
            startTime = System.nanoTime();
            update();
            render();
            repaint();
            elapsed = System.nanoTime() - startTime;
            wait = targetTime - elapsed / 1000000;
            if (wait > 0) {
                try {
                    Thread.sleep(wait);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public void setRndApple() {
        apple.setPosition(rnd.nextInt(WIDTH), rnd.nextInt(HEIGHT));
    }

    private void setFPS(int fps) {
        targetTime = 1000 / fps;
    }

    private void resetLevel() {
        snake = new Snake(SIZE, WIDTH / 2, HEIGHT / 2);
        apple = new Entity(SIZE);
        setRndApple();
        score = 0;
        gameover = true;
        level = 1;
        setFPS(level * 10);
    }

    private void showMessages() {
        g2d.setFont(g2d.getFont().deriveFont(Font.BOLD, SIZE));
        g2d.setColor(Color.WHITE);
        if (gameover)
            g2d.drawString("PRESS ENTER!", MESSAGE_X, MESSAGE_Y);

        FontMetrics fm = g2d.getFontMetrics();
        g2d.drawString("SCORE: " + score, SIZE / 2, SIZE);
        String levelStr = level + " :LEVEL";
        g2d.drawString(levelStr, WIDTH - SIZE - fm.stringWidth(levelStr), SIZE);

    }

    public void start() {
        running = false;
        if (thread != null)
            while (thread.isAlive())
                ;
        thread = new Thread(this);
        thread.start();
    }

    private void update() {
        if (gameover)
            return;
        snake.move();
        if (snake.isCollision()) {
            text[TXT_GAMEOVER].start(10);
            gameover = true;
            return;
        }
        if (snake.isCollision(apple)) {
            score++;
            setRndApple();
            snake.grow();
            updateLevel();
        }
        snake.adaptBounds(WIDTH - SIZE, HEIGHT - SIZE);
    }

    private void updateLevel() {
        if (score % MAX_LEVEL == 0) {
            level = (++level) % MAX_LEVEL;
            setFPS(level * MAX_LEVEL);
        }
    }

}
Java:
package game;

import java.awt.Graphics2D;
import java.awt.Rectangle;

public class Entity {
    private int x;
    private int y;
    public final int size;
    private Rectangle bound = null;

    public Entity(int size) {
        this.size = size;
        bound = new Rectangle(0, 0, size, size);
    }

    public Rectangle getBound() {
        bound.x = x;
        bound.y = y;
        return bound;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public boolean isCollsion(Entity o) {
        if (o == this)
            return false;
        return getBound().intersects(o.getBound());
    }

    public void move(int dx, int dy) {
        x += dx;
        y += dy;
    }

    public void render(Graphics2D g2d) {
        g2d.fillOval(x+1, y+1, size-2, size-2);
        //g2d.fillRect(x + 1, y + 1, size - 2, size - 2);
    }

    public void setPosition(int x, int y) {
        setX(x);
        setY(y);
    }

    public void setX(int x) {
        this.x = (x / size) * size;
    }

    public void setY(int y) {
        this.y = (y / size) * size;
    }

}
Java:
package game;

import java.awt.Color;
import java.awt.Graphics2D;
import java.util.ArrayList;
import java.util.Iterator;

public class Snake {
    private ArrayList<Entity> snake = new ArrayList<Entity>();
    private int dx = 0;
    private int dy = 0;

    public enum DIRECTION {
        UP, DOWN, LEFT, RIGHT
    }

    public Snake(int size, int x, int y) {
        Entity head = new Entity(size);
        snake.add(head);
        head.setPosition(x, y);
        for (int i = 1; i < 3; i++) {
            Entity e = new Entity(size);
            e.setPosition(head.getX() + (i * size), head.getY());
            snake.add(e);
        }
    }

    public void adaptBounds(int width, int height) {
        Entity head = getHead();
        if (head.getX() < 0)
            head.setX(width);
        if (head.getY() < 0)
            head.setY(height);
        if (head.getX() > width)
            head.setX(0);
        if (head.getY() > height)
            head.setY(0);
    }

    private Entity getHead() {
        return snake.get(0);
    }

    public void grow() {
        Entity e = new Entity(getHead().size);
        e.setPosition(-100, -100);
        snake.add(e);
    }

    public boolean isCollision() {
        Entity head = getHead();
        for (Entity e : snake) {
            if (e.isCollsion(head))
                return true;
        }
        return false;
    }

    public boolean isCollision(Entity apple) {
        return getHead().isCollsion(apple);
    }

    public boolean isMoving() {
        return !(dx == 0 && dy == 0);
    }

    public void move() {
        if (dx == 0 && dy == 0)
            return;
        Entity head = getHead();
        for (int i = snake.size() - 1; i > 0; i--)
            snake.get(i).setPosition(snake.get(i - 1).getX(), snake.get(i - 1).getY());
        head.move(dx, dy);
    }

    public void render(Graphics2D g) {
        Iterator<Entity> it = snake.iterator();
        Entity head = it.next();
        g.setColor(Color.GREEN);
        while (it.hasNext())
            it.next().render(g);
        g.setColor(Color.BLUE);
        head.render(g);
    }

    public void setDirection(DIRECTION dir) {
        int speed = getHead().size;
        if (dir == DIRECTION.UP && dy == 0) {
            dy = -speed;
            dx = 0;
        }
        if (dir == DIRECTION.DOWN && dy == 0) {
            dy = speed;
            dx = 0;
        }
        if (dir == DIRECTION.LEFT && dx == 0) {
            dy = 0;
            dx = -speed;
        }
        if (dir == DIRECTION.RIGHT && dx == 0 && dy != 0) {
            dy = 0;
            dx = speed;
        }
    }
}
Java:
package game;

import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Timer;

public abstract class GameEffect implements ActionListener {
    private Timer animator = null;
    protected int id = 0;
    private boolean over = true;
    private int limit = 0;
    protected int x;
    protected int y;

    public GameEffect(int x, int y, int limit) {
        this.x = x;
        this.y = y;
        this.limit = limit;
    }

    /**
     * Counts up member variable id. If a limit is reached, timer will be stopped
     * and over will be set to true.
     */
    @Override
    public void actionPerformed(ActionEvent e) {
        if (id < limit)
            id++;
        else {
            animator.stop();
            over = true;
        }
    }

    /**
     * Do graphical stuff here.
     *
     * @param g
     */
    public abstract void draw(Graphics g);

    public boolean isOver() {
        return over;
    }

    public void start(int delay) {
        id = 0;
        if (animator == null)
            animator = new Timer(delay, this);
        else {
            animator.stop();
            animator.setDelay(delay);
        }
        over = false;
        animator.start();
    }

}
Java:
package game;

import java.awt.Graphics;
import java.awt.event.ActionEvent;

public class FallingText extends GameEffect {
    protected String text = null;
    protected int range = 20;
    protected double angle = 0;
    protected boolean down = true;

    public FallingText(String text, int x, int y, int limit, int range, boolean down) {
        super(x, y, limit);
        this.text = text;
        this.range = range;
        this.down = down;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        super.actionPerformed(e);
        if (!isOver())
            angle += .05;
    }

    @Override
    public void draw(Graphics g) {
        g.drawString(text, x + (int) (Math.cos(angle) * range), y + (down ? id : -id));
    }

}
Java:
package game;

import java.awt.FontMetrics;
import java.awt.Graphics;

public class SnakeText extends FallingText {

    public SnakeText(String text, int x, int y, int limit, int range) {
        super(text, x, y, limit, range, false);
    }

    @Override
    public void draw(Graphics g) {
        char[] c = text.toCharArray();
        FontMetrics fm = g.getFontMetrics();
        int offX = id;
        for (int i = 0; i < c.length; i++) {
            int offY = (int) (Math.cos(angle) * range) * (i % 2 == 0 ? 1 : -1);
            g.drawString(c[i] + "", x + offX, y + offY);
            offX += fm.stringWidth(" " + c[i]);
        }
    }
}

.
 
Blender3D

Blender3D

Top Contributor
Weil ich mit Spielen nichts am Hut habe: was ist denn aktives Rendern? Ist damit das Zeichnen auf dem Graphics-Kontext außerhalb von repaint() gemeint?
Ja. Das repaint() löst immer eine ganze Kette von Aktionen aus.
In Java besteht die Möglickeit mittels
Java:
private final GraphicsConfiguration gfxConf = GraphicsEnvironment.getLocalGraphicsEnvironment()
            .getDefaultScreenDevice().getDefaultConfiguration(); // used to create image for drawing
Die Grafikumgebung zu ermitteln.
daraus lässt sich ein Image kreieren
Java:
imageBuffer = gfxConf.createCompatibleImage(SCREEN_WIDTH, SCREEN_HEIGHT);

Die Zeichenmethode könnte man dann in etwa so formulieren

Java:
private void paintScreen() {
        Graphics g;
        try {
            g = this.getGraphics();
            if ((g != null) && (imageBuffer != null))
                g.drawImage(imageBuffer, 0, 0, null);
            // Sync the display on some systems.
            // (on Linux, this fixes event queue problems)
            Toolkit.getDefaultToolkit().sync();
            g.dispose();
        } catch (Exception e) { // quite commonly seen at applet destruction
            System.err.println("Graphics error: " + e);
        }
    }

Ein Buchtipp dazu: https://www.amazon.de/Killer-Game-P...ame+programming+in+java&qid=1579263811&sr=8-2
 
Blender3D

Blender3D

Top Contributor
12906


Ein kleiner Breakout Klone den habe ich mit aktivem Rendering gemacht.

 
Ähnliche Java Themen
  Titel Forum Antworten Datum
J SNAKE-GAME-LOOP / (Applet zu Application) Spiele- und Multimedia-Programmierung 4
B "Snake"-Game Logikfehler Spiele- und Multimedia-Programmierung 1
B Snake-Game fürs Handy Spiele- und Multimedia-Programmierung 3
P Snake Java Hilfe Spiele- und Multimedia-Programmierung 4
F Koordinieren und Essen von Snake - Spiel Spiele- und Multimedia-Programmierung 14
B noch ein snake klon Spiele- und Multimedia-Programmierung 2
Viktim Snake - Schlange wächst nicht Spiele- und Multimedia-Programmierung 3
M Hilfe bei Snake Spiele- und Multimedia-Programmierung 4
J LWJGL Update Schleife (Snake) Spiele- und Multimedia-Programmierung 6
N Schulprogramm "Snake" Spiele- und Multimedia-Programmierung 4
R Hilfe bei Snake-Programmierung Spiele- und Multimedia-Programmierung 6
C Hinterteil von Snake programmieren Spiele- und Multimedia-Programmierung 11
S Snake versuch Spiele- und Multimedia-Programmierung 14
J Snake Programmierung Spiele- und Multimedia-Programmierung 18
V Snake Spiel Spiele- und Multimedia-Programmierung 7
D Snake n+1-Thread Spiele- und Multimedia-Programmierung 2
N Hilfe bei snake Spiele- und Multimedia-Programmierung 37
N Snake (Programmier Stil) Spiele- und Multimedia-Programmierung 8
G SNAKE HILFEEE! Spiele- und Multimedia-Programmierung 15
G Snake Alphaversion Spiele- und Multimedia-Programmierung 18
L Spider game, Ist es verloren? Spiele- und Multimedia-Programmierung 4
E Organisation für Game Spiele- und Multimedia-Programmierung 1
G [Game-Multiplayer] Welt vom Server zum Client senden. Spiele- und Multimedia-Programmierung 0
C 3d Game Engine : PERFORMANTE Räumliche Verdeckung Spiele- und Multimedia-Programmierung 5
R Durch String Platform Game erstellen Spiele- und Multimedia-Programmierung 8
G Component System Java 2D Game LibGDX Spiele- und Multimedia-Programmierung 6
G Collision Detection in einem 2D Sandbox Game. Spiele- und Multimedia-Programmierung 2
J Problem mit Game Of Life Spiele- und Multimedia-Programmierung 3
R Game Loop verhält sich eigenartig Spiele- und Multimedia-Programmierung 1
H Game Crash Spiele- und Multimedia-Programmierung 7
B Game of Life: Was mache ich falsch? Spiele- und Multimedia-Programmierung 3
$ Einstieg in Java Game Development Spiele- und Multimedia-Programmierung 11
BraunBerry Java Game Pixel "einfärben" Spiele- und Multimedia-Programmierung 6
BraunBerry Java Game verbesserte Kollisionserkennung Spiele- und Multimedia-Programmierung 18
BraunBerry Java Game Waypoint System Spiele- und Multimedia-Programmierung 3
T Moddable Game Spiele- und Multimedia-Programmierung 6
P java lwjgl Game Spiele- und Multimedia-Programmierung 0
T Game-Rendering Spiele- und Multimedia-Programmierung 5
I 2D-Side-Scrolling-Game in Chunks splitten Spiele- und Multimedia-Programmierung 9
Z 2D Pixer art RPG Game - Alpha auf Indie DB spielbar Spiele- und Multimedia-Programmierung 0
F Game of Life Spiele- und Multimedia-Programmierung 1
S Game/Cheat Spiele- und Multimedia-Programmierung 20
wolfgang63 Mit JavaFX einfaches Game programmieren Spiele- und Multimedia-Programmierung 5
D Textfield im Game ,Problem: while-Schleife Spiele- und Multimedia-Programmierung 3
O Pause Menu im 2D Game, bitte um Rat! Spiele- und Multimedia-Programmierung 4
J Java Game performance Probleme Spiele- und Multimedia-Programmierung 7
K Online Game? Spiele- und Multimedia-Programmierung 1
R Game Loop scheitert.. Spiele- und Multimedia-Programmierung 2
Androbin Verschwindender Spieler in 2D-Game Spiele- und Multimedia-Programmierung 7
Sogomn Game Loop Spiele- und Multimedia-Programmierung 2
M Jump 'n' Run Game - Blöcke? Spiele- und Multimedia-Programmierung 7
J Verständnissfragen zur Game-Loop Spiele- und Multimedia-Programmierung 2
N Game GUI Programmieren Spiele- und Multimedia-Programmierung 16
Black_ixx ManaWar Action Game Spiele- und Multimedia-Programmierung 5
M Multiplayer-Game auf Website Spiele- und Multimedia-Programmierung 2
M Multiplayer Game mit Frontend auf HTML5 - ohne ständigen Client Request - Möglich?! Spiele- und Multimedia-Programmierung 12
P Mein Android Game Spiele- und Multimedia-Programmierung 3
N Animationen für ein 2D game Spiele- und Multimedia-Programmierung 6
S Problem mit Zeitsteuerung der Game Loop Spiele- und Multimedia-Programmierung 4
Kenan89 Kleines Game Spiele- und Multimedia-Programmierung 26
M Vektor Game Spiele- und Multimedia-Programmierung 13
F Bot updaten von einem Browser game Spiele- und Multimedia-Programmierung 7
Samake03 [Game]"Plumbo - Lost in Depth" Preview und Fragen Spiele- und Multimedia-Programmierung 18
K Game Engine für selbstprogrammiertes Spiel Spiele- und Multimedia-Programmierung 27
A Music für Android game Spiele- und Multimedia-Programmierung 3
A Wie wird die Person im Game animiert, dass sie ihre Körperteile bewegen? Spiele- und Multimedia-Programmierung 3
K 3D Game wie Minecraft Spiele- und Multimedia-Programmierung 3
T Torquemada´s erstes Game (Pong) Spiele- und Multimedia-Programmierung 5
X Möglichst komplette 2D Game Engine? Spiele- und Multimedia-Programmierung 12
Kr0e Red Dwarf Game Server Spiele- und Multimedia-Programmierung 5
M Euer erstes Game Spiele- und Multimedia-Programmierung 16
M Netzwerk-Game mit UDP Spiele- und Multimedia-Programmierung 8
ruerob Eure Meinung über Applet-game Spiele- und Multimedia-Programmierung 12
J das erste Game Spiele- und Multimedia-Programmierung 2
M Einen Hobby Game - / Grafik Designer zu finden (Screenshot vom Spiel) Spiele- und Multimedia-Programmierung 7
S Game Client für kleine Competition Spiele- und Multimedia-Programmierung 3
J 2D-Game-Engine? Spiele- und Multimedia-Programmierung 2
F Game mit LWJGL/JOGL in executable JAR packen, wie? Spiele- und Multimedia-Programmierung 6
P 2D Game - Alternative zur TileMap? Spiele- und Multimedia-Programmierung 2
W Memory Game Spiele- und Multimedia-Programmierung 4
Steev EGE - Easy Game Engine Spiele- und Multimedia-Programmierung 2
Developer_X X-Shooter Game Spiele- und Multimedia-Programmierung 21
C 2D Multiplayer Game Spiele- und Multimedia-Programmierung 5
O Mein erstes Game Spiele- und Multimedia-Programmierung 10
Developer_X PingPong-the Game by Developer-X Spiele- und Multimedia-Programmierung 170
K My First Game "ORB" vielen Dank Quaxli Spiele- und Multimedia-Programmierung 23
hdi Das java-forum.org 2D-Game Projekt/Tutorial Spiele- und Multimedia-Programmierung 6
hdi Quiclix : Game download Spiele- und Multimedia-Programmierung 5
A Java 2D Game Spiele- und Multimedia-Programmierung 4
K Game-Tutorial von Quaxli Fragen Spiele- und Multimedia-Programmierung 18
G Suche passende Game api Spiele- und Multimedia-Programmierung 9
K Java Game Programming Buch Spiele- und Multimedia-Programmierung 3
C Hilfe bei einfachen Game Spiele- und Multimedia-Programmierung 15
L Kleines RTS Game -> Netzwerkproblem Spiele- und Multimedia-Programmierung 5
P Suche Leute für ein Hobby-Projekt (Browser Game) Spiele- und Multimedia-Programmierung 4
Q online game programieren Spiele- und Multimedia-Programmierung 8
G Welche Game Api Spiele- und Multimedia-Programmierung 9
S 3d applet - online game Spiele- und Multimedia-Programmierung 4
C Open-Source Game Spiele- und Multimedia-Programmierung 2
sparrow 3D-Game-Engine Spiele- und Multimedia-Programmierung 20

Ähnliche Java Themen

Anzeige

Neue Themen


Oben