[GEiv]第二章:动态绘制 让画面动起来

动态绘制 

让画面动起来

        这篇文章主要介绍图元动画的基本实现。

[画面、动画与游戏]

        在上一章节中,主要讲解了静态绘制的诸多方法。这些API终究是对OPENGL API的简单封装,这离游戏制作尚有一段距离。

        什么是动画呢?动画是一系列画面的有序播放,当播放速度快于人眼视觉暂留时,我们便误以为画面中的物体在移动了,对于视频、动画来讲,这个播放速度大于24帧时,已经达到了不错的效果。

        什么又是游戏呢?游戏是在玩家交互过程中变化的动画,对于计算机游戏来讲,这个交互过程主要依赖IO系统来完成。由于游戏对象根据IO的变化作出及时反应并来让玩家产生实在的控制感,24帧每秒的绘制速度虽然不会影响视觉暂留,但对于“控制感”来讲却会有一些滞后(lag),因此游戏普遍要求帧数在40以上。而Geiv的默认绘制速率被固定在60FPS。也就是说,前一章的静态绘制,实际上是每秒钟绘制60幅相同的画面而已。

[让画面动起来]

例子:

UESI UES = new R();
Obj rect = UES.creatObj(UESI.BGIndex);
rect.addGLRect("00FF00",0,0,200f,200f);//画一个矩形
rect.setGLFill(true);
rect.setPosition(CANExPos.POS_Y_CENTER);       //放在Y轴中间位置
rect.show();
while(rect.getDx() + rect.getWidth() <UES.getScreenWidth()){
       rect.setDx(rect.getDx()+ 1f);//取出Dx,并加上1
       UES.wait(3,17);//延时17ms,约为1000/60
}


结果:

        

但是上面的写法极其不科学!!!

        首先,引擎自己有自己的绘制线程,是基于Timer、及其准确的每秒钟执行60次绘制;而main方法相对于绘制来讲,是一个异步过程。延时17ms并不会准确的落在绘制线程的延时部分。

        第二,线程总是有限的,在执行while循环时,整个main线程都在为这个动画提供服务,无法进行其他运算,如果此时需要5种不同的动画,为他们开辟5个新线程是及其不合理的,纵使引擎组建中有完善的同步锁保证数据安全,但我们也不应该提高无用的并发度。

[帧逻辑]

[interface geivcore.SerialTask]

        SerialTask,字面意义是“串行任务”,这个名字起得不是很好(又是历史原因恩恩),实际的意义,是插入到绘制线程完成绘制之后的额外自定义逻辑。

例子:

//到了这里,我十分建议将类分开写,一个Main类已经满足不了需求了。
//Main.java:
public class Main{
       public static void main(String[] args) {
              UESI UES = new R();
              new MoveableRect(UES);
       }
}

//MoveableRect.java:
public class MoveableRect implements SerialTask{
       Obj rect;
       UESI UES;
       public MoveableRect(UESI UES){
              this.UES = UES;
              rect = UES.creatObj(UESI.BGIndex);
              rect.addGLRect("00FF00",0,0,200f,200f);
              rect.setGLFill(true);
              rect.setPosition(CANExPos.POS_Y_CENTER);
              rect.show();
             
              UES.addSerialTask(this);
       }
       @Override
       public void Serial(int arg0) {
              if(rect.getDx() + rect.getWidth()< UES.getScreenWidth()){
                     rect.setDx(rect.getDx() +1f);
              }
       }
}


        首先说SerialTask接口,它当中只有一个方法:

        public void Serial(int arg0);

        其中,参数是唯一的绘制时序信号,可用可不用。

        之后MoveableRect类实现了Serial方法,在每次绘制过后,将rect的横座标+1,也就是每秒60像素的速度。

        最后,在MoveableRect的构造方法中,使用UESI为参数,并保留一个引用,在构造方法的最后部分,它调用了UES.addSerialTask(this);方法,将这个自定义逻辑(Serial的实现)插入了绘制线程的队列末尾。

        这样,在每次绘制完成后,会精确的执行一次Serial方法,改变座标,在下次绘制时图元便会移动一小段距离,依次类推,图元便移动了起来。

结果:

        

        如果您观察仔细的话,应该能看出来,使用SerialTask实现的动画要比使用其他线程的异步调用相比平滑的多。

SerialTask的注意事项:

        引擎的动态部分应该主要由SerialTask完成。SerialTask的Serial方法中,能够书写的语句比较有限:这点非常重要,所有的Serial实现都要在绘制间隔里执行完毕(17ms),在一个游戏中,Serial的总数量可以成百上千,因此有如下限制:

        1)阻塞方法一定不要有,例如sleep,如果以前进行过awt编程,在onclick方法里调用阻塞方法会让整个绘制线程停止,这里的Serial也是一样。

        2)阻塞IO过程也一定不能有,总共17ms的执行实现,使用Serial调用阻塞IO时间来不及,诸如读取文件、网络访问等不适宜写入Serial。

        3)堆区分配请求过程不能过多,也就是说,尽量不要new对象,但是一些轻量化的数据对象还是可以接受的。

[总结]

        SerialTask接口用来向绘制线程动态地添加一部分自定义运算逻辑,自定义类通过实现Serial方法,适当地进行图元变换以实现各种动态效果。

        Serial尽可能只负责数值运算与类之间的消息传递,不要写费时间的东西。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章