Single Tech Games

3. The WormPanel Class

WormPanel es como el GamePanel que hicimos anteriormente, primero vamos a declarar las variables globales

public class WormPanel extends JPanel implements Runnable {
  private static final int PWIDTH = 500;   // tamaño del panel
  private static final int PHEIGHT = 400;
  private static long MAX_STATS_INTERVAL = 1000L;
    // guardar estadisticas cada 1 segundo
  private static final int NO_DELAYS_PER_YIELD = 16;
  /* Numero de frames con un retraso de 0ms antes d que el hilo de animacion
   * alacnce a otros hilos en ejecucion */
  private static int MAX_FRAME_SKIPS = 5;   // era 2;
    // Numero de frames que pueden ser salteados en un bucle de animacion
    // El estado del juego se actualiza pero no se renderiza
  private static int NUM_FPS = 10;
     // numero de FPS guardados en promedio
  // variables para las estadisticas
  private long statsInterval = 0L;    // en ms
  private long prevStatsTime;
  private long totalElapsedTime = 0L;
  private long gameStartTime;
  private int timeSpentInGame = 0;    // en segundos
  private long frameCount = 0;
  private double fpsStore[];
  private long statsCount = 0;
  private double averageFPS = 0.0;
  private long framesSkipped = 0L;
  private long totalFramesSkipped = 0L;
  private double upsStore[];
  private double averageUPS = 0.0;
  private DecimalFormat df = new DecimalFormat("0.##");  // 2 decimales
  private DecimalFormat timedf = new DecimalFormat("0.####");  // 4 decimales
  private Thread animator;           // hilo q se encarga de la animacion
  private volatile boolean running = false;   // usado para parar el hilo de la animacion
  private volatile boolean isPaused = false;
  private int period;                // periodo entre dibujos, en ms
  private WormChase wcTop;
  private Worm fred;       // El gusano
  private Obstacles obs;   // Los obstaculos
  // usado en el fin del juego
  private volatile boolean gameOver = false;
  private int score = 0;
  private Font font;
  private FontMetrics metrics;
  // renderizado fuera de la pantalla
  private Graphics dbg;
  private Image dbImage = null;

Si leyeron lo de Space Invaders se darán cuenta que no es muy diferente, muchas de las variables que aparecen acá son para elaborar las estadísticas
Ahora el constructor:

 public WormPanel(WormChase wc, int period)//recibe el objeto WormChase y un periodo
  {
    wcTop = wc;
    this.period = period;
    setBackground(Color.white);
    setPreferredSize( new Dimension(PWIDTH, PHEIGHT));
    setFocusable(true);
    requestFocus();    // Jpanel es enfocado, por lo tanto esta atento al teclado
	readyForTermination();
    // crea los componentes de juego
    obs = new Obstacles(wcTop);
    fred = new Worm(PWIDTH, PHEIGHT, obs);
    addMouseListener( new MouseAdapter() {
      public void mousePressed(MouseEvent e)
      { testPress(e.getX(), e.getY()); }
    });
    // setea la fuente para el mensaje
    font = new Font("SansSerif", Font.BOLD, 24);
    metrics = this.getFontMetrics(font);
    // inicializa los elementos para controlar el tiempo
    fpsStore = new double[NUM_FPS];
    upsStore = new double[NUM_FPS];
    for (int i=0; i < NUM_FPS; i++) {
      fpsStore[i] = 0.0;
      upsStore[i] = 0.0;
    }
  }  // Fin de WormPanel()

Hasta acá solo hemos creado los elementos del juego, el mensaje y su fuente se muestra cuando el juego termina, fpsStore[] y upsStore[] contiene los últimos 10 FPS y UPS calculados en las estadísticas
3.1. User Input
Bueno ahora viene la parte del usuario, la función que se invoca cada vez que haces un clic con el mouse es testpress.

private void testPress(int x, int y)
// (x,y) estan cerca de la cabeza o deberiamos agregar un obstaculo?
    {
        if (!isPaused && !gameOver) {
            if (fred.nearHead(x,y)) {   // Hicieron click cerca de la cabeza?
                gameOver = true;
                score =  (40 - timeSpentInGame) + (40 - obs.getNumObstacles());
                // Aumenta el score
            }
            else {   // adhiere un obstaculo, si se peude
                if (!fred.touchedAt(x,y))   // no se toco el cuerpo del gusano?
                    obs.add(x,y);
            }
        }
    }// fin de testPress()

Las variables isPaused y gameOver son manipuladas por los window listener de la aplicación y estas a su vez  utilizan estas funciones:

   // ------------- metodos para el ciclo de vida del juego ------------
  // llamados por los window listener methods
   public void resumeGame()
  // llamado cuando el JFrame esta activado
  {  isPaused = false;  }
    public void pauseGame()
  // llamado cuando el JFrame esta desactivado
  { isPaused = true;   }
  public void stopGame()
  // llamado cuando el JFrame esta cerrando
  {  running = false;   }
// ----------------------------------------------



3.2. The Animation Loop
Quizas ahora viene la función principal de un juego, el ciclo de animación

    public void run ()
    {
        /*Los frames de la animacion son dibujados dentro de while(running){}*/
        long beforeTime, afterTime, timeDiff, sleepTime;
        int overSleepTime = 0;
        int noDelays = 0;
        int excess = 0;
        Graphics g;
        gameStartTime = System.currentTimeMillis();
        prevStatsTime = gameStartTime;
        beforeTime = gameStartTime;
        running = true;
        while(running) {
            gameUpdate();
            gameRender();   // renderiza el juego en un buffer
            paintScreen();  // Dibuja el buffer en pantalla
            afterTime = System.currentTimeMillis();
            timeDiff = afterTime - beforeTime;
            sleepTime = (period - timeDiff) - overSleepTime;
            if (sleepTime > 0) {   // sobro un poco de tiempo
                try {
                    Thread.sleep(sleepTime);  // En ms
                }
                catch(InterruptedException ex){}
                    overSleepTime = (int)((System.currentTimeMillis() - afterTime) - sleepTime);
            }
            else {    // sleepTime <= 0; El frame tomo mas de lo definicio en period
                excess -= sleepTime;  // graba el exceso
                overSleepTime = 0;
                if (++noDelays >= NO_DELAYS_PER_YIELD) {
                    Thread.yield();   // le da chance a otros hilos a funcionar
                    noDelays = 0;
                }
            }
            beforeTime = System.currentTimeMillis();
            /* Si los frames de animacion estan tomando demasiado tiempo
             * actualiza el juego sin renderizar hasta conseguir
             * actualizaciones/seg cerca los FPS requeridos */
            int skips = 0;
            while((excess > period) && (skips < MAX_FRAME_SKIPS)) {
                excess -= period;
                gameUpdate();    // actualiza sin renderizar
                skips++;
            }
            framesSkipped += skips;
            storeStats();
        }
        printStats();
        System.exit(0);   //cerramos
    }//Fin de run()



gameStartTime and prevStatsTime son utilizados en para el calculo de las estadísticas, la verdad es que la función de las estadísticas como bien se habrán dado cuenta es opcional, por ahora lo voy a dejar, pero si un juego no da problemas en el SO que lo necesitan no deberían utilizarlo.
3.3. Statistics Gathering
Bueno no voy a explicar como se calculan las estadísticas, dejare el código pero si quieren entenderlo chequen en el libro

private void storeStats()
  {
    frameCount++;
    statsInterval += period;
    if (statsInterval >= MAX_STATS_INTERVAL) {
      long timeNow = System.currentTimeMillis();
      timeSpentInGame = (int) ((timeNow - gameStartTime)/1000L);  // ms --> secs
      wcTop.setTimeSpent( timeSpentInGame );
      long realElapsedTime = timeNow - prevStatsTime;
      totalElapsedTime += realElapsedTime;
      double timingError =
         ((double)(realElapsedTime - statsInterval) / statsInterval) * 100.0;
      totalFramesSkipped += framesSkipped;
      double actualFPS = 0;
      double actualUPS = 0;
      if (totalElapsedTime > 0) {
        actualFPS = (((double)frameCount / totalElapsedTime) * 1000L);
        actualUPS = (((double)(frameCount + totalFramesSkipped) / totalElapsedTime)
                                                             * 1000L);
      }
      fpsStore[ (int)statsCount%NUM_FPS ] = actualFPS;
      upsStore[ (int)statsCount%NUM_FPS ] = actualUPS;
      statsCount = statsCount+1;
      double totalFPS = 0.0;
      double totalUPS = 0.0;
      for (int i=0; i < NUM_FPS; i++) {
        totalFPS += fpsStore[i];
        totalUPS += upsStore[i];
      }
      if (statsCount < NUM_FPS) {
        averageFPS = totalFPS/statsCount;
        averageUPS = totalUPS/statsCount;
      }
      else {
        averageFPS = totalFPS/NUM_FPS;
        averageUPS = totalUPS/NUM_FPS;
      }
      framesSkipped = 0;
      prevStatsTime = timeNow;
      statsInterval = 0L;
    }
  }
  private void printStats()
  {
    System.out.println("Frame Count/Loss: " + frameCount + " / " + totalFramesSkipped);
	System.out.println("Average FPS: " + df.format(averageFPS));
	System.out.println("Average UPS: " + df.format(averageUPS));
        System.out.println("Time Spent: " + timeSpentInGame + " secs");
        System.out.println("Boxes used: " + obs.getNumObstacles());
  }



3.4. Game-Specific Behaviour
El comportamiento del juego se origina en 2 funciones:

while(running) {
gameUpdate();
gameRender();   // renderiza el juego en un buffer



Recuerden que inicializamos un objeto gusano como Fred, y después lo actualizamos en la función

private Worm fred;
-
-
-
private void gameUpdate()
{ if (!isPaused && !gameOver)
fred.move();
}  // fin de gameUpdate()



gameRender() dibuja el gusano y los obstáculos en un buffer

private void gameRender()
{
if (dbImage == null){
dbImage = createImage(PWIDTH, PHEIGHT);
if (dbImage == null) {
System.out.println("dbImage is null");
return;
}
else
dbg = dbImage.getGraphics();
}
// limpia el background
dbg.setColor(Color.white);
dbg.fillRect (0, 0, PWIDTH, PHEIGHT);
dbg.setColor(Color.blue);
dbg.setFont(font);
//En la esquina izquierda se hara un conteo y promedio de FPS y UPS
dbg.drawString("Average FPS/UPS: " + df.format(averageFPS) + ", " +
df.format(averageUPS), 20, 25);  // was (10,55)
dbg.setColor(Color.black);
// dibuja los elementos del juego
obs.draw(dbg);
fred.draw(dbg);
if (gameOver)
gameOverMessage(dbg);
}  // fin de gameRender()



bueno creo que esta bastante entendible, las funciones que dibujan son : obs.draw(dbg) y fred.draw(dbg), con esto aseguramos que el dibujado se haga en el componente del juego liberando de esa tarea a esta clase
gameOverMessage() se ubicara en el centro de la pantalla

private void gameOverMessage(Graphics g)
  // Centra el mensaje de game-over en el panel
  {
    String msg = "Game Over. Your Score: " + score;
	int x = (PWIDTH - metrics.stringWidth(msg))/2;
	int y = (PHEIGHT - metrics.getHeight())/2;
	g.setColor(Color.red);
    g.setFont(font);
	g.drawString(msg, x, y);
  }  // fin de gameOverMessage()



quedaría ahora la función paintScreen(), que es igual al del capitulo anterior

private void paintScreen()
// constantemente renderiza la imagen del buffer en la pantalla
{
    Graphics g;
    try {
        g = this.getGraphics(); // trae el contexto grafico del panel
        if ((g != null) && (dbImage != null))
            g.drawImage(dbImage, 0, 0, null);
            g.dispose();
        }
        catch (Exception e)
        {   System.out.println("Error del contexto grafico: " + e); }
} // fin de paintScreen()



El resto de funciones son iguales al de game panel del capitulo anterior:

  private void readyForTermination()
{
    addKeyListener( new KeyAdapter() {
        // Escucha a esc, q, fin, ctrl + c
    public void keyPressed(KeyEvent e)
    {
        int keyCode = e.getKeyCode();
        if ((keyCode == KeyEvent.VK_ESCAPE) ||
        (keyCode == KeyEvent.VK_Q) ||
        (keyCode == KeyEvent.VK_END) ||
        ((keyCode == KeyEvent.VK_C) && e.isControlDown()) ) {
        running = false;
    }}});
} // fin de readyForTermination()
public void addNotify () {
        super.addNotify(); // Crea el lugar
        startGame(); // Inicia el hilo
}
private void startGame()
// Inicializa y comienza el hilo
{
    if (animator == null || !running) {
    animator = new Thread(this);
    animator.start();
}
} // fin de startGame()
0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments