Gra Snake - problem z płynną animacją Snake'a

0

Otóż robię grę Snake. I problem na jaki się natknąłem, to problem z animacją, ponieważ chciałem aby była płynna, bez widocznego "przeskakiwania" węża. Wąż ma się poruszać z zadaną zmienną prędkością (int speed). Nie mam pomysłu co zrobić, aby uzyskać płynną animację, a już się trochę nakombinowałem. Proszę więc o pomoc, może ktoś będzie miał jakiś pomysł co mógłbym poprawić.
Poniżej zamieszczam 2 klasy które przede wszystkim odpowiadają za rysowanie węża:

Player.class

 
package snake;

import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyEvent;
import java.util.ArrayList;

import javax.swing.ImageIcon;

public class Player 
{
	private int headSizeX;
	private int headSizeY;
	private int tailSizeX;
	private int tailSizeY;
	private int x;
	private int y;
	private int speed = 5;
	private int snakeSize = 40;
	private Image[] snake = new Image[5];
	private ArrayList<Integer> X,Y;
	private static int imageNr = 0;
	

	private int tmp;
	private boolean death = false;
	static int player;
	private static boolean inverse = false;
	private boolean left = false;
	private boolean right = false;
	private boolean up = true;
	private boolean down = false;
	public Player()
	{
		initPlayer();
		headSizeX = snake[0].getWidth(null);
		headSizeY = snake[0].getHeight(null)-2;
		tailSizeX = snake[4].getWidth(null);
		tailSizeY = snake[4].getHeight(null);
		System.out.println("tail: "+tailSizeX+" "+tailSizeY);
		x = MainGamePanel.getSizeX()/2;
		y= MainGamePanel.getSizeY()-130;
	}
	
	public void initPlayer()
	{
		int imgNr = 5;
		X = new ArrayList<Integer>();
		Y = new ArrayList<Integer>();	
		String name = "green";
		switch (player) 
		{
		case 1:
			name = "green"; break;
		case 2:
			name = "red"; break;
		case 3:
			name = "blue"; break;
		}
		
		for (int nr = 1; nr <= imgNr; nr++)
		{
			snake[nr-1] = new ImageIcon("img/" +name+"/snake" +nr+".png").getImage();
		}
		
		X.add(MainGamePanel.getSizeX()/2);
		Y.add( MainGamePanel.getSizeY()-200);
		X.add( MainGamePanel.getSizeX()/2);
		Y.add( MainGamePanel.getSizeX()/2+33);
		for (int i = 2; i < snakeSize; i++) {
			X.add( MainGamePanel.getSizeX()/2);
			Y.add( MainGamePanel.getSizeX()/2 + i*20+13);
		}
	}
	
	public void paint(Graphics g)
	{
		//g.drawImage(snake[imageNr], x,y,headSizeX,headSizeY,null);
		
		for (int i = 0; i < snakeSize; i++) {
			if (i == 0)
				g.drawImage(snake[imageNr], X.get(i), Y.get(i), null);
			else
				g.drawImage(snake[4], X.get(i), Y.get(i), null);
		}
		
	}

	public void update()
	{
		y = MainGamePanel.getSizeY()-headSizeY - 35;
		for (int i = snakeSize-1; i > 0; i--) {
			X.set(i, X.get(i-1));
			Y.set(i, Y.get(i-1));
		}
		
		if (left) {
			tmp = X.get(0);
			X.set(0, tmp-20);
			imageNr = 3;
		}
		if (right) {
			tmp = X.get(0);
			X.set(0, tmp+20);
			imageNr = 1;
		}
		if (up) {
			tmp = Y.get(0);
			Y.set(0, tmp-20);
			imageNr = 0;
		}
		if (down) {
			tmp = Y.get(0);
			Y.set(0, tmp+20);
			imageNr = 2;
		}
	}
	
	public void keyPressed(KeyEvent e)
	{
		 int key = e.getKeyCode();

	        if ((key == KeyEvent.VK_LEFT) && (!right)) {
	            left = true;
	            up = false;
	            down = false;
	        }

	        if ((key == KeyEvent.VK_RIGHT) && (!left)) {
	            right = true;
	            up = false;
	            down = false;
	        }

	        if ((key == KeyEvent.VK_UP) && (!down)) {
	            up = true;
	            right = false;
	            left = false;
	        }

	        if ((key == KeyEvent.VK_DOWN) && (!up)) {
	            down = true;
	            right = false;
	            left = false;
	        }
	}
	
	public void collision()
	{
		death = true;
	}
	
	public void reset()
	{
		imageNr = 0;
		death = false;
	}
	
	public void setInverse(boolean set)
	{
		inverse = set;
	}
	
	public void increaseSpeed(int inc)
	{
		speed += inc;
	}
	
	public void decreaseSpeed(int dec)
	{
		speed -=dec;
	}
	
	public int getSnakeSize()
	{
		return snakeSize;
	}
	
	public void increaseSnakeSize()
	{
		snakeSize += 1;
	}
	
	public void decreaseSnakeSize(int dec)
	{
		snakeSize -= dec;
	}
}

MainGamePanel.class

 
package snake;

import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferedImage;

import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;

@SuppressWarnings("serial")
public class MainGamePanel extends JPanel implements KeyListener{
	
	private Image bounds;
	private Player player;
	public static int sizex;
	private Image tail;
	private Image background;
	private int lives;
	private int speed;
	private int levels;
	private int counter = 0;
	private int appleCounter = 0;
	private static int level = 1;
	public static int sizeX = 784;
	public static int sizeY = 617;
	public static int applesNumber;
	private boolean gameover = false;
	private boolean gameend = false;
	private boolean death = false;
	private boolean levelCompleted = false;
	private Graphics buffer;
	private BufferedImage img;
	public MainGamePanel(int[] settings)
	{	
		lives = settings[0];
		speed = settings[1];
		levels = settings[2];
		bounds = new ImageIcon("img/bounds.jpg").getImage();
		setFocusable(true);
		addKeyListener(this);
		//new Apple();
	}
	 
	public void init()
	  {
	    player = new Player();
	    img = new BufferedImage(sizeX, sizeY, 2);
	    
		Levels.getLevel(1);
		
	    this.applesNumber = Levels.getApplesNumber();
	    this.background = new ImageIcon("img/bg/" + Levels.getBackground()).getImage();
	  }

	
	public void addNotify()
	  {
	    super.addNotify(); 
	    init();
	  }
	
	public void paintComponent(Graphics g){
		super.paintComponent(g);
		
		buffer = img.getGraphics();
		buffer.drawImage(background, 0,0, sizeX, sizeY, null);
		player.paint(buffer);
		for (int i = 0; i <= getWidth(); i += 12) {
			buffer.drawImage(bounds, i, 0, this);
			buffer.drawImage(bounds, i, getHeight()-12, this);
		}
		
		for (int i = 12; i < getHeight(); i += 12) {
			buffer.drawImage(bounds, 0, i, this);
			buffer.drawImage(bounds, getWidth()-12, i, this);
		}
		g.drawImage(this.img, 0, 0, getWidth(), getHeight(), null);
	
		buffer.clearRect(12, 12, sizeX, sizeY);
		
	}
	
	public void play()
	{
		if (level == this.levels) gameend = true;
		this.player.reset();
		this.counter = 0;
		this.appleCounter = 0;
		long StartTime = System.currentTimeMillis();
		player.update();
		repaint();
		/*do
		{
			Thread.yield();
		}
		while (System.currentTimeMillis() - StartTime < this.speed);*/
	}
	
	public void gameLoop()
	{
		while(true)
		{
			play();
			try
			{
				Thread.sleep(150);
			}
			catch (InterruptedException ex)
			{
				
			}
			if (death)
			{
				lives -= 1;
				death = false; continue;
			}
			levelCompleted = false;
			level+=1;
			
			Levels.getLevel(level);
			
			applesNumber = Levels.getApplesNumber();
			
			
		}
	}
	public static int getApp()
	{
		return applesNumber;
	}
	
	public static int getSizeX()
	{
		return sizeX;
	}
	
	public static int getSizeY()
	{
		return sizeY;
	}
	
	public void setGameOver()
	{
		gameover = true;
	}
	
	@Override
	public void keyPressed(KeyEvent e) 
	{
		if (e.getKeyCode() == KeyEvent.VK_ESCAPE) System.exit(0);
		player.keyPressed(e);	
	}

	@Override
	public void keyReleased(KeyEvent e) {}

	@Override
	public void keyTyped(KeyEvent e) {}

}
0

moze podwójne buforowanie?

0

Jest domyślnie w Swingu? Chyba że wykorzystuję je jakoś nie tak...

0

w klasie Player paint rysuje od razu na zmiennej g. Sprobuj najpierw narysowac w jakiejs innej zmiennej wszystko a później to z tego obiektu? Takie moje przypuszczenie, warto byłoby sprawdzić ;) Chociaż to tak na szybko odpowiedź :)

0

Już to zrobiłem korzystając z timera z klasy javax.swing.timer i dzięki niemu ustawiłem FPS = 60 i działa ładnie gładko :)
Ale pojawił się inny problem. Ponieważ prędkość węża stała się zależna od wartości FPS. Próbowałem różnie uniezależnić te dwie wartości, ale albo nie działało, albo działało zupełnie nie tak jak chciałem.

Dokładnie to chodzi mi o ten kawałek z klasy Player (metodę update(), w której odbywa się aktualizowanie i przesuwanie węża):

 
	public void update()
	{
		
		/*for (int i = snakeSize-1; i > 0; i--) {
			X.set(i, X.get(i-1));
			Y.set(i, Y.get(i-1));
		}*/
		if (left) {
			for (int i = snakeSize-1; i > 0; i--) {
				X.set(i, X.get(i-1));
				X.set(i,X.get(i)+(20-speed));
				Y.set(i, Y.get(i-1));
			}
			tmp = X.get(0);
			X.set(0, tmp-speed);
			imageNr = 3;
			
			
		}
		if (right) {
			for (int i = snakeSize-1; i > 0; i--) {
				X.set(i, X.get(i-1)-(20-speed));
				Y.set(i, Y.get(i-1));
			}
			tmp = X.get(0);
			X.set(0, tmp+speed);
			imageNr = 1;
		
		}
		if (up) {
			for (int i = snakeSize-1; i > 0; i--) {
				X.set(i, X.get(i-1));
				Y.set(i, Y.get(i-1)+(20-speed));
			}
			tmp = Y.get(0);
			Y.set(0, tmp-speed);
			imageNr = 0;
			
		}
		if (down) {
			for (int i = snakeSize-1; i > 0; i--) {
				X.set(i, X.get(i-1));
				Y.set(i, Y.get(i-1)-(20-speed));
			}
			tmp = Y.get(0);
			Y.set(0, tmp+speed);
			imageNr = 2;
			
		}
	}

Zmienna speed określać ma prędkość węża. Jeśli speed jest równy szerokości węża (20) to wszystko jest ok - każda część węża jest poprawnie przesuwana na miejsce poprzedniej (przeglądając wektor położeń X od tyłu). W wypadku gdy w tym kodzie zmienię speed na inny (mniejszy) to części węża nie są przesuwane w miejsce poprzedniego (ruch "tułowia" następuje względem głowy), a są dodawane na końcu poprzedniej części.
Nie wiem czy wytłumaczyłem to poprawnie, ale mam nadzieję, że ktoś to zrozumie i postara mi się pomóc jakoś :)

0

Nie wczytywałem się w twój kod, ale możesz np uzależnić dystans o który wąż się przemieszcza od czasu rysowania ostatniej klatki.

1 użytkowników online, w tym zalogowanych: 0, gości: 1