一、在开始之前我们得确认你已经安装了Java JDK,并已经安装了浏览器软件如IE。本章是以Internet为开发对象,一步一步教大家认识Java的Thread、Applets….以及游戏编程要注意的一些方方面面。并在每一小部分附上了相应的源代码以供大家参考,最后我们还会对我们的游戏程序进行指导性的提示。在文章中我们还穿插了很多建设性的问题,让读者参与到我们的开发中来。但是由于本章指在带领大家进入这个门槛,大部分知识并不会很详细说明,有兴趣的读者可查阅相关的资料补充。在开始之前我们还得确认你已经知道类,继承和java语言的一些基本属性了。
基本applet
Applets是一种Web浏览器上的小程序,由于applet对系统而言绝对安全,所以它做的事比aaplication有限,但是对于客户端的程序,applets仍然是个很强大的工具。为了浏览和运行方便,我们就以applet为开发对象。
开发Applets程序,我们得继承Applet类,并覆写必要的函数,下面几个函数控制了Web页面上的applet生成与执行。
函数 作用
Init() 这个函数会被自动调用,执行applet的初始化动作?包括组件在版面上的分配,你一定得覆写它
Start() 每当浏览器显示applet内容时,都会调用它,让applet开启其正规工作(尤其是那些被stop()关闭的工作),调用init()之后也会调用这个函数
Stop() 每当浏览器不显示内容时,都会调用它。让applet关闭某些耗资源的工作,调用destory()之后也会调用这个函数
Destroy() 浏览器将applet自网页移除之际,便会调用它,以执行”applet不再被使用”应该做的最后释放资源等动作
Paint() 让你在Applet界面上进行相应的绘画动作,每次刷新时都会重画
所有的applet文件源文件名和java应用程序一样都是.java为扩展名,编译后的执行文件扩展名为.class,由于在applet中已经没有了main()函数,它是和html自动集成,所以我们要执行applet,要在html源文件中放入一特定的标签(tag),才能告诉网页如何装载并执行这个applet,这里有一点要注意,我们执行的网页必须能执行java程序。
普通Html 源码格式
<HTML>
<APPLET CODE="HelloWorld.class" WIDTH=300 HEIGHT=500>
</APPLET>
</HTML>
<applet code ="HelloWorld.class" width=300 height=500>这行即applet的执行处。
applet 执行文档为 ="HelloWorld.class" 告诉网页”applet ”扩展文件为HelloWorld.class
width 和 height 告诉浏览器这个显示的applet的大小
有关标签(tag)的说明,大家可在网上找到很多相关的说明文档。
线程
由于apllet,java应用程序的执行都和线程有关。我们来大概了解一下线程的概念。
线程也称为轻型进程 (LWP)。每个线程只能在单个进程的作用域内活动、协作和数据交换,并且在计算资源方面非常廉价。线程需要操作系统的支持,因此不是所有的机器都提供线程。Java 编程语言,作为相当新的一种语言,已将线程支持与语言本身合为一体,这样就对线程提供了强健的支持。
Thread 类是一个具体的类,即不是抽象类,该类封装了线程的行为。要创建一个线程,程序员必须创建一个从 Thread 类导出的新类。程序员必须覆盖 Thread 的 run() 函数来完成有用的工作。用户并不直接调用此函数;而是必须调用 Thread 的 start() 函数,该函数再调用 run()。
但是使用Thread类实现线程,增加了程序的类层次,所以一般程序员都由另一个java线程接口Runnable接口来实现,Runnable接口只有一个函数run(),此函数必须由实现了此接口的类实现。
线程中有几个重要的方法是我们得了解:
Thread.start(): 启动一个线程
Thread.stop(): 停止一个线程
Thread.sleep(time in milliseconds): 暂停线程在一个等待时间内。
二、动画技术
自由降落动画
了解了一些基本概念后,下面我们就开始我们的实质性的工作。我们设计一个球从屏幕顶上降落到屏幕下面,程序实现比较简单,但是这是游戏动画中不可少的一部分。在开始之前我们来看看我们的applet开始语句。
import java.awt.*;
import java.applet.*;
public class Ball
extends Applet implements Runnable
{
public void init() { }
public void start() { }
public void stop() { }
public void destroy() { }
public void run () { }
public void paint (Graphics g) { }
}
在开始函数中我们要新建程序的主线程,并启动这个线程。一旦做好这些准备工作以后,当applet第一次被显示时,就会创建线程对象的一个实例,并把this对象作为建构方法的参数,之后就可以启动动画了
public void start ()
{
// 定义一个新的线程
Thread th = new Thread (this);
// 启动线程
th.start ();
}
现在我们来看看线程的run方法,它在循环while(),中每隔20毫秒重画动画场景。sleep这个方法很重要,如果在run循环中没有这部分,圆的重画动作将执行得很快,其他方法将得不到有效执行,也即我们在屏幕上将看不到球的显示。
public void run ()
{
//
while (true)
{
// 重画applet画面
repaint();
try
{
// 暂停线程20毫秒
Thread.sleep (20);
}
catch (InterruptedException ex)
{
}
}
}
我们接着读下去之前,有几个问题需要回答。你也许会问,浏览器调用Java小程序的start和stop方法吗? run 方法是如何被调用的? 情况是这样的,当浏览器启动了一个内部线程时,就相应地启动了applet 的运行。当网页显示时,就启动了applet的start 方法。Start方法创建一个线程对象,并把applet自身传送给线程,以实现run方法。
此时,两个线程在运行:由浏览器启动的初始线程,以及处理动画的线程。快速查看applet的start方法,可以知道它创建了线程,并启动了它。类似地,当网页被隐藏后,applet的stop方法就调用了线程的stop方法。
注意:在Applets和Threads中的 start/stop子程序
在Applet 和Thread 两个类中都有start和stop方法,但它们的功能不同。一旦Applet 显示时,就调用applet的start方法,一旦applet 隐藏时,就调用applet的stop 方法。相反,线程的start方法将调用run方法,线程的stop方法将停止正在执行的线程。
public void paint(Graphics g)
paint() 方法所传入的参数?? java.awt.Graphics 对象将是一个经裁剪的相关显示区的图像代表(而不会是整个显示区)。我们对圆球图形元素的绘制就是在通过重写 paint()方法,在其中对传入的Graphics 对象g进行操作完成的。
当我们应用程序的逻辑要对系统界面进行更新时,调用 repaint() 方法来通知AWT线程进行刷新操作。repaint() 方法实际会让 AWT线程去调用另外一个方法,update。update方法在默认情况下会做两件事,一是清除当前区域内容,二是调用其 paint()方法完成实际绘制工作。paint、repaint、update 三个方法关系如图所示:
此主题相关图片如下:
但是如何让我们的圆运动呢?这里我们利用函数Graphics 类的fillOval函数来设置了圆的起始位置x,y。现在我们只要在线程run方法中每单位时间增大y的值,线程将在每一个单位时间内重画圆的位置。每单位时间y值越大,下降的速度就会越快。在屏幕上我们就将看到这个圆球做自由降落运动。 如下代码所示:
while (true)
{
// 设置动画移动速度
y +=1;
}
public void paint (Graphics g)
{
//设置球的颜色
g.setColor (Color.blue);
// 从x,y位置处画一个实心的圆
g.fillOval (x , y, 2 * r, 2 * r);
}
在这之前我们需要在开始处设置一些变量,定义好x,y的默认位置值。r 在此处是我们画的圆的半径大小。
int x = 100;
int y = 20;
int r = 10;
我们的自由降落的动画就完了。是不是很简单,如果还有地方不明白,大家可在此处下载完整的代码及应用程序。看看真实的演示效果和代码。下面每一部分我们也将在最后附上相应的源代码及应用程序下载。如果大家有兴趣,可改变y的值,及x的值,你会得到不同的下降效果。
三、双缓冲,消除闪烁
大家可能注意到了上面例子中的我们下降的圆看起来不是很清晰,带着很严重的闪烁。这种现象在写游戏程序中是普遍存在的现象。这是由于我们的repaint()函数导致的结果,由于它在调用paint()函数前会自动清除屏幕,所以在一个毫秒内我们会看到一个空白的屏幕,在快速的变换操作中就出现了闪烁现象。
解决这种闪烁现象有几种方法,下面是两种方法的列举说明,其他的方式大家可以自己尝试。
第一种:我们始终不清除屏幕显示,但是这个方法会带来个附作用,我们下降的圆不在是一个圆了,而是一条直线,因为它的下降过程中没有了断点,保留了所有的圆球的影象。我们只要在Ball.java内加上如下代码update(Graphics g) {paint(g)},你就会看到一条很长的线拉出来。有兴趣的朋友可以试试。
第二种:使用双缓冲机制(Double buffering)
现在大部分的游戏都是采用双缓冲机制来解决屏幕的闪烁现象,我们就以此为例来进行说明,有关缓冲区及相关缓冲机制的概念,大家可参考附录的缓冲说明。
而我们的程序中简单的说就是在显示我们想要的图画之前,把所有的图画先在后台绘制好并存放到相应的图像变量中去。当需要显示时直接复制到前台屏幕就可以了。
具体实现:
1.首先我们用createImage方法新建一后台图像类变量
2.然后使用getGraphics()方法得到当前图像的图形关联
3.在后台处理所有相关的处理,如清除屏幕,后台绘画等等
当完成所有的后台工作后,复制已经绘制好的图像到前台,并覆盖前台的存在图像。这样我们的所有操作都是在后台前行,在屏幕显示新的图像前,这些内容都已经存在于后台了。所以你也将在任何时刻都看不到空屏幕的存在。也即代表闪烁消除了。
下面我们来看看相关的代码说明:
在开始之前我们得先在程序的开始部分声明两个实例变量用来存储后台图画。如下:
private Image bgImage;
private Graphics bg;
然后我们利用update()方法来实现双缓冲机制。
Update()方法要实现下面三个步骤:
1.清除屏幕上的组件
2.设置相关联组件的前景色
3.调用paint方法重画屏幕
public void update (Graphics g)
{
// 初始化buffer
if (bgImage == null)
{
bgImage = createImage (this.getSize().width, this.getSize().height);
bg = bgImage.getGraphics ();
}
// 后台清屏,即设置圆球组件和后台一样的颜色,大小
bg.setColor (getBackground ());
bg.fillRect (0, 0, this.getSize().width, this.getSize().height);
// 绘制相应的元素组件
bg.setColor (getForeground());
paint (bg);
// 在屏幕上重画已经绘制好的圆
g.drawImage (bgImage, 0, 0, this);
}
此处g 为屏幕图形,bg为g的后台关联。而bgimage包含了bg图形。请于此处来看看我们的源代码例子及演示效果。
改变运动方向
我们已经解决了动画的两个很重要的问题,移动动画和闪烁消除。但是我们很快会发现一个问题,球从屏幕顶上落下来后,就不见了。这可不是我们所需要的。我们要的是一个生动的画面。如何让我们的球不穿过屏幕而始终在屏幕上活动呢?在开始之前,我建议大家自己想办法解决,如果你能自己处理好了。你的水平将会有一个很大的提高。如果没有想出好办法,没关系,下面我们将很详细的说明球的方向改变的技术。
不知道大家注意了没有,在上面我们说到球的移动时,我们是通过增加y的值,让线程重画新的圆位置和图形。如果改变y的值的大小球的下降速度也会改变。不错,这就是我们的解决方法 ,我们只要用一个变量来存储这个速度的大小而不用固定的值。在线程执行也即run方法处我们用代码改变速度的方向,球的方向也会改变。即设置这个变量”speed”为”-1”。当然在设置值前我们要进行判断,你是想让球穿过屏幕从别一边开始显示,还是来回反弹呢!如果想来回反弹,我们只要不让球的半径值超过applet屏幕显示区域就可以了。此处我们用r/2来表示球的半径。
//反弹下落球
if (y > appletsize_y ?C r/2)
{
// 改变方向
x_speed = -1;
}
// 反弹上升球
else if ( < r/2)
{
// 改变方向
x_speed = +1;
}
至于如何让球穿过从屏幕顶上重新下降,我们在此没有说明,也不会说明了。留给大家自己去想想,已经很简单了。在下面我们附上了两种方式的源代码和执行文件。如果大家运行程序,大家可能会发现,我们的球的大小和速度有一些改变。这里是为了更好的反应演示效果。
延伸阅读
文章来源于领测软件测试网 https://www.ltesting.net/