Java2游戏编程读书笔记(11-1)

来源:互联网 发布:如何变成淘宝达人 编辑:程序博客网 时间:2024/05/12 17:54

第11章   实现一个布景管理系统

在第10章中学习了编写一个灵活的,可重用的游戏实体类——Actor2D类。虽然做了一些工作才使得Actor2D系统建立并运行,但是现在有了一个创建自定义游戏组件的快速而简单的方法。

在本章中,将学习几种部署自定义布景系统让它自动更新和绘制游戏场景的方法。学完本章后将熟悉下列主题:

*  创建一个框架Scene类来处理游戏场景内容

*  使用QuadTree类来分离并控制复杂的场景

*  在全屏独占方式下绘制场景

*  创建自定义场景,比如可滑动的场景,等视角砖块式场景又及包装场景

在学习布景系统的框架Scene类,让我们先来讨论使用场景管理系统的一些好处。

11.1      为什么要实现一个布景管理器

实现场景管理器的主要原因在于它把先前由Applet类所控制的处理任务和负担移了出来。与方法类似,让类只执行单一任务常常也是一个很好的思想,记住,Applet类应该只处理程序的初始化并驱动主事件循环。updatepaint方法不应该执行游戏过程,而应该把这些任务委派给其他的对象,这样将让用户能够模块化编程,这种做法将使项目组织良好,方便修改和改进。

下面将学习Scene抽象类,以及如何扩展它来提供自定义的游戏场景。

11.2      Scene

Scene类只是一个带有几个更新并绘制场景的抽象方法的框架类。Scene类定义如下:

import java.awt.*;

import java.awt.geom.*;

 

 

//提供场景管理的框架类

public abstract class Scene{

       //场景的总体边界

       protected Rectangle2D bounds;

      

       //场景中可见的部分,通常就是applet窗体的大小

       protected Rectangle2D viewable;

      

       //用传入的边界和可见区域创建一个新的Scene对象

       public Scene(Rectangle2D v,Rectangle2D b){

              setViewable(v);

              setBounds(b);

       }

      

       //添加一个Actor到场景中,使用Actor2D对象的子类应该覆盖个方法

       public void add(Actor2D a){

       }

      

       public final void setViewable(Rectangle2D r){

              viewable=new Rectangle2D.Double(r.getX(),r.getY(),

                                                                      r.getWidth(),r.getHeight());

       }

      

       public final Rectangle2D getViewable(){

              return viewable;

       }

      

       public final void setBounds(Rectangle2D r){

              bounds=new Rectangle2D.Double(r.getX(),r.getY(),

                                                                      r.getWidth(),r.getHeight());

       }

      

       public final Rectangle2D getBounds(){

              return bounds;

       }

      

       //更新场景

       public abstract void update();

      

       //Graphics2D容器上绘制场景

       public abstract void paint(Graphics2D g2d);

}//Scene

注意,Scene定义的是一个类,而不是接口。这样做有几个原因,首先,这里希望它包含两个Rectangle2D对象来容纳场景的大小和可见窗口的大小(记住,接口不能包含除类常量之外的属性);另外一个不把Scene类声明为接口的原因是不想让所有的方法都是未完成的。例如,add方法允许把一个Actor2D对象加到场景中来,虽然这个方法在这一层次上是空的,这里不想使用abstract类来强制子类实现它。如果Scene类的子类不使用Actor2D对象,那么它无须实现这个方法。

扩展Scene类其实非常容易,因为没有许多方法需要覆盖,一旦决定了要创建的场景的种类,就可以只覆盖updatepaint方法,因为它们被声明为abstract

复杂的场景——包含很多物体的场景——其中冲突检测和绘制这样这的过程会使处理器变慢。在下面,我们会看到一个数据结构,可以用这个数据结构来使复杂的场景变得更容易控制一些。

1使用QuadTree来进行场景管理

正如刚才所提到的,像冲突检测这样的操作是很费劲的,可能很快使得处理器变慢。如果场景每一帧需要检测一次冲突,那么会造成处理循环的瓶颈。这样的瓶颈不值得花那么多时间,特别是物体不是十分接近的情况下。

如果可以设计一种机制,只在确认会发生冲突的物体间进行冲突检测,那么会怎样呢?事实上,精确地进行冲突检测可能是很荒谬的,可以设计一种方法从冲突检测这样的过程中去掉一些消耗。这样的方法就是四方树(Quad Tree)的使用。

四方树无论是对于演示还是实际应用都是最好的数据结构之一。

和大多数的树结构类似,一个四方树只含有一个根节点,分支连接子节点,最后叶子节点在树技的尖上。四方树以把它划分为4个更小的四方树的方式工作。每一组四方树被分成更小的四方树的次数称为树的深度,这种递归的机制可以按照需要进行下去。

下图所示为一个深度为4的四方树。


节点从左下角开始按照逆时针方向用数字标记。代表整个场景主节点(或者根节点)的是节点0,标有数字1,2,3,4的节点是根节点的直接派生节点。这里把节点5,6,7,8划为节点3的派生节点,依此类推。

现在,让编号机制继续下去,那么如何放置场景中的物体呢?在图中,节点924都是叶节点,也就是说,它们没有派生节点,这些节点容纳实际场景中的物体。每一个叶节点会包含一个和叶子交叉或完全在叶节点边界之内的物体的列表。现在,当对场景执行冲突检测时,只有在同一个节点中的那些物体需要进行冲突检测

如果一个小行星跨多个叶节点,必须把这个小行星加到每一个节点中。实现代码时这是一个要记住的重要事实。

这就牵扯到关于四方树的实现中另一个需要注意的地方。假设行星和飞船都在两个以上的节点中,飞船的冲突检测将会重复进行。为了防止这个结果,在冲突检测过程中给每一个物体一个已经进行过冲突检测的列表可能是一个好的想法。可以使用Actor2D类的addCollision方法,下面是它的代码:

//在冲突列表中添加一个冲突对象

public void addCollision(Moveable other){

       if(collisionList==null){

              collisionList=new LinkedList();

              collisionList.add(other);

              return;

       }

      

       if(!collisionList.contains(other)){

              collisionList.add(other);

       }

}

在上面的方法中,只要一个Moveable对象没有被冲突列表包含,就把它添加到冲突列表中。在整个场景的冲突检测完成之后,已经检测出至少一次冲突的物体就执行冲突代码,这将防止物体过多执行冲突代码。

下面继续来看QuadNode类的实际代码。其中的一些方法本质上是递归的,所以,如果需要复习递归是如何进行的,可以参考第2章的练习12

import java.awt.*;

import java.awt.geom.*;

import java.util.*;

 

//QuadTree结构中的一个节点

public class QuadNode{

       //分配给节点的一个惟一ID

       protected static int UNIQUE_ID=0;

      

       protected int id;

      

       //包含这个节点的父节点

       protected QuadNode parent;

      

       //这个QuadNode的子节点数组

       protected QuadNode[] nodes;

      

       //如果这个节点为叶节点,则为true(即没有子节点的节点)

       protected boolean leaf;

      

       //这个节点所包含物体的链表

       protected LinkedList objects;

      

       //节点边界

       protected Rectangle2D bounds;

      

       //默认构造函数

       private QuadNode(){

              id=-1;

              parent=null;

              nodes=null;

              leaf=true;

              objects=null;

              bounds=null;

       }

      

       //给定父亲点,深度和边界构建一个QuadNode

       public QuadNode(QuadNode p,int depth,Rectangle2D r){

              parent=p;

              bounds=r;

             

              id=UNIQUE_ID++;

             

              //如果剩余的深度为零则这个节点为叶节点

              if(depth==0){

                     leaf=true;

                     objects=new LinkedList();

                     nodes=null;

                    

//                   QuadTree.addLeaf(this);

              }else{//否则这个节点包含4个孩子节点

                     leaf=false;

                     objects=null;

                    

                     nodes=new QuadNode[4];

                     double x=bounds.getX();

                     double y=bounds.getY();

                     double w=bounds.getWidth();

                     double h=bounds.getHeight();

                    

                     //创建子节点

                     nodes[0]=new QuadNode(this,depth-1,

                                                        new Rectangle2D.Double(x,y+h/2,w/2,h/2));

                     nodes[1]=new QuadNode(this,depth-1,

                                                        new Rectangle2D.Double(x+w/2,y+h/2,w/2,h/2));

                     nodes[2]=new QuadNode(this,depth-1,

                                                        new Rectangle2D.Double(x+w/2,y,w/2,h/2));

                     nodes[3]=new QuadNode(this,depth-1,

                                                        new Rectangle2D.Double(x,y,w/2,h/2));

                    

              }

       }

      

       //如果这个节点是叶子节点则返回true

       public final boolean isLeaf(){

              return leaf;

       }

      

       //尝试将一个传入的物体插入到这个节点中

       public void insert(Moveable c,                           //要插入的Moveable物体

                                          boolean propagate){             //如果为真,则在c不在这个节点的边界内时向上一级插入

              //尝试插入

              if(bounds.contains(c.getBounds())||bounds.intersects(c.getBounds())){

                     //如果这个节点是一个叶子节点,则将物体插入链表中

                     if(isLeaf()){

                            if(objects==null){

                                   objects=new LinkedList();

                            }

                            if(!objects.contains(c)){

                                   objects.add(c);

                            }

                     }else{//如果这个节点不是叶节点则继续向下一层插

                            for(int i=0;i<4;i++){

                                   if(nodes[i]!=null){

                                          nodes[i].insert(c,false);

                                   }

                            }

                     }

              }else{//否则,在允许的情况下向上一级插入物体

                     if(propagate){

                            if(parent!=null){

                                   parent.insert(c,true);

                            }

                     }

              }

       }

      

       //将这个节点所包含的所有物体平移(x,y)

       public void moveAll(double x,double y){

              if(objects==null||objects.isEmpty())return;

             

              Actor2D a;

              for(int i=0;i<objects.size();i++){

                     a=(Actor2D)objects.get(i);

                     a.moveBy(x,y);

              }

       }

      

       public void update(){

              if(objects==null||objects.isEmpty())return;

             

              //更新链表,将不再被这个节点包含的物体删除

              Moveable m;

              for(int i=0;i<objects.size();i++){

                     m=(Moveable)objects.get(i);

                     m.update();

                    

                     //测试物体是否离开这个节点,如果是,向上一级插

                     if(!bounds.contains(m.getBounds())&&

                            !bounds.intersects(m.getBounds())){//可以向上插

                                   insert((Moveable)objects.remove(i),true);

                     }

              }

             

              //因为可能有物体已经被删除,所以需要获得更新后的大小

              int size=objects.size();

             

              //检测这个节点之内物体之间的冲突

              for(int i=0;i<size-1;i++){

                     Moveable a=(Moveable)objects.get(i);

                    

                     for(int j=i+1;j<size;j++){

                            Moveable b=(Moveable)objects.get(j);

                           

                            if(a.collidesWith(b)){

                                   a.addCollision(b);

                                   b.addCollision(a);

                            }

                     }

              }

       }

      

       public void paintBounds(Graphics2D g2d,Color c){

              if(c==null)c=Color.red;

              g2d.setPaint(c);

              g2d.draw(bounds);

       }

      

}//QuadNode

QuadNode类中包含了我们前面讨论过的内容。每一个节点包含一个Rectangle2D对象,它表示该节点所控制的区域。一个节点还包含一个对它的父节点的引用及一个对它4个子节点的引用。如果该节点是一个叶节点,则它的子节点会指向null(默然:无子节点的节点为叶节点)objects属性会指向一个有效的LinkedList对象(默然:只有叶节点可以包含游戏中的物体,这个LinkedList就是用来存储在叶节点范围内的游戏物体的),否则(默然:这个否则的意思,就是说如果这个节点不是一个叶节点)objects会指向null而子节点指向有效的QuadNode对象。注意,节点属性是在构造时确定的,如果depth为零,那么该节点将是一个叶节点(默然:这就是一个规定!背下来,不要问为什么!)。而非叶节点将给它的每一个派生节点传一个depth1的值(默然:伟大的递归开始了!)

这里想对QuadNode类补充说明的最后一个地方是关于它的insert方法的,也就是它的progagate参数。如果progagate设为false,则这个方法只把传入的对象“往下”插入,所以,如果物体在节点的边界内,则它要么被添加到对象列表中(前提是:这个节点是叶节点),要么被传给它的4个子节点。如果物体不在节点的边界之中,则插入操作会中止(默然:这时,insert方法会将progagate设为true),换句话说,如果progagatetrue,那么说明物体不在节点边界之中,则插入操作会转移给它的父节点(默然:即一个更大的矩形,以期望包含物体,并找到正确的叶节点将其插入。呀,伟大的递归!)

  您好:
    当您在阅读和使用我所提供的各种内容的时候,我非常感谢,您的阅读已是对我最大的支持。
    我更希望您能给予我更多的支持。
    1.希望您帮助我宣传我的博客,让更多的人知道它,从中获益。
    2.希望您能多提出宝贵意见,包括我所提供的内容中的错误,建设性的意见,更希望获得哪些方面的帮助,您的经验之谈等等。
    3.更希望能得到您经济上的支持。
    
    我博客上面的内容均属于个人的经验,所有的内容均为开源内容,允许您用于任何非商业用途,并不以付费为前提,如果您觉得在阅读和使用我所提供的各种内容的过程中,您得到了帮助,并能在经济上给予我支持,我将感激不尽。
    
    您可以通过银行转帐付款给我:
    招商银行一卡通:
    卡号:6225888712586894
    姓名:牟勇
    
    您也可以通过汇款的方式:
    通讯地址:云南省昆明市女子(28)中学人民中路如意巷1号
    收信人:陈谦转牟勇收
    邮编:650021
    
    无论您给予我怎么样的支持,我都衷心的再次感谢。
    欢迎光临我的博客,欢迎宣传我的博客
    http://blog.csdn.net/mouyong
    http://blog.sina.com.cn/mouyong
    EMail:mouyong@yeah.net