2013-05-03 135 views
1

我一直在通用的2D平铺风格的游戏。目前在我的主线程中,我有一个循环,其速度尽可能快,并调用另一个处理游戏内容的类中的JPanel重绘方法。它看起来像这样:Java渲染循环和逻辑循环

public class Main { 
    public static void main(String[] args) { 
     Island test = new Island(); 
     while(true) { 
      test.getCanvas().requestFocus(); 
      test.getCanvas().repaint(); 
     } 
    } 
} 

getCanvas()只是返回JPanel。

目前这已经做了我想要的,现在我已经开始为玩家添加动作了,显然我不想尽可能快地将他移动到屏幕上。所以我在我的Island课堂中有一个输入地图和动作地图,它检测按键和释放,并告诉我的玩家分类哪些按键被保持。然后我用一个摆动计时器在玩家班级内部移动我的玩家,每10ms调用一次。所以我猜这就像我的游戏一样,我的游戏会尽可能快地创建帧,然后游戏每秒完成100次所有的逻辑事件,当然我会增加更多的游戏逻辑,而不仅仅是移动。

在做了一些研究之后,它会出现摆动计时器并不是这样做的最好方式,因为它是专为做小任务和摆动任务而设计的。所以我想我的问题是,以我的主要方法制作帧是否明智,以及如何让我的游戏每10ms可靠地进行打勾或什么是一种好方法?我有一些想法,比如也许我应该创建一个处理游戏逻辑的新线程,并使用System.getnanotime或任何其称为的方法来测量执行勾选所需的时间,然后执行一次thread.sleep直到我们达到10ms然后重复。

我很高兴发布更多的代码,如果你想:),并提前感谢。

回答

3

这样做的标准方法是在一个线程中。下面有一个标准的游戏准系统游戏线程

public class GameThread implements Runnable { 
    private Thread runThread; 
    private boolean running = false; 
    private boolean paused = false; 
    public GameThread() { 
    } 

    public void start() { 
     running = true; 
     paused = false; 
     if(runThread == null || !runThread.isAlive()) 
      runThread = new Thread(this); 
     else if(runThread.isAlive()) 
      throw new IllegalStateException("Thread already started."); 
     runThread.start(); 
    } 

    public void stop() { 
     if(runThread == null) 
      throw new IllegalStateException("Thread not started."); 
     synchronized (runThread) { 
      try { 
       running = false; 
       runThread.notify(); 
       runThread.join(); 
      } catch (InterruptedException e) { 
       e.printStackTrace(); 
      } 
     } 
    } 

    public void pause() { 
     if(runThread == null) 
      throw new IllegalStateException("Thread not started."); 
     synchronized (runThread) { 
      paused = true; 
     } 
    } 

    public void resume() { 
     if(runThread == null) 
      throw new IllegalStateException("Thread not started."); 
     synchronized (runThread) { 
      paused = false; 
      runThread.notify(); 
     } 
    } 

    public void run() { 
     long sleep = 0, before; 
     while(running) { 
      // get the time before we do our game logic 
      before = System.currentTimeMillis(); 
      // move player and do all game logic 
      try { 
       // sleep for 100 - how long it took us to do our game logic 
       sleep = 100-(System.currentTimeMillis()-before); 
       Thread.sleep(sleep > 0 ? sleep : 0); 
      } catch (InterruptedException ex) { 
      } 
      synchronized (runThread) { 
       if(paused) { 
        try { 
         runThread.wait(); 
        } catch (InterruptedException e) { 
         e.printStackTrace(); 
        } 
       } 
      } 
     } 
     paused = false; 
    } 
} 

注意你需要调用gameThead.start()来开始你的游戏!

+0

这看起来就像我以后一样!我刚刚完成实施它,但它并不完全按照预期工作。我认为它的运行速度非常快,所以我只是放大地图的缩放比例,我所拥有的是基本的,所以我没有检测到地图的边缘。我会在更多的调整后不久作出回应,但现在感谢你! :) – Iain 2013-05-03 18:08:17

+0

只是一个简单的问题,不应该是runThread.sleep()或者现在它是如何工作的? – Iain 2013-05-03 18:15:04

+0

Thread.sleep是一个静态方法,可以处理任何执行该代码行的线程。 – 2013-05-03 18:19:00

1

创建一个好的(窗口应用程序)gameloop不用吃CPU 100%实际上是一项非常棘手的任务。 Sidescroll以恒定速度对精灵进行动画处理,如果有人看到,则容易引入急动。

运行几个想法后,这是最好的发现,sidescrolling大多数时候黄油顺利。 VSYNCing是可以在窗口模式下工作的好东西,我在不同的机器和操作系统上发现了不同的结果。

测试应用程序不使用SwingUI,因为大多数游戏不需要它。 Gameloop是一个活动的更新 - 渲染循环,无需外部线程,使编程更容易。使用keyPressed回调更新firePressed = true等标志变量并在循环中使用值。

运行测试程序 C:> java命令./classes GameLoop2 “全屏=假” “FPS = 60”, “VSYNC =真正的”

//http://www.javagaming.org/index.php/topic,19971.0.html 
//http://fivedots.coe.psu.ac.th/~ad/jg/ch1/ch1.pdf 

import java.util.*; 

import java.awt.Color; 
import java.awt.Frame; 
import java.awt.Graphics; 
import java.awt.GraphicsConfiguration; 
import java.awt.GraphicsDevice; 
import java.awt.GraphicsEnvironment; 
import java.awt.Toolkit; 
import java.awt.event.KeyEvent; 
import java.awt.event.KeyListener; 
import java.awt.geom.Rectangle2D; 
import java.awt.image.BufferStrategy; 

import java.awt.DisplayMode; // for full-screen mode 

public class GameLoop2 implements KeyListener { 
    Frame mainFrame; 

    private static final long NANO_IN_MILLI = 1000000L; 

    // num of iterations with a sleep delay of 0ms before 
    // game loop yields to other threads. 
    private static final int NO_DELAYS_PER_YIELD = 16; 

    // max num of renderings that can be skipped in one game loop, 
    // game's internal state is updated but not rendered on screen. 
    private static int MAX_RENDER_SKIPS = 5; 

    private static int TARGET_FPS = 60; 

    //private long prevStatsTime; 
    private long gameStartTime; 
    private long curRenderTime; 
    private long rendersSkipped = 0L; 
    private long period; // period between rendering in nanosecs 

    long fps; 
    long frameCounter; 
    long lastFpsTime; 

    Rectangle2D rect, rect2, rect3; 

    /** 
    * Create a new GameLoop that will use the specified GraphicsDevice. 
    * 
    * @param device 
    */ 
    public GameLoop2(Map<String,String> args, GraphicsDevice device) { 
     try { 
      if (args.containsKey("fps")) 
       TARGET_FPS = Integer.parseInt(args.get("fps")); 


      // Setup the frame 
      GraphicsConfiguration gc = device.getDefaultConfiguration(); 

      mainFrame = new Frame(gc); 
      //mainFrame.setUndecorated(true); 
      mainFrame.setIgnoreRepaint(true); 
      mainFrame.setVisible(true); 
      mainFrame.setSize(640, 480); 
      //mainFrame.setLocationRelativeTo(); 
      mainFrame.setLocation(700,100); 
      mainFrame.createBufferStrategy(2); 
      mainFrame.addKeyListener(this); 

      if ("true".equalsIgnoreCase(args.get("fullscreen"))) { 
       device.setFullScreenWindow(mainFrame); 
       device.setDisplayMode(new DisplayMode(640, 480, 8, DisplayMode.REFRESH_RATE_UNKNOWN)); 
      } 

      final boolean VSYNC = "true".equalsIgnoreCase(args.get("vsync")); 

      // Cache the buffer strategy and create a rectangle to move 
      BufferStrategy bufferStrategy = mainFrame.getBufferStrategy(); 

      rect = new Rectangle2D.Float(0,100, 64,64); 
      rect2 = new Rectangle2D.Float(0,200, 32,32); 
      rect3 = new Rectangle2D.Float(500,300, 128,128); 

      // loop initialization 
      long beforeTime, afterTime, timeDiff, sleepTime; 
      long overSleepTime = 0L; 
      int noDelays = 0; 
      long excess = 0L; 
      gameStartTime = System.nanoTime(); 
      //prevStatsTime = gameStartTime; 
      beforeTime = gameStartTime; 

      period = (1000L*NANO_IN_MILLI)/TARGET_FPS; // rendering FPS (nanosecs/targetFPS) 
      System.out.println("FPS: " + TARGET_FPS + ", vsync=" + VSYNC); 
      System.out.println("FPS period: " + period); 


      // Main loop 
      while(true) { 
       // **2) execute physics 
       updateWorld(0);     

       // **1) execute drawing 
       Graphics g = bufferStrategy.getDrawGraphics(); 
       drawScreen(g); 
       g.dispose(); 

       // Synchronise with the display hardware. Note that on 
       // Windows Vista this method may cause your screen to flash. 
       // If that bothers you, just comment it out. 
       if (VSYNC) Toolkit.getDefaultToolkit().sync(); 

       // Flip the buffer 
       if(!bufferStrategy.contentsLost()) 
        bufferStrategy.show(); 

       afterTime = System.nanoTime(); 
       curRenderTime = afterTime; 
       calculateFramesPerSecond(); 

       timeDiff = afterTime - beforeTime; 
       sleepTime = (period-timeDiff) - overSleepTime; 
       if (sleepTime > 0) { // time left in cycle 
        //System.out.println("sleepTime: " + (sleepTime/NANO_IN_MILLI)); 
        try { 
        Thread.sleep(sleepTime/NANO_IN_MILLI);//nano->ms 
        } catch(InterruptedException ex){} 
        overSleepTime = (System.nanoTime()-afterTime) - sleepTime; 
       } else { // sleepTime <= 0; 
        System.out.println("Rendering too slow"); 
        // this cycle took longer than period 
        excess -= sleepTime; 
        // store excess time value 
        overSleepTime = 0L; 
        if (++noDelays >= NO_DELAYS_PER_YIELD) { 
        Thread.yield(); 
        // give another thread a chance to run 
        noDelays = 0; 
        } 
       } 

       beforeTime = System.nanoTime(); 

       /* If the rendering is taking too long, then 
        update the game state without rendering 
        it, to get the UPS nearer to the 
        required frame rate. */ 
       int skips = 0; 
       while((excess > period) && (skips < MAX_RENDER_SKIPS)) { 
        // update state but don’t render 
        System.out.println("Skip renderFPS, run updateFPS"); 
        excess -= period; 
        updateWorld(0); 
        skips++; 
       } 
       rendersSkipped += skips; 
      } 
     } catch (Exception ex) { 
      ex.printStackTrace(); 
     } finally { 
      device.setFullScreenWindow(null); 
     } 
    } 

    private void updateWorld(long elapsedTime) { 
     // speed: 150 pixels per second 
     //double xMov = (140f/(NANO_IN_MILLI*1000)) * elapsedTime; 
     double posX = rect.getX() + (140f/TARGET_FPS); 
    if (posX > mainFrame.getWidth()) 
     posX = -rect.getWidth();  
    rect.setRect(posX, rect.getY(), rect.getWidth(), rect.getHeight()); 

     posX = rect2.getX() + (190f/TARGET_FPS); 
    if (posX > mainFrame.getWidth()) 
     posX = -rect2.getWidth(); 
    rect2.setRect(posX, rect2.getY(), rect2.getWidth(), rect2.getHeight());   

     posX = rect3.getX() - (300f/TARGET_FPS); 
    if (posX < -rect3.getWidth()) 
     posX = mainFrame.getWidth(); 
    rect3.setRect(posX, rect3.getY(), rect3.getWidth(), rect3.getHeight());   

    } 

    private void drawScreen(Graphics g) { 
     g.setColor(Color.BLACK); 
     g.fillRect(0, 0, mainFrame.getWidth(), mainFrame.getHeight()); 
     g.setColor(Color.WHITE); 
     g.drawString("FPS: " + fps, 40, 50); 

     g.setColor(Color.RED); 
     g.fillRect((int)rect.getX(), (int)rect.getY(), (int)rect.getWidth(), (int)rect.getHeight()); 

     g.setColor(Color.GREEN); 
     g.fillRect((int)rect2.getX(), (int)rect2.getY(), (int)rect2.getWidth(), (int)rect2.getHeight()); 

     g.setColor(Color.BLUE); 
     g.fillRect((int)rect3.getX(), (int)rect3.getY(), (int)rect3.getWidth(), (int)rect3.getHeight()); 
    } 

    private void calculateFramesPerSecond() { 
     if(curRenderTime - lastFpsTime >= NANO_IN_MILLI*1000) { 
      fps = frameCounter; 
      frameCounter = 0; 
      lastFpsTime = curRenderTime; 
     } 
     frameCounter++; 
    } 

    public void keyPressed(KeyEvent e) { 
     if(e.getKeyCode() == KeyEvent.VK_ESCAPE) { 
      System.exit(0); 
     } 
    } 

    public void keyReleased(KeyEvent e) { } 
    public void keyTyped(KeyEvent e) { } 

    public static void main(String[] args) { 
     try { 
     Map<String,String> mapArgs = parseArguments(args); 

      GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment(); 
      GraphicsDevice device = env.getDefaultScreenDevice(); 
      new GameLoop2(mapArgs, device); 
     } catch (Exception ex) { 
      ex.printStackTrace(); 
     } 
    } 


    /** 
    * Parse commandline arguments, each parameter is a name-value pair. 
    * Example: java.exe MyApp "key1=value1" "key2=value2" 
    */ 
    private static Map<String,String> parseArguments(String[] args) { 
     Map<String,String> mapArgs = new HashMap<String,String>(); 

     for(int idx=0; idx < args.length; idx++) { 
      String val = args[idx]; 
      int delimIdx = val.indexOf('='); 
      if (delimIdx < 0) { 
       mapArgs.put(val, null); 
      } else if (delimIdx == 0) { 
       mapArgs.put("", val.substring(1)); 
      } else { 
       mapArgs.put(
        val.substring(0, delimIdx).trim(), 
        val.substring(delimIdx+1) 
       ); 
      } 
     } 

     return mapArgs; 
    } 

} 

Gameloop逻辑可能看起来疯狂,但请相信我这个工程出奇的好。试试看。 编辑:运行TaskManager同时在流畅的动画中创造了一些动作,我想更新仪器统计数据会带来沉重的打击。

+0

这与我去年夏天写的一些代码非常相似。虽然这似乎是一个简洁的版本,但我记得睡眠时遇到了一些麻烦,并在具有相同的目标速率时保持每秒更新*和每秒帧数*同步。我可能会在未来某个时候从这里借鉴一些想法。 – afsantos 2013-05-03 20:41:58

+0

对不起,我只注意到你刚才的答案。现在已经晚了,所以明天我会更好地看看它。我非常感谢您的意见!但是我注意到的一件事是,它有一个固定的FPS,我认为尽可能多地制作更多的fps可能更好,并且会按照设定的时间间隔执行游戏逻辑,以便我的游戏可以在更多种类的机器上运行?无论哪种方式,谢谢:) – Iain 2013-05-03 23:39:25

+0

我不是一个专家和gameloop真的很复杂的问题,甚至更多的时候物理学起到作用。我很想看到替代(Java)解决方案,其中三个精灵移动到<->,就像我的例子那样。有关详情,请参阅http://gafferongames.com/game-physics/fix-your-timestep/和http://gamedev.stackexchange.com/questions/1589/fixed-time-step-vs-variable-time-step讨论。 。 – Whome 2013-05-04 10:36:28