一、大概流程如下
点击界面按钮生成Actor->移动鼠标Actor的位置随着鼠标移动移动->点击鼠标左键确定Actor的位置
使用了盒体检测GetWorld()->SweepSingleByChannel()函数检测是否发生碰撞通过 FCollisionQueryParams CollisionParams;CollisionParams.AddIgnoredActor(AActor*);将盒体附加在某个Actor忽略
SweepSingleByChannel参数是 当起始位置和结束位置相同时检测当前物体和其他物体是否重叠,参数不同检测从位置开始到位置结束的这段距离根据碰撞形状检测物体是否会发生碰撞
cpp
FCollisionQueryParams CollisionParams;
CollisionParams.AddIgnoredActor(PreviewBuilding); // 忽略附加到的Actor
FHitResult HitResult;
bool bHit = World->SweepSingleByChannel(
HitResult, // 碰撞结果
Location, // 起始位置
Location, // 结束位置
Rotation.Quaternion(), // 旋转
ECC_WorldDynamic, // 使用适合你项目的碰撞通道
FCollisionShape::MakeBox(Extents), // 碰撞的形状
CollisionParams // 碰撞参数
);
关于使用SetHiddenInGame();SetVisibility();的一点问题
当多次调用这两个函数,并且函数的参数没有改变,在另一个地方想改变参数比如SetHiddenInGame(false)变成SetHiddenInGame(true),SetHiddenInGame(true)函数会设置成功,但是游戏中不会产生对应的反应,也就是注意不要把etHiddenInGame();SetVisibility();放在循环中去执行多变
如何在点击按钮后知道生成Actor的位置
使用FHitResult HitResult;GetHitResultUnderCursor(ECC_Visibility, false, HitResult); 本质上GetHitResultUnderCursor也是一种射线检测的方法
获得鼠标的位置的方法(在PlayController中)
DeprojectMousePositionToWorld(); // 获得三维世界中鼠标的位置
GetMousePosition();// 获得屏幕上鼠标的位置
鼠标左键确定actor位置的方法
因为我在点击按钮后直接生成了Actor,在鼠标移动时使用SetActorLocation();函数改变了Actor的位置,所以在点击鼠标左键后使用 InputComponent->RemoveAxisBinding("BuildMouseXY");将鼠标移动的处理函数接触绑定即可
被生成的Actor有哪些组件,一个UStaticMeshComponent和一个UBoxComponent,手动调整UBoxCompnent的大小和包围的物体

改进完善可以创建碰撞和无碰撞的两种材质,通过SetMetrial进行设置
二、创建子系统
我想实现一个用于整个游戏专门处理建造逻辑的类,创建类UBuildSubsystem父类是UGameInstanceSubsystem,
UGameInstanceSubsystem的声明周期是游戏开始到游戏结束,在UE编辑器中对游戏进行调试时点击结束按钮UGameInstanceSubsystem不会被销毁
获得方法
1.使用PlayController中
PlayerController->GetGameInstance()->GetSubsystem<UBuildSubsystem>();
2.使用World指针
World->GetGameInstance()->GetSubsystem<UBuildSubsystem>();
3.使用Gengine
GEngine->GetCurrentPlayWorld()->GetGameInstance()->GetSubsystem<UBuildSubsystem>();
三、在BuildSubSystem中实现生成Actor,改变Actor的碰撞框颜色,进行碰撞查询,设置Actor的位置的功能
cpp
#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "BuildSubsystem.generated.h"
/**
*
*/
UCLASS()
class AFARMSIMULATION_API UBuildSubsystem : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
private:
// 执行碰撞检测
bool CheckBuildLocation(const FVector& Location, const FRotator& Rotation, const FVector& Extents) const;
UPROPERTY()
AActor* PreviewBuilding; // 生成的Actor
// 建造系统生成Actor函数
bool BuildSystemSpawnActor(TSubclassOf<AActor> BuildingTemplate, const FVector& Location, const FRotator& Rotation, FActorSpawnParameters SpawnParams);
// 设置碰撞框的颜色
void SetCollisionBoxColor();
public:
// 生成Actor
UFUNCTION(BlueprintCallable, Category = "Build System")
bool ShowPreview(TSubclassOf<AActor> BuildingTemplate, const FVector& Location, const FRotator& Rotation);
//销毁Actor
UFUNCTION(BlueprintCallable, Category = "Build System")
void DestoryCurrentActor();
// 改变Actor的位置
UFUNCTION(BlueprintCallable, Category = "Build System")
void ChangePreviewActorPosition(FVector position);
// 设置碰撞框在游戏中的可见性
void SetCollisionBoxVisibilityHide();
// 获得当前的位置是否合适
bool GetCurrentIsRight();
// 记录Actor的位置
UPROPERTY(VisibleAnywhere, Category = "Build System")
FVector SpawnLocation;
};
cpp
#include "BuildSubsystem.h"
#include "Components/BoxComponent.h"
#include "Kismet/GameplayStatics.h"
#include "Engine/World.h"
void UBuildSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
SpawnLocation = FVector::ZeroVector;
}
void UBuildSubsystem::Deinitialize()
{
Super::Deinitialize();
}
bool UBuildSubsystem::CheckBuildLocation(const FVector& Location, const FRotator& Rotation, const FVector& Extents) const
{
UWorld* World = GetWorld();
if (!World) return false;
//UE_LOG(LogTemp, Warning, TEXT("Location is : %s -- Extents is : %s"), *Location.ToString() , *Extents.ToString());
// 设置碰撞查询参数
FCollisionQueryParams CollisionParams;
CollisionParams.AddIgnoredActor(PreviewBuilding); // 忽略自身
// 执行盒体追踪检测
FHitResult HitResult;
bool bHit = World->SweepSingleByChannel(
HitResult,
Location,
Location,
Rotation.Quaternion(),
ECC_WorldDynamic, // 使用适合你项目的碰撞通道
FCollisionShape::MakeBox(Extents),
CollisionParams
);
//if (HitResult.GetActor())
//{
// UE_LOG(LogTemp, Warning, TEXT("%s hitname = %s") , *HitResult.BoneName.ToString() , *HitResult.GetActor()->GetName());
//}
//DrawDebugBox(GetWorld(), Location, Extents , FColor::Blue);
//if (bHit)
//{
// UE_LOG(LogTemp, Warning, TEXT("bHit is true"));
//}
//else
//{
// UE_LOG(LogTemp, Warning, TEXT("bHit is false"));
//}
//UBoxComponent* BoxComponent = PreviewBuilding->GetComponentByClass<UBoxComponent>();
//if (BoxComponent)
//{
// BoxComponent->OnComponentHit
//}
return bHit;
}
bool UBuildSubsystem::BuildSystemSpawnActor(TSubclassOf<AActor> BuildingTemplate, const FVector& Location, const FRotator& Rotation, FActorSpawnParameters SpawnParams)
{
UWorld* World = GetWorld();
if (!World) return false;
PreviewBuilding = World->SpawnActor<AActor>(
BuildingTemplate,
Location,
Rotation,
SpawnParams
);
if (PreviewBuilding)
{
TArray<UBoxComponent*> BoxComponents;
PreviewBuilding->GetComponents<UBoxComponent>(BoxComponents);
for (auto ite : BoxComponents)
{
ite->SetHiddenInGame(false); // 关键:在游戏中显示
ite->SetVisibility(true, true);
ite->SetLineThickness(2.0f); // 设置线框粗细
}
return true;
}
return false;
}
void UBuildSubsystem::SetCollisionBoxColor()
{
TArray<UBoxComponent*> BoxComponents;
PreviewBuilding->GetComponents<UBoxComponent>(BoxComponents);
for (int i = 0; i < BoxComponents.Num(); i++)
{
FVector BoxExtents = BoxComponents[i]->GetScaledBoxExtent();
FVector BoxLocation = BoxComponents[i]->GetComponentLocation();
FRotator BoxRotation = BoxComponents[i]->GetComponentRotation();
bool bFlag = CheckBuildLocation(BoxLocation, BoxRotation, BoxExtents);
// 检查位置是否可用
if (bFlag)
{
// 有碰撞 红色
BoxComponents[i]->ShapeColor = FColor::Red;
//UE_LOG(LogTemp, Warning, TEXT("有碰撞"));
}
else
{
// 无碰撞 绿色
BoxComponents[i]->ShapeColor = FColor::Green;
}
}
}
bool UBuildSubsystem::ShowPreview(TSubclassOf<AActor> BuildingTemplate, const FVector& Location, const FRotator& Rotation)
{
if (!BuildingTemplate) return false; //if (PreviewBuilding) PreviewBuilding->Destroy();
// 创建预览建筑
FActorSpawnParameters SpawnParams;
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
if (BuildSystemSpawnActor(BuildingTemplate, Location, Rotation, SpawnParams))
{
SpawnLocation = Location;
}
else
{
SpawnLocation = FVector::ZeroVector;
return false;
}
SetCollisionBoxColor();
return true;
}
void UBuildSubsystem::DestoryCurrentActor()
{
if (PreviewBuilding)
{
PreviewBuilding->Destroy();
PreviewBuilding = nullptr;
}
}
void UBuildSubsystem::ChangePreviewActorPosition(FVector position)
{
if (PreviewBuilding == nullptr) return;
PreviewBuilding->SetActorLocation(position);
SpawnLocation = position;
SetCollisionBoxColor();
}
void UBuildSubsystem::SetCollisionBoxVisibilityHide()
{
TArray<UBoxComponent*> BoxComponents;
PreviewBuilding->GetComponents<UBoxComponent>(BoxComponents);
for (int i = 0; i < BoxComponents.Num(); i++)
{
BoxComponents[i]->SetHiddenInGame(true);
BoxComponents[i]->SetVisibility(false, true);
BoxComponents[i]->SetLineThickness(0.0f); // 设置线框粗细
BoxComponents[i]->SetComponentTickEnabled(false);
UE_LOG(LogTemp, Warning, TEXT("Component Valid: %d"), IsValid(BoxComponents[i]));
UE_LOG(LogTemp, Warning, TEXT("HiddenInGame: %d"), BoxComponents[i]->bHiddenInGame);
}
PreviewBuilding->SetActorLocation(SpawnLocation);
}
bool UBuildSubsystem::GetCurrentIsRight()
{
TArray<UBoxComponent*> BoxComponents;
PreviewBuilding->GetComponents<UBoxComponent>(BoxComponents);
for (int i = 0; i < BoxComponents.Num(); i++)
{
if (BoxComponents[i]->ShapeColor == FColor::Red)
{
return false;
}
}
return true;
}
四、HUD界面绑定按钮回调函数
cpp
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "BuildWidget.generated.h"
class UButton;
/**
*
*/
UCLASS()
class AFARMSIMULATION_API UBuildWidget : public UUserWidget
{
GENERATED_BODY()
protected:
virtual bool Initialize() override;
private:
UFUNCTION()
void CreateHouseMesh();
public:
UPROPERTY(meta = (BindWidget))
UButton* ButtonHouse;
};
cpp
#include "BuildWidget.h"
#include "Components/Button.h"
#include "AFarmSimulation/Controller/AFarmPlayerController.h"
#include "Kismet/GameplayStatics.h"
#include "AFarmSimulation/SystemSystem/BuildSubsystem.h"
bool UBuildWidget::Initialize()
{
if (!Super::Initialize())
{
return false;
}
if (ButtonHouse)
{
ButtonHouse->OnClicked.AddDynamic(this , &UBuildWidget::CreateHouseMesh);
}
return true;
}
void UBuildWidget::CreateHouseMesh()
{
//UE_LOG(LogTemp, Warning, TEXT("CreateHouseMesh"));
UWorld* World = GetWorld();
if (World)
{
AAFarmPlayerController* PlayerController = Cast<AAFarmPlayerController>(UGameplayStatics::GetPlayerController(World, 0));
if (PlayerController)
{
PlayerController->OpenBuildMode();
}
}
}
五、在PlayController中实现界面按钮点击后的执行逻辑,创建鼠标移动的处理的事件
cpp
public:
void MouseLeftClicked();
void OpenBuildMode();
void MouseXY(float XY);
UPROPERTY(EditAnywhere)
TSubclassOf<class ATreeActor> TreeClass;
FVector SpawnLocation;
cpp
void AAFarmPlayerController::MouseLeftClicked()
{
UBuildSubsystem* Build = GetGameInstance()->GetSubsystem<UBuildSubsystem>();
if (Build->GetCurrentIsRight())
{
Build->SetCollisionBoxVisibilityHide();
InputComponent->RemoveActionBinding("MouseLeftClicked", IE_Pressed);
InputComponent->RemoveAxisBinding("BuildMouseXY");
}
}
void AAFarmPlayerController::OpenBuildMode()
{
InputComponent->BindAction("MouseLeftClicked", IE_Pressed, this, &AAFarmPlayerController::MouseLeftClicked);
InputComponent->BindAxis("BuildMouseXY", this, &AAFarmPlayerController::MouseXY);
FHitResult HitResult;
GetHitResultUnderCursor(ECC_Visibility, false, HitResult); // ECC_Visibility 表示检测可见物体
FRotator Ratotor(0, 0, 0);
UBuildSubsystem* Build = GetGameInstance()->GetSubsystem<UBuildSubsystem>();
if (Build)
{
//UE_LOG(LogTemp, Warning, TEXT("Build is success"));
if (Build->ShowPreview(TreeClass, HitResult.Location, Ratotor))
{
SpawnLocation = HitResult.Location;
}
}
}
void AAFarmPlayerController::MouseXY(float XY)
{
FHitResult HitResult;
GetHitResultUnderCursor(ECC_WorldStatic, false, HitResult); // ECC_Visibility 表示检测可见物体
UBuildSubsystem* Build = GetGameInstance()->GetSubsystem<UBuildSubsystem>();
FRotator Ratotor(0, 0, 0);
if (Build)
{
Build->ChangePreviewActorPosition(HitResult.Location);
}
}