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

来源:互联网 发布:mysql 注入攻击 编辑:程序博客网 时间:2024/09/21 08:58
 
现在已经知道如何创建一个四方树节点,对于像插入,更新和绘制这样的场景操作我们需要一个“进入点”,对于整个树的主根节点用数值零来标志它。
这里创建了QuadTree类来实现四方树的进入点。由于QuadTree类还表示一个四方树节点,所以它继承了QuadNode类。这个类将提供把物体添加到场景中的方法,以及更新和绘制离开场景(默然:意思就是,所有游戏物体的绘制也将由QuadTree来完成)。为了方便,还提供了几个访问叶节点和可绘制对象的快捷方式。
下面就是QuadTree类的代码,在通读它的过程中,注意它和QuadTree类的相似性以及它所做的扩展。
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
QuadTree类构建自己的方式和QuadNode类很相似,不过它用自己的objects列表作为不在场景边界中的物体的溢出列表。然后QuadTree类将监视这些物体,看是否进入场景中。一旦它们进入场景中,它们将被从溢出列表中删除并被添加到一个或多个叶子节点中;反之亦然。一旦一个物体离开场景,它将被从叶子节点中删除并被添加到溢出列表中。
其他需要注意的是paintList和leafList这两个LinkedList对象,这些列表只是访问叶子以及应该绘制的物体的快捷方式。由于在构造时节点就被发现是叶子节点,所以它们可以调用静态的addLeaf方法,这个方法将把它们加到叶子节点列表中。采用这种方式,当树调用像update这样的方法时,它已经有一个预先准备好的列表来快速访问所有的叶子节点对象。对于paintList列表也是这样,当场景需要绘制时,树可以遍历可绘制物体的列表并让每一个物体绘制自己。leafList和paintList都可以无须在每一次需要时重复搜索同样的物体。
回到关于场景管理的讨论,我们先看一个简单的场景,它实现了一个四方树来容纳场景中的物体。下面的TreeScene类简单地控制了一个QuadTree对象并提供封装好的添加物体和维护场景物体的方法。
import java.awt.*;
import java.awt.geom.*;
import java.util.*;
 
public class TreeScene extends Scene{
       //用来容纳场景中的物体的QuadTree
       private QuadTree tree;
      
       //用给定的边界构造一个TreeScene
       public TreeScene(Rectangle2D bounds){
              super(bounds,bounds);
             
              tree=new QuadTree(3,bounds);
       }//init
      
       //把传入的Actor2D添加到QuadTree中
       public void add(Actor2D a){
              tree.insert(a);
       }
      
       //将更新委派给QuadTree
       public void update(){
              tree.update();
       }
      
       //绘制QuadTree和叶子节点的轮廓
       public void paint(Graphics2D g2d){
              tree.paint(g2d);
              tree.paintBounds(g2d,Color.black);
       }//paint
}//TreeScene
当然,用户很可能需要对TreeScene类做像角色控制这些的扩充,可以随心所欲地去做。
虽然四方树结构实现起来可能比较麻烦,但是它提供一种相当巧妙的方法来控制场景并迅速判别像冲突检测这样的事情。一定要选择一个可以最大限度地让场景快起来的全局深度,全局深度太小会产生很多不正确的冲突检测,太大的全局深度则会迫使单个物体被包含在8,16甚至更多的叶子节点中,这样会产生使得需要重新插入的次数太多之类的问题。考虑场景中一般物体的数量和大小,尝试使用不同的全局深度来从四方树支撑的场景中获得最大的效率。
对QuadNode和QuadTree类还可以做很多优化。本章未尾有几个练习会要求读者改进这两个类的总体效率。
现在让我们看看比较麻烦的全屏独占方式,这是Java 1.4版新加的一个特性。
2.全屏独占模式探险
几乎没有一个大型的商业游戏是在一个窗口或者窗体内进行绘制的,进一步说,它们进入全屏模式并使用整个可绘制的屏幕。如果你是一个DirectX爱好者,那么可能对于全屏独占模式的概念很熟悉。简单地说,全屏独占模式强迫操作系统挂起它的窗口环境并允许全屏应用程序独占,或者直接访问屏幕。进入全屏模式可以允许游戏绕过当前桌面系统的观感并使用整个显示器屏幕,这样做给游戏增加沉浸感。
全屏模式下的绘制是通过Frame对象包含的GraphicsDevice来实现的。
使用全屏模式的一个很好的理由是这样做可以实现更高级的绘制技术,比如画面交换(page flipping)和链交换(chain flipping)。这两种技术都是通过java.awt.image包中的BufferStrategy类实现的。BufferStrategy对象可以通过Window和Frame类的createBufferStrategy方法来创建,这个方法允许指定缓冲区的数量。系统会根据当前的图形配置和目标缓冲区数目尝试使用最好的绘制方法。如果不可以使用画面交换,就会尝试使用加速图像的链交换机制,如果这也失败了,那么就会使用一种不对图像加速的链交换策略。
让我们通过一个例子看看如何应用目前所学到的所有的绘制技术。下面的例子FullscreenTest用一个窗口开始,用两个按钮选择,一个按钮使用窗口的方式打开一个新的窗体,另一个使用全屏独占模式打开一个窗体。
这个applet中用到的概念绝大多数前文已经讨论过,把剩下的留给读者来思索。
import java.awt.*;
import java.awt.image.BufferStrategy;
import java.applet.*;
import java.awt.event.*;
import java.util.*;
 
public class FullscreenTest extends Applet implements Runnable,
                                                                                    ActionListener,
                                                                                    MouseMotionListener{
       //用来绘制的窗体frame或者全屏frame
       protected Frame frame;
      
       //图形环境可用的图像设备
       protected GraphicsDevice device;
      
       //是窗体模式还是全屏模式的标志
       protected boolean frameWindowed;
      
       //frame的大小
       protected Rectangle bounds;
      
       //窗体方式绘制的屏外图像
       protected Image offscreen;
      
       //用来选择是用窗体模式还是全屏模式的按钮
       protected Button windowed;
       protected Button fullscreen;
       protected Label desc;
      
       //Actor2D对象数组
       protected Actor2D[] actors;
      
       //动画线程
       protected Thread animation;
      
       public void init(){
              //创建applet的可视化组件
              Panel p;
             
              setLayout(new BorderLayout());
             
              p=new Panel();
              p.add(new Label("选择你的命运!"));
              add(p,BorderLayout.NORTH);
             
              p=new Panel();
              windowed=new Button("给我蓝色的药丸!");
              windowed.addActionListener(this);
              windowed.addMouseMotionListener(this);
              p.add(windowed);
             
              fullscreen=new Button("我要红色药丸!");
              fullscreen.addActionListener(this);
              fullscreen.addMouseMotionListener(this);
              p.add(fullscreen);
             
              add(p,BorderLayout.CENTER);
             
              p=new Panel();
              desc=new Label("                      ");
              p.add(desc);
              add(p,BorderLayout.SOUTH);
             
              //设置frame的可视化组件
             
              ActorGroup2D group=new AsteroidGroup();
              group.init(this);
             
              group.MAX_X_POS=800;
              group.MAX_Y_POS=600;
             
              group.MIN_X_POS=0;
              group.MIN_Y_POS=0;
             
              Random random=new Random();
              actors=new Asteroid[10];
              for(int i=0;i<10;i++){
                     actors[i]=new Asteroid(group);
                     actors[i].setPos(Math.abs(random.nextInt(800)),
                                                        Math.abs(random.nextInt(600)));
              }
             
              bounds=null;
             
              //为动画创建一个新的线程
              animation=new Thread(this);
       }
      
       public void stop(){
              animation=null;
       }
      
       public void run(){
              //全屏模式的代码
              if(!frameWindowed){
                     //创建一个链交换策略
                     frame.createBufferStrategy(3);
                    
                     Thread x=Thread.currentThread();
                     while(x==animation&&frame.isShowing()){
                            BufferStrategy bufferStrategy=frame.getBufferStrategy();
                           
                            //更新并绘制frame
                            do{
                                   Graphics2D g2d=(Graphics2D)
                                                 bufferStrategy.getDrawGraphics();
                                  
                                   for(int i=0;i<10;i++){
                                          actors[i].update();
                                   }
                                  
                                   paintFrame(g2d);
                                   bufferStrategy.show();
                                   g2d.dispose();
                            }while(bufferStrategy.contentsLost());
                           
                            try{
                                   Thread.sleep(10);
                            }catch(InterruptedException e){
                                   break;
                            }
                     }
              }else{//窗体模式代码
                     Graphics2D g2d;
                    
                     Thread x=Thread.currentThread();
                     frame.show();
                     while(x==animation&&frame.isVisible()){
                            //更新并绘制frame
                            g2d=(Graphics2D)offscreen.getGraphics();
                           
                            for(int i=0;i<10;i++){
                                   actors[i].update();
                            }
                           
                            paintFrame(g2d);
                            g2d.dispose();
                            frame.getGraphics().drawImage(offscreen,0,0,this);
                           
                            try{
                                   Thread.sleep(10);
                            }catch(InterruptedException e){
                                   break;
                            }
                     }
              }
       }
      
       //绘制一帧动画,传入的Graphics2D容器可以进行窗体模式绘制
       //也可以进行全屏模式绘制
       protected void paintFrame(Graphics2D g2d){
              g2d.setPaint(Color.black);
              g2d.fillRect(0,0,bounds.width,bounds.height);
             
              for(int i=0;i<10;i++){
                     actors[i].paint(g2d);
              }
       }
      
       //以窗体模式或者全屏模式打开frame
       protected void openFrame(){
              //用全屏模式打开窗体的代码
              if(!frameWindowed){
                     try{
                            //从图形环境中得到默认的图形设备
                            device=GraphicsEnvironment.
                                                        getLocalGraphicsEnvironment().
                                                                             getDefaultScreenDevice();
                            //使用图形设备创建一个Frame
                            frame=new Frame(device.getDefaultConfiguration());
                           
                            //不要绘制frame的修饰
                            frame.setUndecorated(true);
                           
                            //由于绘制是主动进行的,所以忽略操作系统发来的绘制请求
                            frame.setIgnoreRepaint(true);
                           
                            //创建全屏窗口
                            device.setFullScreenWindow(frame);
                           
                            //如果支持,调整窗体大小
                            if(device.isDisplayChangeSupported()){
                                   //在恢复到窗体模式之前尝试几种显示模式
                                   if(displayModeSupported(800,600,32)){
                                          device.setDisplayMode(new DisplayMode(800,600,32,0));
                                   }else if(displayModeSupported(800,600,16)){
                                          device.setDisplayMode(new DisplayMode(800,600,16,0));
                                   }else if(displayModeSupported(640,480,16)){
                                          device.setDisplayMode(new DisplayMode(640,480,16,0));
                                   }
                            }
                           
                            bounds=frame.getBounds();
                           
                            //启动动画
                            animation.start();
                     }catch(Exception e){
                            e.printStackTrace();
                     }
              }else{//窗体模式
                     //创建一个大小为800*600像素的标准Frame
                     offscreen=createImage(800,600);
                    
                     frame=new Frame();
                     frame.setSize(800,600);
                     frame.addWindowListener(new WindowAdapter(){
                            public void windowClosing(WindowEvent e){
                                   frame.hide();
                                   frame.setVisible(false);
                                   frame.dispose();
                            }
                     });
                     bounds=frame.getBounds();
                    
                     animation.start();
              }
       }
      
       //判断当前的图形设备是否支持所传入的显示模式
       private boolean displayModeSupported(int width,int height,int bitDepth){
              DisplayMode[] modes=device.getDisplayModes();
              for(int i=0;i<modes.length;i++){
                     if(width==modes[i].getWidth()&&height==modes[i].getHeight()
                            &&bitDepth==modes[i].getBitDepth()){
                                   return true;
                            }
              }
             
              //找不到兼容的显示模式
              return false;
       }
      
       //以全屏或者窗体模式打开frame
       public void actionPerformed(ActionEvent e){
              if(windowed==e.getSource()){
                     frameWindowed=true;
                     windowed.setEnabled(false);
                     fullscreen.setEnabled(false);
                     openFrame();
              }else if(fullscreen==e.getSource()){
                     frameWindowed=false;
                     windowed.setEnabled(false);
                     fullscreen.setEnabled(false);
                     openFrame();
              }
       }
      
       public void mouseMoved(MouseEvent e){
              if(windowed==e.getSource()){
                     desc.setText("选择窗口模式");
              }else if(fullscreen==e.getSource()){
                     desc.setText("选择全屏模式");
              }
       }
      
       public void mouseDragged(MouseEvent e){
       }
}//FullscreenTest
默然:下面是有关FullscreenTest类里用到的一个角色类和一个角色组类的源代码。它们扩展了Actor2D与ActorGroup2D,完成了小行星这个角色(蛮有意思哦),代码原理类似于第10章中的Robot类这里就不多作说明。原书上没有这两个类的源代码,我从随书光盘中翻了出来,并把它们贴在这里了。
import java.awt.*;
import java.util.*;
 
public class Asteroid extends Actor2D
{
 protected final double ONE_RADIAN = Math.toRadians(1);
 protected static Random random = new Random();
 
 protected int size;
 protected double rotation;
 
 public Asteroid(ActorGroup2D grp)
 {
       super(grp);
 
       while(vel.getX() == 0)
       {
            vel.setX(3+random.nextInt()%4);
       }
       while(vel.getY() == 0)
       {
            vel.setY(3+random.nextInt()%4);
       }
 
       System.out.println(vel);
 
       animWait = 1;
 
       size = (int) Math.abs(random.nextInt()%3);
       rotation = 0.0;
       currAnimation = group.getAnimationStrip(size);
 
       updateBounds();
 }
 
 public void checkBounds()
 {
       if(bounds.getX() < group.MIN_X_POS)
       {
            pos.setX(group.MIN_X_POS);
            vel.setX(-vel.getX());
       }
 
       else if(bounds.getX() + frameWidth > group.MAX_X_POS)
       {
            pos.setX(group.MAX_X_POS - frameWidth);
            vel.setX(-vel.getX());
       }
 
       if(bounds.getY() < group.MIN_Y_POS)
       {
            pos.setY(group.MIN_Y_POS);
            vel.setY(-vel.getY());
       }
 
       else if(bounds.getY() + frameHeight > group.MAX_Y_POS)
       {
            pos.setY(group.MAX_Y_POS - frameHeight);
            vel.setY(-vel.getY());
       }
 }
 
 
 public void update()
 {
       pos.translate(vel);
      
       xform.setToIdentity();
 
       xform.translate(+pos.getX()+frameWidth/2, +pos.getY()+frameHeight/2);                           
       xform.rotate(rotation);
       xform.translate(-frameWidth/2, -frameHeight/2);                           
 
       updateBounds();
       checkBounds();
 }
 
 public void paint(Graphics2D g2d)
 {
       if(collisionList.size() > 0)
       {
            g2d.setPaint(Color.BLUE);
            g2d.fill(bounds);
            g2d.setPaint(null);
            collisionList.clear();                   
       }
 
       super.paint(g2d);              
 }
 
}    // Asteroid
下面是AsteroidGroup类
import java.applet.*;
 
public class AsteroidGroup extends ActorGroup2D
{
 public static final int SIZE_LARGE = 0;
 public static final int SIZE_MEDIUM = 1;
 public static final int SIZE_SMALL = 2;
 
 public final int IMAGE_WIDTH = 90;
 public final int IMAGE_HEIGHT = 80;
 
 public AsteroidGroup()
 {
       super();
 
       animations = new AnimationStrip[3];
 }
 
 public void init(Applet a)
 {
       ImageLoader loader;
       int i;
 
       loader = new ImageLoader(a, "asteroid.gif", true);
 
       animations[SIZE_LARGE] = new AnimationStrip();
       animations[SIZE_LARGE].addFrame(loader.extractCell(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT));              
       animations[SIZE_LARGE].setAnimator(new Animator.OneShot());
 
       animations[SIZE_MEDIUM] = new AnimationStrip();
       animations[SIZE_MEDIUM].addFrame(loader.extractCellScaled(
                       0, 0, IMAGE_WIDTH, IMAGE_HEIGHT, 2*IMAGE_WIDTH/3, 2*IMAGE_HEIGHT/3));              
       animations[SIZE_MEDIUM].setAnimator(new Animator.OneShot());
 
       animations[SIZE_SMALL] = new AnimationStrip();
       animations[SIZE_SMALL].addFrame(loader.extractCellScaled(
                       0, 0, IMAGE_WIDTH, IMAGE_HEIGHT, IMAGE_WIDTH/3, IMAGE_HEIGHT/3));              
       animations[SIZE_SMALL].setAnimator(new Animator.OneShot());
 }
 
}    // AsteroidGroup2D
如果计算机配有加速硬件,那么FullscreenTest applet中全屏独占窗口显示的绘制效果会和使用VolatileImage绘制的applet窗体一样好(甚至更好)。这里决定使用三缓冲的BufferStrategy类来绘制。还要注意,Graphics2D绘制容器可以通过BufferStrategy类的getDrawGraphics方法来获得。在场景被绘制完后,show方法会显示当前的活动帧。在每一次BufferStrategy发现其内容变少时都会进行重新绘制。
前面已经提到,全屏模式不仅使用显示器的整个绘制区域,而且允许我们选择显示分辨率。GraphicsDevice类的setFullScreenWindow方法使用一个Frame对虾为全屏显示。一旦结束全屏模式,只需要再一次调用setFullScreenWindow方法,并使用null作为参数,这将把桌面恢复到原来的分辨率和状态。只要建立了全屏窗口,就可以改变分辨率(如果当前设备支持改变显示)。
这里还需要提一下在全屏绘制中需要注意的几个问题。首先,记住全屏绘制是主动进行的。也就是说,它不依赖于操作系统发送的消息,因此,最好让窗体调用Component类的setIgoreRepaint方法来确认所有操作系统级的重要消息立即被程序忽略,这将提高整体性能;其次,在调用Frame类的setUndecorate方法之前,一定要去掉像窗体边框这样的装饰;最后,注意全屏独占模式的游戏可能难于发布,因为这要求用户必须显示地设置允许程序进入全屏独占模式,必须设置这个安全许可,因为那些不道德的代码可能在让程序进入全屏模式后造成极大的破坏,让用户手工设置这样的许可也会让他们感到麻烦(默然:总之一句话,最好不要使用全屏独占模式?!其实,经我自己测试验证,Windows似乎并不支持全屏独占模式!天呀~~)。


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