01 项目基础

返回:[[00-课程索引]] 下一章:[[02-英雄角色与基础框架]]

[!info] 资料口径
代码内容按课程作者公开的 WarriorRPG 提交历史与最终源码交叉核对。P1、P4 是概念课,没有独立代码提交;示例使用项目后续真实代码说明概念,不代表这些代码在该视频中已经实现。

本章架构目标

  • 建立 UE C++ 项目的 Target → Module → Source 结构。
  • 使用独立功能测试地图,降低后续输入、动画、GAS 和 AI 的验证成本。
  • 理解硬引用、软引用和同步/异步加载的取舍。

P1 Introduction(05:58)

类型:课程介绍;没有独立代码提交。

技术路线

1
2
3
4
5
Enhanced Input → Gameplay Tag → ASC → Gameplay Ability
Gameplay Ability → Montage / Gameplay Event → Combat Component
Combat Component → GameplayEffectSpec → AttributeSet
AttributeSet → UI Component → UMG
AI Perception → Blackboard → Behavior Tree / EQS → Enemy Ability
  • C++ 负责稳定的类型、组件、算法和生命周期。
  • Blueprint/Data Asset 负责能力编排、动画资产、数值和表现配置。
  • GAS 是战斗系统中枢:能力、效果、属性、状态 Tag 和 Gameplay Cue 都围绕 ASC 协作。

学习检查

  • 能说明 CharacterControllerAnimInstanceASC 的边界。
  • 能区分数值逻辑、状态逻辑和表现逻辑。

P2 Create a New C++ Project(03:31)

提交依据f461369 Initial commitfde5c56 Inital Commite Done 只补充 .vsconfig

工程关键文件

文件 职责
Warrior.uproject 项目版本、运行时模块和插件声明
Source/Warrior.Target.cs Game Target
Source/WarriorEditor.Target.cs Editor Target
Source/Warrior/Warrior.Build.cs Warrior 模块编译依赖
Source/Warrior/Warrior.cpp 主模块注册入口
Source/Warrior/Warrior.h 模块公共基础头文件

主模块入口

1
IMPLEMENT_PRIMARY_GAME_MODULE(FDefaultGameModuleImpl, Warrior, "Warrior");
  • FDefaultGameModuleImpl:使用默认游戏模块实现。
  • Warrior:C++ 模块标识,决定 WARRIOR_API 导出宏名称。
  • "Warrior":模块字符串名称,必须与 .uprojectBuild.cs 对应。

Target 与 Module

1
2
3
4
5
6
7
// Warrior.Target.cs
Type = TargetType.Game;
ExtraModuleNames.Add("Warrior");

// WarriorEditor.Target.cs
Type = TargetType.Editor;
ExtraModuleNames.Add("Warrior");
1
2
3
4
5
6
Target
├─ WarriorTarget:独立游戏
└─ WarriorEditorTarget:编辑器环境
└─ Warrior Module
├─ Public:可被其他模块包含的接口
└─ Private:模块内部实现

Build.cs 原则

课程最终项目会逐步加入:

  • GameplayTags:原生 Gameplay Tag。
  • EnhancedInput:Input Action/Mapping Context。
  • GameplayTasks、Gameplay Abilities 插件:GAS。
  • AnimGraphRuntime:动画运行时工具。
  • MotionWarping:攻击 Root Motion 对齐。
  • Niagara:投射物和溶解特效。
  • NavigationSystem:AI 导航。

[!warning] 不要把最终依赖列表误认为 P2 一次性加入。每引入一个模块公开类型,再补充对应依赖,能更清楚地理解模块边界。

Git 与生成目录

1
2
提交:Source、Config、Content、.uproject
忽略:Binaries、Intermediate、Saved、DerivedDataCache、.vs、*.sln

Content/** 是二进制资产,课程仓库使用 Git LFS 管理。项目文件和解决方案可重新生成,不应把编译产物当源码提交。

验收

  • Development Editor 能编译并启动。
  • 知道 XXX_API 来自模块名,而不是项目显示名称。
  • 知道 Target.csBuild.cs 解决的是不同问题。

P3 Set Up Test Map(03:24)

提交依据99adefa TestMapCreated

资产和配置

  • 新建:Content/Maps/FeatureDevMap.umap
  • 同步产生 Content/__ExternalActors__/Maps/FeatureDevMap/...__ExternalObjects__
  • 修改:Config/DefaultEngine.iniConfig/DefaultInput.ini

地图引用路径:

1
/Game/Maps/FeatureDevMap.FeatureDevMap

为什么建立 FeatureDevMap

  • 将底层功能测试与正式关卡隔离。
  • 保持地面、光照、PlayerStart、NavMesh 等环境可重复。
  • 缩短启动和复现问题的时间。
  • 后续能逐项验证相机、动画、武器、敌人和 GAS,而不被大型场景干扰。

UE5 External Actors

开启 One File Per Actor/World Partition 后,一个地图修改可能同时变更:

1
2
3
FeatureDevMap.umap
__ExternalActors__/Maps/FeatureDevMap/**
__ExternalObjects__/Maps/FeatureDevMap/**

这些文件应由编辑器管理,不能手工移动散列目录。提交地图时要把相关 External Actor 一并提交。

地图配置的区别

  • GameDefaultMap:普通游戏启动时打开。
  • EditorStartupMap:编辑器启动后打开。
  • GlobalDefaultGameMode:地图未覆盖 GameMode 时使用。

[!note] 最终仓库的 EditorStartupMap 后来改为生存模式测试地图;P3 创建和使用的是 FeatureDevMap

验收

  • PIE 后角色从有效 PlayerStart 生成。
  • 地面碰撞、光照和输入环境稳定。
  • Git 变更包含地图对应的 External Actor 文件。

P4 Hard and Soft Reference(10:33)

类型:概念课;没有独立提交。以下示例来自本课程后续真实源码。

硬引用

1
2
3
4
5
UPROPERTY(EditDefaultsOnly)
UInputMappingContext* WeaponInputMappingContext;

UPROPERTY(EditDefaultsOnly)
TSubclassOf<UWarriorHeroGameplayAbility> AbilityToGrant;
  • 对象硬引用通常是 UObject*/TObjectPtr<T>
  • 类硬引用通常是 TSubclassOf<T>/UClass*
  • 优点:对象已加载时直接使用,代码简单。
  • 代价:会扩大资产依赖图;宿主加载可能把引用链一起带入内存。

软对象引用

课程后续角色启动数据:

1
2
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "CharacterData")
TSoftObjectPtr<UDataAsset_StartUpDataBase> CharacterStartUpData;

同步加载流程:

1
2
3
4
5
6
7
if (!CharacterStartUpData.IsNull())
{
if (UDataAsset_StartUpDataBase* LoadedData = CharacterStartUpData.LoadSynchronous())
{
LoadedData->GiveToAbilitySystemComponent(WarriorAbilitySystemComponent);
}
}
  • IsNull():检查是否配置软路径。
  • LoadSynchronous():阻塞加载并返回对象。
  • Get():只取得已加载对象,不保证主动加载成功。

软类引用与异步加载

课程后续生存模式使用:

课程最终源码将软类引用放在每波敌人配置中:

1
2
3
4
struct FWarriorEnemyWaveSpawnerInfo
{
TSoftClassPtr<AWarriorEnemyCharacter> SoftEnemyClassToSpawn;
};
1
2
3
4
5
6
7
8
9
10
11
12
UAssetManager::GetStreamableManager().RequestAsyncLoad(
SpawnerInfo.SoftEnemyClassToSpawn.ToSoftObjectPath(),
FStreamableDelegate::CreateLambda([SpawnerInfo, this]()
{
if (UClass* LoadedClass = SpawnerInfo.SoftEnemyClassToSpawn.Get())
{
PreLoadedEnemyClassMap.Emplace(
SpawnerInfo.SoftEnemyClassToSpawn,
LoadedClass);
}
})
);

典型流程:

1
软路径 → RequestAsyncLoad → 回调 → Get() → 缓存 UClass* → SpawnActor

选择表

场景 建议
常驻、体积小、创建时必需 硬引用
地图、大型角色、敌人类型、可选 UI/特效 软引用
当前帧必须得到且资源很小 LoadSynchronous()
大型资源或批量生成 RequestAsyncLoad()

[!warning] 软引用不是无成本方案。它引入加载状态、回调生命周期、失败处理和缓存管理;不要机械地把所有硬引用改成软引用。

验收

  • 能在 Reference Viewer 中识别硬依赖链。
  • 能解释 TSoftObjectPtrTSoftClassPtr 的区别。
  • 能解释 Get()LoadSynchronous()RequestAsyncLoad() 的行为差异。

本章复盘

  • 理解 Target → Module → Source
  • 能独立创建并配置功能测试地图。
  • 知道 External Actor 为什么必须一起提交。
  • 能根据资源大小和使用时机选择硬/软引用。

本文从 LearnByCompany 原始文档 自动同步。