- ·
- ·
- ·
- ·
- ·

When building a big‑budget game, dozens of artists, designers, and programmers touch the same files.
If the project’s folder structure is a mess, you spend more time hunting for a texture, dealing with merge conflicts, and rebuilding the whole project just because a new asset was added in the wrong place.
A clean folder structure:
DerivedDataCache folders in the repo.Below is a layout that works for most AAA studios and is based on the real‑world examples from the SLAY project and Lyra Starter.
MyGame/ ← Unreal project root (.uproject is here)
├─ Engine/ ← Engine files – never touch
├─ Templates/
├─ Binaries/ ← Compiled binaries (not versioned)
├─ Intermediate/
├─ DerivedDataCache/
├─ Saved/
├─ Config/
├─ Plugins/
├─ Source/ ← C++ code, one folder per module
│ ├─ GameCore/
│ │ ├─ Public/
│ │ └─ Private/
│ ├─ Gameplay/
│ │ ├─ Public/
│ │ └─ Private/
│ └─ UI/
│ ├─ Public/
│ └─ Private/
├─ Content/ ← Runtime assets
│ ├─ Characters/
│ ├─ Audio/
│ ├─ UI/
│ ├─ Cinematics/
│ ├─ Maps/
│ ├─ Materials/
│ ├─ Effects/
│ ├─ Gameplay/
│ ├─ Data/
│ └─ Localization/
└─ .gitignore/Quick tip: Never commit anything from Binaries, Intermediate, or DerivedDataCache they’re regenerated on every build.
Unreal’s own style guide recommends a simple naming pattern:
<Prefix>_<BaseName>_<Variant>_<Suffix>
Rule: Always start with a prefix, no spaces, and keep the rest in camelCase or PascalCase.
If you’re tired of creating a new project from scratch every time, run this script once and you’ll get a perfectly‑structured skeleton in seconds.
python ue_folder_organizer.py /path/to/MyAwesomeGame --spec spec.jsonspec.json – the recipe
{
"top_level": ["Content", "Source", "Plugins", "Config"],
"content_domains": [
"Characters",
"Audio",
"UI",
"Cinematics",
"Maps",
"Materials",
"Effects",
"Gameplay",
"Data",
"Localization"
],
"source_modules": [
"GameCore",
"Gameplay",
"UI",
"Physics",
"EditorTools"
],
"create_placeholders": true,
"ignore": ["CustomLogs/"]
}
Python Script
#!/usr/bin/env python3
"""
Unreal Engine Folder Organizer
"""
import json, os, sys
from pathlib import Path
import argparse
# ---- Naming rules (you can add more) ----
VALID_PREFIXES = {"SM_","BP_","AK_","UI_","FG_"}
def validate_name(name: str) -> bool:
if " " in name: return False
return any(name.startswith(p) for p in VALID_PREFIXES)
def make_dir(path: Path):
path.mkdir(parents=True, exist_ok=True)
print(f"✔️ {path}")
def placeholder(module_path: Path):
public = module_path / "Public"
private = module_path / "Private"
public.mkdir(parents=True, exist_ok=True)
private.mkdir(parents=True, exist_ok=True)
name = module_path.name
h = public / f"{name}.h"
cpp = private / f"{name}.cpp"
h.write_text(f"// Header for {name}\n#pragma once\n\nclass {name} {{}};\n")
cpp.write_text(f'#include "{name}.h"\n')
print(f" ➜ {h.name} & {cpp.name} created")
def gitignore(project_root: Path, extra=None):
patterns = [
"Binaries/", "Intermediate/", "DerivedDataCache/", "Saved/",
"*.tbc", "*.tbn", "*.tmp", "*.log",
"*.sdf", "*.cache", "*.bak", "*.db", "*.zip",
"*.suo", "*.sln", "*.vcxproj", ".vs/", "*.obj", "*.pdb",
"UE4Editor-*.exe"
]
if extra: patterns += extra
(project_root / ".gitignore").write_text("\n".join(patterns))
print(f" ➜ .gitignore generated")
def main():
parser = argparse.ArgumentParser()
parser.add_argument("root", help="Project root path (.uproject here)")
parser.add_argument("--spec", required=True, help="JSON spec file")
args = parser.parse_args()
root = Path(args.root).resolve()
if not any(root.glob("*.uproject")):
print("❌ No .uproject found")
sys.exit(1)
with open(args.spec) as f:
spec = json.load(f)
# 1. Top‑level folders
for tl in spec.get("top_level", []): make_dir(root / tl)
# 2. Content domains
content = root / "Content"
for dom in spec.get("content_domains", []):
if not validate_name(dom):
print(f"⚠️ Invalid name (no prefix): {dom}")
make_dir(content / dom)
# 3. Source modules
src = root / "Source"
for mod in spec.get("source_modules", []):
if not validate_name(mod):
print(f"⚠️ Invalid name (no prefix): {mod}")
mod_path = src / mod
make_dir(mod_path)
if spec.get("create_placeholders"):
placeholder(mod_path)
# 4. .gitignore
gitignore(root, spec.get("ignore"))
print("\n✅ Project skeleton ready!")
if __name__ == "__main__":
main()Give it a try on your next Unreal project and watch the chaos disappear! 🚀
Meshes, textures, sounds, widgets |
Source/ | C++ code split into modules | GameCore, Gameplay, UI |
Plugins/ | Re‑usable extensions | PhysicsSim, GameplayFramework |
Config/ | Engine settings you control | DefaultEngine.ini, GameUserSettings.ini |
Binaries/ … Saved/ | Generated data that changes every build | DLLs, shader cache, autosaves |
SM_ for static mesh, BP_ for blueprint, UI_ for widgets)SM_WoodTable |
BaseName | Logical group | EnemyOrc |
Variant | Different versions | LongHair, ShortHair |
Suffix | Sub‑type (e.g. _S for specular) | SM_WoodTable_S |