Java游戏学习

Posted on 2006-11-06 18:45 luosheng 阅读(377) 评论(0)  编辑  收藏

这几天一直在看 <<Killer Game Programming in Java>>,非常经典的好书,现在对游戏有了一个基本的认识,过几天就写个贪吃蛇出来.
因为看得有点快,真正准备写代码的时候又发现自己对一些基本知识点还是比较模糊,又返回去看前面.边看边做笔记,感觉确实理解得要更加清楚了.

*FPS 和 按时间准确的Sleeping*
============================

FPS
--------
  一个测量animation速度的常用指标就是桢速(每秒显示桢的数量:frames per second),简称FPS.在下面的
代码中,做一次gameUpdate和gameRender的循环就对应一个桢.
  比如100FPS表示run()中的每次迭代应该用1000/100 == 10ms.这个迭代时间存在period变量中.

//code example:

public void run() {
  long beforeTime, timeDiff, sleepTime;
  beforeTime = System.currentTimeMillis();
  running = true;
  while(running) {
    gameUpdate();//计算game中的model
    gameRender();//画一个image, "double buffer"
    paintScreen();//在screen上显示image
    
    timeDiff = System.currentTimeMillis() - beforeTime;
    sleepTime = period - timeDiff;//计算需要sleep的时间

    if (sleepTime <= 0)
      sleepTime = 5;

    try {
      Thread.sleep(sleepTime);
    }catch(InterruptedException e);

    beforeTime = System.currentTimeMillis();
  }
}

--------
Timer Resolution
  连续调用两次timer中间必需的最小时间.这样才能保证每次调用返回不同的时间.
  比如:
          long t1 = System.currentTimeMillis( );
        long t2 = System.currentTimeMillis( );
        long diff = t2 - t1;  // 实际输出是0,单位是ms

  在win95和win98上,resolution值是55ms,说明只有在每隔55ms后调用timer才会返回不同的值.
  在animation loop中,resolution的会导致animation比期望的要慢而且减小了FPS.因为如果gameUpdate
和gameRender的时间小于55ms,那么timeDiff变量就会设为0,那么sleepTime就会比实际需要的时间要大.
  为了防止这个问题,每个循环周期时间必须大于55ms,表示最高限制是大约18FPS.这个frame rate被广泛接
受,因为屏幕刷新过慢会表现得象闪屏(excessive flicker)一样.
 
  在Windows2000, NT和XP上, currentTimeMillis()的resolution是10到15ms,这样就可以获得67-100FPS.
这个值对游戏来说是可以接受的.在Mac OS X和Linux上的resolution是1ms,相当好了.

--------
改进过的J2SE Timers

  J2SE 1.4.2有一个没有被写入到文档的精确到微秒的timer class: sum.misc.Perf.
  Pref计算diff的方法:

      Pref perf = Perf.getPerf();
    long countFrep = perf.highResFrequency();
    
    long count1 = perf.highResCounter();
    long count2 = perf.highResCounter();
    long diff = (count2 - count1) * 1000000000L / countFreq;
                    //转换成纳秒nanoseconds
  nanoseconds:十忆分之1秒

  J2SE 5.0中解决了这个timer的问题,System.nanoTime(),可以象Pref timer一样来计算时间.
      long count1 = System.nanoTime();
    long count2 = System.nanoTime();
    long diff = (count1 - count2);//单位是纳秒

--------
Non-J2SE Timers

  Java 3D timer的计算方法:
      long t1 = J3DTimer.getValue();
    long t2 = j3DTimer.getValue();
    long diff = t2 - t1;//单位是纳秒


*更好的Sleeping*
================
  animation循环依赖一个好的timer和精确的sleep方法调用.现在在前面的基础上改进代码,以保证需要
的桢速.
//code example:

private static final int NO_DELAYS_PER_YIELD = 16;

public void run() {
  long beforeTime, afterTime, timeDiff, sleepTime;
  long overSleepTime = 0L;
  int noDelays = 0;

  beforeTime = J3DTimer.getValue();
 
  running = true;
  while(running) {
    gameUpdate();
    gameRender();
    paintScreen();

    afterTime = J3DTimer.getValue();
    timeDiff = afterTime - beforeTime;
    sleepTime = (period - timeDiff) - overSleepTime;

    if (sleepTime > 0) {
      try{
        Thread.sleep(sleepTime/1000000L); //nano -> ms
      }catch(InterrruptedException ex){}
      overSleepTime = (J3DTimer.getValue() - afterTime) - sleepTime;
    }else {    //sleepTime <= 0; 桢的时间大于期望的period,
        //不sleep直到sleepTime > 0 或 连续运行了NO_DELAYS_PER_YIELD次
      overSleepTime = 0L;
     
      if (++noDelays >= NO_DELAYS_PER_YIELD) {
        Thread.yield();
     noDelays = 0;
      }
    }
    
    beforeTime = J3DTimer.getValue();

  }
}

  如果sleep()设置成sleep 10 ms,但是确用了12 ms,那么overSleepTime会被设置成2 ms,下次就会少
sleep 2ms.
  如果桢的时间大于期望的period,那么就不浪费时间sleep,而是一直循环,一定次数后调用Thread.yield(),
这样来节省时间而又保证其它线程有机会运行.

*FPS和UPS*
==========

  除了FPS,还有一个有用的测量animation速度的指标:UPS. 在现在的animation循环中每次迭代拥有一次
update和render.但是这个对应不是必需的.在循环中,可以每一次render前做两次updates.
 
//code example:
 
public void run() {
  ...
  running = true;
  while(running) {
    gameUpdate();//update 游戏状态
    gameUpdate();//再一次update 游戏状态
 
    gameRender();
    paintScreen();

    //sleep
  }
  System.exit(0);
}
  在上面的代码中,如果游戏提供了50FPS,那么就每秒就会做100次updates.

从Rendering中分离Updates
--------

  对于高FPS速率的一个限制是update和render所需要的时间.假设period = 5ms(1000/5 == 200FPS),如果
update和render需要的时间大于5ms,那么200FPS就不可能达到.而它们所需要的时间中大部分是被render所消耗的.
  在这种情况下,增加游戏速度的方法是增加UPS的速率.在编程中,也就是在每次迭代中增加gameUpdate的次数.
但是注意,如果增加gameUpdate的次数过多的话会造成游戏不连续,因为有许多游戏状态没有显示出来.

新的代码:

//code example:

private static int MAX_FRAME_SKIPS = 5;

public void run() {
  long beforeTime, afterTime, timeDiff, sleepTime;
  long overSleepTime = 0L;
  int noDelays = 0;
  long excess = 0L;

  beforeTime = J3DTimer.getValue();
 
  running = true;
  while(running) {
    gameUpdate();
    gameRender();
    paintScreen();

    afterTime = J3DTimer.getValue();
    timeDiff = afterTime - beforeTime;
    sleepTime = (period - timeDiff) - overSleepTime;

    if (sleepTime > 0) {
      try{
        Thread.sleep(sleepTime/1000000L); //nano -> ms
      }catch(InterrruptedException ex){}
      overSleepTime = (J3DTimer.getValue() - afterTime) - sleepTime;
    }else {    
      excess -= sleepTime;
      overSleepTime = 0L;
     
      if (++noDelays >= NO_DELAYS_PER_YIELD) {
        Thread.yield();
     noDelays = 0;
      }
    }
    
    beforeTime = J3DTimer.getValue();

    int skips = 0;
    while((excess > period) && (skips < MAX_FRAME_SKIPS)) {
      excess -= period;
      gameUpdate();
      skips++;
    }

  }
}

  如果update/render实际需要12ms,但是需要的period是10ms,那么sleepTime会是-2ms(由于引入overSleepTime,
所以可能会更小一点).额外的执行时间被加到excess变量中.
  当excess达到period大小时,那么相当于丢失了一个桢,在while循环中,为每次丢失执行gameUpdate.但是限制在
MAX_FRAME_SKIPS里.
  这样做的优点是,如果一个游戏的update/render速度不能满足期望的FPS时,那么就会另外执行gameUpdate.
这样改变了游戏的状态但是没有马上显示出来,最后用户会看见游戏移动更"快"了,虽然每秒钟显示的桢数并没有
改变.

from:http://blog.csdn.net/starshus/archive/2006/11/03/1364979.aspx

 

posts - 4, comments - 0, trackbacks - 0, articles - 0

Copyright © luosheng

welcome to my blog ! !