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

本文介绍了Java Applet中的组件和容器概念,包括不同类型的容器及其布局管理器的使用方法。通过具体示例展示了如何利用自定义组件来增强Applet的功能,并探讨了如何创建更复杂的用户界面。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 
一个容器可以容纳另外一个组件——甚至是另一个容器。
容器分为两个大类:一类是可以自由移动的(Window),另一类保持在一个固定的位置(Panel)。
                                                        Component
                                                               |
                                                        Container
                                          Window————+————Panel
                                    Dialog—+—Frame                 |
                                          |                                       Applet
                                   FileDialog
可以把几个Panel或者Container对象附着在一个父Container对象上,每一个附着在父Container上的对象可以拥有它自己的布局管理器,使用这个模型,可以在applet上创建一些更精致的布局。
下面的程序PanelTest,演示了一个Panel布局。其中,创建了两个Panel对象,每一个都有一个不同的布局方式。在每一个Panel上都添加了几个Button对象,然后这两个Panel被放到了applet中,这个applet使用默认的FlowLayout布局管理器来显示这两个Panel。
import java.awt.*;
import java.applet.*;
 
public class PanelTest extends Applet{
       public void init(){
              //设置applet的布局为默认的FlowLayout
              this.setLayout(new FlowLayout());
             
              //创建一个2×2的GridLayout的Panel,并添加4个按钮
              //然后把这个Panel放到applet上
              Panel p1=new Panel();
              p1.setLayout(new GridLayout(2,2));
              p1.add(new Button("B1"));
              p1.add(new Button("B2"));
              p1.add(new Button("B3"));
              p1.add(new Button("B4"));
              add(p1);
             
              //创建第二个Panel,布局为BorderLayout,并添加5个按钮
              //然后把这个Panel放到applet上
              Panel p2=new Panel();
              p2.setLayout(new BorderLayout());
              p2.add(new Button("North"),BorderLayout.NORTH);
              p2.add(new Button("South"),BorderLayout.SOUTH);
              p2.add(new Button("East"),BorderLayout.EAST);
              p2.add(new Button("West"),BorderLayout.WEST);
              p2.add(new Button("Center"));
              add(p2);
       }
}
由于上面的程序没有保存每一个按钮的引用,所以不能捕获按钮产生的事件。当然一个实际的applet会实现ActionListener并且把按钮的引用保存为private的成员,上面的例子只是一个简单的示例。上面程序的运行结果嵌在一个150×125的窗体中。
假设正在开发一个用户可以定制其角色的角色扮演游戏。角色扮演游戏的角色开发中的一个重要方面是技能设置,给定一个初始数目的“技能点”,用户可以把点设置到不同的技能中去。一个属性得到的技能点越多,该角色的这种技能就越强。
下面的AttributeTest applet允许用户把10个技能点分配到4种技能中:力量,智慧,敏捷和魔法。这个applet的代码引入了两个类:AttributeButton和AttributePanel,它们通过继承基本AWT组件来增强程序的功能。AttributeButton继承Button类,添加了一种把它自己和AttributePanel联系起来并更新自己的内容的方法;AttributePanel类包含一个属性的描述以及分配给这个属性的技能点,它还包含两个分配属性点数的AttributeButton对象。用户可以反复修改各个属性所分配的点数直到满意为止。
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
 
//允许改变属性
class AttributeButton extends Button{
       //放这个按钮的Panel
       private AttributePanel parent;
      
       public AttributeButton(String label,AttributePanel ap){
              super(label);
              parent=ap;
       }
      
       //更新父控件的属性
       public int updatePanel(int pointsRemaining){
              //点+号为plus按钮分配一个点值
              if(this.getLabel().equals("+")){
                     //仅在还有点剩余时分配
                     if(pointsRemaining>0){
                            parent.allocatePoints(1);
                            return -1;
                     }
                     else{
                            return 0;
                     }
              }else{//否则扣除一个点
                     //当点数大于零的时候才进行扣点
                     if(parent.getPointsAllocated()>0){
                            parent.allocatePoints(-1);
                            return 1;
                     }else{
                            return 0;
                     }
              }
       }
}
 
//允许角色更改一个属性
class AttributePanel extends Panel{
       //属性的文本描述
       private String attribute;
      
       //显示给这个属性所分配点值的Label
       private Label pointsAllocated;
      
       public AttributePanel(String attr,ActionListener l){
              attribute=attr;
             
              pointsAllocated=new Label("0",Label.CENTER);
             
              //设置Panel的布局为3×1的网格
              setLayout(new GridLayout(3,1));
             
              setBackground(Color.green);
             
              //添加描述属性的标签
              add(new Label(attr,Label.CENTER));
              add(pointsAllocated);
             
              //把+/-按钮添加到父ActionListener上
              Button incr=new AttributeButton("+",this);
              incr.addActionListener(l);
              Button decr=new AttributeButton("-",this);
              decr.addActionListener(l);
             
              //添加另一个有加,减按钮的Panel
              Panel p=new Panel();
              p.add(incr);
              p.add(decr);
              add(p);
       }
      
       //更新pointsAllocated label
       public void allocatePoints(int n){
              int value=getPointsAllocated()+n;
              pointsAllocated.setText(""+value);
       }
      
       //返回给这个属性分配的点值
       public int getPointsAllocated(){
              return Integer.parseInt(pointsAllocated.getText());
       }
      
       public String toString(){
              //返回这个属性的详细描述
              return attribute+":"+getPointsAllocated();
       }
}
 
public class AttributeTest extends Applet implements ActionListener{
       //所有剩余的点值
       Label pointsRemaining;
      
       //这个applet的属性
       private final String ATTRS[]={"力量","智慧","敏捷","魔法"};
      
       public void init(){
              pointsRemaining=new Label("点数剩余:10",Label.CENTER);
             
              this.setLayout(new FlowLayout(FlowLayout.CENTER,5,10));
             
              //添加组件
              for(int i=0;i<ATTRS.length;i++){
                     add(new AttributePanel(ATTRS[i],this));
              }
             
              add(pointsRemaining);
       }
      
       public void actionPerformed(ActionEvent e){
              //得到剩余的点值
              int n=Integer.parseInt(pointsRemaining.getText().substring(5));
             
              //更新按钮Panel的标签和主标签
              n+=((AttributeButton)e.getSource()).updatePanel(n);
              pointsRemaining.setText("点数剩余:"+n);
       }
}
前面的例子演示了怎么使用基本的applet组件来为一个角色扮演游戏创建一个角色轮廓,虽然它对于开始学习applet而言已经很不错了,但是还可又做些工作来进一步改善它,比如:
q        添加更多的属性面板。如果允许用户输入名字,选择职业和性别,并且能够回顾所选属性的列表,这样就更好了。
q        增加复杂度。如果想有多个属性菜单,可能让它们从单一的基类继承会好一些,这个基类必须包含所有属性菜单所共有的方法。这种方式可又让代码更加灵活,这样使得扩展和维护applet变得更加容易。
CharacterBuiler applet的思路是创建一个让一系列按钮来回切换的CardLayout。布局中的每一个卡片由含有一个单一角色属性的面板组成,用户可又访问每一个卡片来选择自己的爱好,比如姓名,职业和性别;用户还可又分配给定的有限技能点,就像在前面的AttributeTest applet中所展示的一样。
//AttributePanel.java
import java.awt.*;
 
public abstract class AttributePanel extends Panel{
       //属性的文本描述
       protected String attribute;
      
       public AttributePanel(String attr){
              attribute=attr;
       }
      
       public final String getAttribute(){
              return attribute;
       }
      
       //强制子类覆盖toString方法
       public abstract String toString();
}
抽象类AttributePanel提供了两个主要用途,首先,它允许几个属性和所有含有角色属性选项的面板相关联,每一个AttributePanel都定义了一个String对象来描述这个面板,并且定义了一个String对象来描述属性本身;另一个用途是可以定义一个AttributePanel对象而无须提前知道它们最后运行时的类型。
由于AttributePanel类被声明为抽象类,不能直接实例化AttributePanel,但是这没什么问题。它被设计为功能不完善的,目的是可以根据特定的角色属性需要而被扩充。
下面的TextFieldPanel创建了允许用户输入其姓名的一个文本域(TextField类)和标签(Label类)
//TextFieldPanel.java
import java.awt.*;
 
//在Panel内放置String属性
public class TextFieldPanel extends AttributePanel{
       //放置属性的TextField
       private TextField textField;
      
       public TextFieldPanel(String attr,String prompt,int textLength){
              super(attr);
             
              this.setLayout(new FlowLayout(FlowLayout.CENTER,15,0));
             
              //如果提示是一个有效字符串,则添加一Label
              if(prompt!=null){
                     add(new Label(prompt,Label.LEFT));
              }
             
              //创建TextField,并把它添加到Panel上
              textField=new TextField(textLength);
              add(textField);
       }
      
       public String toString(){
              //返回属性,一个"不确定"的消息
              if(textField.getText().trim().equals("")){
                     return attribute+":未定义";
              }
             
              return attribute+":"+textField.getText().trim();
       }
}
CheckboxPanel类允许用户在几个选项中作出唯一选择,比如选择性别和职业。下面是它的代码
//CheckboxPanel.java
import java.awt.*;
 
public class CheckboxPanel extends AttributePanel{
       //容纳Checkbox的CheckboxGroup
       protected CheckboxGroup cbg;
      
       //重写Applet类的init方法
       public CheckboxPanel(String attr,String[]items,String selectedItem){
              super(attr);
             
              this.setLayout(new GridLayout(items.length+1,1,5,5));
             
              add(new Label(attribute,Label.CENTER));
             
              //创建CheckboxGroup
              cbg=new CheckboxGroup();
             
              for(int i=0;i<items.length;i++){
                     add(new Checkbox(items[i],cbg,items[i].equals(selectedItem)));
              }
       }
      
       public String toString(){
              return attribute+":"+cbg.getSelectedCheckbox().getLabel();
       }
}
最后是属性选项面板的清单。SkillPanel类允许用户对不同的技能分配技能点。它实现的功能和在AttributeTest applet中实现的很类似:
//SkillPanel.java
 
import java.awt.*;
import java.awt.event.*;
 
//提供了一个可以修改技能值的按钮
class SkillButton extends Button{
       //显示给技能分配点的Label
       private Label pointsAllocated;
      
       public SkillButton(String desc,Label label){
              super(desc);
              pointsAllocated=label;
       }
      
       public int getPointsAllocated(){
              return Integer.parseInt(pointsAllocated.getText());
       }
      
       private void allocatePoints(int n){
              int value=getPointsAllocated()+n;
              pointsAllocated.setText(""+value);
       }
      
       //更新父属性的值
       public int update(int pointsRemaining){
              //如果是"+"则分配点数
              if(getLabel().equals("+")){
                     //只在有点剩余时才分配
                     if(pointsRemaining>0){
                            allocatePoints(1);
                            return -1;
                     }
              }else{
                     if(getPointsAllocated()>0){
                            allocatePoints(-1);
                            return 1;
                     }
              }
             
              //分配/回收失败
              return 0;
       }
}
 
//容纳不同角色的技能值
public class SkillPanel extends AttributePanel implements ActionListener{
       //每一项技能所分配的点数
       Label[] pointsAllocated;
      
       //剩余可分配的总点数
       Label pointsRemaining;
      
       //这个applet的属性
       private String[] skills;
      
       public SkillPanel(String attr,String[] sk,int alloc){
              super(attr);
             
              skills=sk;
             
              //创建pointsRemaining标签
              pointsRemaining=new Label("点数剩余:"+alloc,Label.CENTER);
             
              //把applet的layout设为FlowLayout
              setLayout(new FlowLayout(FlowLayout.CENTER,5,10));
             
              //添加组件
              pointsAllocated=new Label[skills.length];
              for(int i=0;i<skills.length;i++){
                     pointsAllocated[i]=new Label("0",Label.CENTER);
                     addSkill(skills[i],pointsAllocated[i]);
              }
             
              add(pointsRemaining);
       }
      
       private void addSkill(String skill,Label label){
              Panel p=new Panel();
             
              //设置面板布局为3×1网格
              p.setLayout(new GridLayout(3,1));
             
              p.setBackground(Color.green.darker());
             
              //添加一个描述属性的标签
              p.add(new Label(skill,Label.CENTER));
              p.add(label);
             
              //把+/-按钮添加到父ActionListener
              Button incr=new SkillButton("+",label);
              incr.addActionListener(this);
              Button decr=new SkillButton("-",label);
              decr.addActionListener(this);
             
              //添加另一个有加,减按钮的Panel
              Panel buttonPanel=new Panel();
              buttonPanel.add(incr);
              buttonPanel.add(decr);
              p.add(buttonPanel);
             
              add(p);
       }
      
       public String toString(){
              //返回一个包含每一种技能分配情况的String
              String s="";
              int points=0;
              for(int i=0;i<skills.length;i++){
                     points=Integer.parseInt(pointsAllocated[i].getText());
                     s=s+skills[i]+"("+points+")    ";
              }
             
              return s;
       }
      
       public void actionPerformed(ActionEvent e){
              //得到可分配的点数
              int n=Integer.parseInt(pointsRemaining.getText().substring(5));
             
              //更新按钮面板的主标签
              n+=((SkillButton)e.getSource()).update(n);
              pointsRemaining.setText("点数剩余:"+n);
       }
}
前面还提过一个显示用户输入概要的面板,它对主applet的每一个AttributePanel对象都有一个引用,允许它们的toString方法定义所要显示的概要文本。
//Summarypanel.java
import java.awt.*;
 
//包含属性概述的Panel
public class SummaryPanel extends Panel{
       //描述每一个属性的Label
       private Label[] summaries;
      
       //AttributePanel数组
       private AttributePanel[] panels;
       public SummaryPanel(AttributePanel[] ap){
              super();
              panels=ap;
              setLayout(new GridLayout(panels.length+1,1,5,5));
             
              add(new Label("描述:"));
             
              //把Label添加到Panel
              summaries=new Label[panels.length];
              for(int i=0;i<panels.length;i++){
                     summaries[i]=new Label("",Label.LEFT);
                     add(summaries[i]);
              }
       }
      
       /**
        *由于不知道到底是哪一个panel被更新,所以让每一个
        *AttributePanel更新它的标签
        */
        public void update(){
             for(int i=0;i<panels.length;i++){
                    summaries[i].setText(panels[i].toString());
             }
        }
}
现在可以把这些类组装到一起。下面的CharaterBuilder applet在一个CardLayout中定义了4个属性选择面板和一个概要面板,单击back和next按钮可以切换。
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
 
//CharacterBuilder类包含几个放在一个CardLayout中的Panel,还有back和next按钮
public class CharacterBuilder extends Applet implements ActionListener{
       //在cardPanel中选择下一个和上一个按钮
       private Button back;
       private Button next;
      
       private Panel attributePanel;
       private SummaryPanel summaryPanel;
      
       //描述不同属性选项的常数String
       private final String[] GENDERS={"男","女"};
       private final String[] SKILLS={"力量","智慧","敏捷","魔法"};
       private final String[] PROFESSIONS={"武士","吟游诗人","弓箭手","术士","铁匠","德鲁依"};
      
       public void init(){
              //创建一个GridLayout来容纳卡片和按钮
              setLayout(new GridLayout(2,1));
             
              //得到给角色分配的技能点数
              int skillPoints;
              try{
                     skillPoints=Integer.parseInt(getParameter("SkillPoints"));
              }catch(NumberFormatException e){
                     skillPoints=10;
              }
             
              //创建一组属性面板,其中一个是角色的姓名
              //性别,技能和职业
              AttributePanel[] panels=new AttributePanel[]{
                     new TextFieldPanel("姓名","键入您的姓名",20),
                     new CheckboxPanel("性别",GENDERS,GENDERS[0]),
                     new SkillPanel("技能",SKILLS,skillPoints),
                     new CheckboxPanel("职业",PROFESSIONS,PROFESSIONS[0]),
                     };
             
              //创建一个Panel来放置CardLayout
              attributePanel=new Panel();
              attributePanel.setLayout(new CardLayout());
             
              //把AttributePanels添加到主面板
              for(int i=0;i<panels.length;i++){
                     attributePanel.add(panels[i],panels[i].getAttribute());
              }
             
              //创建SummaryPanel,并把它加到CardLayout中
              summaryPanel=new SummaryPanel(panels);
              attributePanel.add(summaryPanel,"描述");
             
              //添加attributePanel
              add(attributePanel);
             
              //创建并添加back和next按钮
              Panel p=new Panel();
             
              back=new Button("上一页");
              back.addActionListener(this);
              p.add(back);
             
              next=new Button("下一页");
              next.addActionListener(this);
              p.add(next);
             
              p.setBackground(Color.BLACK);
             
              add(p);
       }
      
       //在单击back或者next按钮时调用
       public void actionPerformed(ActionEvent e){
              CardLayout cardLayout=(CardLayout)attributePanel.getLayout();
             
              if(e.getSource()==back){
                     cardLayout.previous(attributePanel);
              }else if(e.getSource()==next){
                     cardLayout.next(attributePanel);
              }
             
              //在每一次变更后更新概要
              summaryPanel.update();
       }
}
注意CharaterBuilder类的init方法中getParameter方法的使用。可以把参数传送给applet,就像可以把参数传给控制台程序一样。当希望不经过重新编译而给applet传送信息时,这是很有用的。上面的代码清单读入一个参数,该参数表示可分配的技能点。下面的代码演示了怎样在.html文档中包含参数:
<applet height="300" width="300" code="CharacterBuilder">
       <param name="SkillPoints" value="30"/>
</applet>
如果要在applet中读入SkillPoints参数,只需像这样:
skillPoints=Integer.parseInt(getParameter("SkillPoints"));
由于点值是一个整数,这里把上述代码嵌入到一个try/catch中,只是为了处理一些聪明的玩家在网页中输入非法数据的特殊情况。applet参数是对代码作出快速改变且无须浪费时间重新编译的好方式。当希望最终用户可以快速而简单地定义参数时,使用applet参数也是很方便的。
记住,Java applet只是嵌入到另一个应用程序中的一段程序。applet是一种把游戏发布给用户的非常方便而且非常流行的方法。
本章没有讲解所有的AWT组件,主要是介绍组件和容器,在第6章中,我们将继续学习像Graphics等在本章被一带而过的主题。使用AWT组件是一种快捷地向用户表述交互内容的方法,像按钮,单选按钮和标签这样的组件可以用几行代码放到applet上。然而,AWT有几个限制,虽然它对设计独立于窗体方案的软件不错,本书还是建议对组件的放置做更好的控制。读者还应该结合对游戏的“感觉”来创造更且有吸引力的组件,而不是使用默认的窗体和绘制方式,但是不要完全低估原始的AWT,它们的柔性和使用上的便捷在后面是很灵便的。
我们已经对Applet类的作用有了初步的认识,只要几行代码,就可以把按钮,文本域和标签嵌入到applet窗体上,但是,这并没有结束。在第7章,我们将学习在applet中绘制线条,图形和文本。
5.1预计下列代码段的输出:
public B extends Applet{
       public static void main(String[] args){
              System.out.println(“I am a Java guru!”);
       }
}
5.2在使用像Button这样的组件时,为什么实现EventListener接口对Applet类来说很重要?如果不是很清楚,可以查看Button类addActionListener方法的原型。
5.3请描述组件和容器之间的异同。
5.4修改ButtonTest applet,使得它对color数组中的每一种颜色都有一个单独的按钮,和红色关联的按钮应该以Red为标签,都以currentColor作为索引。一个更加健壮的方法是施展Button类,在它内部变换颜色。可以在自定义Button的构造函数中给它关联一个颜色并让actionPerformed方法允许Button刷新窗体。
5.5修改AudiochoiceTest applet,让它实现一个List对象来容纳声音文件的文件名。应该让List具备在任何时候多选的能力,所以应该仔细修改相应的声音控制代码。
5.6描述怎样用多个Panel对象来创建更精致的布局。
5.7什么是卸载不当引起的速度消耗?
5.8简要描述5个按钮在给定如下几种不同的布局管理器下如何排列:FlowLayout,GridLayout,CardLayout和BoderLayout。
5.9写一个applet用Label对象显示当前日期和时间。这个applet应该有规律地刷新自己,以便日期和时间可以看起来像是连续的。
5.10修改CharacterBuilder类,让它能读一个描述默认角色名字的applet参数。可能需要修改TextFieldPanel的构造函数或者添加一个方法来设置这个文本域的值,还需要在.html文件中添加如下的代码来添加一个参数标签:
<param name=”DefaultName” value=”Merlin”>
 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

默然说话

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

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

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

打赏作者

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

抵扣说明:

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

余额充值