分类: Java
2008-05-02 08:47:46
前言
今天我们会为游戏添加动画效果,这将会涉及到图形处理,还有线程的基本概念,这可是一项非常有意思的工作哦。本期源程序下载(, 2KB, winzip压缩)。
我们完成了游戏界面的设计,可是游戏的功能却还十分单薄,比如说几个人同时玩一个游戏,我们怎么比较谁玩得比较好呢,就算是自己一个人在玩,也得知道自己每次的成绩啊。竞技是游戏的主要动力之一,为了提高游戏的可玩性,我们得加入计时和计分的功能才行。
关于游戏的计时和计分功能,因为与我们Swing的主题关系不大,这部分功能的实现我就不说了,如果大家感兴趣的话可以参考一下我的,里面有详细的说明,在这里我们就只讨论一下如何将显示界面做得漂亮。
JLabel和Font
还记得最开始的主界面是如何设计的吗?我们将界面分成了三个部分:系统菜单、游戏区、用户交互区。用户交互区的作用就是放置用户的操作及反馈信息,计时和计分功能自然就是放在这里。
首先我们可以放置一个文本控件,然后将这个控件的背景设置为界面的背景色或透明色。那么这个控件应该是哪个呢?这个控件就是JLabel!JLabel 的用法并不难,如果你能够熟练使用JButton的话,那么JLabel控件也会是小case了,让我们一起来试试吧:
JLabel score = new JLabel("307"); // 假设用户当前的分数为307分
actionPanel.add(score);
看看程序运行的结果,感觉如何?你也许会说:字太小了,而且颜色也太暗了,看不太清楚。没关系,这很容易解决:
JLabel score = new JLabel("307");
Font font = new Font("宋体", Font.BOLD, 48);
score.setForeground(Color.yellow);
score.setFont(font);
actionPanel.add(score);
在上面的代码中,我们首先创建了一个字体对象font,Font构造函数中3个参数的意义依次为:使用字体的名称、字体的样式(普通、粗体、斜体等)以 及字体的大小等;然后,我们使用setForeground来设计JLabel的前景色;最后,我们将JLabel的字体改为我们创建的字体。现在我们再 来看看程序运行的结果,是不是效果好多了(见图1)?
在游戏的过程中,只要时间或者分数发生变化,我们就可以使用JLabel.setText(String s)来更新显示。
细心的朋友可能会发现,在我们显示时间和分数的过程中,如果将字体设置得很大,显示内容虽然更容易看清,但视觉效果却变得很差。这是因为字体越大,字体上的锯齿就会越明显,这是Swing控件显示文字的通病,遗憾的是暂时还没有很有效的解决方法(见图2)。
在前面所有的章节中,我们都是使用现成的控件来满足自己的需要,现成的控件虽然使用方便,却不能完全满足我们的要求。这时我们就需要了解一下如何不使用控件来绘制图形。
无论是Swing控件也好,AWT控件也好,他们都会有一个paint(Graphics g)方法,一般情况下我们不会去理它的,然而,当我们需要自己绘制图形的时候,我们就不得不对这个方法有所了解。
为了让大家能够接受自己绘制图形的方法,我们还是从paint(Graphics g)方法讲起吧:paint(Graphics g)方法将控件看作一张空白的纸,当我们需要绘制图形的时候,就可以使用Graphics提供的一些基本方法来进行绘制。为了方便大家理解,我们新建一个 Clock类来完成用户时间的显示,代码如下:
import javax.swing.*;
import java.awt.*;
public class Clock
extends JPanel {
Font font48 = new Font("serif", Font.BOLD, 48);
this.setMinimumSize(new Dimension(156, 48));
this.setPreferredSize(new Dimension(156, 48));
}
public void paint(Graphics g) {
g.setColor(new Color(111, 146, 212));
g.clearRect(0, 0, this.getWidth(), this.getHeight());
g.setColor(Color.yellow);
g.setFont(font48);
g.drawString("307", 16, 40);
}
}
以上内容应该在我们新建的Clock.java文件中,代码并不难读懂,大家可以自己查看文档找到相关函数的使用说明。然后,我们将原来程序中显示时间的代码部分改为:
Clock clock = new Clock();
actionPanel.add(clock);
怎么样,运行结果是否相同?虽然此时我们仍然没有消除文字上的锯齿,不过已经对Swing中自己绘制图形有了一个大致的了解。
随心所欲的Graphics2D
对于追求最佳视觉效果的我们,文字上的锯齿显示是不能容忍的。那么如何解决这个问题呢?既然这是Swing控件显示字体的通病,并且就连Graphics也无能为力,看来,我们只有另辟蹊径了。
好在Sun也意识到了这一点,因此在Java中提供了Graphics2D类来解决这种问题。下面,我们将依然使用代码来说明问题:
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
Dimension d = getSize();
g2.setBackground(new Color(111, 146, 212));
g2.clearRect(0, 0, d.width, d.height);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(Color.yellow);
g2.setFont(font48);
g2.drawString("123", 16, 40);
}
在上述的代码中,我们将Graphics对象转换成Graphics2D对象后,只需使用简单的一句setRenderingHint()就完成了抗锯齿的功能。现在再来看看程序运行的效果,你还满意吗(见图3)?
渴望无限,创造无限。如果能把计分和计时功能做成动画,一定会更加生动。最简单的做法就是让数字依次变化,比如说时间,我们可以每隔1秒钟就显示一次数值,比如说上面的代码,我们可以简单改为:
for (int i = 1000; i < 2000; i++) {
g2.setBackground(new Color(111, 146, 212));
g2.clearRect(0, 0, d.width, d.height); // 清除当前绘图区域
g2.setColor(Color.yellow);
g2.drawString("" + i, 16, 40);
try {
Thread.sleep(1000); // 延时 1 秒钟
} catch (InterruptedException ex) {
}
}
当你执行以上代码以后便会知道,上面的代码实际上有问题!我们使用了Thread.sleep(1000)来让程序进行延时,每当执行Thread.sleep()的时候,程序会进入休眠状态,不响应其他任何操作,就连显示也不会更新,所以也就无从显示动画效果了。
线程(Thread)是惟一的解决方法,它可以让程序在同一个时刻完成两件或者两件以上的事情。想让你的程序支持线程,你可以选择以下两种方法的任意一种:
①public class Clock extends Thread
②public class Clock implements Runnable
第一种方法要求你的程序继承Thread类,可是,如果继承Thread类之后,我们就没有办法再继承JPanel了,因为Java不支持多重继承,如 果不能继承JPanel,那么paint(Graphics g)也没有用武之地了。而第二种方法只要求你实现Runnable接口,而Java允许一个类实现多个接口,所以我们应该选择第二种方法。
首先需要把Clock的声明改成public class Clock extends JPanel implements Runnable,然后添加public void run() { }
上面是实现Runnable接口的最基本的构架。有的时候,为了让线程能够具备开始和停止的功能,我们还得再扩充一下。首先是线程的开始:
public void start() {
startTime = System.currentTimeMillis();
thread = new Thread(this);
thread.start();
}
这里我们使用了System.currentTimeMillis()来获得开始时的系统时间,以便于以后计算用户的游戏时间。
然后是运行的过程:
public void run() {
Thread currentThread = Thread.currentThread();
while (thread == currentThread) {
long time = System.currentTimeMillis();
usedTime = time - startTime;
try {
repaint();
thread.sleep(100);
}
catch (InterruptedException ex) {
}
}
}
第一句话Thread currentThread = Thread.currentThread();获得系统当前的线程,然后在循环中进行判断是否为当前线程,这在在线程比较多而且线程之间的依赖性比较强 的情况下非常有必要。repaint() 是AWT/Swing控件的方法,它会自动调用paint(Graphics g)方法以便重新绘制显示区域。最后,我们使用了thread.sleep(100)来让程序每隔0.1秒运行一次。由于thread.sleep()方 法会抛出异常,所以我们还需要捕获异常。
由于paint(Graphics g)每次显示的内容都会不同,因此,我们需要使用一个全局全量usedTime来记录当前的时间,在输出的时候,我们应写成:
g2.drawString("TimE:" + getTime(), 16, 40);
其实getTime()方法是我们自己写的,其作用是获得用户当前使用的时间,并将时间格式化后传出。最后,别忘记了在我们的主程序中调用start()方法,如:
Clock clock = new Clock();
clock.start();
actionPanel.add(clock);
小方糖
Thread的前世今生
最早的Thread提供了Thread.start() 、Thread.pause()/Thread.resume() 、Thread.stop() 这三个方法来完成线程的开始、暂停和结束功能,然而,在后来的JDK中,Sun却不推荐使用pause()/resume()和stop()方法,理由是 pause()和stop()方法在某些场合会让程序进入死锁状态,因此,在你的程序中,假如你使用了Thread的以上几种方法,编译的时候编译器会告 诉你
"xxx.java": stop() in java.lang.Thread has been deprecated at line xx, column xx
这时候你就要注意了!
小结
今天我们学习了使用Graphics/Graphics2D来满足自己特殊的需要,再也不需要看控件的脸色了。同时,我们也知道了如何使用线程来让程序 同时进行多个操作,并且结合了以上两点来实现计时功能。但是我没有在这里完成计分功能,就把这当作留给大家的小小作业吧!
chinaunix网友2010-07-16 18:56:58
KK娱乐视频网,快乐齐分享 www.yulekk.com 搞笑视频,动漫视频,美女写真,靓丽车模,美女翻唱,精彩MV,经典DV