DDNET 知识库

中文 Wiki

DDNet Workshop 中文知识库,收录 79 Wiki 词条,涵盖游戏技巧、机制等内容,提升广大玩家游戏理解

游戏开发 Development
地图制作与外部工具•地图制作与外部工具

游戏开发 Development

DDNet 游戏开发指南,包含环境搭建、代码规范和常用代码片段

开发C++源代码编译
2024-01-01

本文旨在教你如何对 DDNet 开发,因为这是一款开源游戏(源代码公开),它依赖于一些善良的人们来用他们的放松时间贡献。


你的开发环境

截至目前,出于以下原因,我们强烈建议使用 Linux 环境在 DDNet 中开发:

  • 大多数 DDNet 贡献者实际上使用 Linux 来贡献
  • 更容易的软件包管理,您可以轻松地安装所有需要的库并开始贡献
  • 本文还没有 Windows 版本,主要关注 Linux

编程语言

DDNet 是使用 C++ 语言编写的,您需要相当熟悉它,但您也可以了解基础知识并了解更多信息。

学习 C++ 的有用资源:

版本控制

DDNet 的源代码使用 Git 托管,这是一个版本控制系统,也是与多个开发人员协作的重要工具。

如果您的 Linux 发行版中还没有 git,请安装它:

bash
# 在大多数基于 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

bash
sudo pacman -S --needed base-devel cmake curl freetype2 git glew gmock libnotify opusfile python sdl2 sqlite wavpack

Debian

bash
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-tools

编译 DDNet

我们使用 CMake 来控制编译进程,如果你所有的依赖项都下载了,那么它会很简单:

bash
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

一些重要实体:

实体文件说明
CCharactersrc/game/server/entities/character.h代表着一个 tee 是活着的,当一个 tee 产生时它被实例化,当它死亡时它被删除
CPlayersrc/game/server/player.h有关玩家在死亡之间的信息

客户端

客户端由组件组成,这些是继承 CComponent 的类:这些组件可以实现虚方法如 OnInit、OnMessage 等来提供它们的功能。

网络

网络协议主要由输出 C++ 代码的 Python 脚本生成,例如,datasrc/network.py 定义了所有网络数据包。


代码规范

关于代码规范的持续讨论位于:ddnet#2945

缩进风格

使用 Allman 风格。

这种风格将与控制语句关联的大括号放在下一行,缩进到与控制语句相同的级别。大括号内的语句缩进到下一级别。

cpp
while(x == y)
{
    Something();
    SomethingElse();
}
FinalThing();

类和结构体

必须以 C 为前缀(出于历史原因,某些地方的结构体忽略此规则,例如图形代码中)或 I 表示接口。新的 struct 应该以 S 为前缀。

示例:

cpp
class CCharacter : public CEntity
{
    // ...
}

枚举和常量

应该使用大写蛇形命名法,例如:MAX_PLAYERS

cpp
enum
{
    FAKETUNE_FREEZE = 1,
    FAKETUNE_SOLO = 2,
    FAKETUNE_NOJUMP = 4,
    FAKETUNE_NOCOLL = 8,
    FAKETUNE_NOHOOK = 16,
    FAKETUNE_JETPACK = 32,
    FAKETUNE_NOHAMMER = 64,
};

变量命名

  • 变量名包含 3 个部分:限定符、前缀和名称
  • 变量名应该以大写字母开头,除非它们是没有前缀或限定符的单字符,例如:i、x、y
  • 变量可以有多个限定符(或零个)和多个前缀(或零个)

格式:[qualifiers]_[prefixes][Name]

限定符

限定符含义示例
m成员变量m_MyVariable
s静态变量s_MyStaticVariable、ms_MyStaticMemberVariable
g具有外部链接的全局变量gs_MyGlobalStaticVar

前缀

前缀含义示例
p指针pMyPointer、m_pCharacter、ppMyPointerToPointer
a数组aMyArray、aBuf
vstd::vectorvMyVector、vpvMyVectorOfPointersToVectors
fn函数pfnMyCallback、m_papfnMyPointerToArrayOfCallbacks

常用代码片段

以下是您可能在代码库中经常看到的代码列表:

格式化文本

cpp
char aBuf[128];
str_format(aBuf, sizeof(aBuf), "number: %d", 2);

有关所有格式说明符的列表,请参阅 printf 文档。

遍历所有玩家

cpp
for(int i = 0; i < MAX_PLAYERS; i++)
{
    if(!m_apPlayers[i])
        continue;
    // 对 m_apPlayers[i] 进行操作
}

打印到游戏控制台

cpp
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "wikiprint", "Hello from the ddnet wiki!");

调试打印

cpp
int i = 2;
dbg_msg("wikiprint", "Hello from the ddnet wiki: %d", i);

在客户端中翻译文本

Localize 可以在游戏客户端中使用,从用户选择的语言文件中获取特定字符串的翻译。

cpp
DoButton(..., Localize("Connect"), ...);

字符串也可以包含格式说明符。翻译后的字符串必须包含相同的格式说明符。

cpp
char aBuf[128];
str_format(aBuf, sizeof(aBuf), Localize("%d of %d servers"), NumServers, TotalServers);

使用脚本扫描代码中的 Localize 调用并收集字符串以更新翻译文件。因此,对 Localize 的调用不能包含任何其他代码,否则脚本无法正确确定文本。

cpp
// 不要这样做:
const char *pStr = Localize(Team == TEAM_RED ? "Red team" : "Blue team");

// 应该这样做:
const char *pStr = Team == TEAM_RED ? Localize("Red team") : Localize("Blue team");

外部资源


小提示

  • Linux 是 DDNet 开发的首选环境,大多数贡献者都使用 Linux
  • DDNet 使用 C++17 标准编译,但代码中仍有很多 C 风格的写法
  • 代码使用 Allman 缩进风格(大括号另起一行)
  • 类名以 C 开头,接口以 I 开头,结构体以 S 开头
  • 变量命名遵循 [限定符]_[前缀][名称] 的格式
  • Localize() 函数用于客户端文本翻译,但调用中不能包含条件表达式
  • 网络协议由 Python 脚本生成,定义在 datasrc/network.py
  • 服务器使用实体组件系统,主类是 CEntity