Documentation
1.使用代码来配置材质.md
2025/12/16预计阅读 10 分钟
定义属性
// 预览材质 - 可放置(绿色)
UPROPERTY(EditDefaultsOnly, Category = "Trap Placement")
UMaterialInterface* ValidPlacementMaterial;
// 预览材质 - 不可放置(红色)
UPROPERTY(EditDefaultsOnly, Category = "Trap Placement")
UMaterialInterface* InvalidPlacementMaterial;
构造函数中初始化
AMyBasePlayerController::AMyBasePlayerController()
{
static ConstructorHelpers::FObjectFinder<UMaterialInstance> ValidMaterialFinder(
TEXT(
"/Script/Engine.MaterialInstanceConstant'/Game/Material/Trap/M_TrapPreview_Valid_Inst.M_TrapPreview_Valid_Inst'")
);
if (ValidMaterialFinder.Succeeded())
{
ValidPlacementMaterial = ValidMaterialFinder.Object;
UE_LOG(LogTemp, Log, TEXT("✅ 成功加载有效放置材质"));
}
else
{
UE_LOG(LogTemp, Warning, TEXT("❌ 加载有效放置材质失败"));
}
static ConstructorHelpers::FObjectFinder<UMaterialInstance> InvalidMaterialFinder(
TEXT(
"/Script/Engine.MaterialInstanceConstant'/Game/Material/Trap/M_TrapPreview_Invalid_Inst.M_TrapPreview_Invalid_Inst'")
);
if (InvalidMaterialFinder.Succeeded())
{
InvalidPlacementMaterial = InvalidMaterialFinder.Object;
UE_LOG(LogTemp, Log, TEXT("✅ 成功加载无效放置材质"));
}
else
{
UE_LOG(LogTemp, Warning, TEXT("❌ 加载无效放置材质失败"));
}
}
下面是来自 AI的解释
没问题!这段代码是 Unreal Engine 中非常标准的**“生成预览物体(Ghost Actor)”**的流程。常用于建造系统,让玩家在点击放置之前,看到一个半透明的虚影。
下面我为你逐行添加了详细的注释,并在代码下方附带了核心知识点的深度解析。
详细注释版代码
/**
* 函数功能:创建用于预览的陷阱Actor(虚影)
* 参数 TrapClass:想要预览的陷阱的类类型(蓝图类或C++类)
*/
void AMyBasePlayerController::CreatePreviewActor(TSubclassOf<AMyTrapBase> TrapClass)
{
// ====================================================
// 1. 清理阶段:防止同时存在多个预览物体
// ====================================================
// 检查当前是否已经存在一个预览Actor
if (PreviewTrapActor)
{
// 如果存在,必须先销毁它,否则切换道具时旧的虚影会残留在原地
PreviewTrapActor->Destroy();
// 将指针置空,防止悬空指针(Dangling Pointer)导致崩溃
PreviewTrapActor = nullptr;
}
// ====================================================
// 2. 校验阶段
// ====================================================
// 如果传入的类是空的(例如玩家取消了手持物品),直接返回,不执行后续逻辑
if (!TrapClass)
{
return;
}
// ====================================================
// 3. 生成配置阶段
// ====================================================
// 定义生成参数结构体,用于控制 SpawnActor 的行为
FActorSpawnParameters SpawnParams;
// 核心设置:始终生成,忽略碰撞冲突。
// 默认情况下,如果生成位置有东西挡住,UE可能会禁止生成。
// 但作为预览虚影,我们需要它即使在墙里也能显示出来,所以设为 AlwaysSpawn。
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
// 设置为 true 表示生成过程不应该失败(在某些边缘情况下保证稳定性)
SpawnParams.bNoFail = true;
// ====================================================
// 4. 实例化阶段
// ====================================================
// 在世界中生成 Actor。
// FVector::ZeroVector 和 FRotator::ZeroRotator 是临时位置/旋转。
// 因为在 Tick 函数中,我们会根据射线检测每帧更新它的位置,所以这里初始值不重要。
PreviewTrapActor = GetWorld()->SpawnActor<AMyTrapBase>(
TrapClass, // 要生成的类
FVector::ZeroVector, // 初始位置 (0,0,0)
FRotator::ZeroRotator, // 初始旋转 (0,0,0)
SpawnParams // 上面配置的参数
);
// ====================================================
// 5. 初始化设置阶段(关键:把实体变成“虚影”)
// ====================================================
if (PreviewTrapActor)
{
// --- 逻辑层设置 ---
// 禁用 Actor 层级的碰撞。
// 预览物体绝不能阻挡玩家移动,也不能触发子弹打击或物理模拟。
PreviewTrapActor->SetActorEnableCollision(false);
// 禁用 Tick(每帧运行)。
// 真正的陷阱可能有复杂的逻辑(如索敌),预览时不需要运行这些逻辑,节省性能。
PreviewTrapActor->SetActorTickEnabled(false);
// --- 视觉层设置 ---
// 获取该 Actor 身上所有的静态网格体组件(即物体的模型部分)
TArray<UStaticMeshComponent*> MeshComponents;
PreviewTrapActor->GetComponents<UStaticMeshComponent>(MeshComponents);
// 遍历每一个网格体组件进行设置
for (UStaticMeshComponent* MeshComp : MeshComponents)
{
if (MeshComp)
{
// 彻底关闭组件的物理碰撞(双重保险,防止物理引擎计算它)
MeshComp->SetCollisionEnabled(ECollisionEnabled::NoCollision);
// 关闭阴影投射。
// 预览虚影通常是发光的或者半透明的,投射阴影会显得很怪且浪费性能。
MeshComp->SetCastShadow(false);
// --- 材质透明度处理 ---
// 遍历模型上的每一个材质槽(Material Slot)
for (int32 i = 0; i < MeshComp->GetNumMaterials(); ++i)
{
// 创建“动态材质实例” (MID - Material Instance Dynamic)。
// 只有创建了 MID,我们才能在游戏运行时修改材质参数(如颜色、透明度)。
if (UMaterialInstanceDynamic* DynMat = MeshComp->CreateDynamicMaterialInstance(i))
{
// 修改名为 "Opacity" 的标量参数,设为 0.5(半透明)。
// 【注意】:这要求你的材质(Material)中必须有一个名为 "Opacity" 的参数,
// 且材质的混合模式(Blend Mode)必须设为 Translucent(半透明),否则这行代码无效。
DynMat->SetScalarParameterValue(FName("Opacity"), 0.5f);
}
}
}
}
// --- 状态管理 ---
// 初始设为隐藏。
// 这样做是为了防止 Actor 刚生成在 (0,0,0) 时闪烁一下。
// 等到下一次 Tick 检测到射线击中点并设置好正确位置后,再将其设为显示。
PreviewTrapActor->SetActorHiddenInGame(true);
// 记录当前正在预览的类型,用于后续逻辑判断(比如判断玩家是否换了武器)
CurrentPreviewTrapClass = TrapClass;
// 打印日志,方便调试(仅在 Output Log 中显示)
UE_LOG(LogTemp, Log, TEXT("创建陷阱预览Actor: %s"), *TrapClass->GetName());
}
}
💡 核心知识点总结(学习重点)
1. ESpawnActorCollisionHandlingMethod::AlwaysSpawn
这是建造系统中最关键的设置之一。
- 如果不加这行:如果你对着墙角或者非常狭窄的地方尝试生成预览,UE 会检测到新生成的 Actor 会和墙壁重叠,于是拒绝生成,导致
PreviewTrapActor为空。 - 加上这行:强制生成,哪怕它穿模了。对于“虚影”来说,穿模是完全可以接受的。
2. CreateDynamicMaterialInstance (MID)
这是让物体变色、变透明的核心技术。
- 静态材质:游戏运行时不能改。
- 动态材质实例 (MID):是静态材质的克隆体,可以在运行时随意修改参数。
- 重要前提:你的材质蓝图(Material Graph)里必须真的连入了一个叫
"Opacity"的 Scalar Parameter 节点,并且材质本身支持半透明,这行代码SetScalarParameterValue才会生效。
3. 性能优化:SetActorTickEnabled(false)
这是一个良好的编程习惯。
- 假设你的
AMyTrapBase里有一个Tick函数用来检测敌人。 - 当你只是想预览放置位置时,不需要这些检测逻辑运行。
- 禁用 Tick 可以避免不必要的计算开销,特别是在低配机器上。
4. PreviewTrapActor->Destroy() 的时机
在函数开头销毁旧 Actor 是为了实现**“单例预览”**。
- 如果不销毁:玩家每按一次切换武器,场景里就会多一个没人管的虚影,最后内存溢出。
- 正确的逻辑是:旧的不去,新的不来。
5. SetActorHiddenInGame(true) 的作用
这是一种视觉上的“防抖动”处理。
- Actor 生成是在
(0,0,0)(通常是地图中心地下)。 - 如果不先隐藏,玩家可能会在瞬间看到地图中心闪过一个物体,然后它才跳到玩家准星位置。
- 先隐藏,等位置计算好了(在另一个 Update 函数里)再显示,体验最丝滑。