2.GamePlay

入门案例

案例

创建一个FloatingActor类于/Source/UPPLearn/QuickStart/文件夹下,对应有.h和.cpp文件。
然后点击添加->添加内容包->内容->添加到项目
../attachment/image_2.png
../attachment/image_1.png

//FloatingActor.h



#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "FloatingActor.generated.h"

UCLASS()
class UPPLEARN_API AFloatingActor : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AFloatingActor();

	//选项1:将使用是静态网格体写死在代码中
	UPROPERTY();
	UStaticMeshComponent* VisualMesh;

	//选项2:用于自定义网格体的属性
	//UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mesh")
	//UStaticMesh* CustomMesh;

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	
	
};


//FloatingActor.cpp
#include "QuickStart/FloatingActor.h"


// Sets default values
AFloatingActor::AFloatingActor()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	//创建一个组件明明为Mesh,并将他挂在到根组件上
	VisualMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
	VisualMesh->SetupAttachment(RootComponent);

	//RootComponent = VisualMesh;
	 
	//指定一个路径将一个静态网格体读取为一个ConstructorHelpers::FObjectFinder<UStaticMesh>对象
	static ConstructorHelpers::FObjectFinder<UStaticMesh> CubeVisualAsset(TEXT("/Game/StarterContent/Shapes/Shape_Cube.Shape_Cube"));


	if (CubeVisualAsset.Succeeded())
	{
		//如果读取成功,则VisualMesh的组件会被设置为此FObjectFinder对象所找到的静态网格体(FObjectFinder相当于在UE内容浏览器中选中的网格体)
		VisualMesh->SetStaticMesh(CubeVisualAsset.Object);
		VisualMesh->SetRelativeLocation(FVector(0.0f, 0.0f, 0.0f));
	}
}

// Called when the game starts or when spawned
void AFloatingActor::BeginPlay()
{
	Super::BeginPlay();

	//如果需要使用自定义网格体,则可以在这里设置,
	//if (CustomMesh)
	//{
	//	VisualMesh->SetStaticMesh(CustomMesh);
	//}
	
}

// Called every frame
void AFloatingActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	FVector NewLocation = GetActorLocation();

	FRotator NewRotation = GetActorRotation();

	float RunningTime = GetGameTimeSinceCreation();

	float DeltaHeight = (FMath::Sin(RunningTime + DeltaTime) - FMath::Sin(RunningTime));
	NewLocation.Z += DeltaHeight * 20.0f;

	float DeltaAngle = DeltaTime * 20.0f;
	NewRotation.Yaw += DeltaAngle;

	SetActorLocationAndRotation(NewLocation, NewRotation);
}




将cpp文件拖入场景运行就会旋转,和mc的掉落物一样。

练习

%% 尝试实现了一个有默认mesh的actor,且根据用户选择而切换的网格体。 %%
//xlhFloatingActor.h


#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "xlhFloatingActor.generated.h"

UCLASS()
class UPPLEARN_API AxlhFloatingActor : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AxlhFloatingActor();

	UPROPERTY(BlueprintReadWrite,EditAnywhere)
	UStaticMesh* CustomMesh;

	UStaticMeshComponent* DefaultMesh;

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	
	
};


//xlhFloatActor.cpp



#include "xlhFloatingActor.h"


// Sets default values
AxlhFloatingActor::AxlhFloatingActor()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	DefaultMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
	DefaultMesh->SetupAttachment(RootComponent);

	static ConstructorHelpers::FObjectFinder<UStaticMesh> CubeAsset(TEXT("/Game/StarterContent/Shapes/Shape_Cube.Shape_Cube"));
	if (CubeAsset.Succeeded())
	{
		DefaultMesh->SetStaticMesh(CubeAsset.Object);
	}

}

// Called when the game starts or when spawned
void AxlhFloatingActor::BeginPlay()
{
	Super::BeginPlay();

	if (CustomMesh)
	{
		DefaultMesh->SetStaticMesh(CustomMesh);
		DefaultMesh->SetRelativeLocation(FVector(0.0f, 0.0f, 0.0f));
	}
}

// Called every frame
void AxlhFloatingActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	FVector NewLocation = GetActorLocation();
	FRotator NewRotation = GetActorRotation();
	float RunningTime = GetGameTimeSinceCreation();
	
	float DeltaHeight = (FMath::Sin(RunningTime + DeltaTime) - FMath::Sin(RunningTime));
	NewLocation.Z += DeltaHeight * 20.0f;
	
	float DeltaAngle = DeltaTime * 20.0f;
	NewRotation.Yaw += DeltaTime * 20.0f;


	SetActorLocationAndRotation(NewLocation, NewRotation);

}



理解

  1. 这里涉及两个类UStaticMesh,和UStaticMeshComponent,点进去后基本的注释说明就是这玩意静态多边形组成的几何形状。StaticMeshComponent用于创建UStaticMesh实例,说明StaticMeshComponet内部可能有可以创建UStaticMesh对象的方法。
  2. CreateDefaultSubobject点进去后发现是一个返回指定类型对象的一个方法。这里传入UStaticMeshComponent是为了初始化UStaticMeshComponet DefaultMesh.就是不知道为撒谎不能用new来初始化,要整这么复杂一出。指定mesh其实是这个对象的名字。
  3. SetupAttachment,完全看不懂啥意思,没有注释
  4. FObjectFinder是个结构体里的结构体,
    1. 里面的方法CheckIfIsInConstructor进去没看出个所以然,再深入FUObjectThreadContext就更看不懂了,只能估摸着,这个是用于确定mesh存在与否用的;PathName就是路径了
    2. StripObjectClass没看懂,看签名应该是定位名称之类的吧?
    3. FindOrLoadObject拿拆出来的路径名和标志获取对象吧,变量名叫Object,因为用了T作为泛型表示,所以此处Object变量存了一个UStaticMesh
    4. ValidateObject估计拿来核实读取的。
    5. 拿下面的那个Succed函数也是用于核实的吧
  5. SetStaticMesh进不去,重新去看UStaticMeshComponent的方法,确实存在SetStaticMesh这个方法,需要的参数刚好是一个指针对得上。
  6. 其他略,基本能看懂意思,嗯不想深究了,看得脑袋痒痒的,感觉要长脑子了,这不太舒服,总之ue确实复杂,先学会用法吧。后面慢慢深究原理,包括这里也只是尝试理解用以帮助学会用法而已,不指望能真的弄明白,也没打算分清一些细节,先自圆其说吧。

游戏摄像机

独立摄像机

案例

创建XGCameraDirector这个类(继承自Actor),放在Source/UppLearn/GameCamera/下

//XGCameraDirector.h


#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "XGCameraDirector.generated.h"

UCLASS()
class UPPLEARN_API AXGCameraDirector : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AXGCameraDirector();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

public:
	UPROPERTY(EditAnywhere, Category = "XG")
	AActor* CameraOne;

	UPROPERTY(EditAnywhere, Category = "XG")
	AActor* CameraTwo;

	float TimeToNextCameraChange = 0.f;
	
};


//XGCameraDirector.cpp



#include "XGCameraDirector.h"
#include "Kismet/GameplayStatics.h"

// Sets default values
AXGCameraDirector::AXGCameraDirector()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

}

// Called when the game starts or when spawned
void AXGCameraDirector::BeginPlay()
{
	Super::BeginPlay();
	
}

// Called every frame
void AXGCameraDirector::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	const float TimeBetweenCameraChanges = 2.0f;

	const float SmoothBlendTime = 0.75f;

	TimeToNextCameraChange -= DeltaTime;

	if (TimeToNextCameraChange <= 0.0f)
	{
		TimeToNextCameraChange += TimeBetweenCameraChanges;

		//查找处理本地玩家控制的actor,在有玩家加入或离开前,此变量稳定不变
		APlayerController* OurPlayerController = UGameplayStatics::GetPlayerController(this, 0);

		if (OurPlayerController)
		{
			if ((OurPlayerController->GetViewTarget() != CameraOne) && (CameraOne != nullptr))
			{
				//立即切换到camera1
				OurPlayerController->SetViewTarget(CameraOne);
			}
			else if((OurPlayerController->GetViewTarget() != CameraTwo) && (CameraTwo != nullptr))
			{
				//立即切换到camera2
				OurPlayerController->SetViewTargetWithBlend(CameraTwo,SmoothBlendTime);
			}

		}


	}

}


../attachment/Pasted image 20250810193448.png
选中一个已经存在的camera对象。和一个任意物体(cube)
../attachment/Pasted image 20250810193512.png
之后运行就会两秒自动切视角。

练习

官网的练习就挺有挑战的

//xlhCameraDirectorModify.h
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "xlhCameraDirectorModify.generated.h"


USTRUCT()
struct FxlhToggleCameraInfo
{
	GENERATED_BODY()

public:
	UPROPERTY(EditAnywhere,Category="XG")
	AActor* CameraOne = nullptr;

	UPROPERTY(EditAnywhere, Category = "XG")
	float CameraBlendTime = 0.2f;

};




UCLASS()
class UPPLEARN_API ACameraDirectorModify : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	ACameraDirectorModify();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

private:
	UPROPERTY(VisibleAnywhere, Category = "XG")
	TArray<FxlhToggleCameraInfo> CameraInfoList;

	float TimeToNextCameraChange = 0.0f;

	int32 CameraIndex = -1;

};

//xlhCameraDirectorModify.cpp
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "xlhCameraDirectorModify.generated.h"


USTRUCT()
struct FxlhToggleCameraInfo
{
	GENERATED_BODY()

public:
	UPROPERTY(EditAnywhere,Category="XG")
	AActor* CameraOne = nullptr;

	UPROPERTY(EditAnywhere, Category = "XG")
	float CameraBlendTime = 0.2f;

};




UCLASS()
class UPPLEARN_API ACameraDirectorModify : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	ACameraDirectorModify();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

private:
	UPROPERTY(VisibleAnywhere, Category = "XG")
	TArray<FxlhToggleCameraInfo> CameraInfoList;

	float TimeToNextCameraChange = 0.0f;

	int32 CameraIndex = -1;

};

../attachment/Pasted image 20250810224752.png
效果稳定

理解

  1. 头文件
    1. FxlhToggleCameraInfo结构体。
      1. 告诉虚幻引擎,我需要一个Actor类型的指针,初始化为nullptr
      2. 告诉虚幻引擎,我需要一个CameraBlendTime,初始化为0.2f
    2. AxlhCameraDirectorModify类(主类)
      1. 告诉虚幻,我要一个一个存放定义的那个结构体的一个TArray同质容器 CameraInfoList;
      2. 告诉虚幻,我需要一个时间来作为视角切换的间隔,TimeToNextCameraChange
      3. 向虚幻所要一个int32的cameraIndex变量,用于存放播放的摄像头的索引号。
  2. 源文件
    1. 需要引入头文件
      1. #include "Kismet/GameplayStatics.h"
      2. #include "Camera/CameraActor.h"
    2. BegainPlay函数:
      1. 声明一个Actor容器:MyCameraActors
      2. UGameplayStatics::GetAllActorsOfClass(this,ACameraActor::StaticClass(),MyCameraActors);this是当前cpp类所在的世界上下文(估计就是关卡中存放的元素),第二个参数没看懂,估计是返回对应的引用(ACameraActor类的此函数就是返回Camera的引用),然后放入容器。
      3. CameraInfoList.Empty();:清空CameraInfoList这个TArray。
      4. check(MyCameraActors.Num() > 1);:TArray的常用属性,太熟了,尤其在Slack哪里。
      5. FxlhToggleCameraInfo& CameraInfo = CameraInfoList.AddDefaulted_GetRef();:给CameraInfoList容器添加一个默认值(占位用的值吧),并将引用返回出来。
      6. CameraInfo.CameraOne = MyCameraActors[Index];将拿到的引用中的camera给替换成世界中搜罗到的。
      7. CameraInfo.CameraBlendTime:看不太懂,切换时间吧?
      8. 初始化CameraIndex用于后续遍历Camera
    3. Tick函数
      1. const float TimeBetweenCameraChanges = 2.0f;驻留在一个摄像机上的时间吧,不过tick函数是时刻监控的,在里面声明变量很浪费计算吧?
      2. 一旦TimeToNextCameraChange减去deltatime后小于零就会进行if逻辑
        1. 1给摄像机切换倒计时加2,2切换摄像机当前索引
        2. 这个轮询用求模算法会比较好吧?
        3. 获取一个APlayerController指针对象,不知到能干啥。定义一个指针指向当前索引的摄像机的结构中的摄像机对象。获取一个浮点数,不知道干啥的。
        4. 判断是否有controller对象,以及当前索引指向的摄像机对象是否存在
        5. 存在就设置视口到新的相机。并持续nextblenttime时间。

附着摄像机

创建一个Pawn的cpp 类,在PlayerCamer文件夹

案例

//XGPawnWithCamera.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "XGPawnWithCamera.generated.h"

UCLASS()
class UPPLEARN_API AXGPawnWithCamera : public APawn
{
	GENERATED_BODY()

public:
	// Sets default values for this pawn's properties
	AXGPawnWithCamera();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

protected:
	//输入函数
	void MoveForward(float AxisValue);

	void MoveRight(float AxisValue);

	void PitchCamera(float AxisValue);

	void YawCamera(float AxisValue);

	void ZoomIn();

	void ZoomOut();

protected:
	UPROPERTY(EditAnywhere)
	class USpringArmComponent* SpringArmComp;
	
	UPROPERTY(EditAnywhere)
	class UCameraComponent* CameraComp;

	UPROPERTY(EditAnywhere)
	class UStaticMeshComponent* StaticMeshComp;


	//输入变量
	FVector2D MovementInput;
	FVector2D CameraInput;
	float ZoomFactor=1.0f;
	bool bZoomingIn=false;
};


//XGPawnWithCamera.cpp
// Fill out your copyright notice in the Description page of Project Settings.


#include "PlayerCamera/XGPawnWithCamera.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
#include "Components/StaticMeshComponent.h" 

// Sets default values
AXGPawnWithCamera::AXGPawnWithCamera()
{
 	// Set this pawn to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	//创建组件实例
	RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootComponent"));
	StaticMeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MeshComponent"));
	SpringArmComp = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArmComponent"));
	CameraComp = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComponent"));

	//绑定组件
	StaticMeshComp->SetupAttachment(RootComponent);
	SpringArmComp->SetupAttachment(StaticMeshComp);
	CameraComp->SetupAttachment(SpringArmComp, USpringArmComponent::SocketName);
	
	//SpringArm类的变量赋值
	SpringArmComp->SetRelativeLocationAndRotation(FVector(0.0f, 0.0f, 50.0f), FRotator(-60.0f, 0.0f, 0.0f));
	SpringArmComp->TargetArmLength = 400.f;
	SpringArmComp->bEnableCameraLag = true;
	SpringArmComp->CameraLagSpeed = 3.0f;

	//控制默认玩家
	AutoPossessPlayer = EAutoReceiveInput::Player0;
}

// Called when the game starts or when spawned
void AXGPawnWithCamera::BeginPlay()
{
	Super::BeginPlay();
	
}

// Called every frame
void AXGPawnWithCamera::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	
	//如果按下了放大按钮则放大,否则则缩小
	{
		if (bZoomingIn)
		{
			ZoomFactor += DeltaTime / 0.5f;
		}
		else {
			ZoomFactor -= DeltaTime / 0.25f;
		}
		ZoomFactor = FMath::Clamp<float>(ZoomFactor, 0.0f, 1.0f);
		
		//根据ZoomFactor来设置摄像机的现场和弹簧臂长度
		CameraComp->FieldOfView = FMath::Lerp(90.0f, 60.0f, ZoomFactor);
		SpringArmComp->TargetArmLength = FMath::Lerp(400.0f, 300.0f, ZoomFactor);


		//根据Actor的旋转来设置摄像机的旋转,让相机和Actor相互绑定
		{
			FRotator NewRotation = GetActorRotation();
			NewRotation.Yaw += CameraInput.X;
			SetActorRotation(NewRotation); 
		}

		//旋转相机的俯仰角度。
		{
			FRotator NewRotation = SpringArmComp->GetRelativeRotation();
			NewRotation.Pitch = FMath::Clamp(NewRotation.Pitch + CameraInput.Y, -80.0f, 15.0f);
			SpringArmComp->SetWorldRotation(NewRotation);
		}

		//基于MoveX和MoveY进行移动
		{
			if(!MovementInput.IsZero())
			{
				MovementInput = MovementInput.GetSafeNormal() * 100.0f;
				FVector NewLocation = GetActorLocation();
				NewLocation += GetActorForwardVector() * MovementInput.X * DeltaTime;
				NewLocation == GetActorRightVector() * MovementInput.Y * DeltaTime;
				SetActorLocation(NewLocation);
			}
		}
	}

}

// Called to bind functionality to input
void AXGPawnWithCamera::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	//绑定Zoomin事件
	InputComponent->BindAction("ZoomIn", IE_Pressed, this, &AXGPawnWithCamera::ZoomIn);
	InputComponent->BindAction("ZoomOut", IE_Pressed, this, &AXGPawnWithCamera::ZoomOut);

}

void AXGPawnWithCamera::MoveForward(float AxisValue)
{
	MovementInput.X = FMath::Clamp<float>(AxisValue, -1.0f, 1.0f);
}

void AXGPawnWithCamera::MoveRight(float AxisValue)
{
	MovementInput.Y = FMath::Clamp<float>(AxisValue, -1.0f, 1.0f);
}


void AXGPawnWithCamera::PitchCamera(float AxisValue)
{
	CameraInput.Y = AxisValue;
}

void AXGPawnWithCamera::YawCamera(float AxisValue)
{
	CameraInput.X = AxisValue;
}

void AXGPawnWithCamera::ZoomIn()
{
	bZoomingIn = true;
}

void AXGPawnWithCamera::ZoomOut()
{
	bZoomingIn = false;
}

../attachment/Pasted image 20250813081844.png
这里图中的配置要和cpp中命名对得上。不知道为啥转成陀螺了,给我,但是至少说明了问题出在代码上。总归能完成映射了。这里照抄一下混个脸熟后面再深入看吧。

碰撞

案例

  1. 基于常见类Pawn创建一个XGCollidingPawn到Source/ComponentAndCollision下。
  2. 基于所有类中的PawnMovementComponent类创建一个XGCollidingPawnMovementComponent类于ComponentAndCollision下重点:命名时千万别让命名太长,会出现诡异问题,可能是因为windows上的路径长度是用宏写死的的原因吧。
  3. 编写代码
  4. 创建一个目录010_ComponentAndCollision创建对应的蓝图类BP_XGCollidingPawn类。
    不知道问题出在哪儿了,效果不好,我也无心此处细琢。打算先糊弄过去,等这章结束,自己做个练习理解一下。
//XGCollidingPawn.h    
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "XGCollidingPawnMovementComponent.h" 
#include "XGCollidingPawn.generated.h"

UCLASS()
class UPPLEARN_API AXGCollidingPawn : public APawn
{
	GENERATED_BODY()

public:
	// Sets default values for this pawn's properties
	AXGCollidingPawn();

	virtual UPawnMovementComponent* GetMovementComponent() const override;

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

	UPROPERTY()
	class UParticleSystemComponent* OurParticleSystem;

	UPROPERTY()
	class UXGCollidingPawnMovementComponent* OurMovementComponent;

	void MoveForward(float AxisValue);

	void MoveRight(float AxisValue);

	void Turn(float AxisValue);

	void ParticleToggle();

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

};

//XGCollidingPawn.cpp
// Fill out your copyright notice in the Description page of Project Settings.


#include "ComponentAndCollision/XGCollidingPawn.h"
#include "UObject/ConstructorHelpers.h"
#include "Particles/ParticleSystem.h"//粒子特效
#include "Components/SphereComponent.h"
#include "Components/StaticMeshComponent.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "Particles/ParticleSystemComponent.h"

#include "XGCollidingPawnMovementComponent.h"
// Sets default values
AXGCollidingPawn::AXGCollidingPawn()
{
 	// Set this pawn to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	//根组件将称为对物理反应的球体
	USphereComponent* SphereComponent = CreateDefaultSubobject<USphereComponent>(TEXT("RootComponent"));
	RootComponent = SphereComponent;
	SphereComponent->InitSphereRadius(40.0f);
	SphereComponent->SetCollisionProfileName(TEXT("Pawn"));

	//创建并放置网格体组件,以便查看球体位置
	UStaticMeshComponent* SphereVisual = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("VisualRepresentation"));
	SphereVisual->SetupAttachment(RootComponent);

	static ConstructorHelpers::FObjectFinder<UStaticMesh> SphereVisualAsset(TEXT("/Game/StartContent/Shapes/Shapes_Sphere.Shape_Sphere"));
	if (SphereVisualAsset.Succeeded())
	{
		SphereVisual->SetStaticMesh(SphereVisualAsset.Object);
		SphereVisual->SetRelativeLocation(FVector(0.0f, 0.0f, -40.0f));
		SphereVisual->SetWorldScale3D(FVector(0.8f));
	}

	//创建可激活或停止的粒子系统
	OurParticleSystem = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("MovementParticles"));
	OurParticleSystem->SetupAttachment(SphereVisual);  
	OurParticleSystem->bAutoActivate = false;
	OurParticleSystem->SetRelativeLocation(FVector(-20.0f, 0.0f, 20.0f));
	static ConstructorHelpers::FObjectFinder<UParticleSystem> ParticleAsset(TEXT("/Game/StartContent/Particles/P_Fire.P_Fire"));
	if (ParticleAsset.Succeeded())
	{
		OurParticleSystem->SetTemplate(ParticleAsset.Object);
	}

	//创建一个弹簧臂组件给予摄像机平滑自然的运动感。
	USpringArmComponent* SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraAttachmentArm"));
	SpringArm->SetupAttachment(RootComponent);
	SpringArm->SetRelativeRotation(FRotator(-45.0f, 0.0f, 0.0f));
	SpringArm->TargetArmLength = 400.0f;
	SpringArm->bEnableCameraLag = true;
	SpringArm->CameraLagSpeed = 3.0f; 

	//创建摄像机并附加到弹簧臂上
	UCameraComponent* Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("ActualCamera"));
	Camera->SetupAttachment(SpringArm, USpringArmComponent::SocketName);

	//创建移动组件的实例,以要求其更新根组件的位置
	OurMovementComponent = CreateDefaultSubobject<UXGCollidingPawnMovementComponent>(TEXT("MovementComponent"));
	OurMovementComponent->UpdatedComponent = RootComponent;


	//控制默认玩家
	AutoPossessPlayer = EAutoReceiveInput::Player0;

}

UPawnMovementComponent* AXGCollidingPawn::GetMovementComponent() const
{
	return OurMovementComponent;
}

void AXGCollidingPawn::MoveForward(float AxisValue)
{
	if (OurMovementComponent && (OurMovementComponent->UpdatedComponent == RootComponent))
	{
		OurMovementComponent->AddInputVector(GetActorForwardVector() * AxisValue);
	}
}
void AXGCollidingPawn::MoveRight(float AxisValue)
{
	if (OurMovementComponent && (OurMovementComponent->UpdatedComponent == RootComponent))
	{
		OurMovementComponent->AddInputVector(GetActorRightVector() * AxisValue);
	}
}
void AXGCollidingPawn::Turn(float AxisValue)
{
	FRotator NewRotation = GetActorRotation();
	NewRotation.Yaw += AxisValue;
	SetActorRotation(NewRotation);
}
void AXGCollidingPawn::ParticleToggle()
{
	if (OurParticleSystem && OurParticleSystem->Template)
	{
		OurParticleSystem->ToggleActive();
	}
}

// Called when the game starts or when spawned
void AXGCollidingPawn::BeginPlay()
{
	Super::BeginPlay();
	
}

// Called every frame
void AXGCollidingPawn::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

// Called to bind functionality to input
void AXGCollidingPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);
	PlayerInputComponent->BindAction("ParticleToggle", IE_Pressed, this, &AXGCollidingPawn::ParticleToggle);
	PlayerInputComponent->BindAxis("MoveForward", this, &AXGCollidingPawn::MoveForward);
	PlayerInputComponent->BindAxis("MoveRight", this, &AXGCollidingPawn::MoveRight);
	PlayerInputComponent->BindAxis("Turn", this, &AXGCollidingPawn::Turn);

}

//XGCollidingPawnMovementComponent.h
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/PawnMovementComponent.h"
#include "XGCollidingPawnMovementComponent.generated.h"

/**
 * 
 */
UCLASS()
class UPPLEARN_API UXGCollidingPawnMovementComponent : public UPawnMovementComponent
{
	GENERATED_BODY()
	
	void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
};


//XGCollidingPawnMovementComponent.cpp
// Fill out your copyright notice in the Description page of Project Settings.


#include "ComponentAndCollision/XGCollidingPawnMovementComponent.h"

void UXGCollidingPawnMovementComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
	//确保所有事物持续有效
	if (!PawnOwner || !UpdatedComponent || ShouldSkipUpdate(DeltaTime))
	{
		return;
	}
	FVector DesiredMovementThisFrame = ConsumeInputVector().GetClampedToMaxSize(1.0f) * DeltaTime ;

	if (!DesiredMovementThisFrame.IsNearlyZero())
	{
		FHitResult Hit;
		SafeMoveUpdatedComponent(DesiredMovementThisFrame, UpdatedComponent->GetComponentRotation(), true, Hit);

		//若发生碰撞,则尝试滑过去
		if (Hit.IsValidBlockingHit())
		{
			SlideAlongSurface(DesiredMovementThisFrame,1.f - Hit.Time, Hit.Normal, Hit);
		}
	}

}

定时器

案例

//XlhCountDownTimerActor.h


#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Engine/TimerHandle.h"
#include "Components/TextRenderComponent.h"
#include "XlhCountDownTimerActor.generated.h"

UCLASS()
class UPPLEARN_API AXlhCountDownTimerActor : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AXlhCountDownTimerActor();

	class UTextRenderComponent* CountdownText;
protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

	void AdvanceTimer();

	void UpdateTimerDisplay();
	
	UFUNCTION(BlueprintNativeEvent)
	void CountdownHasFinished();

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

protected:
	UPROPERTY(EditAnywhere)
	int32 CountdownTime = 3;

	FTimerHandle CountdownTimerHandle;

	
	
};
//XlhCountDownTimerActor.cpp



#include "XlhCountDownTimerActor.h"
#include "Components/TextRenderComponent.h"


// Sets default values
AXlhCountDownTimerActor::AXlhCountDownTimerActor()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = false;

	CountdownText = CreateDefaultSubobject<UTextRenderComponent>(TEXT("CountdownNumber"));
	CountdownText->SetHorizontalAlignment(EHTA_Center);
	CountdownText->SetWorldSize(150.0f);
	RootComponent = CountdownText;
	CountdownTime = 3;

}

// Called when the game starts or when spawned
void AXlhCountDownTimerActor::BeginPlay()
{
	Super::BeginPlay();

	UpdateTimerDisplay();

	GetWorldTimerManager().SetTimer(CountdownTimerHandle, this, &AXlhCountDownTimerActor::AdvanceTimer, 1.0f, true);

}

void AXlhCountDownTimerActor::AdvanceTimer()
{
	--CountdownTime;

	UpdateTimerDisplay();

	if (CountdownTime < 1)
	{
		//IsInGameThread(); //判断当前是否在主线程
		GetWorldTimerManager().ClearTimer(CountdownTimerHandle);
		CountdownHasFinished();
	}
	
}

//宏中使用native,就需要在尾部添加Implementation
void AXlhCountDownTimerActor::CountdownHasFinished_Implementation()
{
	CountdownText->SetText(FText::FromString(TEXT("GO!")));
}

// Called every frame
void AXlhCountDownTimerActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

void AXlhCountDownTimerActor::UpdateTimerDisplay()
{
	CountdownText->SetText(FText::FromString(FString::FromInt(FMath::Max(CountdownTime, 0))));
}

理解

.h文件


1. CountdownText是一个UTextRenderComponent类的指针,这个类有Text属性,以及一个material属性,font属性。有点类似哪种浮动文字用到的属性。
2. CountdownTime倒计时的秒数
3. CountdownTimerHandle是一个FTimerHandle类结构体数据,存放一批数据并提供一些方法,简介说明是提供唯一标识符,那就是类似指针一样的操作方式。
4. 其他定义了几个函数AdvanceTimer,UpdateTimerDisplay,CountdownHasFinished

.cpp文件

1. 初始化函数
	1. CountdownText = CreateDefaultSubobject<UTextRenderComponent>(TEXT("CountdownNumber"));
		1. CreateDefaultSubobject是一个模板函数,根据指定的类型提供返回类型,作用是创建一个子对象,或者说组件
		2. TEXT("CountdownNumber"),给此组件在蓝图的名称。
	2. CountdownText->SetHorizontalAlignment(EHTA_Center):让文本在这个文本框中水平居中。
	3. CountdownText->SetWorldSize(150.0f);:设置文本尺寸
	4. RootComponent = CountdownText;将XlhCountDownTimerActor的根组件设置成这个3d文本框,根组件提供了世界位置,旋转和缩放;并为子组件提供相对位置。

2. BeginPlay
	1. Super::BeginPlay();:先执行父类的逻辑。
	2. 执行定义的函数UpdateTimerDisplay()
	3. GetWorldTimerManager().SetTimer(CountdownTimerHandle, this, &AXlhCountDownTimerActor::AdvanceTimer, 1.0f, true);:通过GetWorldTimerManager获取所有定时器,通过SetTimer来配置。
		1. 通过CountdownTimerHandle这个句柄进行SetTimer
		2. 指定将定时器绑定到this上(AXlhCountDownTimerActor这个类的对象上)
		3. 绑定的函数,每次出发定期器调用一次
		4. 每1秒出发一次定时器
		5. 定时器循环执行

3. AdvanceTimer
	1. 指定UpdateTimerDisplay();函数
	2. 进行判断,GetWorldTimerManager().ClearTimer(CountdownTimerHandle);清楚指定句柄的计时器
	3. 调用CountdownHasFinished函数

4. CountdownHasFinished_Implementation
	1. 因为在.h文件中的宏是BlueprintNativeEvent,所以.cpp命名需要加上Implementtation。
	2. CountdownText->SetText(FText::FromString(TEXT("GO!")));通过子组件的指针切换文本。FText::FromString用于将字符串转化成文本。

委托

委托需要在函数前加上F,本质上来说,我觉得是类似于js里的监听机制,js监听的是浏览器的操作事件,委托监听的是游戏事件。
整体的话,小刚这里教的委托过于枯燥,复杂,我打算自己去直接做个项目来学习。
选择的话就火饼qq堂吧。

字符串

  1. FName
  2. FText常用于格式化文本,不可改的字符串。
  3. FString最常用,开销较大,但是可改(底层有数组移动等实现,所以长度可变),常用于跨语言常跨引擎使用(深有体会)

声明

FString TestString1 = FString(TEXT("This is a test"));

FString TestString2 = FString("This is a test");

FString TestString3 = "This is a test";

三者转换

FString TestString = TEXT("This is a str test");
//FString->FName
FName TestName = FName(*TestString);

//FString->FText
FText TestText = FText::FromString(TestString);

//FName->FString
FString BackString1 = TestName.ToString();

//FText->FString
FString BackString2 = TestText.ToString();

//FText->FName不能直接转
FName BackName = FName(TestText.ToString());

//FName ->FText
FText BackText = FText::FromName(TestName);

其他转换

//float->String
float MyFloat = 109.3f;
FString FloatFString = FString::SanitizeFloat(MyFloat);

//int32->FString
int32 MyInt = 99;
FString IntString = FString::FromInt(MyInt);

//bool->FString
bool bMyBool = true;
FString BoolString = bMyBool ? TEXT("true") : TEXT("false");

//FVector->FString 3维向量到String
FVector MyLocation = FVector(100.0f, 20.f, 30.f);
FString MyVectorString = MyLocation.ToString();

//FVector2D->FString
FVector2D MyVector2D = FVector2D(100.0f, 20.f);
FString MyVector2DString = MyVector2D.ToString();

//FRotator->FString
FRotator MyRotator = FRotator(40.f, 50.f, 60.f);
FString MyRotatorString = MyRotator.ToString();

//FLinearColor ->FString
FLinearColor MyColor = FLinearColor::Blue;
FString MyColorString = MyColor.ToString();

//UObject ->FString
AActor* MyActor = this;
FString MyActorString = (MyActor != nullptr) ? MyActor->GetName() : FString(TEXT("None"));

//FString->bool
bool MyBackBool = BoolString.ToBool();

//FString->Int
int32 MyBackint = FCString::Atoi(*IntString);

//FString->float
float MyBackFloat = FCString::Atoi(*FloatFString);

FString 子串和比较

FString Str1 = TEXT("Test");
FString Str2 = TEXT("test");

//忽视大小写比较是否相同    不忽视大小写比较是否相同
bool bEqual = Str1 == Str2;
bool bEqualCase = Str1.Equals(Str2, ESearchCase::CaseSensitive);

FString Str = TEXT("UnrealEngie5");

//判断是否包含了某个子字符串,从头找或从后找
bool bContainASC = Str.Contains(TEXT("Engine"), ESearchCase::IgnoreCase, ESearchDir::FromStart);
bool bContainDESC = Str.Contains(TEXT("Engine"), ESearchCase::IgnoreCase, ESearchDir::FromEnd);

//获取子串的索引,从前找和从后找。
int32 bFindASC = Str.Find(TEXT("real"),ESearchCase::IgnoreCase,ESearchDir::FromStart);
int32 bFindDESC = Str.Find(TEXT("real"), ESearchCase::IgnoreCase, ESearchDir::FromEnd);

FString 追加替换

//追加字符串    合并成新的字符串
FString Str = TEXT("This is my good code!");
Str += TEXT("What is your code?");
FString NetString = Str + TEXT("Today is a good day");

//追加单个字符   追加一个字符串
TCHAR MyChar = *TEXT("A");
NetString.AppendChar(MyChar);
NetString.Append(TEXT("BCD"));

//替换字符串并产生一个新字符串     原位替换(返回替换了的字符的数量)
FString AnotherNetString = NetString.Replace(TEXT("BCD"), TEXT("BCDEF"));
int32 ReplaceNum = AnotherNetString.ReplaceInline(TEXT("Today"), TEXT("Tomorrow"));

FString切割

  1. 基于索引的分割
//通过索引进行分割,Left获取索引5左侧的字符子串,Mid获取索引5右侧的字符子串,Right获取从右往左索引6右侧字符子串
FString MyStr = TEXT("This is my good code!");
FString LeftStr = MyStr.Left(5);
FString MidStr = MyStr.Mid(5);
FString RightStr = MyStr.Right(6);
  1. 基于字符的分割
//预设两个字串,将一个字符串根据一个字符批分并分别给到两个字串
FString MyStr = TEXT("MyLife= 998");
FString MyLeftKey = TEXT("");
FString MyRightKey = TEXT("");
MyStr.Split(TEXT("="), &MyLeftKey, &MyRightKey);

//预设一个TArray<FString>容器,将字符串根据一个字符进行批分并抽取到容器中
FString MyArrayStr = TEXT("MyLife,MyMoney,MyHealth,MyMana");
TArray<FString> MyStrArray;
MyArrayStr.ParseIntoArray(MyStrArray, TEXT(","));

编码转换

官方不推荐的做法

FString MyStr = TEXT("This is my good code !");
char* MyCharAnsi = TCHAR_TO_ANSI(*MyStr);
FString  MyBackStr = ANSI_TO_TCHAR(MyCharAnsi);

FString MyStrUTF8 = TEXT("This is my good code!生命多么美好!");
char* MyCharUTF8 = TCHAR_TO_UTF8(*MyStrUTF8);
FString MyBackStrUTF8 = ANSI_TO_TCHAR(MyCharUTF8);

//%% 太长会截断 %%
FString StrLong = TEXT("This is my good code!This is my good code!This is my good code!This is my good code!This is my good code!This is my good code!This is my good code!This is my good code!This is my good code!This is my good code!This is my good code!");
char* MyAnsiCharLong = TCHAR_TO_ANSI(*StrLong);
FString MyBackStrLong = ANSI_TO_TCHAR(MyAnsiCharLong);

int32 a = 1;

官方推荐

FString StrLong = TEXT("This is my good code!This is my good code!This is my good code!This is my good code!This is my good code!This is my good code!This is my good code!This is my good code!This is my good code!This is my good code!This is my good code!");
FTCHARToUTF8 Convert(*StrLong);
TArray<uint8> Data;
Data.Append((uint8*)Convert.Get(), Convert.Length());
FUTF8ToTCHAR BackConvert((const ANSICHAR*)Data.GetData(), Data.Num());
FString UTF8Text(BackConvert.Length(), BackConvert.Get());

auto UTF8String = StringCast<UTF8CHAR>(*StrLong);
TArray<uint8> NewMethodData;
NewMethodData.SetNum(UTF8String.Length());



FMemory::Memcpy(NewMethodData.GetData(), UTF8String.Get(), UTF8String.Length());

auto Cnv = StringCast<TCHAR>((const UTF8CHAR*)NewMethodData.GetData(), NewMethodData.Num());
//auto Cnv = StringCast<TCHAR>((const ANSICHAR*)NewMethodData.GetData(), NewMethodData.Num()); 乱码示例
FString FinalStr(Cnv.Get(), Cnv.Length());

int32 a = 1;

FName特殊

示例

FName MyFName1 = FName(TEXT("OnlyTest"));
FName MyFName2 = FName(TEXT("OnlyTest"));

bool bEqual = MyFName1 == MyFName2;

int32 CommpareResult = MyFName1.Compare(MyFName2);

if (FName(TEXT("pelvis"), FNAME_Find) != NAME_None)
{
	int32 a = 1;
}
if (FName(TEXT("OnlyTest"), FNAME_Find) != NAME_None)
{
	int32 bb = 1;
}
int32 aa=1;

FName会保存大小写内容,但是他对大小写不敏感(不理解但会抄)。FName是通过索引来比较所以很快。

FText转换

%% 定义文本命名空间,指定键名OwnCode,值MyCode %%
#define LOCTEXT_NAMESPACE "xlhStringActor"
FText MyCodeText = LOCTEXT("OwnCode", "MyCode");
%% 忽略并定义文本命名空间“CodeTypeNamespace”,指定键名CodeType,值ZhangSan %%
FText CodeTypeText = NSLOCTEXT("CodeTypeNamespace", "CodeType", "ZhangSan");
#undef LOCTEXT_NAMESPACE
#define LOCTEXT_NAMESPACE "xlhStringActor"
FString PlayerName = TEXT("XG");
FText PlayerText = FText::Format(LOCTEXT("PlayerNameFmt", "{0} is really cool"),FText::FromString(PlayerName));
FText::AsNumber(15689.33f);
FText PercentTextg = FText::AsPercent(0.33f);
FText MemoryText = FText::AsMemory(1234);
FText MoneyText = FText::AsCurrencyBase(9987,TEXT("EUR"));

FDateTime MyDataTime = FDateTime::Now();
FText DataText = FText::AsDate(MyDataTime);
FText TimeText = FText::AsTime(MyDataTime);

#undef LOCTEXT_NAMESPACE

GameplayTag

蓝图编辑tag

是5.0虚幻才有的东西,理解他用处很大。
在蓝图可以这样添加一个tag左侧类型是gameplaytag和gameplaytag contaienr
../attachment/Pasted image 20250830144955.pnggameplaytag有三层标签。

项目设置也有此tag配置

项目设置->Project->GameplayTags,可以在此表单进行管理。

ini文件添加tag

略,估计没啥人这么干

编辑界面创建表单

../attachment/Pasted image 20250830145931.png
../attachment/Pasted image 20250830150047.png
../attachment/Pasted image 20250830150427.png
之后就能通过表单指定了。
../attachment/Pasted image 20250830150556.png

使用

蓝图中提供了HasTag,HasAllTags,HasAnyTags,DoesContainerMatchTagQuery等函数可以获取标签,通过逻辑运算针对不同标签的actor使用一些有限制的方法。

CPP使用GameplayTag

  1. 创建一个Tag文件夹,创建XGTagActro的h和cpp还有XGTagType的h和cpp。
  2. 在.build.cs中添加“GameplayTags”的引入。
  3. 对XGTagType的修改,我感觉是一种类似于蓝图中那个data table的东西
    1. 在XGTagType.h写#include "NativeGameplayTags.h和#include "GameplayTagContainer.h"引入GameplayTag的 库。
    2. 在.h文件写:UE_DECLARE_GAMEPLAY_TAG_EXTERN(XG_Mode_Forcoding);
    3. 在.cpp文件中写:UE_DEFINE_GAMEPLAY_TAG_COMMENT(XG_Mode_Forcoding, "XG.Mode.Forcoding", "XGCoding tag");
  4. 对XGTagActor的操作,获取TagType的数据
    1. 在XGTagActor.h引入XGTagType.h。
    2. 在XGTagActor.cpp中写UE_DEFINE_GAMEPLAY_TAG_STATIC(XG_Mode_Idle,"XG.Mode.Idle)
  5. 剩余地方太多不好按顺序罗列索性一股脑写上来,整体而言,小刚的课应该是有过很长一段时间的开发才会学着明白的,因为它讲课的逻辑也挺意识流。我听者有点吃力,哪怕在公司写蓝图,且有别的语言基础还是吃力,后面自己做个demo熟悉熟悉。
//XGTagActor.h其他
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "XGTagType.h"
#include "GameplayTagAssetInterface.h"
#include "XGTagActor.generated.h"

UCLASS()
class UPPLEARN_API AXGTagActor : public AActor,public IGameplayTagAssetInterface
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AXGTagActor();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

	virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

public:
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "XG");
	FGameplayTagContainer MyTagContainer;

	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "XG");
	FGameplayTagContainer MyStatusTagContainer;
};

//XGTagActor.cpp
// Fill out your copyright notice in the Description page of Project Settings.


#include "XGTagActor.h"

UE_DEFINE_GAMEPLAY_TAG_STATIC(XG_Mode_Idle,"XG.Mode.Idle")
UE_DEFINE_GAMEPLAY_TAG_STATIC(XX_Mode_Idle, "XX.Mode.Idle")

// Sets default values
AXGTagActor::AXGTagActor()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

}

// Called when the game starts or when spawned
void AXGTagActor::BeginPlay()
{
	Super::BeginPlay();
	
	MyTagContainer.AddTag(FGameplayTag::RequestGameplayTag("XG"));
	MyTagContainer.AddTag(XX_Mode_Idle);
}

void AXGTagActor::GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const
{
	TagContainer.AppendTags(MyTagContainer);

	TagContainer.AppendTags(MyStatusTagContainer);
}

// Called every frame
void AXGTagActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
}

日志

//XGLogType.h
#pragma once
#include "CoreMinimal.h"
DECLARE_LOG_CATEGORY_EXTERN(LogXGSample, Log, All)



//XGLogType.cpp
#include "XGLogType.h"
DEFINE_LOG_CATEGORY(LogXGSample);


//XGLogActor.h


#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "XGLogActor.generated.h"

UCLASS()
class UPPLEARN_API AXGLogActor : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AXGLogActor();

	UFUNCTION(BlueprintCallable, Category = "XG")
	void XGLog();

	UFUNCTION(BlueprintCallable, Category = "XG")
	void XGLogString();

	static void PrintLogString(FString InStr);

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	
	
};

//XGLogActor.cpp



#include "XGLogActor.h"
#include "XGLogType.h"
#include "Logging/StructuredLog.h"

DEFINE_LOG_CATEGORY_STATIC(LogXGLogActor,Log,All)

// Sets default values
AXGLogActor::AXGLogActor()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

}

void AXGLogActor::XGLog()
{
	UE_LOG(LogXGSample, Log, TEXT("This is log"));
	UE_LOG(LogXGSample, Error, TEXT("This is log"));
	UE_LOG(LogXGSample, Warning, TEXT("This is log"));
}

void AXGLogActor::XGLogString()
{
	int32 MyInt = 32;
	FString MyString = TEXT("test");
	float MyFLoat = 32.2f;
	UE_LOG(LogXGSample, Error, TEXT("int32:[%d],FString:[%s],float:[%f]"),MyInt,*MyString);
	FString MyNewString = FString::Printf(TEXT("New---int32:[%d],FString:[%s],float:[%f]"), MyInt, *MyString);
	UE_LOG(LogXGSample, Error, TEXT("[%s]"), *MyNewString);
	UE_LOG(LogXGSample, Error, TEXT("[%s]"), *FString(__FUNCTION__));

	PrintLogString(MyNewString);

}

void AXGLogActor::XGLogFormatString()
{
	FString MyName = TEXT("XG"); 
	int32 ErrorCode = 998;
	FString loadFlags = TEXT("dFlags");
	UE_LOGFMT(LogXGSample, Verbose, "OnPreloadEvent:{Path} should be{Action} the preload set",MyName,ErrorCode); 
	UE_LOGFMT(LogXGSample, Verbose, "OnPreloadEvent:{Path} should be{Action} the preload set", ("Name", MyName), ("Error", ErrorCode), ("Flags", loadFlags));

}

void AXGLogActor::PrintLogString(FString InStr)
{
	AsyncTask(ENamedThreads::GameThread, [InStr]() {
		UE_LOG(LogXGSample, Error, TEXT("Async--[%s]"), *InStr);
	});
}


// Called when the game starts or when spawned
void AXGLogActor::BeginPlay()
{
	Super::BeginPlay();
	
}

// Called every frame
void AXGLogActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}



编程子系统

简入

//XGWorkActor.h


#pragma once

#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "XGSimpleSubsystem.generated.h"


UCLASS()
class UPPLEARN_API UXGSimpleSubsystem : public UGameInstanceSubsystem
{
	GENERATED_BODY()
	
public:	
	
	virtual bool ShouldCreateSubsystem(UObject* Outer) const override;
	
	virtual void Initialize(FSubsystemCollectionBase& Collection) override;

	virtual void Deinitialize() override;
	
public:
	UFUNCTION(BlueprintCallable, Category = "XG")
	void AddHealth(int32 InHealthToAdd);
	UFUNCTION(BlueprintCallable, Category = "XG")
	int32 GetHealth(int32 InHealthToAdd);

private:
	int32 CurrentHealth = 100;
};


//XGWorkActor.cpp



#include "XGWorkActor.h"


// Sets default values
AXGWorkActor::AXGWorkActor()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

}

// Called when the game starts or when spawned
void AXGWorkActor::BeginPlay()
{
	Super::BeginPlay();
	
}

// Called every frame
void AXGWorkActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}



创建子蓝图,拖入关卡运行。
../attachment/Pasted image 20250830203833.png

与多播配合

//XGSimpleSubsystem.h
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "XGSimpleSubsystem.generated.h"

// 委托参数名称要与实际使用保持一致
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FXGSubsystemActorLocation, FString, InActionName, int32, InActionIndex);

UCLASS()
class UPPLEARN_API UXGSimpleSubsystem : public UGameInstanceSubsystem
{
	GENERATED_BODY()
	
public:	
	
	virtual bool ShouldCreateSubsystem(UObject* Outer) const override;
	
	virtual void Initialize(FSubsystemCollectionBase& Collection) override;
	virtual void Deinitialize() override;
	
public:
	UFUNCTION(BlueprintCallable, Category = "XG")
	void AddHealth(int32 InHealthToAdd);
	
	// GetHealth 不需要参数,只是获取当前血量
	UFUNCTION(BlueprintCallable, Category = "XG")
	int32 GetHealth();
	
	UFUNCTION(BlueprintCallable, Category = "XG")
	void CallLocation(FString InActionName, int32 InActionIndex);

	UFUNCTION(BlueprintCallable, Category = "XG")
	static UXGSimpleSubsystem* GetXGSubsystemMyself();
	
	UPROPERTY(BlueprintAssignable)
	FXGSubsystemActorLocation XGSubsystemActorLocationDelegate;
	
private:
	int32 CurrentHealth = 100;

	static UXGSimpleSubsystem* MySubsystemPtr;
};

//XGSimpleSubsystem.cpp



#include "XGSimpleSubsystem.h"



UXGSimpleSubsystem* UXGSimpleSubsystem::MySubsystemPtr = nullptr;

bool UXGSimpleSubsystem::ShouldCreateSubsystem(UObject* Outer) const
{
	return true;
}
void UXGSimpleSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
	Super::Initialize(Collection);
	MySubsystemPtr = this;
}

void UXGSimpleSubsystem::Deinitialize()
{
	MySubsystemPtr = nullptr;
	Super::Deinitialize();
}

void UXGSimpleSubsystem::AddHealth(int32 InHealthToAdd)
{
	CurrentHealth += InHealthToAdd;
}

int32 UXGSimpleSubsystem::GetHealth()
{
	return CurrentHealth;
}

void UXGSimpleSubsystem::CallLocation(FString InActionName, int32 InActionIndex)
{
	XGSubsystemActorLocationDelegate.Broadcast(InActionName, InActionIndex);
}

UXGSimpleSubsystem* UXGSimpleSubsystem::GetXGSubsystemMyself()
{
	return MySubsystemPtr;
}

//XGWorkActor.h


#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "XGWorkActor.generated.h"

UCLASS()
class UPPLEARN_API AXGWorkActor : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AXGWorkActor();

public:
	UFUNCTION(BlueprintCallable,Category="XG",meta=(WorldContext = "InWorldContextObject"))
	void ObjectGetSubsystem(const UObject* InWorldContextObject);

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	
	
};
//XGWorkActor.cpp



#include "XGWorkActor.h"
#include "XGSimpleSubsystem.h"

// Sets default values
AXGWorkActor::AXGWorkActor()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

}

void AXGWorkActor::ObjectGetSubsystem(const UObject* InWorldContextObject)
{
	if (InWorldContextObject && InWorldContextObject->GetWorld())
	{
		UGameInstance* MyGameInstance = InWorldContextObject->GetWorld()->GetGameInstance();
		if (MyGameInstance)
		{
			UXGSimpleSubsystem* MyXGSimpleSubsystem = MyGameInstance->GetSubsystem<UXGSimpleSubsystem>();
			MyXGSimpleSubsystem->AddHealth(2000);
		}
	}
}

// Called when the game starts or when spawned
void AXGWorkActor::BeginPlay()
{
	Super::BeginPlay();
	
	UGameInstance* MyGameInstance = GetGameInstance();
	if(MyGameInstance)
	{
		UXGSimpleSubsystem* MyXGSimpleSubsystem = MyGameInstance->GetSubsystem<UXGSimpleSubsystem>();
		MyXGSimpleSubsystem->AddHealth(998); 
	}
	if (UXGSimpleSubsystem* SelfSubsystemPtr = UXGSimpleSubsystem::GetXGSubsystemMyself())
	{
		SelfSubsystemPtr->AddHealth(500);
	}

}

// Called every frame
void AXGWorkActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}


子系统的Tick

U类没有tick如何拓展?
继承自FTickableObjectBase接口.
这一整章从委托开始就不咋看得懂,计划近期做个demo来熟悉一下这部分。

//XGSimpleSubsystem.h
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "Engine/Engine.h"
#include "XGSimpleSubsystem.generated.h"

// 委托参数名称要与实际使用保持一致
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FXGSubsystemActorLocation, FString, InActionName, int32, InActionIndex);

UCLASS()
class UPPLEARN_API UXGSimpleSubsystem : public UGameInstanceSubsystem,public FTickableGameObject
{
	GENERATED_BODY()
	
public:	
	
	virtual bool ShouldCreateSubsystem(UObject* Outer) const override;
	
	virtual void Initialize(FSubsystemCollectionBase& Collection) override;
	virtual void Deinitialize() override;

public:
	virtual void Tick(float DeltaTime) override;

	virtual bool IsTickable() const override;

	virtual TStatId GetStatId() const override;

	
public:
	UFUNCTION(BlueprintCallable, Category = "XG")
	void AddHealth(int32 InHealthToAdd);
	
	// GetHealth 不需要参数,只是获取当前血量
	UFUNCTION(BlueprintCallable, Category = "XG")
	int32 GetHealth();
	
	UFUNCTION(BlueprintCallable, Category = "XG")
	void CallLocation(FString InActionName, int32 InActionIndex);

	UFUNCTION(BlueprintCallable, Category = "XG")
	static UXGSimpleSubsystem* GetXGSubsystemMyself();
	
	UPROPERTY(BlueprintAssignable)
	FXGSubsystemActorLocation XGSubsystemActorLocationDelegate;
	
private:
	int32 CurrentHealth = 100;

	static UXGSimpleSubsystem* MySubsystemPtr;
};

//XGSimpleSubsystem.cpp



#include "XGSimpleSubsystem.h"



UXGSimpleSubsystem* UXGSimpleSubsystem::MySubsystemPtr = nullptr;

bool UXGSimpleSubsystem::ShouldCreateSubsystem(UObject* Outer) const
{
	return true;
}
void UXGSimpleSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
	Super::Initialize(Collection);
	MySubsystemPtr = this;
}

void UXGSimpleSubsystem::Deinitialize()
{
	MySubsystemPtr = nullptr;
	Super::Deinitialize();
}

void UXGSimpleSubsystem::Tick(float DeltaTime)
{
	UE_LOG(LogTemp, Warning, TEXT("Test"));
}

bool UXGSimpleSubsystem::IsTickable() const
{
	return !IsTemplate();
}

TStatId UXGSimpleSubsystem::GetStatId() const
{
	RETURN_QUICK_DECLARE_CYCLE_STAT(UXGSimpleSubsystem, STATGROUP_Tickables);
}

void UXGSimpleSubsystem::AddHealth(int32 InHealthToAdd)
{
	CurrentHealth += InHealthToAdd;
}

int32 UXGSimpleSubsystem::GetHealth()
{
	return CurrentHealth;
}

void UXGSimpleSubsystem::CallLocation(FString InActionName, int32 InActionIndex)
{
	XGSubsystemActorLocationDelegate.Broadcast(InActionName, InActionIndex);
}

UXGSimpleSubsystem* UXGSimpleSubsystem::GetXGSubsystemMyself()
{
	return MySubsystemPtr;
}

posted @ 2025-08-30 23:48  抓泥鳅的小老虎  阅读(45)  评论(0)    收藏  举报