一、大概流程如下
点击界面按钮生成Actor->移动鼠标Actor的位置随着鼠标移动移动->点击鼠标左键确定Actor的位置
使用了盒体检测GetWorld()->SweepSingleByChannel()函数检测是否发生碰撞通过 FCollisionQueryParams CollisionParams;CollisionParams.AddIgnoredActor(AActor*);将盒体附加在某个Actor忽略
SweepSingleByChannel参数是 当起始位置和结束位置相同时检测当前物体和其他物体是否重叠,参数不同检测从位置开始到位置结束的这段距离根据碰撞形状检测物体是否会发生碰撞
FCollisionQueryParams CollisionParams;
CollisionParams.AddIgnoredActor(PreviewBuilding); // 忽略附加到的ActorFHitResult 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的位置的功能
#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:// 生成ActorUFUNCTION(BlueprintCallable, Category = "Build System")bool ShowPreview(TSubclassOf<AActor> BuildingTemplate, const FVector& Location, const FRotator& Rotation);//销毁ActorUFUNCTION(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;
};
#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界面绑定按钮回调函数
#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;
};
#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中实现界面按钮点击后的执行逻辑,创建鼠标移动的处理的事件
public:void MouseLeftClicked();void OpenBuildMode();void MouseXY(float XY);UPROPERTY(EditAnywhere)TSubclassOf<class ATreeActor> TreeClass;FVector SpawnLocation;
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);}}