最近有人提醒我,很多人都在詢問遊戲開發的問題,但關於這個話題的文章卻不多。我決定就遊戲從頭到尾的開發過程概覽性地介紹一下。請記住,這主要是一個概述,並且:A. 不適用於所有專案。B. 不是一個完整的、一步一步的指導。您仍然需要自己弄清楚很多東西才能製作出一款遊戲。
第一步:選擇您的遊戲庫
除非您想自己編寫所有瑣碎的圖形/聲音程式設計庫,否則您可能需要獲取一個遊戲庫。市面上有許多遊戲庫,但它們都提供相同的基本功能
您希望您的庫具備的功能
- 載入和渲染影像的方式
- 載入和播放音訊的方式
- 基本的影像處理(旋轉等)
- 基本繪圖功能(圓形、線條、矩形、點等)
- 渲染文字的能力
- 跟蹤時間和等待的能力
- 建立和控制執行緒的能力(有益,但非必需)
一些遊戲庫包括
第二步:定義概念
所有遊戲都從這裡開始,僅僅是某人腦中的想法。
首先,想出一個遊戲創意。一旦有了簡單的想法,就擴充套件它。例如,如果是一款棋盤遊戲,目標/獲勝方式是什麼?規則是什麼樣的?等等。如果您的遊戲有角色或故事情節,就去創造它們。確保您對完成後的遊戲有一個相當明確的概念。遊戲越複雜,越應該在開始時就規劃好,這樣在編寫程式碼時就不必過多地擔心遊戲本身。請記住,您的遊戲在建立過程中會不斷發展。
第三步:規劃您的引擎
如果您製作的是棋盤遊戲或基本的街機遊戲,您可以完全跳過此步驟,直接編寫您的遊戲。然而,對於更復雜的遊戲,您可能需要考慮使用預製引擎,或編寫自己的“引擎”。您可能會問,什麼是遊戲引擎?雖然它們在結構和整體功能上差異很大,但您可以將遊戲引擎視為一個超級強大的庫,它提供更高階的功能,如物理、資源處理和遊戲實體管理。無論您選擇使用現有引擎還是建立自己的引擎,都取決於您,並且取決於您實際想做多少程式設計。使用預製引擎將大大簡化您的工作,使您更多地專注於編寫遊戲玩法/事件的指令碼。
為什麼我說“規劃”而不是“選擇”?因為您很可能不會製作下一個《上古卷軸》,因此,您可以建立自己的“引擎”。請記住,您不會建立下一個虛幻引擎,並且您編寫的大部分程式碼以及打算使其可重用的程式碼(這正是引擎的意義)最終將與您的遊戲邏輯糾纏不清,以至於難以輕易重用。考慮到這一點,如果您的“引擎”的某些部分依賴於特定於遊戲的 C++ 程式碼,不必擔心,這很正常。與其專注於建立一個完全可重用、超級健壯的框架,不如專注於確保程式碼可讀、有組織且功能齊全。先專注於製作遊戲,然後再嘗試建立可移植模組。如果您絕對必須編寫一些有用且可重用的東西,資源管理器和其他各種實用類是很好的起點。
第四步:編寫您的引擎(如果您打算自己寫)
現在是時候開始編寫您的引擎了,前提是您選擇了這條路線。這不一定是指遊戲本身,而是核心渲染、物理和檔案處理;基本上是用於構建遊戲的函式和類。簡單的遊戲實際上不需要太多框架,可以直接使用遊戲庫進行程式設計。大型遊戲中最重要也是最被忽視的元件之一是資源管理器。資源管理器(據推測)是一個類,負責載入資源(如圖形和聲音),確保資源僅載入一次,並在不再需要時解除安裝資源。RAM 不是無限的,所以如果您的遊戲為宇宙中的每一塊草載入同一影像的單獨副本,那您就麻煩了。下面是 Xander314 提供的一個優秀資源管理器示例。
Xander314 的資源管理器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
|
/*
ResourceManagerB.hpp - Generic template resource manager
(C) Alexander Thorne (SFML Coder) 2011
<a href="http://sfmlcoder.wordpress.com/">http://sfmlcoder.wordpress.com/</a>
Manages loading and unloading of a resource type specified by a
template argument.
****************************************************************/
#include <map>
#include <string>
#include <exception>
typedef const std::string URI;
// exceptions
namespace Exceptions {
// thrown if user requests a resource URI not present in the manager's list
class URINotFound : public std::runtime_error
{
public:
URINotFound(const std::string& Message = "The specified URI was not found in the resource index.")
: runtime_error(Message) { }
};
// thrown if a resource allocation fails
class BadResourceAllocation : public std::runtime_error {
public:
BadResourceAllocation(const std::string& Message = "Failed to allocate memory for resource.")
: runtime_error(Message) {}
};
}
template <class Resource> class ResourceManagerB {
typedef std::pair<URI, Resource*> ResourcePair;
typedef std::map<URI, Resource*> ResourceList;
// the list of the manager's resources
ResourceList Resources;
public:
~ResourceManagerB() { UnloadAll(); }
// Load a resource with the specified URI
// the URI could represent, e.g, a filename
URI& Load(URI& Uri);
// unload a resource with the specified URI
void Unload(URI& Uri);
// unload all resources
void UnloadAll();
// get a pointer to a resource
Resource* GetPtr(URI& Uri);
// get a reference to a resource
Resource& Get(URI& Uri);
};
template <class Resource>
URI& ResourceManagerB<Resource>::Load(URI& Uri)
{
// check if resource URI is already in list
// and if it is, we do no more
if (Resources.find(Uri) == Resources.end())
{
// try to allocate the resource
// NB: if the Resource template argument does not have a
// constructor accepting a const std::std::string, then this
// line will cause a compiler error
Resource* temp = new (std::nothrow) Resource(Uri);
// check if the resource failed to be allocated
// std::nothrow means that if allocation failed
// temp will be 0
if (!temp)
throw Exceptions::BadResourceAllocation();
// add the resource and it's URI to the manager's list
Resources.insert(ResourcePair(Uri, temp));
}
return Uri;
}
template <class Resource>
void ResourceManagerB<Resource>::Unload(URI& Uri)
{
// try to find the specified URI in the list
ResourceList::const_iterator itr = Resources.find(Uri);
// if it is found...
if (itr != Resources.end())
{
// ... deallocate it
delete itr->second;
// then remove it from the list
Resources.erase(Uri);
}
}
template <class Resource>
void ResourceManagerB<Resource>::UnloadAll()
{
// iterate through every element of the resource list
ResourceList::iterator itr;
for (itr = Resources.begin(); itr != Resources.end(); itr++)
// delete each resource
delete itr->second;
// finally, clear the list
Resources.clear();
}
template <class Resource>
Resource* ResourceManagerB<Resource>::GetPtr(URI& Uri)
{
// find the specified URI in the list
ResourceList::const_iterator itr;
// if it is there...
if ((itr = Resources.find(Uri)) != Resources.end())
// ... return a pointer to the corresponding resource
return itr->second;
// ... else return 0
return 0;
}
template <class Resource>
Resource& ResourceManagerB<Resource>::Get(URI& Uri)
{
// get a pointer to the resource
Resource* temp = GetPtr(Uri);
// if the resource was found...
if (temp)
// ... dereference the pointer to return a reference
// to the resource
return *temp;
else
// ... else throw an exception to notify the caller that
// the resource was not found
throw Exceptions::URINotFound();
}
|
您的引擎/框架的另一個重要方面是介面。當您編寫遊戲邏輯本身時,您不應該花費 4 個小時來編寫主遊戲迴圈,因為您要搜尋數百個更新函式,試圖找出您實際需要哪些。保持簡單明瞭。如果您能透過一兩個函式呼叫更新所有遊戲邏輯,並透過一兩個函式呼叫渲染場景,那麼您就走在正確的道路上了。利用面向物件原則,如繼承和純虛基類(想想“介面”)是建立具有良好結構的框架的好方法。
例如,所有遊戲物件的基類可以這樣定義:
1 2 3 4 5 6 7 8 9 10 11
|
class GameObject
{
public:
virtual ~GameObject()=0;
virtual Vector2f getPosition();
virtual bool interact(Object* o);
virtual void draw(); //highly library dependent
};
|
現在所有子類都遵循這個介面,可以有一個持有實體,它能夠輕鬆地儲存和管理您定義的任何和所有物件,而不管該物件實際上是什麼。隨著您對您選擇的語言的更多學習和程式設計,您會發現更多利用其各種功能來發揮優勢的方法。
第五步:媒體(音訊和圖形)
到目前為止,您可能至少已經考慮過遊戲的外觀,並且可能已經有一套媒體資源。然而,如果您和我一樣,您可能會因為自己想出的“漂亮設計”而感到興奮和投入,以至於到測試階段卻沒有任何影像可以在螢幕上舞動。現在是開始獲取所需資源的好時機。如果您有藝術天賦,那太棒了。如果您沒有,別擔心,希望仍在。透過谷歌搜尋可以找到大量的免費圖形和音效。Audacity 和 GIMP 是編輯您獲取或建立的任何內容的必備工具。
第六步:編寫您的遊戲
一旦您選擇了引擎或擁有了自己的框架,您就可以著手編寫遊戲邏輯本身了。理想情況下,您在投入無數個小時來建立一個“引擎”而該引擎的功能越界到幾乎無法使用但又不足以獨立執行之前,已經完整地閱讀了本文至少一次。您的框架應該提供一個基礎,用於構建物件互動(但不一定定義它),並處理所有渲染和其他低階細節,如物理。遊戲邏輯本身將定義物件互動(例如,透過定義 GameObject 的子類)、遊戲規則(例如,什麼構成輸贏)以及遊戲的初始狀態(例如,先載入哪個地圖,您開始時擁有什麼物品等),並且將包含“主遊戲迴圈”。
到底什麼是主遊戲迴圈?簡單來說:它是一個迴圈,主迴圈。想想遊戲執行時不斷重複的事情,這些就是包含在這個神秘迴圈中的內容。例如,每次迭代,遊戲都應該更新所有物件,然後將它們全部繪製到螢幕上。除了更新和繪製之外,主迴圈還可能負責計時。更新過多的遊戲會顯得速度極快,對使用者來說很可能太難了。想想光速的乒乓球。理想情況下,這個迴圈將使用您之前建立的框架,並且本身會非常簡單。請參閱下面的示例:
遊戲迴圈
1 2 3 4 5 6 7 8 9 10
|
while (!Game.playerLost())
{
world.update(); //assume this world object owns all of the GameObjects and updates them as well
screen.clear();
world.draw(screen);
screen.display();
ensureProperFPS(); //just a placeholder, put the actual timing logic right here in the loop
}
|
第七步:從中吸取教訓
我談論將框架與遊戲邏輯分開建立的主要原因是為了讓您學習編寫可重用程式碼。然後我告訴您不要擔心使其真正可重用,而是專注於編寫遊戲。我堅持這一點,初學者放棄專案的一個主要原因是他們花費大量時間和精力來為他們的遊戲“編寫引擎”,但他們還不知道一個好的引擎應該包含什麼,或者一個真正可行的結構/介面。在浪費了所有這些時間之後,他們一無所獲,然後因此感到氣餒並放棄。透過先專注於編寫遊戲,然後是可重用程式碼,您最終應該會得到一些可以看到的東西。這是您辛勤付出的有形回報,也是您繼續努力工作的原因。
現在您已經有了一款滿意的可玩遊戲,您可以嘗試將程式碼模組化為可移植的。您是否編寫了一個很棒的資源管理器或處理鍵盤輸入的絕佳類?嘗試使其完全可移植,這樣您就可以輕鬆地將原始檔複製到其他專案中,“開箱即用”。如果您想在下一個專案中完全從頭開始,那也沒關係。您不必從專案中實際提取程式碼才能從中獲得東西。只要您在這個過程中學到了東西,一切都是值得的。
第八步:打包和分發
經過所有這些工作,您可能希望人們實際玩您的遊戲!將所有必需的檔案打包到一個 zip 檔案、壓縮存檔或可執行安裝程式中,然後傳送給所有人!
技巧
我從製作遊戲中學習了很多東西,有些是艱難的。以下是一些您應該做的事情:
- 首先,保持條理!您應該有一個良好的組織系統來處理一切;您的程式碼、媒體、文件等。資料夾有其存在的理由,請使用它們!
- 另外,儘量保持程式碼乾淨、易讀。給函式起有意義的名字,並儘可能簡化一切。
- 記錄!我在文章中沒有真正談到它,但請記錄所有內容!記錄所有資料檔案的格式,並記錄所有函式和類的作用。您根本不知道這能節省多少時間以及可以避免多少頭痛,直到您真正去做。
- 從小處著手。不要一開始就嘗試製作下一個《寶可夢》遊戲。從小型、可管理的開始,並根據您的技能擴充套件您的目標。嘗試承擔一個超出您能力範圍的專案只會讓您氣餒。
- 著眼於目標!我太多專案失敗的最大原因之一是我過分糾結於細節而忽略了更大的圖景。是的,我的雲、雨和雷聲、腳印和霧效都很漂亮,但我最終沒有完成遊戲。您可以稍後使其變得漂亮,先製作遊戲!
- 享受樂趣!遊戲的意義在於樂趣,製作遊戲也可以很有趣。如果您喜歡工作,完成任務總是更容易的。當然,有時您會感到沮喪,但如果您發現自己過於生氣,那就休息一下!散步並思考其他事情通常是解決問題的最佳方法。回來後,您將有一個新的開始,這可以幫助您找到一個在之前的思路中可能想不到的解決方案。
chrisname 的一些入門建議
您不必如此辛苦。您需要做的是,去學習程式設計教程(例如此網站上的那個)。不要一天做太多,否則您會感到厭倦和失去動力。不要設定基於時間的 C++ 目標,那樣沒用。如果您在中途停止學習,您會忘記很多東西。按照此網站上的教程進行學習(https://cplusplus.tw/doc/tutorial/)。目標是每天完成兩課。不要在中途停止(除非是為了短暫休息,那是好事),並且不要一次學太多,否則您根本記不住。我建議閱讀並複製每一個示例(不是複製貼上;自己輸入,這將幫助您理解您正在做什麼),編譯它,看看執行它時會發生什麼,然後修改內容以檢視會發生什麼變化。我還建議您檢視別人的程式碼(幫助我的一件事是獲取別人損壞的程式碼並嘗試修復它,儘管一開始不要太糾結於此,因為閱讀別人的程式碼很難)。閱讀時,請嘗試重新表述:“如果你不能簡單地解釋它,你就沒有足夠好地理解它”(阿爾伯特·愛因斯坦)。
一旦您完成了這個教程,以及也許還有一些其他的教程(我讀了大約三個不同的教程,因為用不同的方式解釋東西很有用——我發現用兩種不同的方式解釋東西對於理解和記住它很有幫助),您可以閱讀 SFML 的教程(http://sfml-dev.org/tutorials/1.6/)。學習 SFML 將教會您製作 2D 遊戲。我也推薦學習 SDL(http://lazyfoo.net/SDL_tutorials/index.php),因為很多遊戲都使用它,您很可能會在某個時候遇到它。
在那之後,如果您想製作 3D 遊戲,您應該開始學習 OpenGL 程式設計。SFML 使這一點變得非常容易,SFML 教程中包含了使用 OpenGL 的教程。對於 OpenGL,也許這裡有人可以推薦您一本書或一個教程。
在所有這些過程中,您應該記住,掌握節奏很重要。不要試圖一次性吸收太多東西,否則您會忘記很多。而且,如果您後天有考試,就不要熬到凌晨 3 點……