23-游戏状态机
8.9.1 游戏状态机
状态机是一个编程的结构,它允许游戏在任何时间都只处于单一的应用状态之中。这里将为游戏创建一个状态机,命名为application state。它包含7个状态,由以下7个常量来代表:
- GAME_STATE_TITLE;
- GAME_STATE_NEW_GAME;
- GAME_STATE_NEW_LEVEL;
- GAME_STATE_PLAYER_START;
- GAME_STATE_PLAY_LEVEL;
- GAME_STATE_PLAYER_DIE;
- GAME_STATE_GAME_OVER。
为每个状态创建一个函数对象。每个函数包含了该状态所要运行的游戏逻辑,并且在适当的时候进入新的状态。在创建每一个新游戏时都可以使用相同的结构,只需要将每个状态所引用的函数内容换掉即可。
下面看看如何实现这个架构的基本版本。使用一个名为currentGameStateFunction的变量引用函数,以及一个名为currentGameState的整数变量保存当前应用状态的常量值。
var currentGameState = 0;
var currentGameStateFunction = null;
创建一个名为switchAppState()的函数,当切换到新状态时,调用这个函数。
function switchGameState(newState){
currentGameState = newState;
switch (currentGameState){
case GAME_STATE_TITLE:
currentGameStateFunction = gameStateTitle;
break;
case GAME_STATE_PLAY_LEVEL:
currentGameStateFunctionappStatePlayeLevel;
break;
case GAME_STATE_GAME_OVER:
currentGameStateFunction = gameStateGameOver;
break;
}
}
在gameLoop()函数中,通过调用setTimeOut()函数,触发runGame()函数的重复调用,从而启动应用程序。在setTimeout()函数中设置重复调用runGame()函数。runGame()函数在每个帧时隙中调用currentGameStateFunction所引用的函数。这样可以根据应用状态的变化,很轻松地切换runGame()所调用的函数。
gameLoop();
function gameLoop() {
runGame();
window.setTimeout(gameLoop, intervalTime);
}
function runGame(){
currentGameStateFunction();
}
下面看一下完整的代码。为每个不同的应用状态函数创建一个壳函数。在应用启动前,先调用switchGameState()函数,传入一个常量值,将想要运行的函数赋值给currentGameStateFunction变量。
//*** 应用程序启动
switchGameState(GAME_STATE_TITLE);
在例8-9中,使用GAME_STATE_TITLE状态绘制一个简单的标题画面,并在每一帧中重新绘制。
例8-9 标题画面状态
<script type="text/javascript">
window.addEventListener('load', eventWindowLoaded, false);
function eventWindowLoaded(){
canvasApp();
}
function canvasApp(){
var theCanvas = document.getElementById('canvas');
if (!theCanvas || !theCanvas.getContext){
return;
}
var context = theCanvas.getContext('2d');
if (!context){
return;
}
//应用状态
const GAME_STATE_TITLE=0;
const GAME_STATE_NEW_LEVEL=1;
const GAME_STATE_GAME_OVER=2;
var currentGameState=0;
var currentGameStateFunction=null;
function switchGameState(newState){
currentGameState=newState;
switch (currentGameState){
case GAME_STATE_TITLE:
currentGameStateFunction=gameStateTitle;
break;
case GAME_STATE_PLAY_LEVEL:
currentGameStateFunctionappStatePlayeLevel;
break;
case GAME_STATE_GAME_OVER:
currentGameStateFunction=gameStateGameOver;
break;
}
}
function gameStateTitle(){
ConsoleLog.log("appStateTitle");
// 绘制背景和文字
context.fillStyle = '#000000';
context.fillRect(0, 0, 200, 200);
context.fillStyle = '#ffffff';
context.font = '20px sans-serif';
context.textBaseline = 'top';
context.fillText ("Title Screen", 50, 90);
}
function gameStatePlayLevel(){
ConsoleLog.log("appStateGamePlay");
}
function gameStateGameOver(){
ConsoleLog.log("appStateGameOver");
}
function runGame(){
currentGameStateFunction();
}
//*** 应用程序启动
switchGameState(GAME_STATE_TITLE);
//**** 应用循环
var FRAME_RATE=40;
var intervalTime=1000/FRAME_RATE;
gameLoop();
function gameLoop() {
runGame();
window.setTimeout(gameLoop, intervalTime);
}
}
//***** 对象原型 *****
//*** consoleLog工具对象
//创建构造函数
function ConsoleLog(){
}
//创建函数并将函数添加到类中
console_log=function(message){
if(typeof(console)!== 'undefined' && console != null){
console.log(message);
}
}
//通过赋值向类中添加一个类方法/静态方法
ConsoleLog.log=console_log;
// consolelog日志对象结束
</script>
提示
例8-9中添加了一个在之前章节没有出现过的Consolelog对象。持续使用这个工具在浏览器的JavaScript日志窗口中输出有用的调试信息。因为如果没有打开控制台选项,有的浏览器可能会崩溃。添加Consolelog对象可以解决这个问题。不过,绝大多数支持Canvas的浏览器都不会发生崩溃。
本书将继续带领读者学习状态机,并在8.10节中为游戏创建一个状态机。