游戏开发基础

本文内容整理自Android 4游戏入门经典

一、基础游戏框架

任何游戏都需要一些基本框架,用于实现抽象化,并减轻与底层操作系统交互的痛苦。通常这一框架分成几个模块:

应用程序和窗口管理

用于创建一个窗口和处理一些操作,如关闭窗口、暂停或恢复Android应用程序等。

输入

与窗口管理模块相关联,用于跟踪用户的输入,如触摸事件、按键事件、加速计读取等。

Android系统中,这些输入会被调度到当前具有焦点的窗口中,你可以很容易的注册和记录这些事件。

而这些输入的消费方式通常有两种:

  • 轮询,消费方通过轮询检查输入设备的当前状态,这种方式适用于检查用户输入,但是注意,这些事件的顺序将会丢失;
  • 基于事件的处理,通过事件队列保存事件,然后一个一个处理,从而保证事件的时间顺序。

下面是一个用于轮询触摸屏、键盘、加速计的接口,同时它可以访问触摸屏和按键事件:

public interface Input {
    public static class KeyEvent {
        //表示按下按键
        public static final int KEY_DOWN = 0;
        //表示放开按键
        public static final int KEY_UP = 1;

        //按键类型:按下或放开
        public int type;
        //按键的键码
        public int keyCode;
        //按键的Unicode字符。按键按下和放开的Unicode字符可能是不同的,因为可能同时按下了shift键或其他类似作用的键。所以此处需要保存Unicode信息
        public char keyChar;
    }

    public static class TouchEvent {
        //按下
        public static final int TOUCH_DOWN = 0;
        //抬起
        public static final int TOUCH_UP = 1;
        //拖动
        public static final int TOUCH_DRAGGED = 2;

        public int type;
        public int x, y;
        //保存此触摸事件的手指的指针索引,用于多点触摸情景的处理
        public int pointer;
    }

    public boolean isKeyPressed(int keyCode);

    public boolean isTouchDown(int pointer);

    public int getTouchX(int pointer);

    public int getTouchY(int pointer);

    //加速计相关接口。注意,加速计只能使用轮询的方式,而不能通过事件的方式
    public float getAccelX(int pointer);

    public float getAccelY(int pointer);

    public float getAccelZ(int pointer);

    public List<KeyEvent> getKeyEvents();

    public List<TouchEvent> getTouchEvents();
}

文件I/O

允许从硬盘上将资源文件读取到应用程序中。

下面是简单接口的定义:

public interface FileIO{
    public InputStream readAsset(String fileName) throws IOException;
    public InputStream readFile(String fileName) throws IOException;
    public OutputStream writeFile(String fileName) throws IOException;
}

音频

该模块负责加载和播放一些我们能听到的声音。
音频从使用方面可以分为音乐和音效。

  • 音乐。指比较大的音频,如果预加载到内存中会占用大量内存,所以需要以流式方式播放,如背景音乐。
  • 音效。指较短的音频,如爆炸声、枪击声等,通常需要多次、同时的播放这类音频,所以一般会直接将其读取到内存中,然后直接从内存中播放。

音频的简单接口定义如下:

public interface Audio {
    public Music newMusic(String fileName);

    public Sound newSound(String fileName);
}

public interface Music {
    public void play();

    public void stop();

    public void pause();

    public void setLooping(boolean looping);

    public void setVolume(float volume);

    public boolean isPlaying();

    public boolean isStopped();

    public boolean isLooping();

    public void dispose();
}

public interface Sound {
    public void play(float volume);

    public void stop();

    public boolean isPlaying();

    public boolean isStopped();

    public void dispose();
}

图形

这是游戏开发中除实际游戏外最复杂的模块,它负责加载图形并绘制在画面上。

首先我们要明确几个概念:

  • 光栅。
    现在的显示器是基于光栅的,它是一种图像元素的2D网格,也可以理解为像素。光栅网格具有宽度和高度,通常用每行/每列的像素总数表示。
  • 像素。
    像素是光栅上面的一个点,其具有两个属性:位于光栅的位置和颜色。
  • 像素的位置。
    像素的位置用一个离散坐标系统中的2D坐标表示,此2D坐标系的原点位于光栅的左上角,正X轴向右,正Y轴向下。注意,X轴坐标的最大值为光栅宽度减1,Y轴坐标的最大值为光栅高度减1。
  • 像素的颜色。
  • 刷新率。
    显示器接收来自图形处理器的信息流后会刷新自身的状态,这里的刷新速度称为刷新率,单位为赫兹。
  • 帧缓冲区。
    图形处理器会从一个特殊的内存空间读取信息并显示在显示器上,这个特殊的内存空间称为视频随机访问内存或者VRAM,也叫帧缓冲区。显示器光栅的每个像素在帧缓冲区都有一个对应的内存地址,用于记录该像素的颜色。当我们想改变显示器的显示时,只需要改变帧缓冲区中的像素的颜色即可。
  • 双缓冲区。我们不知道显示器什么时候会从缓冲区中读取数据,为了防止在写入新数据时显示器读取数据,我们采用双缓冲区的方式:维护一个前端缓冲区和一个后端缓冲区。前端缓冲区用于显示,后端缓冲区用于绘制下一帧。
  • 垂直同步。双缓冲区不能完全解决上面提到的问题,因为显示器在刷新其内容时,仍有可能向缓冲区写数据,所有引入垂直同步,当对缓冲区进行读写时,GPU将被阻塞,直到显示器发出信号说已完成刷新。
  • 颜色的表示。
    通常我们使用的颜色模型为RGB模型,其用红绿蓝三色的混合表示所有的颜色。RGB模型有三个分量,每个分量的值介于0.0-1.0之间,表示每个分量的颜色的占比。但是如果用浮点数表示RGB模型,将会需要12个字节/24个字节的空间(取决于使用的浮点数是32位还是64位)。为了节省空间,我们用一个无符号字节表示一个RGB分量,每个分量的强度值范围是0~255,这样表示一个像素就只需要3个字节了。
    当然,我们还可以用其他方式表示RGB模型,如用一个字(16位)表示一个像素,红色分量用5位表示,绿色用6位,蓝色用5位,这里我们不做研究。
  • alpha合成。
    alpha合成的知识相对复杂,本文不做描述。

在知道上面的概念后,我们可以给出一个简单的图像接口:

public interface Graphics {
    //像素的编码格式,如ARGB8888表示数据按照alpha、red、green、blue顺序存放分量数据,每个分量占8位
    public static enum PixmapFormat {
        ARGB8888, ARGB4444, RGB565
    }

    //使用JPEG或PNG加载一幅图片
    public Pixmap newPixmap(String fileName, PixmapFormat format);

    //用指定颜色清除整个帧缓冲区原来的颜色
    public void clear(int color);

    public void drawPixel(int x, int y, int color);

    public void drawLine(int x, int y, int x2, int y2, int color);

    public void drawRect(int x, int y, int width, int height, int color);

    public void drawPixmap(Pixmap pixmap, int x, int y, int srcX, int srcY, int srcWidth, int srcHeight);

    public void drawPixmap(Pixmap pixmap, int x, int y);

    public int getWidth();

    public int getHeight();

}

public interface Pixmap {
    public int getWidth();

    public int getHeight();

    public Graphics.PixmapFormat getFormat();

    public void dispose();
}

游戏框架

游戏框架集合了上面的所有部分,为我们编写游戏提供了一个易用的基础。

在讨论真正的游戏框架前,我们需要明确游戏要完成的功能:

  • 游戏有不同的画面,但是这些画面做的工作都类似:评估用户输入、将输入转变为画面状态、渲染场景等。有的画面可能不需要用户输入,但是会在一段时间后切换到另一界面。
  • 画面需要用某种方式进行管理。
  • 游戏画面需要响应不同的模块,如图形、声音、用户输入,这样才能加载资源、捕获用户输入、播放声音、渲染帧缓冲区等。
  • 游戏一般是实时的,我们必须尽可能多的更新当前画面的状态并进行渲染。这个过程一般在一个主循环中进行,只有在游戏退出后该循环才会终止。循环的一次简单迭代成为一帧,每秒我们进行的帧数称为帧率。
  • 我们需要追踪上一帧到现在的时间间隔,它用于帧无关的运动中。
  • 游戏需要保持跟踪窗口状态,如暂停、恢复,并用事件通知当前画面。
  • 游戏框架将负责设置窗口和UI组件。

下面我们设计一个简单的游戏接口,它将:

  • 创建窗口和UI组件并连接到回调函数。
  • 启动主循环线程。
  • 跟踪当前画面,并在每一次主循环迭代中都更新并显示它。
  • 将所有窗口事件从UI线程传输给主循环线程,并将它们递交给当前画面以改变画面状态。
  • 开放我们前面设计的那些模块: Input、FileIO、Graphics、Audio。

下面是一个非常简单的Game接口,它隐藏了所有复杂性,另外是一个抽象的Screen类,表示画面:

public interface Game {
    public Input getInput();

    public FileIO getFileIO();

    public Graphics getGraphics();

    public Audio getAudio();

    public void setScreen(Screen screen);

    public Screen getCurrentScreen();

    public Screen getStartScreen();
}

public abstract class Screen {
    protected final Game game;

    public Screen(Game game) {
        this.game = game;
    }

    public abstract void update(float deltaTime);

    public abstract void present(float deltaTime);

    public abstract void pause();

    public abstract void resume();

    public abstract void dispose();

}

实际上游戏框架还应该包含网络编程,不过这部分比较高级,如果对该主题感兴趣,可以到www.gamedev.net找相关教程

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值