開發
本文皆在教您如何對 DDNet 開發 ,因為這是一款開源遊戲(原始碼公開),它依賴於一些善良的人們在空餘時間的貢獻。
開發環境
截至目前,基於以下原因,我們強烈推薦使用Linux系統進行DDNet開發:
- 大多數DDNet貢獻者使用Linux,使用Linux與之前的開發者交流起來更加方便。
- 軟件包管理更簡單,您可以輕鬆地安裝所有需要的庫。
- 本文專注於Linux,還沒有涉及Windows。
首先,DDNet由 C++ 語言編寫,開發者需要對它相當熟悉,但您也可以在只有基礎知識的情況下慢慢學習深入。
學習C++的一些有用資源:
- learncpp.com
- cppreference.com
- 瀏覽器搜素引擎。
DDNet的原始碼使用Git託管,這是一個版本控制系統,也是與多人協作開發的重要工具。
如果您的Linux發行版中還沒有git,請安裝它,例如在大多數基於debian/ubuntu的發行版中,您可以打開終端,輸入:sudo-apt-install git
。
獲取原始碼
原始碼位於Github,您沒有帳戶的情況下可以通過Clone獲得原始碼,但如果想更改官方原始碼,則需要登錄賬號。
如果您不熟悉git/github,可以在這裏學習基礎知識: Hello World - Github
下載依賴項
如果您在Linux上,您可以通過閱讀DDNet github頁面上的自述文件來安裝所有需要的依賴項:https://github.com/ddnet/ddnet#dependencies-在linux上--macos
對於Arch Linux:
控制台輸入
sudo pacman -S --needed base-devel cmake curl freetype2 git glew gmock libnotify opusfile python sdl2 sqlite wavpack
For Debian:
sudo apt install build-essential cargo cmake git glslang-tools google-mock libavcodec-extra libavdevice-dev libavfilter-dev libavformat-dev libavutil-dev libcurl4-openssl-dev libfreetype6-dev libglew-dev libnotify-dev libogg-dev libopus-dev libopusfile-dev libpng-dev libsdl2-dev libsqlite3-dev libssl-dev libvulkan-dev libwavpack-dev libx264-dev python rustc spirv-tools4
編譯DDNet
我們使用CMake來控制編譯進程,如果你所有的依賴項都下載了,那麼它會很簡單,只要輸入這些命令(確保你在DDNet文件夾下):
mkdir build
cd build
cmake ..
make -j$(nproc)
通用信息
以下是一些通用信息:
- 目前,原始碼是使用C++17標準編譯的,但您會看到代碼的許多部分更像C,因為只有大多數新代碼使用C++17。
std::string
很少使用,字符數組加system.h
方法才是常態。- 大多數輸入輸出代碼、格式化和Print都是使用
system.h
提供的方法完成的。
原始碼佈局
現在你可以構建DDNet了,可以開始編輯它了。
src/base 目錄
由於DDNet是一個跨平台遊戲,需要一個抽象層來簡化開發,因此該目錄包含許多有用的函數來處理這一問題。
src/engine 目錄
遊戲引擎就在這裏,它處理大多數與遊戲無關的東西,比如圖形、聲音、網絡等。
src/game 目錄
所有玩遊戲的代碼都在這裏,分為客戶端和伺服器。
伺服器端
這個遊戲使用自己的實體組件系統,所有其他實體派生到的主類是CEntity
,位於src/game/server/Entity.h
中。
這些實體由位於此處的遊戲世界管理src/game/server/gameworld.h
一些重要實體有:
CCharacter: 代表着一個tee 是活着的, 當一個tee產生時它被實例化,當它死亡時它被刪除。有關玩家在死亡之間的信息,請參閱CPlayer.
客戶端
The client side is made up of components, these are classes that inherit CComponent
: These components can implement the virtual methods such as OnInit
, OnMessage
, etc. to provide their functionality.
Networking
The network protocol is mostly generated by python scripts that output C++ code, for example, datasrc/network.py
defines all network packets.
Code conventions
The ongoing discussion on code conventions is located here: ddnet#2945
Currently, the following applies:
Indentation style
Allman style is used.
This style puts the brace associated with a control statement on the next line, indented to the same level as the control statement. Statements within the braces are indented to the next level.
while(x == y)
{
Something();
SomethingElse();
}
</div>
<div lang="en" dir="ltr" class="mw-content-ltr">
FinalThing();
Classes and Structs
Must be prefixed by C
(for legacy reasons this is ignored for structs in some places, such as in graphics code) or I
for interfaces. New struct
s should be prefixed by S
.
Example:
class CCharacter : public CEntity
{
// ...
}
Enums and constants
Should be screaming snake case, for example: MAX_PLAYERS
enum
{
FAKETUNE_FREEZE = 1,
FAKETUNE_SOLO = 2,
FAKETUNE_NOJUMP = 4,
FAKETUNE_NOCOLL = 8,
FAKETUNE_NOHOOK = 16,
FAKETUNE_JETPACK = 32,
FAKETUNE_NOHAMMER = 64,
};
Variable naming
- The names of variables contain 3 parts: qualifier, prefix and name.
- Variable names should start with uppercase unless they are 1 char long without any prefix or qualifier, for example:
i
,x
,y
. - Variables can have more than 1 qualifier (or zero) and more than 1 prefix (or zero).
These are laid out like this: [qualifiers]_[prefixes][Name]
Qualifiers
m
for member variables:m_MyVariable
.s
for static variables:s_MyStaticVariable
,ms_MyStaticMemberVariable
.g
for global variables with external linkage:gs_MyGlobalStaticVar
.
Prefixes
p
for pointers:pMyPointer
,m_pCharacter
,ppMyPointerToPointer
.a
for arrays:aMyArray
,aBuf
.v
forstd::vector
s:vMyVector
,vpvMyVectorOfPointersToVectors
.fn
for functions:pfnMyCallback
,m_papfnMyPointerToArrayOfCallbacks
.
Common snippets
Here is a list of code that you may frequently see across the codebase:
Formatting text
char aBuf[128];
str_format(aBuf, sizeof(aBuf), "number: %d", 2);
See printf documentation for a list of all format specifiers.
Iterating over all players
for(int i = 0; i < MAX_CLIENTS; i++)
{
// Server-side
CPlayer *pPlayer = GameServer()->m_apPlayers[i];
}
Printing to the game console
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "wikiprint", "Hello from the ddnet wiki!");
Debug printing
int i = 2;
dbg_msg("wikiprint", "Hello from the ddnet wiki: %d", i);
Translating text in the client
Localize
can be used in the game client to get the translation for a specific string from the language file selected by the user.DoButton(..., Localize("Connect"), ...);
char aBuf[128];
str_format(aBuf, sizeof(aBuf), Localize("%d of %d servers"), NumServers, TotalServers);
Localize
and collect the strings to update the translation files. For this reason, the call to Localize
must not contain any other code, or the script cannot determine the text correctly.// Do NOT do this:
const char *pStr = Localize(Team == TEAM_RED ? "Red team" : "Blue team");
</div>
<div lang="en" dir="ltr" class="mw-content-ltr">
// Do this instead:
const char *pStr = Team == TEAM_RED ? Localize("Red team") : Localize("Blue team");
External resources
- UI Code in DDraceNetwork by Ryozuki
- An intro to the DDraceNetwork game source code by Ryozuki
- Code conventions in DDraceNetwork by Ryozuki
- Implementing a chat command in DDraceNetwork by Ryozuki
- Auto generated docs
- Technical documentation of Teeworlds file formats and network protocol
- The Anatomy of a One Tick Unfreeze
- Teeworlds programming YouTube tutorial by ChillerDragon