Руководство по стилю
Last updated
Last updated
Данное руководство является переводом:
Однако, данный перевод имеет некоторые от оригинала.
Идентификатор - это всё, что напоминает или служит «именем». Например, имя ассета, имя материала, свойство blueprint, переменная, имя папки, имя строки таблицы и т. д.
Есть несколько разных способов написания идентификаторов. Вот некоторые распространенные варианты:
PascalCase
Используйте каждое слово с заглавной буквы и удалите все пробелы, например
DesertEagle
,StyleGuide
,ASeriesOfWords
.camelCase
Первая буква всегда строчная, но каждое последующее слово начинается с прописной, например
desertEagle
,styleGuide
,aSeriesOfWords
.Snake_case
Слова могут произвольно начинаться с верхнего или нижнего регистра, но слова разделяются символом подчеркивания, например
desert_Eagle
,Style_Guide
,a_Series_of_Words
.
Слова «переменная» и «свойство» в большинстве контекстов взаимозаменяемы. Но если они оба используются вместе в одном и том же контексте, то:
Обычно относится к переменной, определенной в классе. Например, если у BP_Barrel
есть переменная bExploded
, то bExploded
можно назвать свойством BP_Barrel
.
Обычно это либо аргумент функции, либо просто локальная переменная внутри функции.
Если вы работаете над проектом или с командой, в которой уже есть руководство по стилю, его следует придерживаться. При любом несоответствии между существующим руководством по стилю и стилем в вашей команде предпочтение должно отдаваться стилю вашей команды.
«Споры о стиле бессмысленны. Должно быть руководство по стилю, и вы должны ему следовать».
Переход от одного проекта к другому не должен вызывать повторного изучения стиля и структуры. Соответствие руководству по стилю устраняет ненужные догадки и двусмысленности.
Это также позволяет более продуктивно создавать и поддерживать проект, поскольку не нужно думать о стиле. Просто следуйте инструкциям. Это руководство по стилю написано с учётом лучших практик, а это означает, что, следуя этому руководству по стилю, вы также сведёте к минимуму проблемы, которые трудно отследить.
Если вы видите, что кто-то работает либо против руководства по стилю, либо вообще без такового, постарайтесь это исправить.
Если вы помогаете кому-то, чья работа соответствует другому, но последовательному и разумному руководству по стилю, вы должны быть в состоянии адаптироваться к нему. Если они не соответствуют какому-либо руководству по стилю, отправьте их сюда.
Когда вы присоединяетесь к команде Unreal-разработчиков, одним из ваших первых вопросов должен быть: «Есть ли у вас руководство по стилю?». Если ответ отрицательный, вы должны скептически относиться к их способности работать в команде.
Gamemakin LLC не является юристом, но, пожалуйста, не привносите в проект незаконные действия и поведение, включая, помимо прочего:
Не распространяйте контент, на распространение которого у вас нет прав
Не нарушайте чьи-либо авторские права или товарные знаки
Не воруйте контент
Соблюдайте лицензионные ограничения контента
Ни в каком идентификаторе никогда не используйте следующие символы, кроме случаев крайней необходимости:
Пробельный символ любого типа
Обратная косая черта\
Символы, такие как #!@$%
Любой Unicode-символ
Любой идентификатор должен стремиться иметь только следующие символы, когда это возможно (RegEx [A-Za-z0-9_]+
)
ABCDEFGHIJKLMNOPQRSTUVWXYZ
abcdefghijklmnopqrstuvwxyz
1234567890
_
Соблюдение этих правил обеспечит наибольшую совместимость всех данных на всех платформах во всех инструментах и поможет предотвратить простои из-за потенциально неправильной обработки символов идентификаторов в коде, который вы не контролируете.
Соглашения об именовании ассетов следует рассматривать как закон. В проекте, где поддерживается это соглашение, очень легко управлять ассетами, искать, анализировать и поддерживать их.
Большинство ассетов имеют префикс, который обычно представляет собой аббревиатуру типа ассета, за которой следует символ подчеркивания.
Prefix_BaseAssetName_Variant_Suffix
Все ассеты должны иметь базовое имя ассета. Базовое имя ассета представляет собой логическую группу связанных ассетов. Любой ассет, входящий в эту логическую группу, должен соответствовать стандарту: Prefix_BaseAssetName_Variant_Suffix
.
Помнить шаблон Prefix_BaseAssetName_Variant_Suffix
и руководствоваться здравым смыслом, как правило, достаточно, чтобы гарантировать хорошее имя ассета. Вот несколько подробных правил относительно каждого элемента.
BaseAssetName
должно определяться коротким и легко узнаваемым именем, относящимся к контексту данной группы ассетов. Например, если у вас есть персонаж по имени Боб, все ассеты Боба будут иметь BaseAssetName
- Bob
.
Для уникальных и специфических вариантов ассетов используется Variant
. Variant
- это короткое и легко узнаваемое имя, представляющее логическую группу ассетов, являющихся подмножеством базового имени ассета. Например, если у Боба есть несколько скинов, эти скины все равно должны использовать Bob
как BaseAssetName
. Но при этом скин «Evil» будет называться Bob_Evil
, а скин «Ретро» будет называться Bob_Retro
.
Для уникальных, но универсальных вариантов ассетов Variant
- это двузначное число, начинающееся с 01
. Например, если у вас есть художник по окружающей среде, создающий невзрачные камни, они будут называться Rock_01
, Rock_02
, Rock_03
и т. д. За редчайшим исключением, у вас никогда не должен быть трехзначный номер варианта. Если у вас более 100 ассетов, вам следует рассмотреть возможность организации их с разными базовыми именами или с использованием нескольких вариантов имён.
В зависимости от того, как создаются варианты ваших ассетов, вы можете связать вместе имена вариантов. Например, если вы создаете элементы напольного покрытия для проекта Arch Viz, вы должны использовать базовое имя Flooring
со связанными вариантами, такими как Flooring_Marble_01
, Flooring_Maple_01
, Flooring_Tile_Squares_01
.
Тип ассета
Имя ассета
Skeletal Mesh
SK_Bob
Material
М_Bob
Texture (Diffuse/Albedo)
T_Bob_D
Texture (Normal)
T_Bob_N
Texture (Evil Diffuse)
T_Bob_Evil_D
Тип ассета
Имя ассета
Static Mesh (01)
S_Rock_01
Static Mesh (02)
S_Rock_02
Static Mesh (03)
S_Rock_03
Material
M_Rock
Material Instance (Снег)
MI_Rock_Snow
Level / Map
Level (Persistent)
_P
Level (Audio)
_Audio
Level (Lighting)
_Lighting
Level (Geometry)
_Geo
Level (Gameplay)
_Gameplay
Blueprint
BP_
Material
M_
Static Mesh
SM_
Skeletal Mesh
SK_
Texture
T_
_?
Particle System
PS_
Niagara System
NS_
Widget Blueprint
WBP_
Aim Offset
AO_
Aim Offset 1D
AO_
Animation Blueprint
ABP_
Animation Composite
AC_
Animation Montage
AM_
Animation Sequence
A_
Blend Space
BS_
Blend Space 1D
BS_
Level Sequence
LS_
Morph Target
MT_
Paper Flipbook
PFB_
Rig
Rig_
Skeletal Mesh
SK_
Skeleton
SKEL_
AI Controller
AIC_
Behavior Tree
BT_
Blackboard
BB_
Decorator
BTDecorator_
Service
BTService_
Task
BTTask_
Environment Query
EQS_
EnvQueryContext
EQS_
Context
Blueprint
BP_
Blueprint Component
BP_
Component
Например, BP_InventoryComponent
Blueprint Function Library
BPFL_
Blueprint Interface
BPI_
Blueprint Macro Library
BPML_
Желательно не использовать библиотеки макросов
Enumeration
E
Без черты подчёркивания
Structure
F
Без черты подчёркивания
Tutorial Blueprint
TBP_
Widget Blueprint
WBP_
Material
M_
Material (Post Process)
PP_
Material Function
MF_
Material Instance
MI_
Material Parameter Collection
MPC_
Subsurface Profile
SP_
Physical Materials
PM_
Decal
M_, MI_
_Decal
Texture
T_
Texture (Diffuse/Albedo/Base Color)
T_
_D
Texture (Normal)
T_
_N
Texture (Roughness)
T_
_R
Texture (Alpha/Opacity)
T_
_A
Texture (Ambient Occlusion)
T_
_O
Texture (Bump)
T_
_B
Texture (Emissive)
T_
_E
Texture (Mask)
T_
_M
Texture (Specular)
T_
_S
Texture (Metallic)
T_
_M
Texture (Packed)
T_
_*
Texture Cube
TC_
Media Texture
MT_
Render Target
RT_
Cube Render Target
RTC_
Texture Light Profile
TLP
Обычной практикой является упаковка нескольких слоёв данных в одну текстуру. Примером этого является объединение Emissive, Roughness, Ambient Occlusion в качестве красного, зеленого и синего каналов текстуры соответственно. Чтобы определить суффикс, просто сложите буквы соответствующего суффикса из таблицу сверху вместе. Получится, например, _ERO
.
Обычно допустимо включать слой Alpha/Opacity в альфа-канал Diffuse/Albedo, и, поскольку это обычная практика, добавление суффикса
A
к_D
необязательно.
Упаковывать 4 канала данных в текстуру (RGBA) не рекомендуется, за исключением маски Alpha/Opacity в альфа-канале Diffuse/Albedo, поскольку текстура с альфа-каналом влечёт за собой больше накладных расходов, чем текстура без него.
Animated Vector Field
VFA_
Camera Anim
CA_
Curve Color
Curve_
_Color
Curve Table
Curve_
_Table
Data Asset
*_
Префикс должен основываться на классе
Data Table
DT_
Curve Float
Curve_
_Float
Foliage Type
FT_
Force Feedback Effect
FFE_
Landscape Grass Type
LG_
Landscape Layer
LL_
Matinee Data
Matinee_
Media Player
MP_
File Media Source
FMS_
Object Library
OL_
Redirector
Должны быть исправлены как можно скорее.
Sprite Sheet
SS_
Static Vector Field
VF_
Substance Graph Instance
SGI_
Substance Instance Factory
SIF_
Touch Interface Setup
TI_
Vector Curve
Curve_
_Vector
Paper Flipbook
PFB_
Sprite
SPR_
Sprite Atlas Group
SPRG_
Tile Map
TM_
Tile Set
TS_
Physical Material
PM_
Physics Asset
PHYS_
Destructible Mesh
DM_
Dialogue Voice
DV_
Dialogue Wave
DW_
Media Sound Wave
MSW_
Reverb Effect
Reverb_
Sound Attenuation
ATT_
Sound Class
Без префикса/суффикса, а также должны быть помещены в папку под названием SoundClasses
Sound Concurrency
_SC
Должен быть назван в соответствии с SoundClass
Sound Cue
S_
_Cue
Sound Mix
Mix_
Sound Wave
S_
Font
Font_
Slate Brush
Brush_
Slate Widget Style
Style_
Widget Blueprint
WBP_
Particle System
PS_
Niagara System
NS_
Niagara Emitter
NE_
Niagara Module
NM_
Niagara Function Script
NM_
_S
Material (Post Process)
PP_
Столь же важным, как и имена ассетов, является структура каталога Content. Соглашения об именовании ассетов и структура каталогов Content идут рука об руку, и нарушение любого из них приводит к ненужному хаосу.
Существует несколько способов размещения содержимого проекта на UE. В этом стиле мы будем использовать структуру, которая, в основном, полагается на возможности фильтрации и поиска в Content Browser, чтобы найти ресурсы определенного типа, вместо структуры, которая группирует типы ассетов с помощью папок.
Причины такой структуры перечислены в следующих подразделах.
Это общие правила именования любой папки в Content.
PascalCase означает, что имя начинается с заглавной буквы, а затем вместо использования пробелов каждое последующее слово также начинается с заглавной буквы. Например, DesertEagle
, RocketPistol
и ASeriesOfWords
.
Использование других символов вне a-z
, A-Z
и 0-9
таких как @
, -
, _
, ,
, *
и #
также может привести к неожиданным и трудно отслеживаемым проблемам на других платформах, в системе контроля версий и в различных дополнительных инструментах.
Все ассеты проекта должны находиться в папке с именем проекта. Например, если ваш проект называется «Generic Shooter», всё его содержимое должно находиться в папке Content/GenericShooter
.
Есть несколько причин для такого подхода.
Часто в руководствах по стилю кода написано, что не следует загрязнять глобальное пространство имен, и данный подход следует тому же принципу. Когда ассетам разрешено существовать за пределами папки проекта, часто становится намного сложнее обеспечить строгую структуру, поскольку ассеты, не находящиеся в папке проекта, поощряют плохое поведение, связанное с отсутствием необходимости организовывать ассеты.
При работе над несколькими проектами команда обычно копирует ассеты из одного проекта в другой, если она сделала что-то, что нужно обоим проектам. Когда это происходит, самый простой способ выполнить копирование — использовать функцию Migrate в Content Browser, так как она копирует не только выбранный ресурс, но и все его зависимости.
Эти зависимости могут легко доставить вам неприятности. Если у двух ресурсов проекта нет папки верхнего уровня, и они имеют аналогичные имена или уже ранее перенесенные ресурсы, новая миграция может случайно стереть любые изменения.
Это также основная причина, по которой персонал Epic Marketplace применяет ту же политику для представленных ассетов.
После миграции безопасное слияние ресурсов можно выполнить с помощью инструмента «Replace References» в Content Browser. Также это позволяет понимать, что те ассеты, которые находятся за пределами папки проекта, очевидно, ожидают слияния. После полной миграции ресурсов и их объединения в дереве Content не должно быть другой папки верхнего уровня. Этот метод на 100% гарантирует полную безопасность любых миграций.
Например, предположим, что вы создали мастер-материал в одном проекте, который хотели бы использовать в другом проекте, поэтому вы перенесли этот ассет. Если этот ресурс не находится в папке верхнего уровня, он может иметь имя, например: Content/MaterialLibrary/M_Master
. Если в целевом проекте еще нет ассета с таким же именем, это должно работать без проблем.
По мере продвижения работы над одним или обоими проектами их соответствующие мастер-материалы могут изменяться, чтобы быть адаптированными для их конкретных проектов.
Проблема возникает, когда, например, художник для одного проекта создал хороший модульный набор Static Meshes, а кто-то хочет включить этот набор Static Meshes во второй проект. Если художник, создавший ассеты, использовал экземпляры материалов Content/MaterialLibrary/M_Master
, при выполнении миграции существует большая вероятность конфликта для ранее перенесенного Content/MaterialLibrary/M_Master
.
Эту проблему трудно предсказать и трудно предупредить. Человек, переносящий Static Meshes, может не быть тем же человеком, который знаком с разработкой мастер-материала обоих проектов, и он может даже не знать, что рассматриваемые Static Meshes зависят от экземпляров материала, которые затем зависят от мастер-материала. Однако, для работы инструмента Migrate требуется вся цепочка зависимостей, поэтому он будет вынужден захватить Content/MaterialLibrary/M_Master
, когда копирует эти ассеты в другой проект, и после инструмент Migrate просто перезапишет существующий ассет мастер-материала.
Именно на этом этапе, если мастер-материалы для обоих проектов каким-либо образом несовместимы, вы рискуете, возможно, сломать всю библиотеку материалов для проекта, а также любые другие зависимости, которые, возможно, уже были перенесены, просто потому, что ассеты не были сохранены в папке верхнего уровня. Простая миграция Static Meshes теперь становится очень неприятной и опасной задачей.
Если ваш проект планирует выпустить DLC или имеет несколько связанных с ним подпроектов, которые могут быть либо перенесены, либо просто не включены в сборку, то ресурсы, относящиеся к этим проектам, должны иметь свою собственную отдельную папку содержимого верхнего уровня. Это значительно упрощает подготовку DLC отдельно от основного контента проекта. Подпроекты также можно переносить и удалять с минимальными усилиями. Если вам нужно изменить материал ассета или добавить какое-то особенное поведение ассета в патче, вы можете легко поместить эти изменения в папку патча и работать с ней безопасно, не нарушая основной проект.
Во время разработки проекта члены команды часто имеют своего рода «песочницу», где они могут свободно экспериментировать, не рискуя основным проектом. Поскольку эта работа может продолжаться, эти члены команды могут захотеть поместить свои ассеты на сервер управления исходным кодом проекта. А это может привести к определённым проблемам.
Участнику команды очень легко случайно использовать ассеты, которые ещё не готовы к этому, что вызовет проблемы после удаления этих ассетов. Например, художник может ещё продолжать работу над модульным набором Static Meshes, меняя его размеры и привязку. Если дизайнер уровня увидит эти ассеты в основной папке проекта, он может использовать их на уровне, не подозревая, что они могут подвергнуться серьёзным изменениям и/или удалению. Это вызывает огромное количество повторной работы для всех в команде.
Если бы эти ассеты были помещены в папку Developers
, у дизайнера уровня никогда было не было причин их использовать, и проблема бы не возникла. Content Browser имеет специальные параметры просмотра, которые скрывают папку разработчика (она скрыта по умолчанию), что делает невозможным случайное использование ресурсов разработчика при обычном использовании.
Как только ассеты готовы к использованию, художник просто должен переместить их в папку для конкретного проекта и исправить редиректы. По сути, это «продвижение» ассетов из experimental в production.
Файлы карт сильно отличаются, и каждый проект обычно имеет свою собственную систему именования карт, особенно если они работают с подуровнями или streaming-уровнями. Независимо от того, какая система организации карт используется для конкретного проекта, все уровни должны находиться в /Content/Project/Maps
.
Возможность сказать кому-то открыть конкретную карту, не объясняя, где она находится, — это отличная экономия времени и общее улучшение «качества жизни». Обычно уровни находятся в подпапках Maps
, таких как Maps/Campaign1/
или Maps/Arenas
, но самое главное здесь то, что все они существуют внутри /Content/Project/Maps
.
Это также упрощает работу по сборке проекта. Борьба с уровнями для процесса сборки может быть чрезвычайно неприятной, если им приходится копаться в произвольных папках для них. Если карты команды находятся в одном месте, то случайно не упаковать карту в сборку гораздо сложнее. Это также упрощает расчёт освещения и тестирования.
Используйте папку /Content/Project/Core
для ассетов, которые абсолютно необходимы для работы проекта. Например, базовые GameMode
, Character
, PlayerController
, GameState
, PlayerState
и связанные с ними Blueprints должны находиться в этой папке.
Это создаёт чёткое послание: «Не трогайте это» - для других членов команды. У не-программистов должно быть очень мало причин для входа в папку Core
. Следуя хорошему стилю структуры кода, дизайнеры должны вносить изменения в игровой процесс в дочерних классах, расширяющих функциональность базовых. Дизайнеры должны использовать дочерние классы в отдельных папках, а не напрямую базовые классы.
Например, если в вашем проекте требуются поднимаемые предметы, которые можно разместить на уровне, то должен существовать базовый класс Pickup в Core/Pickups
, определяющий базовое поведение поднимаемого предмета. Конкретные подбираемые предметы, такие как Health или Ammo, должны находиться в папке, например /Content/Project/Placeables/Pickups/
. Game Designers могут определять и настраивать предметы в этой папке, как им заблагорассудится, но они не должны касаться папки Core/Pickups
, так как они могут непреднамеренно сломать базовый класс.
Assets
или AssetTypes
Assets
- излишнеПоскольку все ассеты и так являются ассетами.
Meshes
, Textures
или Materials
является избыточнымВсе имена ассетов названы с учетом их типа. Эти папки предлагают только избыточную информацию, и использование этих папок может быть легко заменено надежной и простой в использовании системой фильтрации, предоставляемой Content Browser.
Хотите просматривать только Static Meshes в папке Environment/Rocks/
? Просто включите фильтр Static Mesh. Если все ассеты названы правильно, они также будут отсортированы в алфавитном порядке независимо от префиксов. Хотите просматривать Static Meshes и Skeletal Meshes? Просто включите оба фильтра. Это устраняет необходимость потенциального выбора с помощью Ctrl-Click
двух папок в дереве Content Browser.
Отсутствие подобных папок также предотвращает неизбежное размещение кем-либо Static Mesh или текстуры в папке Materials
.
Существуют определенные типы ассетов, которые имеют огромный объем связанных файлов, где каждый ресурс имеет уникальное назначение. Двумя наиболее распространенными являются анимация и аудио-ассеты. Если вы обнаружите, что у вас есть более 15 таких ассетов, которые принадлежат друг другу, то их надо разместить все вместе в отдельной папке.
Например, анимация, которая используется несколькими персонажами, должна находиться в подпапках Characters/Common/Animations
. Эти подпапки могут иметь, например, такие имена: Locomotion
или Cinematic
.
MaterialLibrary
Если в вашем проекте используются мастер-материалы, многослойные материалы или любые формы повторно используемых материалов или текстур, которые не принадлежат ни к одному подмножеству ресурсов, то эти ресурсы должны быть расположены в папке Content/Project/MaterialLibrary
.
Таким образом, все «глобальные» материалы имеют своё место и легко обнаруживаются.
Это также позволяет в рамках проекта невероятно легко применять политику: «Применять можно только экземпляры материала». Если все дизайнеры и ассеты должны использовать только экземпляры материалов, то в этой папке должны существовать только обычные материалы. Вам будет легко проверять это, просто выполняя поиск базовых материалов в любой папке, кроме папки
MaterialLibrary
.
Директория MaterialLibrary
не обязательно должна состоять только из материалов. Общие служебные текстуры, функции материалов и другие подобные вещи также должны храниться здесь, а также в папках, обозначающих их предназначение. Например, общие текстуры шума должны находиться в директории MaterialLibrary/Utility
.
Любые материалы для тестирования или отладки должны находиться в директории MaterialLibrary/Debug
. Это позволяет легко удалять отладочные материалы из проекта перед отправкой, а также, благодаря отображению ошибок делает очевидным, если кто-то их ещё использует.
Просто не должно быть пустых папок. Они загромождают Content Browser.
Если вы обнаружите, что в Content Browser есть пустая папка, которую вы не можете удалить, выполните следующие действия:
Убедитесь, что вы используете систему контроля версий.
Немедленно запустите Fix Up Redirectors в своём проекте.
Перейдите к папке на диске и удалите ассеты внутри.
Закройте редактор.
Убедитесь, что ваша система контроля версий синхронизирована (например, если вы используете Perforce, запустите Reconcile Offline Work в вашем каталоге Content).
Откройте редактор. Убедитесь, что всё работает по-прежнему. Если это не так, вернитесь, выясните, что пошло не так, и повторите попытку.
Убедитесь, что папка исчезла.
Отправьте изменения в систему контроля версий.
Все Blueprints должны компилироваться без предупреждений и ошибок. Вы должны немедленно исправлять предупреждения и ошибки Blueprint, поскольку они могут быстро привести к очень пугающему и неожиданному поведению.
Не отправляйте неработающие Blueprint в систему контроля версий. Если вы должны хранить их там, то лучше отложите их.
Сломанные Blueprints могут вызвать проблемы, проявляющиеся по-разному: неработающие ссылки, неожиданное поведение, ошибки при сборке и частая ненужная перекомпиляция. Сломанный Blueprint может сломать всю вашу игру.
Слова переменная
и свойство
могут использоваться взаимозаменяемо.
Все имена небулевских переменных должны быть чёткими, однозначными и описательными существительными.
Score
Kills
TargetPlayer
Range
CrosshairColor
AbilityID
b
для booleanВсе булевские переменные должны быть названы в формате PascalCase, но с префиксом b
в нижнем регистре.
Пример: Используйте bDead
и bEvil
, а не Dead
и Evil
.
Редактор Blueprint знает, что не следует включать b
при отображении переменной.
Все булевские переменные следует именовать с помощью описательных прилагательных, если они представляют общую информацию. Не включайте слова, формулирующие переменную как вопрос, например Is
. Это зарезервировано для функций.
Пример: Используйте bDead
и
bHostile
, а не bIsDead
и bIsHostile
.
Старайтесь не использовать такие глаголы, как bRunning
. Глаголы, как правило, приводят к комплексным состояниям.
Не используйте булевские переменные для представления комплексных и/или зависимых состояний. Это делает добавление и удаление состояний сложными и неудобными для чтения. Вместо этого используйте перечисление.
Пример: при определении оружия не используйте bReloading
и bEquipping
, если оружие нельзя одновременно перезаряжать и экипировать. Вместо этого определите перечисление с именем EWeaponState
и используйте переменную этого типа с именем WeaponState
. Это значительно упрощает добавление новых состояний к оружию.
Пример: Не используйте bRunning
, если вам также нужны bWalking
или bSprinting
. Это должно быть определено как перечисление с чётко определенными именами состояний.
Все имена переменных не должны дублировать свой контекст, поскольку все переменные в Blueprint и так его имеют.
Рассмотрим Blueprint с именем BP_PlayerCharacter
.
Плохие примеры:
PlayerScore
PlayerKills
MyTargetPlayer
MyCharacterName
CharacterSkills
ChosenCharacterSkin
Все эти переменные имеют избыточное имя. Очевидно, что эти переменные и так принадлежат классу BP_PlayerCharacter
, потому что именно он их и определяет.
Хорошие примеры:
Score
Kills
TargetPlayer
Name
Skills
Skin
Атомарные или примитивные переменные — это переменные, которые представляют данные в их простейшей форме, такой как boolean, integer, float и перечисления.
Строки (String) и векторы считаются атомарными с точки зрения стиля при работе с Blueprints, однако технически они не являются таковыми.
В то время как векторы состоят из трёх float, векторами часто можно манипулировать как единым целым, аналогично и с ротаторами.
Не считайте переменные
Text
атомарными, поскольку они содержат в себе функциональность локализации. Атомарный тип строки символов —String
, а неText
.
Атомарные переменные не должны иметь имя своего типа в своём имени.
Пример: используйте Score
, Kills
, Description
, а не ScoreFloat
, FloatKills
, DescriptionString
.
Единственным исключением из этого правила является случай, когда переменная представляет собой «количество» чего-либо и когда использование имени без типа переменной затрудняет понимание её назначения.
Пример: Генератор забора должен сгенерировать X секций. Сохраните X в NumPosts
или PostsCount
вместо просто Posts
, который потенциально может быть воспринят как массив типа Post
.
Неатомарные или сложные переменные — это переменные, представляющие данные в виде набора атомарных переменных. Структуры, классы, интерфейсы и примитивные типы со скрытым поведением, такие как Text
и Name
- все подпадают под это правило.
Несмотря на то, что массив атомарного типа представляет собой список переменных, он всё равно не изменяет «атомарность» хранимого типа.
Эти переменные должны включать имя своего типа, но при этом учитывать их контекст.
Если класс владеет сложной переменной, т. е. если BP_PlayerCharacter
владеет BP_Hat
, она должна храниться с именем своего типа без каких-либо изменений.
Пример: используйте Hat
, Flag
и Ability
, а не MyHat
, MyFlag
и PlayerAbility
.
Если класс не владеет значением, которое представляет сложная переменная, вы должны использовать существительное вместе с типом переменной.
Пример: если у класса BP_Turret
есть возможность нацеливаться на BP_PlayerCharacter
, то он должен хранить свою цель так TargetPlayer
, чтобы в контексте BP_Turret
было ясно, что это ссылка на другой тип сложной переменной, которой он не владеет.
Массивы следуют тем же правилам именования, что и выше, но должны иметь имя в виде существительного во множественном числе.
Пример: используйте Targets
, Hats
и EnemyPlayers
, а не TargetList
, HatArray
и EnemyPlayerArray
.
Все переменные, значение которых можно безопасно изменить для настройки поведения blueprint, должны быть помечены как Editable
.
И наоборот, все переменные, изменение которых небезопасно или которые не должны предоставляться дизайнерам, не должны быть помечены как Editable
, за исключением тех случаев, когда по техническим причинам переменная должна быть помечена как Expose On Spawn
.
Не помечайте переменные как Editable
без веской причины.
Все Editable
переменные, в том числе те, которые помечены как редактируемые только для того, чтобы их можно было пометить как Expose On Spawn
, должны иметь описание в своих Tooltip
-полях, объясняющее, как изменение этого значения влияет на поведение Blueprint.
Все Editable
переменные должны использовать слайдер и диапазоны значений, если существует значение, на которое не следует устанавливать переменную.
Пример: Blueprint, создающий секции забора, может иметь редактируемую переменную с именем PostsCount
. В этом случае значение -1 не имеет никакого смысла. Используйте поля для указания диапазона, чтобы отметить 0 как минимум.
Если редактируемая переменная используется в Construction Script, для нее должен быть определен разумный диапазон слайдера, чтобы кто-то не мог случайно присвоить ей большое значение, которое может привести к сбою редактора.
Диапазон значений необходимо определять только в том случае, если известны границы допустимых значений. В то время как диапазон слайдера предотвращает случайный ввод большого числа, неопределенный диапазон значений позволяет пользователю указать значение за пределами диапазона слайдера, которое может считаться «опасным», но всё ещё допустимым.
Если класс имеет небольшое количество переменных, категории не требуются, но всем переменным Editable
она должна быть присвоена. Распространённая категория — Config
.
Если класс имеет большое количество переменных, все переменные Editable
следует разделить на подкатегории, используя категорию Config
в качестве базовой категории. Нередактируемые переменные следует разделить на категории с содержательными именами, описывающие их использование.
Вы можете определить подкатегории с помощью вертикальной черты
|
, например,Config | Animations
Пример: набор переменных класса оружия может быть организован следующим образом:
В C++ переменные имеют понятие уровня доступа. Открытый (public) означает, что любой код вне класса может получить доступ к переменной. Защищенный (protected) означает, что только класс и любые дочерние классы могут получить доступ к ней. Закрытый (private) означает, что только этот класс может иметь доступ к переменной.
В настоящее время Blueprints не содержат концепции защищенного (protected) доступа для переменных.
Все переменные, включая Editable
, должны быть объявлены закрытыми (private). При необходимости доступа к ним в дочерних классах можно использовать protected get/set
функции, а для доступа за пределами иерархии - public get/set
функции. Разумеется, создавать функции доступа нужно лишь при их реальной необходимости.
Если переменная должна быть редактируемой, но часто нетронутой, пометьте ее как Advanced Display
. Это делает переменную скрытой, пока не будет нажата стрелка расширенного отображения.
Найти параметр Advanced Display
можно при отображении расширенных параметров в настройках переменной.
Transient-переменные — это переменные, значения которых не нужно сохранять и загружать и которые имеют начальное значение равные 0 или null. Это полезно для ссылок на другие объекты и actors, значение которых неизвестно до времени выполнения. Это предотвращает сохранение ссылки редактором и ускоряет сохранение и загрузку Blueprint.
Из-за этого все transient-переменные всегда должны быть инициализированы 0 или null. В противном случае, это приведет к трудным для отладки ошибкам.
Не используйте флаг Config Variable
. Из-за этого дизайнерам сложнее контролировать поведение Blueprint. Config-переменные должны использоваться только в C++ для редко изменяемых переменных. Думайте о них как об Advanced Advanced Display
.
В этом разделе описывается, как создавать функции, события и диспетчеры событий. Всё, что относится к функциям, также относится и к событиям, если не указано иное.
Именование функций, событий и диспетчеров имеет очень большое значение. Основываясь только на названии, можно сделать определённые предположения о функциях. Например:
Это чистая функция?
Получает ли она информацию о состоянии?
Это обработчик?
Это RPC?
Какова её цель?
На эти и многие другие вопросы можно ответить, если назвать функцию надлежащим образом.
Все функции и события выполняют какую-либо форму действия: получение информации, вычисление данных или взрыв чего-либо. Следовательно, все функции должны начинаться с глаголов. Они должны быть сформулированы в настоящем времени, когда это возможно. У них также должен быть некоторый контекст относительно того, что они делают.
Функции OnRep
, обработчики событий и диспетчеры событий являются исключением из этого правила.
Хорошие примеры:
Fire
- хороший пример, если в классе Character/Weapon, так как в этом случае она имеет контекст. Плохо, если в Barrel/Grass/любом другом неоднозначном контексте.
Jump
- хороший пример, если в классе персонажа, в противном случае нужен контекст.
Explode
ReceiveMessage
SortPlayerArray
GetArmOffset
GetCoordinates
UpdateTransforms
EnableBigHeadMode
Плохие примеры:
Dead
- мёртв или умрёт?
Rock
ProcessData
- неоднозначно, поскольку эти слова ничего не значат.
PlayerState
- только лишь существительные всегда являются неоднозначными для функцией.
Color
- глагол без контекста или неоднозначное существительное.
OnRep_Variable
Все функции-уведомления при репликации должны иметь вид OnRep_Variable
. Это принудительное требование редактора Blueprint. Однако, если вы пишете в C++ функцию OnRep
, она также должна следовать этому соглашению при использовании её в Blueprint.
Это чрезвычайно важно, поскольку, если вопрос не задан, можно предположить, что функция выполняет действие и возвращает результат, если это действие было успешным.
Хорошие примеры:
IsDead
IsOnFire
IsAlive
IsSpeaking
IsHavingAnExistentialCrisis
IsVisible
Плохие примеры:
Fire
- в огне сейчас или надо стрелять?
OnFire
- можно спутать с диспетчером событий для стрельбы.
Dead
- мёртв или умрёт?
Visibility
- видно или нет, или надо установить видимость?
On
Использовать слово Handle
не допускается, поскольку Unreal использует именно On
, тогда как другие фреймворки могут использовать Handle
вместо On
.
Хорошие примеры:
OnDeath
- распространённое словосочетание в играх
OnPickup
OnReceiveMessage
OnMessageRecieved
OnTargetChanged
OnClick
OnLeave
Плохие примеры:
OnData
OnTarget
HandleMessage
HandleDeath
Каждый раз, когда создается RPC, функция должна иметь префикс Server
, Client
или Multicast
. Без исключений.
После префикса следуйте всем остальным правилам именования функций.
Хорошие примеры:
ServerFireWeapon
ClientNotifyDeath
MulticastSpawnTracerEffect
Плохие примеры:
FireWeapon
- не указывает, что это какой-то RPC.
ServerClientBroadcast
- запутанно.
AllNotifyDeath
- используйте Multicast
, никогда All
.
ClientWeapon
- нет глагола, двусмысленно.
Все функции должны иметь Return Nodes, причём без исключений.
Return Nodes явно отмечают, что функция завершила своё выполнение. В мире, где Blueprints могут быть заполнены узлами Sequence
, ForLoopWithBreak
и движением линии Exec в обратном направлении (например, на Break у цикла после Loop и Branch), явный поток выполнения важен для удобочитаемости, обслуживания и упрощения отладки.
Компилятор Blueprint может отслеживать поток выполнения и предупреждать вас, если есть ветвь вашего кода с необработанным Return Node.
Например, если программист добавляет новую линию в узле Sequence или добавляет логику после завершения цикла, но при этом выход из функции происходит во время итерации, то такая ситуация часто приводит к случайной ошибке. Предупреждения компилятора Blueprint немедленно предупредят об этих проблемах.
Ни одна функция не должна иметь более 50 узлов. Любая такая большая функция должна быть разбита на более мелкие функции для удобочитаемости и простоты обслуживания.
Следующие узлы не учитываются, поскольку считается, что они не увеличивают сложность функции:
Комментарии
Route
Cast
Получение переменной
Breaking структуры
Входной узел функции
Self
Это правило в большей степени относится к общедоступным или Blueprints для marketplace, чтобы другим было легче ориентироваться и использовать ваш API.
Проще говоря, любая функция, которая имеет спецификатор доступа public
, должна иметь своё описание.
BlueprintCallable
функции подключаемых модулей должны быть классифицированы по имени подключаемого модуляЕсли ваш проект включает модуль, который определяет static
BlueprintCallable
функции, их категория должна быть установлена на имя подключаемого модуля.
Например, Zed Camera Interface
или Zed Camera Interface | Image Capturing
.
В этом разделе рассматриваются вещи, которые применимы ко всем Blueprint Graphs.
Провода (линии) должны иметь чёткие начало и конец. Многие из следующих разделов посвящены уменьшению количества спагетти-кода.
Всегда выравнивайте провода, а не узлы. Вы не всегда можете контролировать размер и расположение выводов на узле, но вы всегда можете контролировать расположение узла и, таким образом, управлять проводами. Прямые провода обеспечивают чёткий линейный поток. Волнистые провода, наоборот, затрудняют понимание. Вы можете выпрямить провода с помощью команды «Straighten Connections» с выбранными узлами. Горячая клавиша: Q.
Хороший пример: вершины узлов расположены в шахматном порядке, чтобы сохранить идеально прямую белую линию выполнения.
Плохой пример: верхние части узлов выровнены, создавая волнистую белую линию выполнения.
Допустимый пример: в рамках некоторых узлов сложно выровнять линию выполнения независимо от того, как вы используете инструменты выравнивания. В этой ситуации постарайтесь свести к минимуму раскачивание, приблизив узел.
Если вам когда-нибудь придется выбирать между выпрямлением линии выполнения (белой линии) и выпрямлением каких-либо линий данных, то всегда выпрямляйте именно линию выполнения.
Наборы узлов должны быть заключены в комментарии, описывающие их поведение на более высоком уровне. В то время как каждая функция должна быть хорошо названа, каждый отдельный набор её узлов должен иметь своё назначение, отражённое в комментариях. Если функция не имеет большого количества узлов и ясно, что узлы служат прямой цели функции, то их не нужно комментировать, так как должно быть достаточно имени и описания функции.
Если функция или событие предполагает, что приведение всегда завершается успешно, оно должно соответствующим образом сообщать об ошибке в логике, если приведение не удаётся. Это позволяет другим узнать, почему то, что «должно работать», не работает. Функция также должна корректно работать и при неудачном приведении, если известно, что оно может быть таковым.
Это не означает, что ошибка приведения в каждом узле Cast должна обрабатываться. Во многих случаях, особенно в случаях, связанных с такими вещами, как коллизия, ожидается, что поток выполнения просто завершится при неудачном приведении типа.
Все узлы во всех Blueprints должны иметь назначение. Вы не должны оставлять болтающиеся узлы, которые не имеют цели или не выполняются.
В этом разделе основное внимание будет уделено ассетам Static Mesh и их внутреннему устройству.
Если Linter сообщает о плохих UV-развертках, и вы не можете это отследить, то откройте полученный .log
файл в папке вашего проекта Saved/Logs
, чтобы узнать точную причину. Я надеюсь включить эти сообщения в отчёт Linter в будущем.
Довольно просто. Все Static Meshes, независимо от того, как они будут использоваться, должны иметь UV.
Довольно просто. Все Static Meshes, независимо от того, как они будут использоваться, должны иметь корректные неперекрывающиеся UV-развёртки.
Это субъективно для каждого проекта, но, как правило, любой Static Mesh, который можно увидеть на разных расстояниях, должен иметь правильные уровни детализации.
Это субъективно для каждого ассета, однако любые модульные ассеты без сокетов должны четко соединяться друг с другом в соответствии с настройками сетки проекта.
От проекта зависит следует ли привязываться к сетке размером 2 юнита или к сетке с размером 10 юнитов. Однако, если вы создаете модульные Static Mesh без сокетов для marketplace, то требование Epic состоит в том, чтобы они чётко привязывались, когда сетка установлена на 10 юнитов или больше.
Независимо от того, будет ли у ассета включена коллизия на уровне, все Static Meshes должны иметь правильное определение коллизий. Это помогает движку с такими вещами, как расчёт границ, occlusion и освещение. Коллизия также должна иметь правильную форму, подходящую для данного Static Mesh.
Это субъективно для каждого проекта, однако, все Static Meshes должны быть правильно масштабированы для проекта. Дизайнеры уровней или Blueprint-разрботчики не должны подгонять масштаб. Масштабирование они должны рассматривать только как переопределение масштаба, а не как его коррекцию.
В этом разделе основное внимание будет уделено ассетам Niagara и их внутреннему устройству.
В этом разделе основное внимание будет уделено ассетам уровней и их внутренностям.
Все уровни должны загружаться без ошибок или предупреждений. Если уровень загружается с какими-либо ошибками или предупреждениями, их следует немедленно исправить, чтобы предотвратить потенциальные проблемы.
Вы можете запустить проверку карты на открытом уровне в редакторе с помощью консольной команды: «map check».
Обратите внимание: Linter еще более строг к проверкам карт, чем редактор в настоящее время, и будет ловить даже ошибки загрузки, которые редактор исправляет самостоятельно.
Во время разработки это нормально, когда уровни иногда не имеют рассчитанного освещения. Однако, при выполнении тестовой/внутренней/shipping сборки или любой другой сборки, которая должна быть распространена, освещение всегда должно быть рассчитано.
На уровнях не должно быть пересекающихся текстур, видимых игроку.
Если проект будет продаваться на UE Marketplace, он должен соответствовать этим правилам.
Если ваш проект содержит ассеты, которые необходимо визуализировать или продемонстрировать, в вашем проекте должна быть карта с названием «Overview».
Например, InteractionComponent_Overview
.
Если ваш проект содержит ассеты, которые должны быть продемонстрированы или снабжены каким-либо учебным пособием, в вашем проекте должна быть карта с названием «Demo». Этот уровень также должен содержать документацию в той или иной форме, иллюстрирующую, как использовать ваш проект. Хорошие примеры того, как это сделать, см. в проекте Epic Content Examples.
Если ваш проект представляет собой некую игровую механику, а не арт-пак, то эта карта может быть такой же, что и карта «Overview».
Например, InteractionComponent_Overview_Demo
, ExplosionKit_Demo
.
В этом разделе основное внимание будет уделено текстурным ассетам и их внутреннему устройству.
Все текстуры, кроме текстур пользовательского интерфейса, должны иметь размеры, кратные степени двойки. Текстуры не обязательно должны быть квадратными.
Например, 128x512
, 1024x1024
, 2048x1024
, 1024x2048
, 1x512
.
Все текстуры должны иметь размер, соответствующий их стандартному варианту использования. Конкретная плотность варьируется от проекта к проекту, но все текстуры в этом проекте должны иметь одну и ту же плотность.
Например, если плотность текстуры проекта составляет 8 пикселей на 1 юнит, текстура, предназначенная для применения к кубу 100x100 единиц, должна иметь размер 1024x1024, поскольку это ближайшая степень числа 2, соответствующая плотности текстуры проекта.
Никакая текстура не должна иметь размер, превышающий 8192 пикселя, если только у вас нет явной причины для этого. Как правило, использование такой большой текстуры - это просто пустая трата ресурсов.
У каждой текстуры есть свойство Texture Group, используемое для LOD, и его следует правильно установить в зависимости от её использования. Например, все текстуры пользовательского интерфейса должны принадлежать группе UI.
Мы рекомендуем вам скопировать это руководство и изменить правила, чтобы они соответствовали стилю вашей команды. Ниже вы можете перечислить некоторые поправки к руководству по стилю.
Убрано упоминание в руководстве, что речь идёт о 4-й версии, поскольку оно актуально и для 5-й версии движка в том числе.
Слово «карта» обычно относится к тому, что обычный человек называет «уровнем», и может использоваться взаимозаменяемо. Смотрите историю этого термина .
Эти принципы были адаптированы из .
При работе в команде или при обсуждении в сообществе, таком как , гораздо проще помогать и просить о помощи, когда люди последовательны. Никому не нравится распутывать чьи-то спагетти в Blueprint или иметь дело с ассетами, названия которых они не могут понять.
Prefix
и Suffix
должны определяться типом ассета с помощью таблиц .
При именовании ассета используйте эти таблицы для определения префикса и суффикса, которые следует использовать с .
См.
См.
Если вы используете , описанное выше, использование папок по типу Meshes
, Textures
и Materials
является избыточной практикой, поскольку типы ресурсов уже отсортированы по префиксу, а также могут быть отфильтрованы в Content Browser.
См. .
Повторно применяя , никогда не используйте пробелы. Пробелы могут привести к сбою различных инструментов и процессов. В идеале, корень вашего проекта также не должен содержать пробелов. Например, он может находиться в D:\Project
вместо C:\Users\My Name\My Documents\Unreal Projects
.
Если одного из ваших игровых персонажей зовут Zoë, имя его папки должно быть Zoe
. Символы Unicode могут быть хуже, чем для различных инструментов, и некоторые части UE также не поддерживают символы Unicode в путях.
В связи с этим, если в вашем проекте есть , а имя пользователя вашего компьютера содержит символ Unicode (например, ваше имя Zoë), любой проект, расположенный в вашей My Documents
папке, будет страдать от этой проблемы. Часто простое перемещение вашего проекта в директорию наподобие D:\Project
, решает эти загадочные проблемы.
Папка Developers
не предназначена для ассетов, от которых зависит ваш проект, и, следовательно, не зависит от проекта. См. для получения подробной информации об этом.
У каждого ассета должна быть цель, иначе ему не место в проекте. Если ассет является экспериментальным, и он не должен использоваться в проекте, его следует поместить в папку .
Расширение версии . Если член команды решит добавить образец, файлы шаблонов или ассеты, купленные на Marketplace, то гарантировано, что эти новые ресурсы не будут влиять на существующие ассеты в вашем проекте.
Вы не можете быть уверены, что содержимое торговой площадки полностью соответствует . Существует много ресурсов, большая часть контента которых находится в папке верхнего уровня, но также, возможно, имеется измененный образец контента Epic, а также файлы уровней, "загрязняющие" папку Content
.
При соблюдении наихудший конфликт на Marketplace может возникнуть, если два пакета на рынке имеют одинаковый образец контента Epic. Если все ваши ассеты находятся в папке конкретного проекта, включая образец контента, который вы, возможно, переместили в свою папку, ваш проект никогда не сломается.
Это можно рассматривать как псевдо-исключение для .
Это не относится к таким ассетам, как текстуры и материалы. Обычно папка Rocks
имеет большое количество текстур, если в ней много камней, однако эти текстуры, как правило, связаны только с несколькими конкретными камнями, поэтому они просто должны иметь соответствующие имена. Даже если эти текстуры являются частью .
В этом разделе основное внимание будет уделено классам Blueprint и их внутреннему устройству. Когда это возможно, правила стиля соответствуют .
Все имена небулевских переменных должны быть в форме .
IsEnemy
-
При написании функции, которая не изменяет состояние и не модифицирует какой-либо объект, и предназначена исключительно для получения информации, состояния или вычисления значения да/нет, должна задавать вопрос. Здесь также применимо .
HasWeapon
-
WasCharging
- Используйте «было» при обращении к «предыдущему кадру» или «предыдущему состоянию».
CanReload
-
Любая функция, которая обрабатывает событие или отправляет событие, должна начинаться с On
и дальше следовать . Однако, глагол может перейти в конец, если прошедшее время читается лучше.
Как упоминалось в , пробелы и все пробельные символы запрещены в идентификаторах. Это особенно важно для систем Niagara, поскольку это значительно усложняет, если не делает невозможной работу с HLSL и другими средствами сценариев в Niagara при попытке сослаться на идентификатор.
См. относительно «уровней» и «карт».
Данная обзорная карта, если она демонстрирует ассеты, должна быть настроена в соответствии с .
Префикс для StaticMesh - SM_, в оригинале S_ (раздел ). Поскольку де-факто SM_ - это самый популярный префикс для StaticMesh, тогда как S_ очень часто встречается у Sound Wave.
Добавлен префикс - NS_ для Niagara System (раздел ).
Префикс для структур - F, в оригинале F или S (раздел ). Я посчитал, что здесь не должно быть выбора, и поскольку в C++ используется префикс именно F, то и в Blueprint для консистентности я выбрал именно F.
Префиксы для Sound Cue и Sound Wave - S_, в оригинале A_ (раздел ). S_ - более логично, а префикс A_ был выбран в оригинале явно по причине того, что S_ они отдали Static Mesh.
Добавлены модификаторы имён ассетов для Niagara (раздел ).
Раздел - изменён, так как в оригинале написано, что, если переменных мало, то категория для Editable-переменных не нужна. Я же считаю, что всегда все Editable-переменные должны находиться в отдельной категории.
Разделы и - сильно изменены, так как не считаю, что применение private стоит использовать лишь в некоторых случаях. Использование public нарушает инкапсуляцию класса и может доставить огромное количество проблем в будущем, поэтому по умолчанию стоит использовать именно private, а не public.