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

 
<script type="text/javascript"> </script><script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script>
12.2.4         RadioButton2D类
如果用户喜欢Button2D类,那么也肯定会喜欢RadioButton2D类。RadioButton2D模仿CheckBox类,它允许用户区别按钮的“on”和“off”状态。它还允许把几个单选按钮关联到一个组中,这样确保在给定的事件内这个组中只有一个成员处于选中状态。
令人惊讶的是,RadioButton2D类比Button2D要简单一些。首先,它只有两个状态,on和off。我们也可以把它看作选中和没选中,或者其他喜欢的词汇。此外,RadioButton2D在被按下时不需要激发事件。很多应用程序主动地检索单选按钮的状态而不是等待它激发事件。
RadioButton2D类还实现了MouseListener接口,但是没有实现MouseMotionListener接口,这是因为鼠标移动事件已不再适用。
下面是RadioButton2D类,其中的文字注释使得它易于理解。
import  java.awt. * ;
import  java.awt.event. * ;
import  java.awt.geom. * ;
import  java.util. * ;
 
public   class  RadioButton2D  extends  Component2D  implements  MouseListener {
       
//和按钮一起显示的标签
       protected Label2D label;
       
       
//这个按钮所从属的组
       protected RadioButtonGroup rbGroup;
       
       
//跟踪按钮状态的常量
       public static final int BUTTON_OFF=0;
       
public static final int BUTTON_ON=1;
       
       
//用给定的标签,图像,按钮组和位置创建一个RadioButton2D
       public RadioButton2D(Label2D lbl,ImageGroup grp,RadioButtonGroup rbg,
                                                                                                                Vector2D p)
{
              
super();
              
              label
=lbl;
              group
=grp;
              rbGroup
=rbg;
              
              
if(rbGroup!=null){
                     rbGroup.add(
this);
              }

              
              setPos(p);
              updateBounds();
              update();
              setSelected(
false);
       }

       
       
//用给定的图像,按钮组和位置创建一个新的RadioButton2D
       public RadioButton2D(ImageGroup grp,RadioButtonGroup rbg,Vector2D p){
              
this(null,grp,rbg,p);
       }

       
       
//用给定的标签,图像和按钮组创建一个新的RadioButton2D
       public RadioButton2D(Label2D lbl,ImageGroup grp,RadioButtonGroup rbg){
              
this(lbl,grp,rbg,Vector2D.ZERO_VECTOR);
       }

       
       
//用给定的图像组创建一个新的RadioButton2D
       public RadioButton2D(ImageGroup grp){
              
this(null,grp,null,Vector2D.ZERO_VECTOR);
       }

       
       
//返回按钮是否被选中
       public boolean isSelected(){
              
return (state==BUTTON_ON);
       }

       
       
//设置按钮的选中状态
       public void setSelected(boolean selected){
              state
=(selected)?BUTTON_ON:BUTTON_OFF;
       }

       
       
//如果有,返回按钮的文本描述
       public String getText(){
              
if(label!=null){
                     
return label.getText();
              }

              
return "";
       }

       
       
//设置按钮和它的标签的可用状态
       public void setEnabled(boolean e){
              
super.setEnabled(e);
              
              
if(label!=null){
                     label.setEnabled(e);
              }

       }

       
       
//变换按钮的状态
       public void mousePressed(MouseEvent e){
              
if(!isEnabled()||!isVisible())return;
              
              
//如果没有按钮组则表明按钮独立行动
              if(rbGroup==null){
                     
if(bounds.contains(e.getPoint())){
                            setSelected(
!isSelected());
                     }

              }
else{//否则,更新整个按钮组
                     if(bounds.contains(e.getPoint())){
                            setSelected(
true);
                            rbGroup.updateGroup(
this);
                     }

              }

       }

       
       
public void mouseEntered(MouseEvent e){
       }

       
       
public void mouseExited(MouseEvent e){
       }

       
       
public void mouseClicked(MouseEvent e){
       }

       
       
public void mouseReleased(MouseEvent e){
       }

       
       
//用当前的变换绘制按钮
       public void paint(Graphics2D g2d){
              
//只在组件可见时绘制
              if(isVisible()){
                     g2d.drawImage(((ButtonImageGroup)group).getFrame(state),
                                                        xform,AnimationStrip.observer);
                     
                     
//如果标签有效则绘制标签
                     if(label!=null){
                            label.paint(g2d);
                     }

              }

       }

       
       
//在给定的位置绘制按钮
       public void paint(Graphics2D g2d,double dx,double dy){
              
//只绘制可见组件
              if(isVisible()){
                     g2d.drawImage(((ButtonImageGroup)group).getFrame(state),
                                                 AffineTransform.getTranslateInstance(pos.getX()
+dx,
                                                 pos.getY()
+dy),AnimationStrip.observer);
                     
                     
//如果标签有效则绘制标签
                     if(label!=null){
                            label.paint(g2d,dx,dx);
                     }

              }

       }

       
       
//返回描述这个按钮的文本
       public String toString(){
              
if(label!=null){
                     
return super.toString()+" "+label.toString();
              }

              
              
return super.toString();
       }

}
// RadioButton2D
原来看起来好像RadioButton2D类应该继承Button2D类,毕竟单选按钮可以被认为是一种按钮。然而,出于前面提到的考虑,自定义单选按钮类与标准按钮类差异太大,难以直接派生。RadioButton2D将承接太多不必要的包袱,这是每一个人都不希望看到的。此外,在运行时使用new运算符把Button2D对象转变为RadioButton2D对象也没有什么好处,它们完全是功能不同的组件。
RadioButton2D需要的一个特性是外部分组,这样可以实现多选一。自定义的RadioButtonGroup类恰好实现了这个特性,它包含一个可以增长(也可以收缩)的Vector对象来容纳单选按钮。通过这种方式,就无须在事前知道组中会有多少个单选按钮。这个类还包含一些方法,这些方法包括把单选按钮添加到组中,找到当前被选中的单选按钮,设置激活和可视状态属性,更新哪个按钮被选中以及绘制组中的单选按钮。
import  java.awt. * ;
import  java.util. * ;
 
// 维护一组按钮,这样在任意时刻只有一个被选中
public   class  RadioButtonGroup {
       
//用来装单选按钮的动态可增长Vector
       protected Vector buttons;
       
       
//遍历上述Vector的枚举器
       protected Enumeration e;
       
       
//创建一个新的RadioButtonGroup对象
       public RadioButtonGroup(){
              buttons
=new Vector();
       }

       
       
//将传入的单选按钮添加到Vector中去
       public void add(RadioButton2D rb){
              buttons.add(rb);
       }

       
       
//得到当前所选中的单选按钮
       
//如果没有按钮被选中,则返回null
       public RadioButton2D getSelection(){
              
for(e=buttons.elements();e.hasMoreElements();){
                     RadioButton2D rb
=(RadioButton2D)e.nextElement();
                     
                     
if(rb.isSelected()){
                            
return rb;
                     }

              }

              
              
return null;
       }

       
       
//整组按钮的可用/不可用状态
       public void setEnabled(boolean b){
              
for(e=buttons.elements();e.hasMoreElements();){
                     ((RadioButton2D)e.nextElement()).setEnabled(b);
              }

       }

       
       
//设置整个按钮组的可见性
       public void setVisible(boolean v){
              
for(e=buttons.elements();e.hasMoreElements();){
                     ((RadioButton2D)e.nextElement()).setVisible(v);
              }

       }

       
       
//更新按钮组,让除了传入的按钮之外的按钮设置为未选中状态
       public void updateGroup(RadioButton2D rb){
              
for(e=buttons.elements();e.hasMoreElements();){
                     Object o
=e.nextElement();
                     
if(rb!=o){
                            ((RadioButton2D)o).setSelected(
false);
                     }

              }

       }

       
       
//绘制组中的每一个按钮
       public void paint(Graphics2D g2d){
              
for(e=buttons.elements();e.hasMoreElements();){
                     ((RadioButton2D)e.nextElement()).paint(g2d);
              }

       }

       
       
//在给定的位置绘制组中的每一个按钮
       public void paint(Graphics2D g2d,double dx,double dy){
              
for(e=buttons.elements();e.hasMoreElements();){
                     ((RadioButton2D)e.nextElement()).paint(g2d,dx,dy);
              }

       }

}
// RadioButtonGroup
这个程序简洁而高效,那么怎么用一种比较好的方式在一个代码例子中演示单个和成组单选按钮呢?下面的RadioButton2DTest applet,包含了一个单选按钮组,它允许用户从几种超能力中作出选择,它还包含一个独立的单选按钮,它可以使包含超能力选择的单选按钮组激活或者失效。这个演示比较有趣,建议读者试一下。
import  java.applet. * ;
import  java.awt. * ;
 
public   class  RadioButton2DTest  extends  Applet  implements  Runnable {
       
//动画线程
       private Thread animation;
       
       
private BufferedGraphics offscreen;
       
       
//是否禁用绝技列表的单选按钮
       private RadioButton2D singleRB;
       
       
//跟踪绝技是否可用
       private boolean powersEnabled;
       
       
//放置绝技列表的按钮组
       private RadioButtonGroup rbGroup;
       
       
//可用的绝技的字符串描述
       private final String[] POWERS={"火球","超踢","酸暴","电击","剃刀爪"};
       
private final int NUM_BUTTONS=POWERS.length;
       
       
//描述选择状态的标签
       private Label2D status;
       
       
public void init(){
              
//为按钮创建一个按钮组
              ButtonImageGroup group=new ButtonImageGroup(2,"radio.gif");
              group.init(
this);
              
              Label2D label;
              Font font
=new Font("Hevetica",Font.PLAIN,18);
              
              
//创建切换绝技是否可用的单选按钮
              label=new Label2D(font,"允许绝技",Color.white);
              singleRB
=new RadioButton2D(label,group,null,new Vector2D.Double(50,50));
              label.centerOn(singleRB.getBounds(),(Graphics2D)getGraphics());
              label.setX(singleRB.getX()
+singleRB.getBounds().getWidth()+5);
              singleRB.setSelected(
true);
              powersEnabled
=singleRB.isSelected();
              addMouseListener(singleRB);
              
              
//创建容纳不同绝技的单选按钮组
              RadioButton2D rb;
              rbGroup
=new RadioButtonGroup();
              
for(int i=0;i<NUM_BUTTONS;i++){
                     label
=new Label2D(font,POWERS[i],Color.white);
                     
                     rb
=new RadioButton2D(label,group,rbGroup,
                                                               
new Vector2D.Double(100,100+(i*35)));
                     label.centerOn(rb.getBounds(),(Graphics2D)getGraphics());
                     label.setX(rb.getBounds().getX()
+rb.getBounds().getWidth()+5);
                     addMouseListener(rb);
                     
                     
//默认情况下,第0个按钮应该被选中
                     if(i==0) rb.setSelected(true);
              }

              
              
//用初始的空白字符串创建状态标签
              status=new Label2D(font,"",new Color(0,255,255));
              status.setPos(
new Vector2D.Double(50,325));
              
              offscreen
=new BufferedGraphics(this);
              
              AnimationStrip.observer
=this;
       }
//init
       
       
public void start(){
              
//启动动画线程
              animation=new Thread(this);
              animation.start();
       }

       
       
public void stop(){
              animation
=null;
       }

       
       
public void run(){
              Thread t
=Thread.currentThread();
              
while(t==animation){
                     repaint();
                     
                     
try{
                            Thread.sleep(
10);
                     }
catch(InterruptedException e){
                            
break;
                     }

              }

       }
//run
       
       
public void update(Graphics g){
              
//如果独立的那个选项改变,则切换绝技选项组状态
              if(powersEnabled!=singleRB.isSelected()){
                     powersEnabled
=singleRB.isSelected();
                     rbGroup.setVisible(powersEnabled);
              }

              
              
//更新标签的描述
              if(powersEnabled==true&&rbGroup.getSelection()!=null){
                     status.setText(
"已选绝技:"+rbGroup.getSelection().getText());
              }
else{
                     status.setText(
"禁用绝技");
              }

              paint(g);
       }

       
       
public void paint(Graphics g){
              Graphics2D bg
=(Graphics2D)offscreen.getValidGraphics();
              bg.setPaint(Color.black);
              bg.fillRect(
0,0,getSize().width,getSize().height);
              
              
//绘制单独的那个按钮
              singleRB.paint(bg);
              
              
//绘制按钮组
              rbGroup.paint(bg);
              
              
//绘制状态标签
              status.paint(bg);
              
              g.drawImage(offscreen.getBuffer(),
0,0,this);
       }
//paint
}
// RadioButton2DTest
可以看到,并不是所有的单选按钮都必须放在一个组中,单选按钮不仅可以把一个选项从多个选项中分离出来,而且可以作为二元属性的标志。单选按钮可以激活或者禁止其他的按钮或者菜单,切换声音,控制绘画质量等。所以,如果要让用户作出选择,也可以使用自定义RadioButton2D类(或者自己设计的单选按钮),并使用自己的图片。
现在,已经展示了可以直接放入游戏中的几个不同的自定义组件。在本章最后一节,我们将看看如何创建面板和菜单这样的自定义容器,这些容器可以容纳其他的像标签和按钮这样的自定义组件。下面将从容纳组件的基类——Container2D开始。
12.2.5         创建Container2D类
在本节中,我们将专注于创建对应于原始的Compnonet体系那样的自定义组件系统,将通过模仿Container类的Container2D类来达到这个目的。回顾一下,Container类是从Component类扩展功能的,同样地,自定义的Container2D也从Component2D类扩展功能。Container2D的目标是尽可能模拟Container类的功能。下面将提供实现这个类的框架,并在章节练习中提出一些建议,让读者使这个类变得完整而牢靠。
像Panel这样的类虽然容纳其他对象,但是它们自身也是可视化的,例如,可以设置panel,window或者frame的背景颜色。我们将赋予Container2D,Panel2D和Menu2D的子类以定义背景图像的能力,这些图像将在它们的组件底下绘制。本书把使用Paint填充背景的任务作为章节练习留给读者自己来完成。
现在让我们看一下Container2D类的代码。注意,其中大多数方法还没有完成,这样使得它成为一个抽象类。
import  java.awt. * ;
import  java.util. * ;
 
// 定义一个可以容纳其他组件的容器
public   abstract   class  Container2D  extends  Component2D {
       
//容纳组件的动态Vector,以及所有组件的枚举
       protected Vector components;
       
protected Enumeration e;
       
       
//使用传入的背景图像和位置创建一个新的Container2D对象
       protected Container2D(Vector2D p){
              
super();
              
              components
=new Vector();
              
              pos
=p;
              
if(pos==null){
                     pos
=new Vector2D.Double();
              }

              
              updateBounds();
       }

       
       
//将传入的组件添加到指定的位置
       public abstract void add(Component2D c,double x,double y);
       
       
//更新容器的边界
       public abstract void updateBounds();
}
// Container2D
虽然Container2D继承自Component2D,这里还是决定覆盖它的updateBounds方法并重新把它声明为abstract。由于子类很可能使用Image对象而不是ImageGroup对象来作为它们的背景,它们可能需要相应地定义自己的边界。还要注意,这里也没有定义Component2D的两个绘制方法,因此,Container2D的子类可能还需要负责定义这两个方法。
在开始构建Container2D类的子类之前,需要指出添加到容器中的组件应该参照组件的原点添加,而不是参照Applet或者Window的原点。这是Container2D类的whole point:在它们边界内实际保持它的子组件。
下面来实现第一个继承Container2D的类:Panel2D。
12.2 .6          Panel2D类
在第5章中已经讲过,Java Panel类对于把相关的组件从视觉上组合到一起是很好的。
前面已经看到了如何创建Container2D类来在父容器内摆放组件,现在可以实现一个类,让这个类来实际实现添加和绘制组件的方法。我们要看的第一个实现这个功能的类就是Panel2D类。像Panel类一样,添加到Panel2D对象中的组件摆放在其内部。Panel2D类的好处在于,组件可以添加到指定的x,y位置,这给了我们在组件布局上的控制权。
Panel2D类的另一个好处是,可以指定一个Image对象,让它作为面板的背景,逻辑上,总应该在绘制子组件前绘制背景图像。
下面的代码清单是完整的Panel2D类。它定义了所有Component2D和Container2D类所声明但没有完成的方法,这使得它成为一个完整的并且可以使用的类。
import  java.awt. * ;
import  java.awt.geom. * ;
import  java.util. * ;
 
// 定义一个可以容纳其他组件的面板
public   class  Panel2D  extends  Container2D {
       
//背景图片
       protected Image background;
       
       
//用指定的背景图片和位置创建一个新的Panel2D对象
       protected Panel2D(Image bgImage,Vector2D p){
              
super(p);
              
              background
=bgImage;
       }

       
       
//在面板上指定的位置添加组件
       
//注意所传入的x,y值是相对于面板原点的(左上角)
       public void add(Component2D c,double x,double y){
              c.setX(getX()
+x);
              c.setY(getY()
+y);
              
              
//确保组件在面板边界之内
              if(c.getX()+c.getBounds().getWidth()>
                                                                      getX()
+getBounds().getWidth()){
                     c.setX(getX()
+getBounds().getWidth()-
                                                                      c.getBounds().getWidth());
              }

              
              
if(c.getY()+c.getBounds().getHeight()>
                                                                      getY()
+getBounds().getHeight()){
                     c.setY(getY()
+getBounds().getHeight()-
                                                                      c.getBounds().getHeight());
              }

              
              c.update();
              c.updateBounds();
              components.add(c);
       }

       
       
//绘制面板和它的组件
       public void paint(Graphics2D g2d){
              
//绘制背景
              if(background!=null){
                     g2d.drawImage(background,(
int)getX(),(int)getY(),
                                                                                    AnimationStrip.observer);
              }

              
              
//绘制面板上的组件
              for(e=components.elements();e.hasMoreElements();){
                     ((Component2D)e.nextElement()).paint(g2d);
              }

       }

       
       
//根据指定的偏移值绘制面板和它的组件
       public void paint(Graphics2D g2d,double dx,double dy){
              
if(background!=null){
                     g2d.drawImage(background,(
int)(getX()+dx),(int)(getY()+dy),
                                                                                    AnimationStrip.observer);
              }

              
              
for(e=components.elements();e.hasMoreElements();){
                     ((Component2D)e.nextElement()).paint(g2d,dx,dy);
              }

       }

       
       
//更新面板的边界,如果面板没有图片,则不会尝试去计算面板的边界
       public void updateBounds(){
              
if(background!=null){
                     frameWidth
=background.getWidth(AnimationStrip.observer);
                     frameHeight
=background.getHeight(AnimationStrip.observer);
              }
else{
                     frameWidth
=Integer.MAX_VALUE;
                     frameHeight
=Integer.MAX_VALUE;
              }

              
              bounds.setRect(pos.getX(),pos.getY(),frameWidth,frameHeight);
       }

}
// Panel2D
正如前面提到的那样,加到Panel2D对象中的组件是参照面板的原点添加的。所以,如果面板位于(100,100)并且按钮被加载到面板的(20,20),则按钮的实际位置是(120,120).如果按钮的最终位置把它放在面板之外,则它会被重新放到面板的范围以内。本章结尾的练习将要求对Container2D和Panel2D作一些改进,比如面板的位置发生了变化,则让其中的组件也相应变化。
注意,Panel2D类在需要判断背景图像的大小时使用静态属性AnimationStrip.observer。所以,要记住在调用observer属性之前需要在applet的init方法中设置这个属性。
下面是一个简单的例子,Panel2DTest applet创建了两个面板并在每一个相同的逻辑位置上添加一些组件。在运行这个applet时,会发现组件是参照父容器摆放的。
import  java.applet. * ;
import  java.awt. * ;
 
public   class  Panel2DTest  extends  Applet  implements  Runnable {
       
//动画线程
       private Thread animation;
       
       
private BufferedGraphics offscreen;
       
       
//用于容纳其他组件的Panel2D对象数组
       private Panel2D[] panels;
       
       
//摆放上述Panel2D对象的坐标
       private Vector2D[] panelPos={new Vector2D.Double(10,10),
                                                               
new Vector2D.Double(200,100)}
;
       
private final int NUM_PANELS=panelPos.length;
       
       
public void init(){
              
//创建一个摆放透明按钮的图像组
              ButtonImageGroup biGroup=new ButtonImageGroup(3,"xpbuttons.gif");
              biGroup.init(
this);
              
              
//为单选按钮创建一个图像组
              ButtonImageGroup rbGroup=new ButtonImageGroup(2,"radio2.gif");
              rbGroup.init(
this);
              
              
//记住设置observer属性,这样面板可以访问它的背景图像的宽和高
              AnimationStrip.observer=this;
              
              
//按钮,单选按钮和标签,用来创建组件来添加到我们的面板上
              Button2D button;
              RadioButton2D radioButton;
              Label2D label;
              
              Font font
=new Font("Helvetica",Font.PLAIN,16);
              
              
//为面板创建背景图像
              Image img=new ImageLoader(this,"panel.gif",true).getImage();
              
              
//创建面板,并把它们添加到画面上
              panels=new Panel2D[NUM_PANELS];
              
for(int i=0;i<NUM_PANELS;i++){
                     
//用背景图像和索引给定的位置创建面板
                     panels[i]=new Panel2D(img,panelPos[i]);
                     
                     
//在面板上添加单选按钮和两个一般按钮
                     radioButton=new RadioButton2D(null,rbGroup,null);
                     panels[i].add(radioButton,
95,95);
                     radioButton.setSelected(
true);
                     addMouseListener(radioButton);
                     
                     label
=new Label2D(font,"Java!",Color.white);
                     button
=new Button2D(label,biGroup);
                     panels[i].add(button,
25,25);
                     label.centerOn(button.getBounds(),(Graphics2D)getGraphics());
                     addMouseListener(button);
                     addMouseMotionListener(button);
                     
                     label
=new Label2D(font,"Java!",Color.white);
                     button
=new Button2D(label,biGroup);
                     panels[i].add(button,
130,185);
                     label.centerOn(button.getBounds(),(Graphics2D)getGraphics());
                     addMouseListener(button);
                     addMouseMotionListener(button);
              }

              
              offscreen
=new BufferedGraphics(this);
       }
//init
       
       
public void start(){
              
//启动动画线程
              animation=new Thread(this);
              animation.start();
       }

       
       
public void stop(){
              animation
=null;
       }

       
       
public void run(){
              Thread t
=Thread.currentThread();
              
while(t==animation){
                     repaint();
                     
                     
try{
                            Thread.sleep(
10);
                     }
catch(InterruptedException e){
                            
break;
                     }

              }

       }
//run
       
       
public void update(Graphics g){
              paint(g);
       }

       
       
public void paint(Graphics g){
              Graphics2D bg
=(Graphics2D)offscreen.getValidGraphics();
              bg.setPaint(Color.black);
              bg.fillRect(
0,0,getSize().width,getSize().height);
              
              
//绘制面板
              for(int i=0;i<NUM_PANELS;i++){
                     panels[i].paint(bg);
              }

              
              g.drawImage(offscreen.getBuffer(),
0,0,this);
       }
//paint
       
}
// Panel2DTest
Panel2D applet使用了标准动画循环并主动更新和绘制面板。那些面板依次更新和绘制它们的孩子组件。
下面看看另外一个Container2D类:Menu2D类,它继承自Panel2D类。
12.2.7         使用Menu2D创建自定义菜单
当游戏变得更复杂并拥有更多特性时,使用一个自定义的菜单系统无疑会比较方便。
菜单的另一个重要的特性是,一旦菜单被调用,一般它会得到用户的全部注意。所有其他的游戏事件都应该暂时挂起。在控制菜单的更新和绘制时一定要记住这个。
下面决定Panel2D为Menu2D来创建自定义菜单系统。由于Panel2D类已经具备了在边界内容纳组件的特性,并且可以指定它自己的背景图像,所以Menu2D类写起来应该很简单。粗略看来,Panel2D类对于作为菜单使用已经足够了,然而,应该给它添加一个比较大的特性,那就是覆盖。如果一个特定的菜单是一个覆盖菜单,那么它将在当前画面的顶层绘制。所以,如果菜单只有画面的一半大小,背景上面的内容依然是可见的,它就是惟一需要绘制的物体。当不希望用户在菜单显示时可以看见游戏活动时,这个特性是很方便的。例如,在一个计时的迷宫游戏中,很可能不希望用户在时间快要用尽的情况下使用选择一个菜单的方法暂停游戏来思考下一步应该如何移动。给Menu2D类添加一个覆盖标志可以指定菜单的表现方式。
记住这个后,看下面的Menu2D类的代码。这个类确实很简单,可以自己输入代码。
import  java.awt. * ;
 
// 提供一个可以在画面上展现的简单菜单类
public   class  Menu2D  extends  Panel2D {
       
//判断菜单只是展现在画面上还是它决定绘制循环
       protected boolean overlay;
       
       
//使用指定的背景图片,位置和覆盖属性值构建一个新的Menu2D对象
       protected Menu2D(Image bgImage,Vector2D p,boolean over){
              
super(bgImage,p);
              
              overlay
=over;
       }

       
       
public final boolean isOverlay(){
              
return overlay;
       }

}
// Menu2D
这里决定覆盖Panel2D类的另一个原因是因为想维护Menu2D类的命名机制。菜单是一个独立于画面表现的组件,它在激活时是获得焦点的。然而另一方面,面板是一个功能组件,它可以共存于画面之中而且不会使游戏活动暂停。简言之,菜单是一种面板,而面板不是一种菜单。
现在对于什么是菜单的职责已经有了一个很好的认识,那么该怎样实际实现一个自定义的菜单系统呢?显然,需要一种方法来从一个菜单跳到另一个菜单并记住移动的轨迹以防需要重新访问一个菜单,这里使用菜单栈来管理菜单移动(如果需要复习Java Stack类是如何工作的,可以回头参看第4章)。通过使用菜单栈,可以在菜单被调用时把它压入栈,然后在它被关掉时把它从栈中弹出。
在绘制画面时,这里使用了下面的算法来相应判断将要绘制什么:
< 清空绘画的表面 >
if ( < 菜单栈非空 > ) {
       
if(<栈顶菜单是一个覆盖菜单>){
              
<绘制画面>
       }

       
<绘制栈顶菜单>
}
else {
       
<绘制画面>
}

根据这个程序,在清空绘制表面后,必须首先检查菜单栈是否为空。如果菜单栈中有菜单,那么检查栈顶菜单是否是一个覆盖菜单。如果是一个覆盖菜单,那么在绘制菜单前需要绘制画面,否则,只需绘制菜单自身。如果菜单栈是空的,那么只需按照正常情况绘制画面,在update方法中也可以使用类似的算法。如果菜单栈至少含有一个菜单,可能需要禁止任何游戏进程来调用paint绘制这一帧;否则,只需照常更新画面。
让我们通过看一个演示多个菜单使用的applet巩固一下这些内容。下面的Menu2DTest applet包含3个不同的菜单,每一个会参照3个相同的按钮。每一个按钮允许展示这3个菜单中的一个。正常地调用当前活动菜单的按钮是禁止的,所以,同样的菜单不能反复调用自身。而且,可以按下Escape键来关掉当前活动的菜单,或者在菜单栈已经空了的情况下显示默认菜单。
import  java.applet. * ;
import  java.awt. * ;
import  java.awt.event. * ;
import  java.util. * ;
 
public   class  Menu2DTest  extends  Applet  implements  Runnable,KeyListener {
       
//动画线程
       private Thread animation;
       
private BufferedGraphics offscreen;
       
       
//给applet的几个例子
       private Menu2D menuA;
       
private Menu2D menuB;
       
private Menu2D menuC;
       
       
//放在上面的菜单上的按钮
       private Button2D goToA;
       
private Button2D goToB;
       
private Button2D goToC;
       
       
//容纳菜单的栈
       private Stack menuStack;
       
       
public void init(){
              AnimationStrip.observer
=this;
              
              
//获取一个Graphics2D容器来使标签中对齐
              Graphics2D g2d=(Graphics2D)getGraphics();
              
              
//为菜单按钮创建一个图像组
              ButtonImageGroup group=new ButtonImageGroup(3,"xpbuttons.gif");
              group.init(
this);
              
              
//设置导航按钮
              Label2D label;
              Font font
=new Font("Helvetica",Font.PLAIN,18);
              
              label
=new Label2D(font,"Menu A",Color.black);
              label.setDisabledPaint(Color.white);
              goToA
=new Button2D(label,group);
              label.centerOn(goToA.getBounds(),g2d);
              addMouseListener(goToA);
              addMouseMotionListener(goToA);
              
              label
=new Label2D(font,"Menu B",Color.black);
              label.setDisabledPaint(Color.white);
              goToB
=new Button2D(label,group);
              label.centerOn(goToB.getBounds(),g2d);
              addMouseListener(goToB);
              addMouseMotionListener(goToB);
              
              label
=new Label2D(font,"Menu C",Color.black);
              label.setDisabledPaint(Color.white);
              goToC
=new Button2D(label,group);
              addMouseListener(goToC);
              addMouseMotionListener(goToC);
              
              
//创建监听器类
              ActionListener listener=new ActionListener(){
                     
//根据哪个按钮被按下而把合适的菜单放到栈中
                     public void actionPerformed(ActionEvent e){
                            
if(goToA==e.getSource()){
                                   
//显示菜单A
                                   menuStack.push(menuA);
                                   updateButtonSettings();
                            }
else if(goToB==e.getSource()){
                                   
//显示菜单B
                                   menuStack.push(menuB);
                                   updateButtonSettings();
                            }
else if(goToC==e.getSource()){
                                   
//显示菜单C
                                   menuStack.push(menuC);
                                   updateButtonSettings();
                            }

                     }

              }
;
              
              goToA.addActionListener(listener);
              goToB.addActionListener(listener);
              goToC.addActionListener(listener);
              
              
//创建自定义菜单
              Label2D header;
              
final Vector2D pos=new Vector2D.Double(100,50);
              
              menuA
=new Menu2D(new ImageLoader(this,"menuA.gif",true).getImage(),
                                                        pos,
false);
              header
=new Label2D(font,"Menu A",Color.black);
              menuA.add(header,
0,0);
              header.centerOn(menuA.getBounds(),g2d);
              header.setY(menuA.getY()
+header.getBounds().getHeight()+5);
              menuA.add(goToA,
78,50);
              menuA.add(goToB,
78,120);
              menuA.add(goToC,
78,190);
              
              menuB
=new Menu2D(new ImageLoader(this,"menuB.gif",true).getImage(),
                                                        pos,
true);
              header
=new Label2D(font,"Menu B",Color.black);
              menuB.add(header,
0,0);
              header.centerOn(menuB.getBounds(),g2d);
              header.setY(menuB.getY()
+header.getBounds().getHeight()+5);
              menuB.add(goToA,
78,50);
              menuB.add(goToB,
78,120);
              menuB.add(goToC,
78,190);
              
              menuC
=new Menu2D(new ImageLoader(this,"menuC.gif",true).getImage(),
                                                        pos,
false);
              header
=new Label2D(font,"Menu C",Color.black);
              menuC.add(header,
0,0);
              header.centerOn(menuC.getBounds(),g2d);
              header.setY(menuC.getY()
+header.getBounds().getHeight()+5);
              menuC.add(goToA,
78,50);
              menuC.add(goToB,
78,120);
              menuC.add(goToC,
78,190);
              
              goToA.centerLabel(g2d);
              goToB.centerLabel(g2d);
              goToC.centerLabel(g2d);
              
              
//创建我们的菜单栈
              menuStack=new Stack();
              goToA.setEnabled(
false);
              goToB.setEnabled(
true);
              goToC.setEnabled(
true);
              
              
//完成一般的applet初始化工作
              offscreen=new BufferedGraphics(this);
              
              addKeyListener(
this);
       }
//init
       
       
//禁止那些会调用当前活动菜单的按钮     
       private void updateButtonSettings(){
              
if(menuStack.empty())return;
              
              Menu2D menu
=(Menu2D)menuStack.peek();
              
              
if(menuA==menu){
                     goToA.setEnabled(
false);
                     goToB.setEnabled(
true);
                     goToC.setEnabled(
true);
              }
else if(menuB==menu){
                     goToA.setEnabled(
true);
                     goToB.setEnabled(
false);
                     goToC.setEnabled(
true);
              }
else if(menuC==menu){
                     goToA.setEnabled(
true);
                     goToB.setEnabled(
true);
                     goToC.setEnabled(
false);
              }

       }

       
       
public void start(){
              
//启动动画线程
              animation=new Thread(this);
              animation.start();
       }

       
       
public void stop(){
              animation
=null;
       }

       
       
public void run(){
              Thread t
=Thread.currentThread();
              
while(t==animation){
                     repaint();
                     
                     
try{
                            Thread.sleep(
10);
                     }
catch(InterruptedException e){
                            
break;
                     }

              }

       }
//run
       
       
public void update(Graphics g){
              paint(g);
       }

       
       
public void paint(Graphics g){
              Graphics2D bg
=(Graphics2D)offscreen.getValidGraphics();
              bg.setPaint(Color.black);
              bg.fillRect(
0,0,getSize().width,getSize().height);
              
              
//确保我们的文本看起来清晰
              bg.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
                                                        RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
              
//如果栈不为空,则处理顶上的菜单
              if(!menuStack.empty()){
                     
//只是在顶层按钮是一个覆盖菜单时才绘制画面
                     if(((Menu2D)menuStack.peek()).isOverlay()){
                            paintScene(bg);
                     }

                     
//在其他的绘制完成后绘制菜单
                     ((Menu2D)menuStack.peek()).paint(bg);
              }
else{
                     paintScene(bg);
              }

              
              g.drawImage(offscreen.getBuffer(),
0,0,this);
       }
//paint
       
       
public void paintScene(Graphics2D g2d){
              
//对于一个实际的画面,绘制一个渐变的paint来作为占位符
              g2d.setPaint(new GradientPaint(0.0f,0.0f,Color.black,
                                          (
float)getSize().width,(float)getSize().height,
                                                                                                  Color.white));
              g2d.fillRect(
0,0,getSize().width,getSize().height);
       }

       
       
public void keyPressed(KeyEvent e){
       }

       
       
public void keyReleased(KeyEvent e){
       }

       
       
//更新菜单栈的顺序
       public void keyTyped(KeyEvent e){
              
//退格会控制菜单顺序
              if(e.getKeyChar()==KeyEvent.VK_ESCAPE){
                     
//如果菜单不是空,则删除顶层元素
                     if(!menuStack.empty()){
                            menuStack.pop();
                            updateButtonSettings();
                     }
else{//否则,把菜单A放到栈上
                            menuStack.push(menuA);
                            updateButtonSettings();
                     }

              }

       }

       
}
// Menu2DTest
如我们所见,按下不同的按钮会把菜单的引用压入栈中,按下Escape键释放栈。因此,可以随便按按钮来调用3个不同的菜单;也可以在按照每一次创建的顺序在菜单系统中退回。
还要注意,在这3个菜单中,只有菜单B是覆盖菜单。为了模仿一个背景画面,这里使用了一个GradientPaint对象,这样可以看出覆盖和非覆盖菜单之间的差异。
好了,关于自定义组件和菜单系统的讲解到这里就结束了。虽然它和原始的AWT不是完全对应,但是它可以被一般的AWT程序员快速掌握和使用。Component2D和Container2D类都是实现自定义组件系统的关键。正如在Menu2D类中所见到的那样,让基类做大部分的工作会让我们轻而易举地创造出强大的基类。
这里把可能改善自定义组件系统的功能的特性留下来,这样,读者可以发挥创造性来改进它。关于改进的几个建议在下面的练习中列出来了,希望读者试一下。在创造这些自定义组件时,希望读者在出于自己的目的改进这些组件时能得到乐趣。
12.1除了自定义Container2D类,我们还对Component2D类进行了扩展,创建了自定义的按钮,单选按钮和变迁类。请扩展Component2D类的功能,创建自定义的Choice2D类和TextField2D类,这些类应该具备和对应的原始AWT类类似的功能。对于TextField2D类,Java Awt提代了很多绘制文本和在文本中插入符号字符的字体工具。如果读者认为需要的话,可以自由地添加任何其他的Component2D类。
12.2对Component2D类再添加一个parent属性,它指定当前容纳它的Container2D对象。如果组件没有parent,这个属性应该被设为null。在组件被添加到它的父容器中时要确保这个属性被更新了。对于程序逻辑而言,知道哪一个对象拥有一个特定的组件,这一点在什么时候会变得有用呢?
12.3修改Container2D类,让它在自己位置变化时更新子组件的位置。这个可以简单地通过在设置新位置前计算位置的改变,然后对每一个组件移动这个偏移量来实现。在做这个练习时,由于已经把parent属性添加到Component2D类中,只要容器的位置发生变化,就让组件参照容器的边界移动。大家将发现只要一次实现这些方法,以后就无须再次做这些事情。这也会进一步巩固Container2D对象在任何时候总会把它的子对象放在边界之内的约定。
12.4考虑使用一个Shape对象(比如Component2D类的bounds属性)来作为绘制Containter2D对象容纳的组件的剪切区域。这将可以把add方法修改为只需要所添加的组件在边界矩形之内,而宽和高可以超出边界,所添加组件的超出部分不必绘制。
12.5为Container2D对象创建一个拖动机制,这样容器可以在窗体上手工拖动。这需要定义一个矩形区域,通常位于容器的上边,它可以捕获鼠标按下和鼠标拖动事件。记住,需要同时更新组件中的子对象。还应该创建一个boolean标志来决定一个特定的组件是可拖动的还是应该保持位置不变的( 默然:我总觉得容器不应该被拖动吧?似乎在我的概念里,只有组件可以被拖动,而容器不能。)。
12.6为了缓解applet执行菜单中包含的组件所发送的事件的直接需要,试着写一个菜单适配器接口,让它提供在菜单触发一个事件时执行动作的方法。这些接口可以扩展为内部类或者匿名内部类,这样它们可以使用applet已经提供的现有属性。
12.7再创建一个Label2D的centerOn方法,它以一个Point2D对象而不是一个Rectangle2D对象作为参数,像下面的原型所显示的这样:
public void centerOn(Rectangle2D r,Graphics2D g2d)
12.8修改Label2D类,让它实现把自己绘制为一个Image对象并使用这个Image对象来绘制。还可以尝试实现一个位图字体系统,这样标签可以使用预先定义的字体图像来绘制。记住,在每一次一个像Paint或者文本串这样的内部属性发生变化时,可能需要重新绘制图像,读者可以根据需要添加任何方法,这个方法会不会提高整体性能?为什么?
12.9修改Panel2D类,让它提供几个不同的背景绘制模式,即
q 一个plain-vanilla背景图片,正如已经见过的。
q 一个Paint对象,比如ColorGradientPaint。
q 一个被Paint对象影响的Image对象(比如为图像平铺准备的TexturePaint)和完全不要背景绘制。
12.10试着开发几个自定义布局管理器,让这些自定义布局管理器模仿像FlowLayout,GridLayout和BorderLayout这样的现有的比较常用的Java布局管理器的功能。注意给这些类提供合适的参数,比如组件间隔等。

 

您好:
    当您在阅读和使用我所提供的各种内容的时候,我非常感谢,您的阅读已是对我最大的支持。
    我更希望您能给予我更多的支持。
    1.希望您帮助我宣传我的博客,让更多的人知道它,从中获益(别忘记了提醒他们帮我点点广告,嘿嘿)。
    2.希望您能多提出宝贵意见,包括我所提供的内容中的错误,建设性的意见,更希望获得哪些方面的帮助,您的经验之谈等等。
    3.更希望能得到您经济上的支持。
   
    我博客上面的内容均属于个人的经验,所有的内容均为开源内容,允许您用于任何非商业用途,并不以付费为前提,如果您觉得在阅读和使用我所提供的各种内容的过程中,您得到了帮助,并能在经济上给予我支持,我将感激不尽。

    您可以通过点击我网站上的广告表示对我的支持。

    您可以通过银行转帐付款给我:
    招商银行一卡通:
    卡号:6225888712586894
    姓名:牟勇
   
    您也可以通过汇款的方式:
    通讯地址:云南省昆明市女子(28)中学人民中路如意巷1号
    收信人:陈谦转牟勇收
    邮编:650021
   
    无论您给予我怎么样的支持,我都衷心的再次感谢。
    欢迎光临我的博客,欢迎宣传我的博客
    http://blog.csdn.net/mouyong
    http://blog.sina.com.cn/mouyong
    EMail:mouyong@yeah.net
    QQ:11167603
    MSN:mouyong1973@hotmail.com

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

默然说话

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值