独行狼死,群聚狼生。
继续针对本书做笔记。另外说明一点本书有一套对应的视频教程,上一篇中已经概括了它的初级内容。先看书再看视频。ps:装上番茄插件,加快编码速度
Chapter04:Actor和Component,要点有三块
1)、GameMode中实例化对象和定时销毁对象
1、GameMode中instantiate an Actor
相对于上一节中的格式化步骤,书中给出了简化版,
FTransform SpawnLocation;
UE_LOG(LogTemp, Warning, TEXT("Actor spawning"));
GetWorld()->SpawnActor<AMagicPills>(AMagicPills::StaticClass(),SpawnLocation);
默认情况下, FTransform is constructed to have zero rotation and a location
at the origin.FTransform初始化为全0.
2、定时销毁一个Actor
.h文件下:
UPROPERTY()
AMagicPills* SpawnedActor;
UFUNCTION()
void DestoryActorFunction();
virtual void BeginPlay()override;
.cpp文件下
void AUE4CookBookGameMode::DestoryActorFunction()
{
if (SpawnedActor != nullptr)
{
SpawnedActor->Destroy();
}
}
void AUE4CookBookGameMode::BeginPlay()
{
Super::BeginPlay();
FTimerHandle Timer;
GetWorldTimerManager().SetTimer(Timer, this,
&AUE4CookBookGameMode::DestoryActorFunction, 10);
FTransform SpawnLocation;
UE_LOG(LogTemp, Warning, TEXT("Actor spawning"));
SpawnedActor=
GetWorld()->SpawnActor<AMagicPills>(AMagicPills::StaticClass(),SpawnLocation);
}
要点如下:
其一结合上一篇的内容来看,这里的代码更为简洁。上一节中有写到:
怎么样在场景中实例化?--UWorld的SpawnActor方法
UWorld* const World = GetWorld();
if (World) {
FVector SpawnLocation = GetRandomPointInVolume();
FRotator SpawnRotation;
SpawnRotation.Pitch = FMath::FRand()*360.f;
SpawnRotation.Roll = FMath::FRand()*360.f;
SpawnRotation.Yaw = FMath::FRand()*360.f;
AMagicPill* SpawnedPill = World->SpawnActor<AMagicPill>(ItemToSpawn, SpawnLocation, SpawnRotation);
}
本书中直接简略为三行代码
UPROPERTY()
AMagicPills* SpawnedActor;
SpawnedActor=
GetWorld()->SpawnActor<AMagicPills>(AMagicPills::StaticClass(),SpawnLocation);
其二:创建定时器处理销毁
SpawnedActor->Destroy();销毁实例通过调用对象的Destroy方法
定时器的创建是通过FTimerHadle
然后GetWorldTimerManager()的SetTimer方法定时调用DestoryActorFunction函数
2)、除了设置定时器来销毁实例化的Actor外,也可以在MagicPills中设置生命周期
具体操作为,
BeginPlay()中加入
SetLifeSpan(10);这使得实例化的对象将在10s后消失
3)、关于Component的说明
为Actor添加component
对于一个通常的Actor来讲,如果没有component,那么就没有位置,并且不能够附加到其他物体上。
一般来说Actor都应该具备一个component。
UPROPERTY()
UStaticMeshComponent* Mesh;
Mesh = CreateDefaultSubobject<UStaticMeshComponent>("BaseMeshComponent");
其中CreateDefaultSubObject负责通知引擎创建一个UststicMeshComponent对象
,返回一个指针,如果为staticMesh实际挂上一个assets需要用到FObjectFinder
auto MeshAsset=ConstructorHelpers::FObjectFinder<UStaticMesh>(TEXT(
"StaticMesh'/Game/StarterContent/Shapes/Shape_NarrowCapsule.Shape_NarrowCapsule'"));
if (MeshAsset.Object != nullptr) {
Mesh->SetStaticMesh(MeshAsset.Object);
}
创建一个ActorComponent,创建一个SceneComponent等操作都类似,看到这我在想作者是不是手动创建的文件。。。不过说实话第四章作者组织的内容并不如前几章,有冗余,很多代码都重复出现两遍,SceneComponet章节并没出现想要的结果
书中也给出了源码,感觉没有写的必要,对于ActorComponent,是可以挂在在Actor身上作为组件
通过组件获取挂载Actor的信息可以通过GetOwner()方法。SceneComponent和ActorComponent类似,
区别是SceneComponet并不会在场景中渲染出来。只用来传递Transform关系
#include "UnrealCookBook.h"
#include "RandomMovementComponent.h"
URandomMovementComponent::URandomMovementComponent()
{
PrimaryComponentTick.bCanEverTick = true;
MovementRadius = 5;
}
void URandomMovementComponent::TickComponent( float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction )
{
Super::TickComponent( DeltaTime, TickType, ThisTickFunction );
AActor* Parent = GetOwner();
if (Parent)
{
Parent->SetActorLocation(
Parent->GetActorLocation() +
FVector(FMath::FRandRange(-1, 1)*MovementRadius,
FMath::FRandRange(-1, 1)*MovementRadius,
FMath::FRandRange(-1, 1)*MovementRadius)
);
}
}
至于Primitive Component往后的部分,目前看来过于复杂,需要有实例应用时再回过来看看
Chapter05:处理事件和委托
1)、使用UboxComponent,重写Overlap事件函数
.h文件
UPROPERTY()
UBoxComponent* TriggerZone;
UFUNCTION()
virtual void NotifyActorBeginOverlap(AActor* OtherActor)override;
UFUNCTION()
virtual void NotifyActorEndOverlap(AActor* OtherActor)override;
.cpp文件
TriggerZone = CreateDefaultSubobject<UBoxComponent>("TriggerVolume");
void AMyTriggerVolume::NotifyActorBeginOverlap(AActor * OtherActor)
{
UE_LOG(LogTemp,Warning,TEXT("%s entered me"), *(OtherActor->GetName()));
}
void AMyTriggerVolume::NotifyActorEndOverlap(AActor * OtherActor)
{
UE_LOG(LogTemp, Warning, TEXT("%s left me"), *(OtherActor->GetName()));
}
2)、创建委托绑定到UFUNCTION上,不同于书上把委托声明在GameMode中,我试着把委托放在了MyTriggerVolume里,发现在DelegateListener里并不好获取到MyTriggerVolume,至少不方便<当然可以使用UPROPERTY(EditAnywhere)手动指定,这种方法也蛮不错的,自然我是看完create a custom event那节才学到的>,所以最后也放到了GameMode里,但实例也并不能运行,需要把书中的AGameMode改为AGameModeBase,布置场景如左图,效果如右图
委托使得我们可以在不知道是那个函数的情况下调用函数,是一种类似于函数指针的机制,
但比原始的函数指针要安全
DECLARE_DELEGATE(FStandardDelegateSignature)放在UCLASS()之前
FStandardDelegateSignature MyStandardDelegate;用于声明委托
写一个DelegateListener用于委托的函数绑定,使用BindUObject方法,传入绑定的函数。
MyGameMode->MyStandardDelegate.BindUObject(this, &ADelegateListener::EnableLight);
Delegate的具体执行在Overlap事件中 MyGameMode->MyStandardDelegate.ExecuteIfBound();
贴出代码:
DelegateListener.cpp
#include "UnrealCookBook.h"
#include "DelegateListener.h"
#include "UE4CookbookGameMode.h"
// Sets default values
ADelegateListener::ADelegateListener()
{
// 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;
PointLight = CreateDefaultSubobject<UPointLightComponent>("PointLight");
RootComponent = PointLight;
PointLight->SetVisibility(false);
}
// Called when the game starts or when spawned
void ADelegateListener::BeginPlay()
{
Super::BeginPlay();
UWorld* TheWorld = GetWorld();
if (TheWorld != nullptr)
{
AGameModeBase* GameMode = UGameplayStatics::GetGameMode(TheWorld);
AUE4CookBookGameMode *MyGameMode = Cast<AUE4CookBookGameMode>(GameMode);
if (MyGameMode != nullptr)
{
MyGameMode->MyStandardDelegate.BindUObject(this, &ADelegateListener::EnableLight);
}
}
}
void ADelegateListener::EnableLight()
{
//PointLight->SetVisibility(true);
PointLight->ToggleVisibility(true);
}
3)、注销委托
有时候,移除委托的绑定十分必要,就像是把一个函数指针设置为空以便它不在引用到任何被删除的物体
放到EndOverlap事件中
MyGameMode->MyStandardDelegate.Unbind();
4)、创建一个接收参数的委托,对原书代码进行一点改变,以实现随机变化灯的颜色
三个步骤:
声明委托,在GameMode中
DECLARE_DELEGATE_OneParam(FParamDelegateSignature,FLinearColor)
FParamDelegateSignature MyPatameterDelegate;
绑定委托,在ParameterListener中
MyGameMode->MyPatameterDelegate.BindUObject(this,
&AParamDelegateListener::SetLightColor);
执行委托绑定的函数并传参,在overlap事件中
MyGameMode->MyPatameterDelegate.ExecuteIfBound(FLinearColor(
FMath::RandRange(0,1),
FMath::RandRange(0, 1),
FMath::RandRange(0, 1), 1));
5)、创建一个组播的委托create a multicast delegate
前面所讲的方法创建的委托,同一时刻只能有一个委托函数发生作用<把多个ParameterListener拖入场景中,只有最后一个灯的颜色会改变>。下面这段话还得留着多看几遍,有助于加深理解
大致有几点:
1、与普通的Deletegate有所不同,组播的delegate使用FDelegateHandle来实例化委托
2、委托的销毁是安全的,当Actor销毁时会调用FDelegateHandle的Remove方法
3、BroadCast相对于ExecuteIfBound()而言,不需要检查是否已经绑定
GameMode中声明委托
DECLARE_MULTICAST_DELEGATE(FMulticastDelegateSignature)
FMulticastDelegateSignature MyMulticasrDelete;
MulticastDelegateListener中声明FDelegateHandle
FDelegateHandle MyDelegateHandle;
通过AddUObject函数绑定
MyDelegateHandle = MyGameMode->MyMulticasrDelete.AddUObject(this,
&AMulticastDelegateListener::ToggleLight);
通过EndPlay定义Actor销毁时的动作
void AMulticastDelegateListener::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
UWorld* TheWorld = GetWorld();
AGameModeBase* GameMode=UGameplayStatics::GetGameMode(TheWorld);
AUE4CookBookGameMode* MyGameMode = Cast<AUE4CookBookGameMode>(GameMode);
if (MyGameMode != nullptr)
{
MyGameMode->MyMulticasrDelete.Remove(MyDelegateHandle);
}
}
6)、自定义事件
这种方式也可以放置多个Listener,只需要指定好Trigger
MyTriggerVolume中
把DECLARE_EVENT(AMyTriggerVolume,FPlayerEntered)放在UCLASS()之前,指定创建的自定义事件名称
public:
FPlayerEntered OnPlayerEntered;
在ovelap事件中,将自定义事件广播出去
OnPlayerEntered.Broadcast();
创建MyTriggerVolumeListener来接收和处理事件
protected:
UPROPERTY()
UPointLightComponent* PointLight;
UPROPERTY(EditAnywhere,Category="TiggerVolume")
class AMyTriggerVolume* TriggerEventSource;
UFUNCTION()
void OnTriggerEvent();
void ATriggerVolumeListener::BeginPlay()
{
Super::BeginPlay();
if (TriggerEventSource != nullptr)
{
TriggerEventSource->OnPlayerEntered.AddUObject(this,
&ATriggerVolumeListener::OnTriggerEvent);
}
}
void ATriggerVolumeListener::OnTriggerEvent() {
PointLight->SetLightColor(FLinearColor(FMath::RandRange(0, 1),
FMath::RandRange(0, 1),
FMath::RandRange(0, 1), 1));
}
Chapter05的两个综合案例:
1)、时钟案例
2)、拾取案例
下一节记录视频教程,巩固本节的知识