绘图板程序设计及其具体实现 第四篇

工具包详细解析

(三) 渲染处理工具类

FrameRate类

FrameRate类是计算帧速率的工具类,在所有需要每帧绘制的程序中都可以引用。

  • FrameRate类字段一览表
修饰符类型名称说明
privateStringframeRate表示帧速率的字符串
privatelonglastTime上一帧的时间
privatelongdelta所有帧的总时间,大于1秒时清零
privateintframeCount表示帧速率的数值
  • FrameRate类方法一览表
修饰符返回值函数名参数说明
publicvoidinitialize()初始化,程序开始时调用一次
publicvoidcalculate()计算帧速率,每帧调用
publicStringgetFrameRate()返回表示帧速率的字符串
  • FrameRate类源代码
package Rendering.utils;

/**
 * This class is used to calculate the FPS(Frames Per Second).
 *
 * At first, you need to call the function initialize() to 
 * initialize the parameters.
 * Then call the function calculate() every time when rendering.
 * Finally, call the function getFrameRate() when you need to use the FPS.
 * @author Mortal
 * @since 1.0
 */
public class FrameRate {
    /**
     * The message String that is needed to display the FPS.
     */
    private String frameRate;

    /**
     * Last time to call the function calculate() or initialize().
     */
    private long lastTime;

    /**
     * Add the millisecond every time when call the function calculate(),
     * When it comes to one second, update the message String 
     * and reset it to zero.
     */
    private long delta;

    /**
     * The count of calling calculate() in one second.
     */
    private int frameCount;

    /**
     * Initialize the parameters.
     */
    public void initialize() {
        lastTime = System.currentTimeMillis();
        frameRate = "FPS 0";
    }

    /**
     * Add the millisecond every time and update the message String 
     * when it comes to one second.
     */
    public void calculate() {
        long current = System.currentTimeMillis();
        delta += current - lastTime;
        lastTime = current;
        frameCount++;
        if (delta > 1000) {
            delta -= 1000;
            frameRate = String.format("FPS %s", frameCount);
            frameCount = 0;
        }
    }

    /**
     * To get to message String of FPS(Frames Per Second).
     * @return The message String of FPS(Frames Per Second).
     */
    public String getFrameRate() {
        return frameRate;
    }
}

Framework类

Framework类是表示窗口的抽象类,具有一个窗口所需的所用功能及接口(API)。它继承了JFrame类,实现了Runnable接口。调用时使run方法在 AWT 事件指派线程上异步执行。 Framework类是采用了抽象方法的设计模式,在父类中定义方法调用的抽象方法和一些接口(API),而在子类,即主程序类中再根据具体的方法来进行实现,Framework类本身可以引用到任何的窗口程序中。

  • Framework类字段一览表
修饰符类型名称说明
privateBufferStrategybufferStrategy缓冲策略(双缓冲)
private volatilebooleanrunning线程是否正在运行
privateThreadgameThread主线程
protectedintvx视图窗口的x位置
protectedintvy视图窗口的y位置
protectedintvw视图窗口的宽度
protectedintvh视图窗口的高度
protectedFrameRateframeRate计算帧速率的工具
protectedRelativeMouseInputmouse鼠标输入处理类
protectedSafeKeyboardInputkeyboard键盘输入处理类
protectedColorappBackground窗口的背景颜色
protectedColorappBorder窗口的边框颜色
protectedColorappFPSColor显示帧速率的颜色
protectedFontappFont窗口的字体
protectedStringappTitle窗口的题目
protectedfloatappBorderScale视图窗口在主窗口的占比
protectedintappWidth主窗口的宽度
protectedintappHeight主窗口的高度
protectedfloatappWorldWidth世界地图的宽度
protectedfloatappWorldHeight世界地图的高度
protectedlongappSleep每帧的休眠时间
protectedbooleanappMaintainRatio视图窗口是否占据整个主窗口
protectedbooleanappDisableCursor鼠标光标是否可见
  • Framework类方法一览表
修饰符返回值函数名参数说明
publicFramework()默认构造函数
protected abstractvoidcreateFramework()创建整个窗口
protected abstractvoidrenderFrame(Graphics g)渲染整个窗口
public abstractintgetScreenWidth()返回视图窗口的宽度
public abstractintgetScreenHeight()返回视图窗口的高度
protectedvoidcreateAndShowGUI()创建整个窗口并运行主线程
protectedvoidsetupInput(Component component)为指定组件设置键盘事件处理和鼠标事件处理
protectedvoidcreateBufferStrategy(Canvas component)根据窗口组件构造双缓冲策略
protectedvoidcreateBufferStrategy(Window window)根据屏幕窗口构造双缓冲策略(全屏时调用)
protectedvoidsetupViewport(int sw,int sh)根据主窗口的大小和比例设置视图窗口的位置和大小
protectedMatrix3x3fgetViewportTransform()返回世界坐标到视图窗口坐标的转换矩阵
protectedMatrix3x3fgetReverseViewportTransform()返回视图窗口坐标到世界坐标的转换矩阵
publicVector2fgetWorldMousePosition()返回映射到世界坐标上的鼠标绝对位置
protectedVector2fgetRelativeWorldMousePosition()返回映射到世界坐标上的鼠标相对上一帧的位置
publicvoidrun()初始化并运行主线程
protectedvoidinitialize()执行初始化操作
protectedvoidterminate()执行资源的释放操作
privatevoidgameLoop(float delta)主线程中的主循环
privatevoidrenderFrame()从双缓冲策略中获取需要绘制的Graphics实例并进行绘制
privatevoidsleep(long sleep)主线程休眠指定时间
protectedvoidprocessInput(float delta)处理输入
protectedvoidupdateObject(float delta)更新每个需要绘制的图形对象
protectedvoidrender(Graphics g)每帧渲染视图窗口
privatevoiddisableCursor()设置鼠标光标不可见
protectedvoidshutDown()关闭窗口
protectedvoidonShutDown()关闭窗口后需要执行的操作
protected staticvoidlaunchApp(final Frameworkapp)使用该静态执行主程序,使run方法在 AWT 事件指派线程上异步执行
  • Framework类源代码
package Rendering.utils;

import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferStrategy;

public abstract class Framework extends JFrame implements Runnable {
    private BufferStrategy bufferStrategy;
    private volatile boolean running;
    private Thread gameThread;

    protected int vx;
    protected int vy;
    protected int vw;
    protected int vh;
    protected FrameRate frameRate;
    protected RelativeMouseInput mouse;
    protected SafeKeyboardInput keyboard;
    protected Color appBackground = Color.BLACK;
    protected Color appBorder = Color.LIGHT_GRAY;
    protected Color appFPSColor = Color.GREEN;
    protected Font appFont = new Font("Courier New", Font.PLAIN, 14);
    protected String appTitle = "TBD-Title";
    protected float appBorderScale = 0.8f;
    protected int appWidth = 640;
    protected int appHeight = 640;
    protected float appWorldWidth = 2.0f;
    protected float appWorldHeight = 2.0f;
    protected long appSleep = 10L;
    protected boolean appMaintainRatio = false;
    protected boolean appDisableCursor = false;

    public Framework() {}

    protected abstract void createFramework();

    protected abstract void renderFrame(Graphics g);

    public abstract int getScreenWidth();

    public abstract int getScreenHeight();

    protected void createAndShowGUI() {
        createFramework();
        if (appDisableCursor) {
            disableCursor();
        }
        gameThread = new Thread(this);
        gameThread.start();
    }

    protected void setupInput(Component component) {
        keyboard = new SafeKeyboardInput();
        component.addKeyListener(keyboard);
        mouse = new RelativeMouseInput(component);
        component.addMouseListener(mouse);
        component.addMouseMotionListener(mouse);
        component.addMouseWheelListener(mouse);
    }

    protected void createBufferStrategy(Canvas component) {
        component.createBufferStrategy(2);
        bufferStrategy = component.getBufferStrategy();
    }

    protected void createBufferStrategy(Window window) {
        window.createBufferStrategy(2);
        bufferStrategy = window.getBufferStrategy();
    }

    protected void setupViewport(int sw, int sh) {
        int w = (int) (sw * appBorderScale);
        int h = (int) (sh * appBorderScale);
        vw = w;
        vh = (int) (w * appWorldHeight / appWorldWidth);
        if (vh > h) {
            vw = (int) (h * appWorldWidth / appWorldHeight);
            vh = h;
        }
        vx = (sw - vw) / 2;
        vy = (sh - vh) / 2;
    }

    protected Matrix3x3f getViewportTransform() {
        return Utility.createViewport(
                appWorldWidth, appWorldHeight,
                getScreenWidth(), getScreenHeight());
    }

    protected Matrix3x3f getReverseViewportTransform() {
        return Utility.createReverseViewport(
                appWorldWidth, appWorldHeight,
                getScreenWidth(), getScreenHeight());
    }

    public Vector2f getWorldMousePosition() {
        Matrix3x3f screenToWorld = getReverseViewportTransform();
        Point mousePos = mouse.getPosition();
        Vector2f screenPos = new Vector2f(mousePos.x, mousePos.y);
        return screenToWorld.mul(screenPos);
    }

    protected Vector2f getRelativeWorldMousePosition() {
        float sx = appWorldWidth / (getScreenWidth() - 1);
        float sy = appWorldHeight / (getScreenHeight() - 1);
        Matrix3x3f viewport = Matrix3x3f.scale(sx, -sy);
        Point p = mouse.getPosition();
        return viewport.mul(new Vector2f(p.x, p.y));
    }

    public void run() {
        running = true;
        initialize();
        long curTime = System.nanoTime();
        long lastTime = curTime;
        double nsPerTime;
        while (running) {
            curTime = System.nanoTime();
            nsPerTime = curTime - lastTime;
            gameLoop((float) (nsPerTime / 1.0E9));
            lastTime = curTime;
        }
        terminate();
    }

    protected void initialize() {
        frameRate = new FrameRate();
        frameRate.initialize();
    }

    protected void terminate() {}

    private void gameLoop(float delta) {
        processInput(delta);
        updateObject(delta);
        renderFrame();
        sleep(appSleep);
    }

    private void renderFrame() {
        do{
            do{
                Graphics g = null;
                try{
                    g = bufferStrategy.getDrawGraphics();
                    renderFrame(g);
                }finally {
                    if(g!=null){
                        g.dispose();
                    }
                }
            }while(bufferStrategy.contentsRestored());
            bufferStrategy.show();
        }while(bufferStrategy.contentsLost());
    }

    private void sleep(long sleep) {
        try {
            Thread.sleep(sleep);
        } catch (InterruptedException ex) {}
    }

    protected void processInput(float delta) {
        keyboard.poll();
        mouse.poll();
    }

    protected void updateObject(float delta) {}

    protected void render(Graphics g) {
        g.setFont(appFont);
        g.setColor(appFPSColor);
        frameRate.calculate();
        g.drawString(frameRate.getFrameRate(),20, 20);
    }

    private void disableCursor() {
        Toolkit tk = Toolkit.getDefaultToolkit();
        Image image = tk.createImage("");
        Point point = new Point(0, 0);
        String name = "CanBeAnything";
        Cursor cursor = tk.createCustomCursor(image, point, name);
        setCursor(cursor);
    }

    protected void shutDown() {
        if (Thread.currentThread() != gameThread) {
            try {
                running = false;
                gameThread.join();
                onShutDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.exit(0);
        } else {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    shutDown();
                }
            });
        }
    }

    protected void onShutDown() {}

    protected static void launchApp(final Framework app) {
        app.addWindowListener(new WindowAdapter() {
            /**
             * Invoked when a window is in the process of being closed.
             * The close operation can be overridden at this point.
             *
             * @param e
             */
            @Override
            public void windowClosing(WindowEvent e) {
                app.shutDown();
            }
        });
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                app.createAndShowGUI();
            }
        });
    }
}

SwingFramework类

SwingFramework类是使用Swing实现的Framework类,主窗口和视图窗口为JPanel实例,在视图窗口的Canvas实例上进行图形的绘制。

  • SwingFramework类字段一览表
修饰符类型名称说明
protectedCanvascanvas进行图形绘制的画布
privateJPanelmainPanel主窗口
privateJPanelcenterPanel视图窗口
  • SwingFramework类方法一览表
修饰符返回值函数名参数说明
protectedJPanelgetMainPanel()返回主窗口
privateJPanelgetCenterPanel()返回视图窗口
privateCanvasgetCanvas()返回绘制的画布
privatevoidsetUpLookAndFeel()设置主题
protectedvoidonCreateAndShowGUI()创建窗口细节
protectedvoidcreateFramework()创建整个窗口
protectedvoidonComponentResized(ComponentEvent e)窗口大小改变时调用,重新绘制画布
protectedvoidrenderFrame(Graphics g)渲染整个窗口
publicintgetScreenWidth()返回视图窗口的宽度,即画布的宽度
publicintgetScreenHeight()返回视图窗口的高度,即画布的高度
  • SwingFramework类源代码
package Rendering.utils;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;

public class SwingFramework extends Framework {
    protected Canvas canvas;
    private JPanel mainPanel;
    private JPanel centerPanel;

    protected JPanel getMainPanel() {
        if (mainPanel == null) {
            mainPanel = new JPanel();
            mainPanel.setLayout(new BorderLayout());
            mainPanel.add(getCenterPanel(), BorderLayout.CENTER);
        }
        return mainPanel;
    }

    private JPanel getCenterPanel() {
        if (centerPanel == null) {
            centerPanel = new JPanel();
            centerPanel.setBackground(appBorder);
            centerPanel.setLayout(null);
            centerPanel.add(getCanvas());
        }
        return centerPanel;
    }

    private Canvas getCanvas() {
        if (canvas == null) {
            canvas = new Canvas();
            canvas.setBackground(appBackground);
        }
        return canvas;
    }

    private void setUpLookAndFeel() {
        try {
            UIManager.setLookAndFeel(
                    "com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel"
            );
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    protected void onCreateAndShowGUI() {}

    @Override
    protected void createFramework() {
        setUpLookAndFeel();
        getContentPane().add(getMainPanel());
        setLocationByPlatform(true);
        setSize(appWidth, appHeight);
        setTitle(appTitle);
        getContentPane().setBackground(appBorder);
        getContentPane().addComponentListener(new ComponentAdapter() {
            /**
             * Invoked when the component's size changes.
             *
             * @param e
             */
            @Override
            public void componentResized(ComponentEvent e) {
                onComponentResized(e);
            }
        });
        setupInput(getCanvas());
        onCreateAndShowGUI();
        setVisible(true);
        createBufferStrategy(getCanvas());
        getCanvas().requestFocus();
    }

    protected void onComponentResized(ComponentEvent e) {
        Dimension size = getContentPane().getSize();
        setupViewport(size.width, size.height);
        getCanvas().setLocation(vx, vy);
        getCanvas().setSize(vw, vh);
        //改变大小时重新绘制
        getCanvas().repaint();
    }

    @Override
    protected void renderFrame(Graphics g) {
        g.clearRect(0, 0, getScreenWidth(), getScreenHeight());
        render(g);
    }

    @Override
    public int getScreenWidth() {
        return getCanvas().getWidth();
    }

    @Override
    public int getScreenHeight() {
        return getCanvas().getHeight();
    }

    public static void main(String[] args) {
        launchApp(new SwingFramework());
    }
}

更多:

第一篇
第二篇
第三篇
第四篇
第五篇
第六篇
第七篇
最终篇
源代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值