翻譯自 iPhone Game Development Developing 2D & 3D games in Objective-C
To solve the large problem of how to create a game on the iPhone, we first need to solvea series of smaller problems such as how to display graphics and play sounds. Theseare problems associated with building parts of a game engine. And just like the humanbody, each part of a game engine is different but vital. Thus begins our chapter on gameengine anatomy. We will discuss each major part of a game engine, including the ap-plication framework, state machine, graphics engine, physics engine, audio engine,player input, and game logic.
為了解決“如何在IPHONE上建立一個遊戲”這個大問題,我們需要首先解決諸如“如何顯示圖像”與“如何播放聲音”等一系列小問題。這些問題關系到建立部分遊戲引擎。就像人類的身體一樣,遊戲引擎的每個部分雖然不同,但是卻都不可或缺。是以,首先從遊戲引擎剖析開始本章。我們将會讨論一個遊戲引擎的所有主要部分,包括應用程式架構、狀态機、圖像引擎、實體引擎、聲音引擎、玩家輸入和遊戲邏輯。*
Writing a serious game is a big task that involves a lot of code. It is important to designthat code in an organized fashion from the start, rather than adding bits and pieces overtime. When building a house, an architect creates blueprints for the whole house, whichthe contractors then build. However, many programmers who are new to game pro-gramming build one part of the “house” from a tutorial, and add each “room” pieceby piece as they learn. It’s no surprise when the end result is bad.
寫一個好玩的遊戲是一項牽扯到很多代碼的大任務。非常有必要從一開始就對項目進行良好的,有組織的設計,而不是随着進度的進行而到處雜亂添加代碼。就像建造房屋一樣,建築師為整幢房屋勾畫藍圖,建築勞工以此來建造。但是,許多對遊戲程式設計不熟悉的程式設計人員會從根據導讀建造出房屋的一部分,并随着學習的進行為其添加房間,這無疑将會導緻不好的結果。*
Figure 2-1 shows the structure of a game engine that works well for most games. Byunderstanding all of the parts of a game engine and how they work together, we candesign the whole game at the start, and build our application the “Right Way.” In thefollowing sections, we’ll cover each part of Figure 2-1:
- Application framework
- Game state manager
- Graphics engine
圖2-1顯示了一個适用于大部分遊戲的遊戲引擎結構。為了了解一個遊戲引擎的所有部分和它們是如何工作在一起的,我們可以先為整個遊戲做設計,然後再建立我們的應用程式。在以下的幾個小節中,我們的講解内容将會涵蓋圖2-1的每個部分。
- 應用程式架構
- 遊戲狀态管理器
- 圖像引擎
2.1 Application Framework 應用架構
The application framework consists of code necessary to get an application started,including creating an instance of the application and initializing the rest of the subsys-tems. Our code first creates a Framework class when our application starts and, in turn,will be in charge of creating and destroying the state machine, graphics engine, and audio engine. If our game is complex enough to require a physics engine, the frameworkwill manage that as well.
應用程式架構包含使應用程式工作的必須代碼,包括建立一個應用程式執行個體和初期化其他子系統。當應用程式運作時,會首先建立一個架構類,并接管建立和銷毀狀态機、圖像引擎和聲音引擎。如果我們的遊戲足夠複雜以至于它需要一個實體引擎,架構也會管理它。
The framework should take care of the peculiarities of the platform we are working on,including handling any system events (such as Shut Down or Sleep), and managing theloading and unloading of resources so that the rest of the code can focus only on the game.
架構必須适應于我們所選擇的平台的獨特性,包括相應任何的系統事件(如關機與睡眠),以及管理載入與載出資源以使其他的代碼隻需要集中與遊戲。
Main Loop 主循環
The framework will provide the main loop that is the driving force behind any inter-active program; on each iteration of the loop, the program will check for and handle incoming events, run updates on the game logic, and draw to the screen as necessary (see Figure 2-2).
架構會提供主循環,它是一切互動程式後的驅動力量。在循環中的每一次疊代過程中,程式會檢查和處理接受到的事件,運作遊戲邏輯中的更新并在必要時将内容描畫到螢幕上。(參見圖2-2)
Figure 2-2. Main loop sequence圖2-2 主循環序列
Exactly how the main loop is implemented depends on the system you are using. For a basic console application, it could be as simple as a while loop that calls each function:
主循環如何實作依賴于你使用的系統。對于一個基本的控制台程式,它可能是一個簡單的while循環中調用各個函數:
while( !finished ) {
handle_events();
update();
render();
sleep();
}
Notice the sleep function here. It puts the code to sleep for some small period each loop so that it does not form a solid loop that would constantly suck up all of the time on the CPU.
Some systems don’t want user code to write loops like that at all; instead, they use a callback system that forces the programmer to release the CPU regularly. This way, when the application starts, the programmer registers some functions to be called back during each loop:
注意到這裡的sleep函數。它使得代碼休眠一小段時間不緻于占用全部的CPU。
有些系統完全不想讓使用者代碼那些寫,它們使用了回調系統以強制程式員正常的釋放CPU。這樣,當應用程式執行後,程式員注冊一些函數給系統在每次循環中回調:
void main(void) {
OS_register_event_handler( myEventHandler );
OS_register_update_function( myUpdate );
OS_register_render_function( myRender );
}
Once the program starts, those functions are called periodically, as needed. The iPhone is closest to this last example, which you will see in the next chapter and in the examples provided with the iPhone SDK.
一旦程式執行後,根據必要情況,那些函數會間隔性的被調用。IPHONE是最接近後面這個例子。你可以在下一章和IPHONE SDK中看到它。
Legacy C and C++ Code |
---|
If you have a C/C++ code base you want to port to the iPhone, or want to keep the option of reusable code from an iPhone application on other platforms such as Win-dows Mobile or Android, Objective-C presents a challenge to porting. The good news is that you can write C++ code that runs directly on the iPhone. However, you will have to write Objective-C to use certain portions of the iPhone SDK. The framework is the perfect place to wrap those interfaces in C++ classes so that the rest of your code can be written in pure C++. If you or your company is dedicated to launching cross-platform titles, we strongly suggest developing engines with similar features and interfaces on each of the platforms. After you isolate your game-specific code this way, it is an easy task to copy code from one project to another and make minor adjustments to get it up and running. With a little care, this technique even works when switching between C++ and Java! |
2.2 Game State Manager遊戲狀态管理器
A serious video game offers more than just a theater of action holding the game: it has a Main menu that allows the player to set options and start a new game or continue a previous one; a Credits screen that shows the names of all the hardworking people who helped make the game; and if your game doesn’t come with a user manual, perhaps a Help section that gives the player a clue about what he’s supposed to be doing.
一個好的視訊遊戲不僅有一組動作來維持遊戲:它會提供一個主菜單允許玩家來設定選項和開始一個新遊戲或者繼續上次的遊戲;制作群屏将會顯示所有辛勤制作這款遊戲的人員的名字;而且如果你的遊戲沒有使用者指南,應該一個幫助區域會給使用者一些提示告訴他們應該做什麼。
Each of these is a game state and represents a separate part of the application code. For instance, the functions and navigation invoked by a player in the Main menu are quite different from those invoked by a player in the Credits screen, so the program logic is a lot different, too. Specifically, in the Main menu, you will likely be drawing a title image and some kind of menu, and listening for player input to select one of the menu options. When you are in the Credits screen, you will be drawing the names of all the people who worked on the game, while listening for player input that will cause your current game state to change from the Credits screen back to the Main menu. And finally, in the Game Play state, you will be rendering the actual game and listening for the player’s input to interact with the game logic.
以上任何一種場合都是一種遊戲狀态,并且代表中一段獨立的應用程式代碼片段。例如,使用者在主菜單調用的函數與導航與使用者在制作群屏調用的是完全不同的,是以程式邏輯也是不同的。特别的是,在主菜單,你可能會放一張圖檔和一些菜單,并且等待使用者選擇哪個選項,而在制作群屏,你将會把遊戲制作人員的名字描繪在螢幕上,并且等待使用者輸入,将遊戲狀态從制作群屏改為主菜單。最後,在遊戲中狀态,将會渲染實際的遊戲并等待使用者的輸入以與遊戲邏輯進行互動。
Each of these game states is responsible for handling player input, rendering to the screen, and providing any application logic that is specific to that state. You might recognize these tasks from our earlier discussion about the main loop, and that’s because they are exactly the same tasks. However, each state implements them in its own way, which is why we have to keep them separate. You don’t want to have to search through the Main menu code to make a change to the Game Play event handler.
以上的所有遊戲狀态都負責相應使用者輸入、将内容渲染到螢幕、并為該遊戲狀态提供相對應的應用程式邏輯的任務。你可能注意到了這些任務都來自于之前讨論的主循環中,這是因為它們就是同樣的任務。但是,每個狀态都會以它們自己的方式來實作這些任務,這也就是為什麼要保持他們獨立。你不必在主菜單代碼中尋找處理遊戲中的事件的代碼。
State Machine狀态機
The Game State Manager is a state machine, which means it keeps track of the current game state. When the application starts, the state machine creates the basic state information. It goes on to create information required by each state and to destroy temporary information when the application leaves a state.
狀态管理器是一個狀态機,這意味着它跟蹤着現在的遊戲狀态。當應用程式執行後,狀态機會建立基本的狀态資訊。它接着建立各種狀态需要的資訊,并在離開每種狀态時銷毀暫時存儲的資訊。
A large number of different objects have state that is maintained by the state machine. One obvious state is the screen the player is on (Main menu, Game Theater, etc.). But if you have an enemy artificial intelligence (AI) agent on the screen that can be in a “sleeping,” “attacking,” or “dead” state, a state machine can be used to keep track of those states as well.
狀态機維護着大量不同對象的狀态。一個明顯的狀态是使用者所在螢幕的狀态(主菜單、遊戲中等)。但是如果你有一個有着人工智能的對象在螢幕上時,狀态機也可以用來管理它的“睡眠”、“攻擊”、“死亡”狀态。
What is the right architecture for a Game State Manager? Let’s take a look at some state machines and decide which design pattern best fits our needs.
什麼是正确的遊戲狀态管理器結構?讓我們看看一些狀态機并決定哪種最适合我們。
There are many ways to implement a state machine, the most basic of which is a simple switch statement:
有許多實作狀态機的方式,最基本的是一個簡單的switch語句:
class StateManager {
void main_loop() {
switch(myState) {
case STATE_01:
state01_handle_event();
state01_update();
state01_render;
break;
case STATE_02:
state02_handle_event();
state02_update();
state02_render;
break;
case STATE_03:
state03_handle_event();
state03_update();
state03_render;
break;
}
}
};
All that is necessary to switch states is to change the value of the myState variable and return to the start of the loop. However, as you can see, the more states we add, the larger that code block gets. Furthermore, we typically have entire blocks of tasks we need to perform predictably when entering or leaving a state: initialize state-specific variables, load new resources (such as images), and deallocate resources used by the previous state. In a simple switch statement, we’d have to add that block to each case and make sure not to forget a step.
改變狀态時所有需要做的事情就是改變myState變量的值并傳回到循環的開始處。但是,正如你看到的,當我們加入越來越多的狀态時,代碼塊會變得越來越大。而且更糟的是,為了使程式按我們預期的執行,我們需要在程式進入或離開某個狀态時執行整個任務塊,初始化該狀态特定的變量,載入新的資源(比如圖檔)和釋放前一個狀态載入的資源。在這個簡單的switch語句中,我們需要加入更多的程式塊并保證不會漏掉任何一個。
This is fine for simple tasks, but something as complex as our Game State Manager needs a better solution. The next best way to implement a state machine is to use function pointers:
以上是一些簡單重複的勞動,但是我們的狀态管理器需要更好的解決方案。下面一種更好的實作方式是使用函數指針:
class StateManager {
//the function pointer:
void (*m_stateHandleEventFPTR) (void);
void (*m_stateUpdateFPTR)(void);
void (*m_stateRenderFPTR)(void);
void main_loop() {
stateHandleEventFPTR();
m_stateUpdateFPTR();
m_stateRenderFPTR();
}
void change_state( void (*newHandleEventFPTR)(void),
void (*newUpdateFPTR)(void),
void (*newRenderFPTR)(void)
) {
m_stateHandleEventFPTR = newHandleEventFPTR;
m_stateUpdateFPTR = newUpdateFPTR;
m_stateRenderFPTR = newRenderFPTR
}
};
Now the main loop is very small and simple, even if we handle many game states. However, this solution still does not help us initialize and deallocate states. Because each game state has not only code but also unique resources, it is appropriate to think of game states as attributes of an object. So, next we will look at an object-oriented programming (OOP) approach.
現在,即使我們處理再多狀态,主循環也足夠小而且簡單。但是,這種解決方案依然不能幫助我們很好的解決初始化與釋放狀态。因為每種遊戲狀态不僅包含代碼,還有各自的資源,是以更恰當的做法是将遊戲狀态作為對象的屬性來考慮。是以,接下來,我們将會看看面向對象(OOP)的實作。
We start by creating a class to represent our game states:
我們首先建立一個表示遊戲狀态的類:
class GameState
{
GameState(); //constructor
virtual ~GameState(); //destructor
virtual void Handle_Event();
virtual void Update();
virtual void Render();
};
Next, we change our state manager to use that class:
接着,我們改變我們的狀态管理器以使用這個類:
class StateManager {
GameState* m_state;
void main_loop() {
m_state->Handle_Event();
m_state->Update();
m_state->Render();
}
void change_state( GameState* newState ) {
delete m_state;
m_state = newState;
}
};
Finally, we create a specific instance of our game state:
最後,我們建立一個指定具體遊戲狀态的類:
class State_MainMenu : public GameState
{
int m_currMenuOption;
State_MainMenu();
~State_MainMenu();
void Handle_Event();
void Update();
void Render();
};
When it is represented by a class, each game state can store its unique variables inside that class. It can also allocate any resources it needs in its constructor and deallocate them in its destructor.
當遊戲狀态以類來表示時,每個遊戲狀态都可以存儲它特有的變量在該類中。該類也可以它的構造函數中載入任何資源并在析構函數中釋放這些資源。
Furthermore, this system keeps our code nicely organized because we have to put the code for each state in separate files. If you are looking for the Main menu code, all you have to do is open the State_MainMenu class and there it is. And the OOP solution makes this code easy to reuse.
而且,這個系統保持着我們的代碼有很好的組織結構,因為我們需要将遊戲狀态代碼分别放在各個檔案中。如果你在查找主菜單代碼,你隻需要打開State_MainMenu類。而且OOP解決方案使得代碼更容易重用。
This seems to best fit our needs, so we will use the OOP solution for our Game State Manager.
這個看起來是最适合我們需要的,是以我們決定使用它來作為我們的狀态管理器。
The Next Level: Concurrent Access |
---|
Another, more complicated, approach to the Game State Manager would be a kernel or scheduler. Very complex game engines, such as the ones found in current-generation PC and console platforms, use these to organize multiple tasks that run simultaneously, such as disk access, physics, and graphics routines. Concurrent processes take advantage of the delays in completing tasks on each CPU,so while one portion of the game is waiting on something such as hard drive access and another part is waiting on the graphics card, you can still use the CPU to calculate physics simulations. This idea is also well suited for hardware with multiple CPU cores. However, concurrent access isn’t very useful for games on the iPhone. First of all, most game designs are limited by the graphics and input provided by the platform, and therefore they demand far fewer resources and calculations than console games. Fur-thermore, there is less to be gained from multithreaded processes on the iPhone because filesystem access is much faster due to the hard drive being much smaller in capacity and responding to only one application at a time. But the iPhone is still a new platform, and developers have yet to tap into its capabilities to the fullest. Just keep in mind that game code should solve the problem put forth by game design. Don’t get carried away if it doesn’t help you implement the game. |
建立于2014-04-01深圳,更新于2016-07-02杭州