二维图形与动画

java图像与动画

java中能创建三种图形游戏:小程序游戏、窗口游戏和全屏游戏。

java小程序游戏是web浏览器中运行的,好处是用户不必安装任何程序。但是,游戏

者要联机并运行web浏览器,而且小程序有几个安全限制,为了防止恶意代码造成的

危害,如小程序无法在用户磁盘上保存选项和游戏之类的信息。另外,小程序只能对

小程序所在的服务器建立网络连接。

java窗口程序没有小游戏一样安全限制。事实上,窗口游戏的外观行为和正常应用程

序一样,有标题栏和关闭按钮等。但是,这些用户界面元素可能分散游戏者的注意力

,特别希望游戏者沿漫道游戏中时更为不利。

java全屏游戏可以完全控制游戏的外观显示,没有标题栏、任务栏和其他用户界面元

素,可以让游戏者完全沿漫到游戏中。

1。1全屏图形

先介绍硬件的工作原理:显示硬件分两个部分:显示卡和显示器。显示卡的内存中将

存储屏幕上的内容,并有几个修改显示内容的功能。同样,显示卡还要内存中的内容

推倒显示器的屏幕上,而显示器则按显示卡的指示来显示内容。

屏幕布局:显示器的屏幕可以用相同尺寸的微小彩色象素显示。象素一词是从图形元

素衍生出来,表示显示器显示的一个光点。屏幕上的垂直与水平象素个数称为屏幕的

分辨率。屏幕的原点在屏幕的左上角,显示内存中存放象素和书本一样,从左上角开

始,从左到右,直到右下角。屏幕上的位置可以表示成(x,y),其中x是距离原点的

水平象素个数,y是距离原点垂直象素个数。

将显示切换到全屏方式:

Window对象是屏幕显示内容的抽象,可以看成绘图的画布,通常实际上将使用

Jframe对象,它是Window类的子类,也可以用于建立窗口应用程序。

DisplayMode对象是指定将显示切换到何种分辨率,位深度和刷新率

GraphicDevice对象用来改变显示方式和检查显示属性,可以看作是显示卡的界面,

GraphicsDevice对象从GraphicsEnviroment对象中取得。

下面就是显示切换到全屏方式的实例代码:

JFrame window=new JFrame();

DisplayMode displayMode=new DisplayMode(800,600,16,75);

GraphicsEnviroment

enviroment=GraphicsEnviroment.getDefaultScreenDevice();

GraphicsDevice device=enviroment.getDefaultScreenDevice();

device.setFullscreenWindow(window);

device.setDisplayMode(displayMode);

//下面要将显示切换到原始的显示方式时,可以将全屏Window设置为null

device.setFullScreen(null);

在缺省情况下,即使显示切换到全屏方式,JFame仍然会显示边框和标题栏。为了处

理和简化功能,这里生成一个包装类SimpleScreenManager类。代码如下:

清单2.1SimpleScreenManager。java

import java.awt.*;
import javax.swing.JFrame;

/**
    The SimpleScreenManager class manages initializing and
    displaying full screen graphics modes.
*/
public class SimpleScreenManager {

    private GraphicsDevice device;

    /**
        Creates a new SimpleScreenManager object.
    */
    public SimpleScreenManager() {
        GraphicsEnvironment environment =
            GraphicsEnvironment.getLocalGraphicsEnvironment();
        device = environment.getDefaultScreenDevice();
    }


    /**
        Enters full screen mode and changes the display mode.
    */
    public void setFullScreen(DisplayMode displayMode,
        JFrame window)
    {
        window.setUndecorated(true);
        window.setResizable(false);

        device.setFullScreenWindow(window);
        if (displayMode != null &&
            device.isDisplayChangeSupported())
        {
            try {
                device.setDisplayMode(displayMode);
            }
            catch (IllegalArgumentException ex) {
                // ignore - illegal mode for this device
            }
        }
    }


    /**
        Returns the window currently used in full screen mode.
    */
    public Window getFullScreenWindow() {
        return device.getFullScreenWindow();
    }


    /**
        Restores the screen's display mode.
    */
    public void restoreScreen() {
        Window window = device.getFullScreenWindow();
        if (window != null) {
            window.dispose();
        }
        device.setFullScreenWindow(null);
    }

}


下面清单就是测试SimpleScreenManager类的用途。

清单2.2 FullScreenTest。java

import java.awt.*;
import javax.swing.JFrame;

public class FullScreenTest extends JFrame {

    public static void main(String[] args) {

        DisplayMode displayMode;

        if (args.length == 3) {
            displayMode = new DisplayMode(
                Integer.parseInt(args[0]),
                Integer.parseInt(args[1]),
                Integer.parseInt(args[2]),
                DisplayMode.REFRESH_RATE_UNKNOWN);
        }
        else {
            displayMode = new DisplayMode(800, 600, 16,
                DisplayMode.REFRESH_RATE_UNKNOWN);
        }

        FullScreenTest test = new FullScreenTest();
        test.run(displayMode);
    }

    private static final long DEMO_TIME = 5000;


    public void run(DisplayMode displayMode) {
        setBackground(Color.blue);
        setForeground(Color.white);
        setFont(new Font("Dialog", 0, 24));

        SimpleScreenManager screen = new SimpleScreenManager();
        try {
            screen.setFullScreen(displayMode, this);
            try {
                Thread.sleep(DEMO_TIME);
            }
            catch (InterruptedException ex) { }
        }
        finally {
            screen.restoreScreen();
        }
    }


    public void paint(Graphics g) {
        g.drawString("Hello World!", 20, 50);
    }
}
还有需要注意u的是paint(0方法中使用Graphics对象。Graphics对象提供绘制文本

、直线等多种功能。具体参考Java API

Paint(0方法如何调用呢? 实际上FullScreenTest是一个Jframe对象,显示

JFrame时,java的抽象工具库就会调用组件的paint()方法。如果要强制调用,则

可以用repaint()方法。实现代码如下

public class MyComponent extends SomeComponent{

......................................

public synchronized void repaintAndWait(){

repaint();

rry{

wait();

}

catch(InterruptedException ex){}

}

public synchronized void paint(Graphics g){

//在此进行绘制

。。。。

//通知绘图完成

notifyAll()];

}

}
2.1.5 抗齿边
FullScreenTest中的hello world文本边沿上有些锯齿形状,这是因为文本没有抗齿

边,所谓抗齿边就是将文本边沿模糊化,使文本颜色与背景颜色相混合,从而达到象

素的台阶更光顺。为了文本抗齿边,就要在绘制任何文本之前设置相应的绘制提示。

绘制提示功能只有Graphics2D类提供,它是Graphics的子类。为了保持向后兼容性

,paint()方法取Graphics对象作为参数。但是,从paint()开始,传入方法中

的参数实际是Graphics2D对象。让文本抗齿边的paint()方法的代码如下所示:
public void paint(Graphics g)
{if(g instanceof Graphics2D){
Graphics2D g2=(Graphics2D)g;
g2.setRenderingHint

(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALI

AS_ON);
}
g.drawString("hello world",20,50);
}
此外还有其他绘制提示,包括让所有几何直线与形体抗齿边,设置绘制质量,具体见

ReneringHints类的文档。
2.1.6 选用显示方式:显示方式有很多种,但游戏至少保证游戏可以在2种分辨率中

运行。一般的原则是让游戏者改变显示方式,即选择出系统中最适合的显示方式。
2.2图像
2.2.1 图像的透明度:图像的透明度有三种:不透明,透明,半透明。
不透明图像将显示图像中的每一个像素
透明图像中每一个像素或者完全显示,或者完全穿透。其中白色背景可能是透明的,

所以绘制时可以看到下面的内容。
半透明图像的象素是半透明的,用来产生鬼怪式的部分穿透效果。同样可以在图像边

沿使用,来产生抗齿边图像。
在处理角色图像中,就要用透明效果效果的白色背景,以便绘制时可以看到下面的内

容,而且在图像的边沿使用半透明效果,来产生抗齿边图像。
2。2.2 文件格式:
图像的基本格式有2种:光栅格式和矢量格式。光栅格式像显示器一样描述图像,使

用指定的位深度象素。矢量格式则用几何方法描述图像,可以缩放而不会使质量下降

。java API没有内置的矢量格式,因此主要介绍光栅格式。java运行环境设置了三

种光栅格式,很容易读取,它们是GIF,PNG,和JPEG。
2.2.3读取图像
这是通过共具库中的getImage()方法完成的,它分析图像文件,并返回一个Image对

象,实现代码如下:
Toolkit toolkit=Toolkit.getDefaultToolkit();
Image image=toolkit.getImage(filename);
代码看起来简单但是并不装入图像,而是在另一线程中实际装入图像。如果在装入图

像完成之前显示图像,那么将显示图像的一部分或什么也不显示。可以用

MediaTracker对象观察图像,等待装入完成,但还有一个方法。ImageIcon类可以用

MediaTracker对象装入图像。javax.swing包中的ImageIcon类用工具库装入图像,

等待装入完成之后再返回,实现代码如下:
ImageIcon icon=new ImageIcon(fileName);
Image image=icon.getImage();
装入图像后,可以试一试。清单2.3 的ImageTest类与FullScreenTest类相似,也使

用SimpleScreenManager的对象。ImageTest将控制一个Jpeg背景图像和4个png前景

图像,等待10秒钟然后退出。
清单2.3 ImageTest.java
import java.awt.*;
import javax.swing.ImageIcon;
import javax.swing.JFrame;

public class ImageTest extends JFrame {

    public static void main(String[] args) {

        DisplayMode displayMode;

        if (args.length == 3) {
            displayMode = new DisplayMode(
                Integer.parseInt(args[0]),
                Integer.parseInt(args[1]),
                Integer.parseInt(args[2]),
                DisplayMode.REFRESH_RATE_UNKNOWN);
        }
        else {
            displayMode = new DisplayMode(800, 600, 16,
                DisplayMode.REFRESH_RATE_UNKNOWN);
        }

        ImageTest test = new ImageTest();
        test.run(displayMode);
    }

    private static final int FONT_SIZE = 24;
    private static final long DEMO_TIME = 10000;

    private SimpleScreenManager screen;
    private Image bgImage;
    private Image opaqueImage;
    private Image transparentImage;
    private Image translucentImage;
    private Image antiAliasedImage;
    private boolean imagesLoaded;

    public void run(DisplayMode displayMode) {
        setBackground(Color.blue);
        setForeground(Color.white);
        setFont(new Font("Dialog", Font.PLAIN, FONT_SIZE));
        imagesLoaded = false;

        screen = new SimpleScreenManager();
        try {
            screen.setFullScreen(displayMode, this);
            loadImages();
            try {
                Thread.sleep(DEMO_TIME);
            }
            catch (InterruptedException ex) { }
        }
        finally {
            screen.restoreScreen();
        }
    }


    public void loadImages() {
        bgImage = loadImage("images/background.jpg";
        opaqueImage = loadImage("images/opaque.png";
        transparentImage = loadImage("images/transparent.png";
        translucentImage = loadImage("images/translucent.png";
        antiAliasedImage = loadImage("images/antialiased.png";
        imagesLoaded = true;
        // signal to AWT to repaint this window
        repaint();
    }


    private Image loadImage(String fileName) {
        return new ImageIcon(fileName).getImage();
    }


    public void paint(Graphics g) {
        // set text anti-aliasing
        if (g instanceof Graphics2D) {
            Graphics2D g2 = (Graphics2D)g;
            g2.setRenderingHint(
                RenderingHints.KEY_TEXT_ANTIALIASING,
                RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        }

        // draw images
        if (imagesLoaded) {
            g.drawImage(bgImage, 0, 0, null);
            drawImage(g, opaqueImage, 0, 0, "Opaque";
            drawImage(g, transparentImage, 320, 0, "Transparent";
            drawImage(g, translucentImage, 0, 300, "Translucent";
            drawImage(g, antiAliasedImage, 320, 300,
                "Translucent (Anti-Aliased)";
        }
        else {
            g.drawString("Loading Images...", 5, FONT_SIZE);
        }
    }


    public void drawImage(Graphics g, Image image, int x, int y,
        String caption)
    {
        g.drawImage(image, x, y, null);
        g.drawString(caption, x + 5, y + FONT_SIZE +
            image.getHeight(null));
    }

}
2.2.4硬件加速图像
硬件加速图像存放在显示内存中而不是存放在系统内存中,硬件加速图像可以比非硬

件加速图像更快的拷贝到屏幕上。java对工具库中的getImage()方法装入的任何

图像进行硬件加速。如果在支持硬件加速图像的系统中强制图像硬件加速,则可以创

建一个VolatileImage对象,它是存放在显示内存中的图像。要生成VolatileImage

对象,可以用Component的createVolatileImage(int w,int h)方法或

GraphicsConfiguration的createCompatibleVolatileImage(int w,int h)方

法,但是要注意VolatileImage只能是不透明的图像。VolatileImage可能随时丢失

,所以是易失的,如启动屏幕保护程序或改变显示方式地等,就要从显示内存中清除

VolatileImage,可以用validate()与contentsLost()方法。下面显示如何检

查和恢复VolatileImage,实现代码如下:
//生成图像
VolatileImage image=createVolatileImage(w,h);
//绘制图像
do{
int valid=image.validate(getGraphicsConfiguration());
if(valid==VolatileImage.IMAGE_INCOMPATIBLE){
image=createVolatileImage(w,h);
}
else if(valid==VolatileImage.IMAGE_RESTORED){
Graphic2D g=image.createGraphics();
MyDrawMethod(g);
g.dispose();
}
else
{ Graphics g=screen.getDrawGraphics();
g.drawImage(image,0,0,null);
g.dispose();
}
}
while(image.contentsLost());
上述代码一直执行循环操作,直到屏幕上成功画出图像为止。执行加速图像到底产生

多大影响,下面代码测试 :
清单2.4 ImageSpeedTest.java
import java.awt.*;
import javax.swing.ImageIcon;
import javax.swing.JFrame;

public class ImageSpeedTest extends JFrame {

    public static void main(String args[]) {

        DisplayMode displayMode;

        if (args.length == 3) {
            displayMode = new DisplayMode(
                Integer.parseInt(args[0]),
                Integer.parseInt(args[1]),
                Integer.parseInt(args[2]),
                DisplayMode.REFRESH_RATE_UNKNOWN);
        }
        else {
            displayMode = new DisplayMode(800, 600, 16,
                DisplayMode.REFRESH_RATE_UNKNOWN);
        }

        ImageSpeedTest test = new ImageSpeedTest();
        test.run(displayMode);
    }

    private static final int FONT_SIZE = 24;
    private static final long TIME_PER_IMAGE = 1500;

    private SimpleScreenManager screen;
    private Image bgImage;
    private Image opaqueImage;
    private Image transparentImage;
    private Image translucentImage;
    private Image antiAliasedImage;
    private boolean imagesLoaded;

    public void run(DisplayMode displayMode) {
        setBackground(Color.blue);
        setForeground(Color.white);
        setFont(new Font("Dialog", Font.PLAIN, FONT_SIZE));
        imagesLoaded = false;

        screen = new SimpleScreenManager();
        try {
            screen.setFullScreen(displayMode, this);
            synchronized (this) {
                loadImages();
                // wait for test to complete
                try {
                    wait();
                }
                catch (InterruptedException ex) { }
            }
        }
        finally {
            screen.restoreScreen();
        }
    }


    public void loadImages() {
        bgImage = loadImage("images/background.jpg";
        opaqueImage = loadImage("images/opaque.png";
        transparentImage = loadImage("images/transparent.png";
        translucentImage = loadImage("images/translucent.png";
        antiAliasedImage = loadImage("images/antialiased.png";
        imagesLoaded = true;
        // signal to AWT to repaint this window
        repaint();
    }


    private final Image loadImage(String fileName) {
        return new ImageIcon(fileName).getImage();
    }


    public void paint(Graphics g) {
        // set text anti-aliasing
        if (g instanceof Graphics2D) {
            Graphics2D g2 = (Graphics2D)g;
            g2.setRenderingHint(
                RenderingHints.KEY_TEXT_ANTIALIASING,
                RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        }

        // draw images
        if (imagesLoaded) {
            drawImage(g, opaqueImage, "Opaque";
            drawImage(g, transparentImage, "Transparent";
            drawImage(g, translucentImage, "Translucent";
            drawImage(g, antiAliasedImage,
              "Translucent (Anti-Aliased)";

            // notify that the test is complete
            synchronized (this) {
                notify();
            }
        }
        else {
            g.drawString("Loading Images...", 5, FONT_SIZE);
        }
    }


    public void drawImage(Graphics g, Image image, String name) {
        int width = screen.getFullScreenWindow().getWidth() -
            image.getWidth(null);
        int height = screen.getFullScreenWindow().getHeight() -
            image.getHeight(null);
        int numImages = 0;

        g.drawImage(bgImage, 0, 0, null);

        long startTime = System.currentTimeMillis();
        while (System.currentTimeMillis() - startTime
            < TIME_PER_IMAGE)
        {
            int x = Math.round((float)Math.random() * width);
            int y = Math.round((float)Math.random() * height);
            g.drawImage(image, x, y, null);
            numImages++;
        }
        long time = System.currentTimeMillis() - startTime;
        float speed = numImages * 1000f / time;
        System.out.println(name + ": " + speed + " images/sec";

    }

}
2.2.6动画:这里介绍的是第一种动画卡通式动画,它显示为连续的一序列图像。下面就介绍卡通式动画的工作方法。
我们以动态的英雄版本为例,它的头发会飘动,眼睛会眨眼。动画中的图像可以称为帧,每一帧显示一定时间,但帧不一定都显示相同时间,如第一帧显示200毫秒,第二秒显示75毫秒。以上就是动画的概念,下面就介绍代码了,首先明确动画中同一个图像可以多次使用,另外,动画是无限循环的,而不是播放一次。清单2.5的Animation类主要有三个重要方法:addFrame(),updateFrame()和getImage().addFrame()方法将图形加入动画中,并指定显示时间(毫秒)。update()方法告诉动画已经经过指定的时间。最后,getImage()方法根据经过的时间取得要显示的图像。
清单2.5 Animation.java
import java.awt.Image;
import java.util.ArrayList;

/**
    The Animation class manages a series of images (frames) and
    the amount of time to display each frame.
*/
public class Animation {

    private ArrayList frames;
    private int currFrameIndex;
    private long animTime;
    private long totalDuration;


    /**
        Creates a new, empty Animation.
    */
    public Animation() {
        frames = new ArrayList();
        totalDuration = 0;
        start();
    }


    /**
        Adds an image to the animation with the specified
        duration (time to display the image).
    */
    public synchronized void addFrame(Image image,
        long duration)
    {
        totalDuration += duration;
        frames.add(new AnimFrame(image, totalDuration));
    }


    /**
        Starts this animation over from the beginning.
    */
    public synchronized void start() {
        animTime = 0;
        currFrameIndex = 0;
    }


    /**
        Updates this animation's current image (frame), if
        neccesary.
    */
    public synchronized void update(long elapsedTime) {
        if (frames.size() > 1) {
            animTime += elapsedTime;

            if (animTime >= totalDuration) {
                animTime = animTime % totalDuration;
                currFrameIndex = 0;
            }

            while (animTime > getFrame(currFrameIndex).endTime) {
                currFrameIndex++;
            }
        }
    }


    /**
        Gets this Animation's current image. Returns null if this
        animation has no images.
    */
    public synchronized Image getImage() {
        if (frames.size() == 0) {
            return null;
        }
        else {
            return getFrame(currFrameIndex).image;
        }
    }


    private AnimFrame getFrame(int i) {
        return (AnimFrame)frames.get(i);
    }


    private class AnimFrame {

        Image image;
        long endTime;

        public AnimFrame(Image image, long endTime) {
            this.image = image;
            this.endTime = endTime;
        }
    }
}
Animation的代码很简单。其中AnimFrame类包含图像及其显示的时间量。大多数工作是在Animation的update()方法中完成的,方法根据经过的时间量取得正确的AnimFrame。
2.2.7活动绘制
  要实现动画就要以高效的方式连续更新屏幕。过去使用paint()方法 进行绘制,通过调用repaint()方法,让AWT事件派生线程重画屏幕,同时使代码得到简化。要使用活动绘制方法,可以调用Component类的getGraphics()方法取得屏幕图形描述表,实现代码如下:
Graphics g=screen.getFullScreenWindow().getGraphics();
draw(g);
g.dispose();
2.2.8动画循环
下面使用活动绘制在循环中连续绘图,这个循环称为动画循环,首先观察者它的步骤
(1)更新任何动画
(2)绘制屏幕
(3)可休眠一会
(4)再次从第一步开始
然后给出动画循环的代码:
while(true)
{ updateAnimations();
Graphics g=screen.getFullScreenWindow().getGraphics();
draw(g);
g.dispose();
try{Thread.sleep(20);
}
catch(InterruptedException ex){}
}
由于显示的动画循环是不能永远循环的,所以在本例中,也让动画循环几秒后停止。下面是测试动画的例子:
清单2.6 AnimationTest1.java
import java.awt.*;
import javax.swing.ImageIcon;
import javax.swing.JFrame;

public class AnimationTest1 {

    public static void main(String args[]) {

        DisplayMode displayMode;

        if (args.length == 3) {
            displayMode = new DisplayMode(
                Integer.parseInt(args[0]),
                Integer.parseInt(args[1]),
                Integer.parseInt(args[2]),
                DisplayMode.REFRESH_RATE_UNKNOWN);
        }
        else {
            displayMode = new DisplayMode(800, 600, 16,
                DisplayMode.REFRESH_RATE_UNKNOWN);
        }

        AnimationTest1 test = new AnimationTest1();
        test.run(displayMode);
    }

    private static final long DEMO_TIME = 5000;

    private SimpleScreenManager screen;
    private Image bgImage;
    private Animation anim;


    public void loadImages() {
        // load images
        bgImage = loadImage("images/background.jpg");
        Image player1 = loadImage("images/player1.png");
        Image player2 = loadImage("images/player2.png");
        Image player3 = loadImage("images/player3.png");

        // create animation
        anim = new Animation();
        anim.addFrame(player1, 250);
        anim.addFrame(player2, 150);
        anim.addFrame(player1, 150);
        anim.addFrame(player2, 150);
        anim.addFrame(player3, 200);
        anim.addFrame(player2, 150);
    }


    private Image loadImage(String fileName) {
        return new ImageIcon(fileName).getImage();
    }


    public void run(DisplayMode displayMode) {
        screen = new SimpleScreenManager();
        try {
            screen.setFullScreen(displayMode, new JFrame());
            loadImages();
            animationLoop();
        }
        finally {
            screen.restoreScreen();
        }
    }


    public void animationLoop() {
        long startTime = System.currentTimeMillis();
        long currTime = startTime;

        while (currTime - startTime < DEMO_TIME) {
            long elapsedTime =
                System.currentTimeMillis() - currTime;
            currTime += elapsedTime;

            // update animation
            anim.update(elapsedTime);

            // draw to screen
            Graphics g =
                screen.getFullScreenWindow().getGraphics();
            draw(g);
            g.dispose();

            // take a nap
            try {
                Thread.sleep(20);
            }
            catch (InterruptedException ex) { }
        }

    }


    public void draw(Graphics g) {
        // draw background
        g.drawImage(bgImage, 0, 0, null);

        // draw image
        g.drawImage(anim.getImage(), 0, 0, null);
    }

}
2.3 避免闪烁和裂开
运行AnimationTest1对象时,可能发现动画角色会闪烁。这是因为不断直接绘制屏幕的结果。当要用背景擦除角色,然后重画角色,因此在某个瞬间,可能在角色位置看到背景,由于这一切发生 得如此之快,所以会出现闪烁。那如何避免闪烁呢?办法就是双缓存。
双缓存:缓存区是绘图绘图时使用的屏外内存区。使用双缓存不是直接绘制屏幕,而是绘制到后缓存区,然后将整个缓存区复制到屏幕上,这样就可以同时更新整个屏幕,游戏者只看到要看到的东西了。后缓存区可能只是普通java图像。可以用Component类的createImage(int w,int h)方法生成后缓存区,如果是不使用活动绘制的小程序使用双缓存,则可以覆盖update()方法,改用双缓存,并用双缓存的图形描述表调用paint()方法,实现代码如下:
private Image doubleBuffer;
public void update(Graphics g){
Dimension size=getSize();
if(doubleBuffer.getWidth(this)!=size.width||doubleBuffer.getHeight(this)!=size.height)
{ doubleBuffer=createImage(size.width,size.height);
}
if(doubleBuffer!=null){
//绘制到双缓存区
Graphics g2=doubleBuffer.getGraphics();
paint(g2);
g2.dispose();
//将双缓存复制到屏幕
g.drawImage(doubleBuffer,0,0,null);
}
else
{//不要双缓存,只绘制屏幕
paint(g);
}
}
public void paint(Graphics g){
//在此绘图
}
2.3.2翻页
 使用双缓存的一个缺点就是后缓存区复制到屏幕上需要较长的时间,如果不用复制缓存区,而直接把后缓存区变成显示缓存区,那该多好!翻页技术就可以实现。使用翻页方法时,要用2个缓存区,一个是后缓存区,一个是显示缓存区。显示指针指向要显示的缓存区,大多数现代系统都可以改变这个显示指针。向后缓存区绘制完毕后,显示指针可以从当前显示缓存区换到后缓存区。改变显示指针时,显示缓存区立即变成后缓存区,而后缓存区立即变成显示缓存区。
2.3.3监视刷新和裂开
显示器的刷新率通常在75赫兹左右,表示显示器每秒刷新75次。但如果在显示器刷新途中发生翻页或缓存区复制操作就会同时显示旧缓存区和新缓存区一部分,这个效果与闪烁相似,称为裂开。为了解决问题,可以在显示器刷新之前完成翻页工作,在java中用BufferStrategy类完成工作。
2.2.4BufferStrategy类
双缓存,翻页和显示刷新等待都是由BufferStrategy类处理的,它会根据系统功能选择最佳的缓存方法。首先,它尝试进行翻页,若无法翻页则尝试进行双缓存,另外翻页之前显示刷新完毕。等待显示刷新的一个缺点就是会限制游戏中每秒可以显示的帧数。如果显示刷新率设置为75HZ,则游戏中每秒可以显示的帧数最多为75,这就不能用游戏帧速率作为测试系统运行速度的基准。Canvas与Window对象都可以有BufferStrategy.利用createBufferStrategy()方法,根据所要的缓存数生成BufferStrategy.要进行翻页与双缓存,至少要有2个缓存区,实现代码如下:
frame.createBufferStrategy(2);
生成BufferStrategy之后,要取得它的引用,可调用getBufferStrategy()方法,然后用getDrawGraphics()方法取得绘制缓存区的图形描述表。绘制完成后,调用show()方法显示绘图缓存区,就可以使用翻页,也可以将绘图缓存区复制到显示缓存区,实现代码如下:
BufferStrategy strategy=frame.getBufferStrategy();
Graphics g=strategy.getDrawGraphics();
draw(g);
g.dispose();
strategy.show();
2.3.5生成屏幕管理器
下面要用这些特性更新SimpleScreenManager类,要增加以下内容:
1。生成BufferStrategy,实现翻页与双缓存
2。getGraphics(),取得显示的图形描述表
3。update(),更新显示
4。getCompatibleDisplayModes(),取得一列兼容显示方式
5。getCurrentDisplayMode(),取得当前显示方式
6。findFirstCompatibleMode(),从显示方式清单中取得第一个兼容显示方式
进行绘制之后就不用JFrame作为全屏窗口,从操作系统接收paint事件,因此可以将其关闭,实现代码如下:
frame.ignoreRepaint(true);
但这并不关闭正常的repaint事件。因为对JFrame调用repaint()方法时,仍然发送paint事件。
清单2.7 所示的ScreenManager类中用这些新特性更新SimpleScreenManager.
清单2。7  ScreenManager.java
import java.awt.*;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.lang.reflect.InvocationTargetException;
import javax.swing.JFrame;

/**
    The ScreenManager class manages initializing and displaying
    full screen graphics modes.
*/
public class ScreenManager {

    private GraphicsDevice device;

    /**
        Creates a new ScreenManager object.
    */
    public ScreenManager() {
        GraphicsEnvironment environment =
            GraphicsEnvironment.getLocalGraphicsEnvironment();
        device = environment.getDefaultScreenDevice();
    }


    /**
        Returns a list of compatible display modes for the
        default device on the system.
    */
    public DisplayMode[] getCompatibleDisplayModes() {
        return device.getDisplayModes();
    }


    /**
        Returns the first compatible mode in a list of modes.
        Returns null if no modes are compatible.
    */
    public DisplayMode findFirstCompatibleMode(
        DisplayMode modes[])
    {
        DisplayMode goodModes[] = device.getDisplayModes();
        for (int i = 0; i < modes.length; i++) {
            for (int j = 0; j < goodModes.length; j++) {
                if (displayModesMatch(modes[i], goodModes[j])) {
                    return modes[i];
                }
            }

        }

        return null;
    }


    /**
        Returns the current display mode.
    */
    public DisplayMode getCurrentDisplayMode() {
        return device.getDisplayMode();
    }


    /**
        Determines if two display modes "match". Two display
        modes match if they have the same resolution, bit depth,
        and refresh rate. The bit depth is ignored if one of the
        modes has a bit depth of DisplayMode.BIT_DEPTH_MULTI.
        Likewise, the refresh rate is ignored if one of the
        modes has a refresh rate of
        DisplayMode.REFRESH_RATE_UNKNOWN.
    */
    public boolean displayModesMatch(DisplayMode mode1,
        DisplayMode mode2)

    {
        if (mode1.getWidth() != mode2.getWidth() ||
            mode1.getHeight() != mode2.getHeight())
        {
            return false;
        }

        if (mode1.getBitDepth() != DisplayMode.BIT_DEPTH_MULTI &&
            mode2.getBitDepth() != DisplayMode.BIT_DEPTH_MULTI &&
            mode1.getBitDepth() != mode2.getBitDepth())
        {
            return false;
        }

        if (mode1.getRefreshRate() !=
            DisplayMode.REFRESH_RATE_UNKNOWN &&
            mode2.getRefreshRate() !=
            DisplayMode.REFRESH_RATE_UNKNOWN &&
            mode1.getRefreshRate() != mode2.getRefreshRate())
         {
             return false;
         }

         return true;
    }


    /**
        Enters full screen mode and changes the display mode.
        If the specified display mode is null or not compatible
        with this device, or if the display mode cannot be
        changed on this system, the current display mode is used.
        <p>
        The display uses a BufferStrategy with 2 buffers.
    */
    public void setFullScreen(DisplayMode displayMode) {
        final JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setUndecorated(true);
        frame.setIgnoreRepaint(true);
        frame.setResizable(false);

        device.setFullScreenWindow(frame);

        if (displayMode != null &&
            device.isDisplayChangeSupported())
        {
            try {
                device.setDisplayMode(displayMode);
            }
            catch (IllegalArgumentException ex) { }
            // fix for mac os x
            frame.setSize(displayMode.getWidth(),
                displayMode.getHeight());
        }
        // avoid potential deadlock in 1.4.1_02
        try {
            EventQueue.invokeAndWait(new Runnable() {
                public void run() {
                    frame.createBufferStrategy(2);
                }
            });
        }
        catch (InterruptedException ex) {
            // ignore
        }
        catch (InvocationTargetException  ex) {
            // ignore
        }
    }


    /**
        Gets the graphics context for the display. The
        ScreenManager uses double buffering, so applications must
        call update() to show any graphics drawn.
        <p>
        The application must dispose of the graphics object.
    */
    public Graphics2D getGraphics() {
        Window window = device.getFullScreenWindow();
        if (window != null) {
            BufferStrategy strategy = window.getBufferStrategy();
            return (Graphics2D)strategy.getDrawGraphics();
        }
        else {
            return null;
        }
    }


    /**
        Updates the display.
    */
    public void update() {
        Window window = device.getFullScreenWindow();
        if (window != null) {
            BufferStrategy strategy = window.getBufferStrategy();
            if (!strategy.contentsLost()) {
                strategy.show();
            }
        }
        // Sync the display on some systems.
        // (on Linux, this fixes event queue problems)
        Toolkit.getDefaultToolkit().sync();
    }


    /**
        Returns the window currently used in full screen mode.
        Returns null if the device is not in full screen mode.
    */
    public JFrame getFullScreenWindow() {
        return (JFrame)device.getFullScreenWindow();
    }


    /**
        Returns the width of the window currently used in full
        screen mode. Returns 0 if the device is not in full
        screen mode.
    */
    public int getWidth() {
        Window window = device.getFullScreenWindow();
        if (window != null) {
            return window.getWidth();
        }
        else {
            return 0;
        }
    }


    /**
        Returns the height of the window currently used in full
        screen mode. Returns 0 if the device is not in full
        screen mode.
    */
    public int getHeight() {
        Window window = device.getFullScreenWindow();
        if (window != null) {
            return window.getHeight();
        }
        else {
            return 0;
        }
    }


    /**
        Restores the screen's display mode.
    */
    public void restoreScreen() {
        Window window = device.getFullScreenWindow();
        if (window != null) {
            window.dispose();
        }
        device.setFullScreenWindow(null);
    }


    /**
        Creates an image compatible with the current display.
    */
    public BufferedImage createCompatibleImage(int w, int h,
        int transparancy)
    {
        Window window = device.getFullScreenWindow();
        if (window != null) {
            GraphicsConfiguration gc =
                window.getGraphicsConfiguration();
            return gc.createCompatibleImage(w, h, transparancy);
        }
        return null;
    }
}
在ScreenManager类代码中,注意update()方法有下列语句。
Toolkit.getDefaultToolkit().sync();
这个方法保证显示与窗口系统同步。在许多系统中,它什么也不干,但在linux中,调用这个方法可以解决AWT事件队列问题。如果不调用这个方法则有些linux系统可能遇到延迟的鼠标和键盘事件。2个指得注意的新方法是displayModesMatch()与createCompatibleImage().displayModesMatch()方法检查2个DisplayMode对象是否匹配,匹配是指具有相同的分辨率,位深度和刷新率。如果位深度或刷新率在一个
DisplayMode对象中没有指定,则将其忽略。createCompatibleImage()方法生成与当前显示器兼容的图像,即与显示器具有相同位深度和颜色模型 的图像。生成的图像类为BufferedImage,是系统内存中存放的非加速图像。这个方法可以生成透明和半透明图像,因为正常的createImage()方法只生成不透明图像。清单2.8 AnimationTest2类,这时程序不再闪烁!
清单2.8 AnimatoinTest.java
import java.awt.*;
import javax.swing.ImageIcon;

public class AnimationTest2 {

    public static void main(String args[]) {
        AnimationTest2 test = new AnimationTest2();
        test.run();
    }

    private static final DisplayMode POSSIBLE_MODES[] = {
        new DisplayMode(800, 600, 32, 0),
        new DisplayMode(800, 600, 24, 0),
        new DisplayMode(800, 600, 16, 0),
        new DisplayMode(640, 480, 32, 0),
        new DisplayMode(640, 480, 24, 0),
        new DisplayMode(640, 480, 16, 0)
    };

    private static final long DEMO_TIME = 10000;

    private ScreenManager screen;
    private Image bgImage;
    private Animation anim;


    public void loadImages() {
        // load images
        bgImage = loadImage("images/background.jpg");
        Image player1 = loadImage("images/player1.png");
        Image player2 = loadImage("images/player2.png");
        Image player3 = loadImage("images/player3.png");

        // create animation
        anim = new Animation();
        anim.addFrame(player1, 250);
        anim.addFrame(player2, 150);
        anim.addFrame(player1, 150);
        anim.addFrame(player2, 150);
        anim.addFrame(player3, 200);
        anim.addFrame(player2, 150);
    }


    private Image loadImage(String fileName) {
        return new ImageIcon(fileName).getImage();
    }


    public void run() {
        screen = new ScreenManager();
        try {
            DisplayMode displayMode =
                screen.findFirstCompatibleMode(POSSIBLE_MODES);
            screen.setFullScreen(displayMode);
            loadImages();
            animationLoop();
        }
        finally {
            screen.restoreScreen();
        }
    }


    public void animationLoop() {
        long startTime = System.currentTimeMillis();
        long currTime = startTime;

        while (currTime - startTime < DEMO_TIME) {
            long elapsedTime =
                System.currentTimeMillis() - currTime;
            currTime += elapsedTime;

            // update animation
            anim.update(elapsedTime);

            // draw and update screen
            Graphics2D g = screen.getGraphics();
            draw(g);
            g.dispose();
            screen.update();

            // take a nap
            try {
                Thread.sleep(20);
            }
            catch (InterruptedException ex) { }
        }

    }


    public void draw(Graphics g) {
        // draw background
        g.drawImage(bgImage, 0, 0, null);

        // draw image
        g.drawImage(anim.getImage(), 0, 0, null);
    }

}


从AnimationTest1转到AnimationTest2没有多大的变化,只是AnimationTest2改变了选择显示的方式,不是使用缺省显示方式或从命令行取得显示方式,而是ScreenManager提供一列可用的显示方式,由ScreenManager选择清单中第一个兼容显示的方式。另外,ScreenManager还生成自己的JFrame对象,所以AnimationTest2不必生成用作全屏窗口的JFrame.
2.3.6 幽灵
动画已经运行了,但只是在一个地方看到动画并不奇妙,所以下面介绍生成幽灵。幽灵是在屏幕上独立移动的图形。其实幽灵也是动画,不过可以同时执行动画和移动操作。除了动画之外,幽灵还有两个特性:位置与速度。这里把速度分解为水平和垂直成分,单位是像素/每秒。为什么要用速度,而不是直接在每帧中将幽灵位置作一定修改呢?如果直接在每帧中将幽灵位置作一定修改,则不同机器的不同速度会将幽灵移到不同的位置上,帧速率越快,幽灵移动越快如果幽灵实时移动,则不管帧之间的时间是长是短,幽灵都按一致的路径移动。和动画一样,幽灵根据上次显示幽灵时经过的毫秒数来更新,如经过50毫秒之后,幽灵会根据速度更新其位置与动画。清单2.9的Sprite类具有动画。位置和速度。
幽灵位置可以使用整数,但幽灵会慢慢移动。假设每次调用updat()方法时,幽灵移动十分之一像素,则九次调用updat()方法时幽灵移动均不明显,到第十次调用时才看到幽灵移动。为此,幽灵位置要用浮点数值,所以要取得精确的像素位置,可以使用Math.round()方法。
清单2.9 Sprite.java
import java.awt.Image;

public class Sprite {

    private Animation anim;
    // position (pixels)
    private float x;
    private float y;
    // velocity (pixels per millisecond)
    private float dx;
    private float dy;

    /**
        Creates a new Sprite object with the specified Animation.
    */
    public Sprite(Animation anim) {
        this.anim = anim;
    }

    /**
        Updates this Sprite's Animation and its position based
        on the velocity.
    */
    public void update(long elapsedTime) {
        x += dx * elapsedTime;
        y += dy * elapsedTime;
        anim.update(elapsedTime);
    }

    /**
        Gets this Sprite's current x position.
    */
    public float getX() {
        return x;
    }

    /**
        Gets this Sprite's current y position.
    */
    public float getY() {
        return y;
    }

    /**
        Sets this Sprite's current x position.
    */
    public void setX(float x) {
        this.x = x;
    }

    /**
        Sets this Sprite's current y position.
    */
    public void setY(float y) {
        this.y = y;
    }

    /**
        Gets this Sprite's width, based on the size of the
        current image.
    */
    public int getWidth() {
        return anim.getImage().getWidth(null);
    }

    /**
        Gets this Sprite's height, based on the size of the
        current image.
    */
    public int getHeight() {
        return anim.getImage().getHeight(null);
    }

    /**
        Gets the horizontal velocity of this Sprite in pixels
        per millisecond.
    */
    public float getVelocityX() {
        return dx;
    }

    /**
        Gets the vertical velocity of this Sprite in pixels
        per millisecond.
    */
    public float getVelocityY() {
        return dy;
    }

    /**
        Sets the horizontal velocity of this Sprite in pixels
        per millisecond.
    */
    public void setVelocityX(float dx) {
        this.dx = dx;
    }

    /**
        Sets the vertical velocity of this Sprite in pixels
        per millisecond.
    */
    public void setVelocityY(float dy) {
        this.dy = dy;
    }

    /**
        Gets this Sprite's current image.
    */
    public Image getImage() {
        return anim.getImage();
    }
}
下面用Sprite类让动画在屏幕上来回弹跳,清单2.10的SpriteTest1程序来完成这个工作,每次幽灵碰到屏幕边沿时,其速度改变,表示弹回。
清单2.10 SjpriteTest.java
import java.awt.*;
import javax.swing.ImageIcon;

public class SpriteTest1 {

    public static void main(String args[]) {
        SpriteTest1 test = new SpriteTest1();
        test.run();
    }

    private static final DisplayMode POSSIBLE_MODES[] = {
        new DisplayMode(800, 600, 32, 0),
        new DisplayMode(800, 600, 24, 0),
        new DisplayMode(800, 600, 16, 0),
        new DisplayMode(640, 480, 32, 0),
        new DisplayMode(640, 480, 24, 0),
        new DisplayMode(640, 480, 16, 0)
    };

    private static final long DEMO_TIME = 10000;

    private ScreenManager screen;
    private Image bgImage;
    private Sprite sprite;

    public void loadImages() {
        // load images
        bgImage = loadImage("images/background.jpg");
        Image player1 = loadImage("images/player1.png");
        Image player2 = loadImage("images/player2.png");
        Image player3 = loadImage("images/player3.png");

        // create sprite
        Animation anim = new Animation();
        anim.addFrame(player1, 250);
        anim.addFrame(player2, 150);
        anim.addFrame(player1, 150);
        anim.addFrame(player2, 150);
        anim.addFrame(player3, 200);
        anim.addFrame(player2, 150);
        sprite = new Sprite(anim);

        // start the sprite off moving down and to the right
        sprite.setVelocityX(0.2f);
        sprite.setVelocityY(0.2f);
    }


    private Image loadImage(String fileName) {
        return new ImageIcon(fileName).getImage();
    }


    public void run() {
        screen = new ScreenManager();
        try {
            DisplayMode displayMode =
                screen.findFirstCompatibleMode(POSSIBLE_MODES);
            screen.setFullScreen(displayMode);
            loadImages();
            animationLoop();
        }
        finally {
            screen.restoreScreen();
        }
    }


    public void animationLoop() {
        long startTime = System.currentTimeMillis();
        long currTime = startTime;

        while (currTime - startTime < DEMO_TIME) {
            long elapsedTime =
                System.currentTimeMillis() - currTime;
            currTime += elapsedTime;

            // update the sprites
            update(elapsedTime);

            // draw and update the screen
            Graphics2D g = screen.getGraphics();
            draw(g);
            g.dispose();
            screen.update();

            // take a nap
            try {
                Thread.sleep(20);
            }
            catch (InterruptedException ex) { }
        }
    }


    public void update(long elapsedTime) {
        // check sprite bounds
        if (sprite.getX() < 0) {
            sprite.setVelocityX(Math.abs(sprite.getVelocityX()));
        }
        else if (sprite.getX() + sprite.getWidth() >=
            screen.getWidth())
        {
            sprite.setVelocityX(-Math.abs(sprite.getVelocityX()));
        }
        if (sprite.getY() < 0) {
            sprite.setVelocityY(Math.abs(sprite.getVelocityY()));
        }
        else if (sprite.getY() + sprite.getHeight() >=
            screen.getHeight())
        {
            sprite.setVelocityY(-Math.abs(sprite.getVelocityY()));
        }

        // update sprite
        sprite.update(elapsedTime);
    }


    public void draw(Graphics g) {
        // draw background
        g.drawImage(bgImage, 0, 0, null);

        // draw sprite
        g.drawImage(sprite.getImage(),
            Math.round(sprite.getX()),
            Math.round(sprite.getY()),
            null);
    }

}
由于Sprite对象处理自己的运动,因此SpriteTest1类中没有什么新东西。最新的东西是update()方法,使幽灵碰到屏幕边沿弹回。幽灵碰到屏幕左右边沿时,水平速度变成相反值。幽灵碰到屏幕上下边沿时,垂直速度变成相反值。
2.4 简单效果
要想增加更多的幽灵,只要生成更多Sprite对象,并更新和绘制每个Sprite对象,实现代码如下:
for(int i=0;i<=sprites.length;i++)
{ sprites[i].update(elaspsedTime);
  g.drawImage(sprites[i].getImage(),Math.round(sprites[i].getX()),Math.round(sprites[i].getY()),null);
}
值得注意的是:因为幽灵更新自己的动画,所以不能共享一个Animation对象,否则会使Animation对象更新太多次。如果要使用大量相同的动画,则可以增加一个clone()方法,使动画复制更容易。
图像变换:一个精彩的效果是图像旋转和比例缩放,可称为图像变换。图像变换可以使图像平移,翻折,缩放,剪切和旋转,甚至可以动态进行。可以用AffineTransform对象描述图像变换。AffineTransform对象用3*3矩阵存储变换数据,但不必了解矩阵如何作用于图形,因为AffineTransform 类提供了roate(),scale()和translate()等简单方法,就可以帮助进行计算了。图像变换实际发生在Graphics2D对象中,Graphics2D类有几个特殊的drawImage()方法,方法取得AffineTransform作为参数,下面的示例将图像画成两倍大小:
AffineTransform transform=new AfffineTransform();
transform.scale(2,2);
transform.translate(100,100);
g.drawImage(image,transform,null);
下面再SpriteTest1对象中加入几个幽灵和一些图像变换。角色通常面朝右边,但移到左边时,也可以让角色面朝左边。这里不必装入一幅面朝左边的图像,而是通过图像的变换动态生成图像的镜像。为此,用-1缩放图像宽度,然后结果平移,使其放到正确的位置,实现代码如下:
transform.scale(-1,1);
transform.translate(-sprite.getWidth(),0);
为了增加趣味,清单2.11 的SpriteTest2 类还增加了淡化效果,当程序首次启动时,图像从黑色变淡,就像打开百叶窗一样,程序退出时则相反。要生成这个效果,可以用Graphics对象的fillRect()方法生成黑色实体百叶窗,它的尺寸取决于运行演示的时间:
清单2.11   SpriteTest2.java

import java.awt.*;
import java.awt.geom.AffineTransform;
import javax.swing.ImageIcon;

public class SpriteTest2 {

    public static void main(String args[]) {
        SpriteTest2 test = new SpriteTest2();
        test.run();
    }

    private static final DisplayMode POSSIBLE_MODES[] = {
        new DisplayMode(800, 600, 32, 0),
        new DisplayMode(800, 600, 24, 0),
        new DisplayMode(800, 600, 16, 0),
        new DisplayMode(640, 480, 32, 0),
        new DisplayMode(640, 480, 24, 0),
        new DisplayMode(640, 480, 16, 0)
    };

    private static final long DEMO_TIME = 10000;
    private static final long FADE_TIME = 1000;
    private static final int NUM_SPRITES = 3;

    private ScreenManager screen;
    private Image bgImage;
    private Sprite sprites[];

    public void loadImages() {
        // load images
        bgImage = loadImage("images/background.jpg");
        Image player1 = loadImage("images/player1.png");
        Image player2 = loadImage("images/player2.png");
        Image player3 = loadImage("images/player3.png");

        // create and init sprites
        sprites = new Sprite[NUM_SPRITES];
        for (int i = 0; i < NUM_SPRITES; i++) {
            Animation anim = new Animation();
            anim.addFrame(player1, 250);
            anim.addFrame(player2, 150);
            anim.addFrame(player1, 150);
            anim.addFrame(player2, 150);
            anim.addFrame(player3, 200);
            anim.addFrame(player2, 150);
            sprites[i] = new Sprite(anim);

            // select random starting location
            sprites[i].setX((float)Math.random() *
                (screen.getWidth() - sprites[i].getWidth()));
            sprites[i].setY((float)Math.random() *
                (screen.getHeight() - sprites[i].getHeight()));

            // select random velocity
            sprites[i].setVelocityX((float)Math.random() - 0.5f);
            sprites[i].setVelocityY((float)Math.random() - 0.5f);
        }

    }


    private Image loadImage(String fileName) {
        return new ImageIcon(fileName).getImage();
    }


    public void run() {
        screen = new ScreenManager();
        try {
            DisplayMode displayMode =
                screen.findFirstCompatibleMode(POSSIBLE_MODES);
            screen.setFullScreen(displayMode);
            loadImages();
            animationLoop();
        }
        finally {
            screen.restoreScreen();
        }
    }


    public void animationLoop() {
        long startTime = System.currentTimeMillis();
        long currTime = startTime;

        while (currTime - startTime < DEMO_TIME) {
            long elapsedTime =
                System.currentTimeMillis() - currTime;
            currTime += elapsedTime;

            // update the sprites
            update(elapsedTime);

            // draw and update screen
            Graphics2D g = screen.getGraphics();
            draw(g);
            drawFade(g, currTime - startTime);
            g.dispose();
            screen.update();

            // take a nap
            try {
                Thread.sleep(20);
            }
            catch (InterruptedException ex) { }
        }

    }


    public void drawFade(Graphics2D g, long currTime) {
        long time = 0;
        if (currTime <= FADE_TIME) {
            time = FADE_TIME - currTime;
        }
        else if (currTime > DEMO_TIME - FADE_TIME) {
            time = FADE_TIME - DEMO_TIME + currTime;
        }
        else {
            return;
        }

        byte numBars = 8;
        int barHeight = screen.getHeight() / numBars;
        int blackHeight = (int)(time * barHeight / FADE_TIME);

        g.setColor(Color.black);
        for (int i = 0; i < numBars; i++) {
            int y = i * barHeight + (barHeight - blackHeight) / 2;
            g.fillRect(0, y, screen.getWidth(), blackHeight);
        }

    }


    public void update(long elapsedTime) {

        for (int i = 0; i < NUM_SPRITES; i++) {

            Sprite s = sprites[i];

            // check sprite bounds
            if (s.getX() < 0.) {
                s.setVelocityX(Math.abs(s.getVelocityX()));
            }
            else if (s.getX() + s.getWidth() >=
                screen.getWidth())
            {
                s.setVelocityX(-Math.abs(s.getVelocityX()));
            }
            if (s.getY() < 0) {
                s.setVelocityY(Math.abs(s.getVelocityY()));
            }
            else if (s.getY() + s.getHeight() >=
                screen.getHeight())
            {
                s.setVelocityY(-Math.abs(s.getVelocityY()));
            }

            // update sprite
            s.update(elapsedTime);
        }

    }


    public void draw(Graphics2D g) {
        // draw background
        g.drawImage(bgImage, 0, 0, null);

        AffineTransform transform = new AffineTransform();
        for (int i = 0; i < NUM_SPRITES; i++) {
            Sprite sprite = sprites[i];

            // translate the sprite
            transform.setToTranslation(sprite.getX(),
                sprite.getY());

            // if the sprite is moving left, flip the image
            if (sprite.getVelocityX() < 0) {
                transform.scale(-1, 1);
                transform.translate(-sprite.getWidth(), 0);
            }

            // draw it
            g.drawImage(sprite.getImage(), transform, null);
        }

    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值