游戏编程用代码构建一个虚拟世界。在这个虚拟世界中,玩家能开心地进行游戏。从技术角度而言,电子游戏指一种多媒体的交互式的实时模拟器,它由若干功能模块构成。例如,要将游戏内容展示给玩家,需要图形显示模块;要将游戏音乐播放给玩家,需要音频系统模块;此外,还需要负责和玩家进行交互的界面模块和AI模块等。我们先介绍游戏编程中的一些特有要素。
除非用户中断游戏,否则游戏程序需要一直运行。所以游戏程序中必然存在一个循环,循环中的代码反复运行。这个循环称为游戏循环(game loop)。每运行一次循环中的代码,称为游戏中的一帧(frame)。基于人类眼球的生理特性,游戏一般采用30或者60的帧率来运行,设置游戏帧率为30,就意味着每秒运行30次循环,换句话说,单次循环消耗的时间要控制在33毫秒左右,不能过多也不能过少。游戏中能否达到这个帧率依赖于很多因素,如果硬件性能很强大,代码中的计算很简单,一个循环中的代码可能只需要几毫秒就运行完毕。
游戏循环中应该包括主要的游戏逻辑代码,例如处理玩家输入、更新游戏数据、生成游戏输出这3个阶段的代码。以经典的射击游戏《坦克大战》为例,玩家输入可能是键盘的按键,即通过键盘控制方向和发射炮弹,当用户按下发射炮弹对应的按键时,程序要保存并处理这个信息。在更新游戏数据阶段,程序要根据用户的输入信息,在内存中生成炮弹对象,如果之前已经发射了炮弹,则需要判断炮弹是否击中敌人,敌人是否被击杀。这里可能要处理上百个对象和多种游戏逻辑。在生成游戏输出阶段,最重要的事情之一就是绘制显示在屏幕上的内容,如坦克、发射的炮弹、爆炸效果等。
游戏角色需要显示在屏幕的某个位置上,这个位置对应的坐标系原点一般在屏幕左上角,坐标的单位是像素点。如图1-1所示,我们建立了一个600px×300px的屏幕窗口,在窗口的左上角位置放置了一个边长为100px的正方形,正方形的左上角坐标(0,0)就是原点。我们也可以通过设置左上角或其他位置的坐标来控制正方形的位置。
图1-1
游戏角色(Spirit)一般由两个要素构成,一个是角色的“外皮”,即用来表现视觉效果的图片文件,另一个是角色的“骨架”,即构成图片外部边缘的边框。角色的图片文件需要在初始化时加载,将其加载到内存后,可以获取其边框对象,然后通过设置边框的坐标值来控制角色的位置。
最后需要注意的是缓冲机制。在游戏主循环中,游戏角色生成后并不会被直接输出到游戏界面窗口中,而是先输出到显卡的内存缓冲区,计算机再将内存缓冲区的游戏角色信息更新并输出到屏幕上。就像在话剧表演中,帷幕落下后,后台人员忙着摆放场景道具;将这些场景道具放置妥当后,帷幕升起。这样做的好处是不会将尚未准备好的舞台展示给观众,而帷幕遮挡的舞台就相当于存放游戏角色信息的内存缓冲区。
游戏编程的一个特点是编写的程序需要和用户交互。用户的输入包含各种各样的信息,例如键盘按键信息、鼠标移动或单击信息,甚至游戏手柄的按键信息等。这些信息通常是通过事件队列存放的,在每次运行游戏主循环中的代码时,程序都需要从事件队列中取出需要的信息进行处理。
举个例子,你是一位在战场上统领大军的将军,你需要关注敌方部队有没有发生移动。于是你每个小时开始时都会派出一队侦察兵,让他们分散在战场四周获取情报,侦察兵会在每个小时结束时将情报传回你的手中。有的情报是关于天气变化的,有的情报是关于敌人动向的,有的情报是关于粮草供应的。每份情报都被写在一张纸上,按顺序叠放在桌上。你的参谋会每小时看一次情报,检查这些情报中是否有关于敌人动向的信息。当参谋发现这一类情报后,你会基于相关情报来调动部队出击。
游戏编程中的输入处理与此类似。每一帧里,程序都会收到各种输入事件,并将它们按顺序存放在一个事件队列中。程序会不停地检查这个队列,当程序“关注”的事件发生时,例如玩家按下了空格键,程序就会运行相应的代码来发射炮弹。
AI能给予游戏更多的乐趣。从工程角度来看,游戏AI并不是越复杂越好,而是能满足游戏的需要。AI领域有一个黄金原则——搜索和知识是相互关联的,当你拥有更多的知识,你就需要更少的搜索;当你拥有更少的知识,你就需要更多的搜索。一个职业围棋选手拥有相当丰富的对弈知识,通常只需针对几个落子选项进行少量的计算。而初版的AlphaGo,在没有足够丰富的对弈知识的背景下,只能反复在海量选项中进行最优落子方案的搜索。
最早的《吃豆人》游戏中,有4个追逐玩家的幽灵角色,其AI设置相当简单。它们追逐玩家的路线都是通过硬编码的规则确定好的,有的是直接追逐玩家,有的是占领某个交通要道,有的是随机选择某条通道。这套游戏AI编写得很简单,但整体的配合却让玩家感觉很智能,因为简单的规则中包含相当丰富的游戏对抗的知识。因此,需要视情况采用复杂程度不同的游戏AI。有时候,简单的随机方法或者贪心算法就可以模拟出一个不错的NPC。
本书将主要介绍游戏中会用到的3种AI算法,分别是深度强化学习、遗传算法和蒙特卡罗树搜索。深度强化学习(deep reinforcement learning)是深度学习与强化学习相结合的产物,它集成了深度学习在函数拟合上的强大能力,以及强化学习基于环境反馈进行试错和决策的能力。深度强化学习可用来解决现实场景中的复杂问题。遗传算法(genetic algorithm)是计算数学中用于解决最优化问题的搜索算法,是进化算法的一种。进化算法是借鉴进化生物学中的一些原理而发展起来的,这些现象包括遗传、突变、自然选择以及杂交等。蒙特卡罗树搜索(Monte Carlo tree search)是一种用于决策过程的启发式搜索算法,它最引人注目的使用场景之一是被用在AlphaGo中。它也用于其他棋牌类等即时电子游戏和不确定性游戏。