参考《Unreal Engine 4 学习总动员》,之前已经将视频的操作一步步的完成,但并没有对知识点进行深入理解和提取,本章针对之前的操作对知识点进行更加深入的总结。
本章以书本而非视频的章节进行扩展,更加深入全面的了解UE4,并且扩展阅读官网教程以及API的知识加以解释。
快速入门主要是简单介绍了UE4蓝图和C++、材质、地形、物理、光照、AI,内容较为简单。
关联github账号:需要在个人账户页面https://www.unrealengine.com/dashboard/settings 中填写Github的用户名
之后收到邮件,确认加入Epic Game Github账号邀请
官网githunb地址:EpicGames/UnrealEngine: Unreal Engine source code (github.com)
UE4 中文手册:虚幻引擎4文档 | 虚幻引擎文档 (unrealengine.com)
UE4音频只支持wav格式
UE4不仅支持使用继承的方式定义新物件,支持使用C++或蓝图定义组件
在UE4中编写逻辑代码
在虚幻中,如果要创建 UObject 的继承类,是像下面这样的初始化:
UMyObject* NewObj = NewObject();
NewObject 和 SpawnActor 函数也能通过给一个 “模板” 对象来工作。虚幻引擎会创建该对象的拷贝,而不是"从零创建一个新的对象"。这会拷贝该对象的所有属性(UPROPERTY)和组件。
AMyActor* CreateCloneOfMyActor(AMyActor* ExistingActor, FVector SpawnLocation, FRotator SpawnRotation)
{
UWorld* World = ExistingActor->GetWorld();
FActorSpawnParameters SpawnParams;
SpawnParams.Template = ExistingActor;
World->SpawnActor(ExistingActor->GetClass(), SpawnLocation, SpawnRotation, SpawnParams);
}
在虚幻 4 中,我们可以利用对象的构造函数达到同样的效果。
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
UPROPERTY()
int32 MyIntProp;
UPROPERTY()
USphereComponent* MyCollisionComp;
AMyActor()
{
MyIntProp = 42;
MyCollisionComp = CreateDefaultSubobject(FName(TEXT("CollisionComponent"));
MyCollisionComp->RelativeLocation = FVector::ZeroVector;
MyCollisionComp->SphereRadius = 20.0f;
}
};
注意CreateDefaultSubobject的使用。
类型转换
UPrimitiveComponent* Primitive = MyActor->GetComponentByClass(UPrimitiveComponent::StaticClass());
USphereComponent* SphereCollider = Cast(Primitive);
if (SphereCollider != nullptr)
{
// ...
}
注意GetComponentByClass和Cast的使用。
销毁:MyActor->Destroy();
销毁 GameObject / Actor (1 秒延迟):MyActor->SetLifeSpan(1);
禁用 GameObjects / Actors:
MyActor->SetActorHiddenInGame(true);
// Disables collision components
MyActor->SetActorEnableCollision(false);
// Stops the Actor from ticking
MyActor->SetActorTickEnabled(false);
通过组件访问 GameObject / Actor: MyComponent->GetOwner()
通过 GameObject / Actor 访问组件:UMyComponent* MyComp = MyActor->FindComponentByClass();
查找 GameObjects / Actors:
// Find Actor by name (also works on UObjects)
AActor* MyActor = FindObject(nullptr, TEXT("MyNamedActor"));
// Find Actors by type (needs a UWorld object)
for (TActorIterator It(GetWorld()); It; ++It)
{
AMyActor* MyActor = *It;
// ...
}
// Find UObjects by type
for (TObjectIterator It; It; ++It)
{
UMyObject* MyObject = *It;
// ...
}
// Find Actors by tag (also works on ActorComponents, use TObjectIterator instead)
for (TActorIterator It(GetWorld()); It; ++It)
{
AActor* Actor = *It;
if (Actor->ActorHasTag(FName(TEXT("Mytag"))))
{
// ...
}
}
为 GameObjects / Actors 添加标签:MyActor.Tags.AddUnique(TEXT(“MyTag”));
为 MonoBehaviours / ActorComponents 添加标签:MyComponent.ComponentTags.AddUnique(TEXT(“MyTag”));
比较 GameObjects / Actors 和 MonoBehaviours / ActorComponents 的标签:
if (MyGameObject.CompareTag("MyTag"))
{
// ...
}
// Checks the tag on the GameObject it is attached to
if (MyComponent.CompareTag("MyTag"))
{
// ...
}
// Checks if an Actor has this tag
if (MyActor->ActorHasTag(FName(TEXT("MyTag"))))
{
// ...
}
// Checks if an ActorComponent has this tag
if (MyComponent->ComponentHasTag(FName(TEXT("MyTag"))))
{
// ...
}
物理:刚体 vs. 图元(Primitive)组件:在虚幻中,任何图元组件(C++ 中为 UPrimitiveComponent)都可以是物理对象。一些通用的图元组件,比如 ShapeComponent(胶囊形,球形,盒形),StaticMeshComponent,以及 SkeletalMeshComponent。
层 vs 通道:在 Unity 中,它们被称为"层(Layer)"。虚幻则称之为碰撞通道(Collision Channel)
RayCast射线检测 vs RayTrace射线检测
APawn* AMyPlayerController::FindPawnCameraIsLookingAt()
{
// You can use this to customize various properties about the trace
FCollisionQueryParams Params;
// Ignore the player's pawn
Params.AddIgnoredActor(GetPawn());
// The hit result gets populated by the line trace
FHitResult Hit;
// Raycast out from the camera, only collide with pawns (they are on the ECC_Pawn collision channel)
FVector Start = PlayerCameraManager->GetCameraLocation();
FVector End = Start + (PlayerCameraManager->GetCameraRotation().Vector() * 1000.0f);
bool bHit = GetWorld()->LineTraceSingle(Hit, Start, End, ECC_Pawn, Params);
if (bHit)
{
// Hit.Actor contains a weak pointer to the Actor that the trace hit
return Cast(Hit.Actor.Get());
}
return nullptr;
}
注意LineTraceSingle的使用
触发器
UCLASS()class AMyActor : public AActor{ GENERATED_BODY() // My trigger component UPROPERTY() UPrimitiveComponent* Trigger; AMyActor() { Trigger = CreateDefaultSubobject(TEXT("TriggerCollider")); // Both colliders need to have this set to true for events to fire Trigger.bGenerateOverlapEvents = true; // Set the collision mode for the collider // This mode will only enable the collider for raycasts, sweeps, and overlaps Trigger.SetCollisionEnabled(ECollisionEnabled::QueryOnly); } virtual void NotifyActorBeginOverlap(AActor* Other) override; virtual void NotifyActorEndOverlap(AActor* Other) override;};
注意NotifyActorBeginOverlap和NotifyActorEndOverlap两个虚函数
刚体运动(Kinematic Rigidbodies)
UCLASS()class AMyActor : public AActor{ GENERATED_BODY() UPROPERTY() UPrimitiveComponent* PhysicalComp; AMyActor() { PhysicalComp = CreateDefaultSubobject(TEXT("CollisionAndPhysics")); PhysicalComp->SetSimulatePhysics(false); PhysicalComp->SetPhysicsLinearVelocity(GetActorRotation().Vector() * 100.0f); }};
注意SetPhysicsLinearVelocity的使用
输入事件
UCLASS()class AMyPlayerController : public APlayerController{ GENERATED_BODY() void SetupInputComponent() { Super::SetupInputComponent(); InputComponent->BindAction("Fire", IE_Pressed, this, &AMyPlayerController::HandleFireInputEvent); InputComponent->BindAxis("Horizontal", this, &AMyPlayerController::HandleHorizontalAxisInputEvent); InputComponent->BindAxis("Vertical", this, &AMyPlayerController::HandleVerticalAxisInputEvent); } void HandleFireInputEvent(); void HandleHorizontalAxisInputEvent(float Value); void HandleVerticalAxisInputEvent(float Value);};
注意BindAction和BindAxis的使用,以及这里回调函数使用Handle开头的写法
虚幻 4 并不处理异常。取而代之的做法是,使用 ‘check()’ 函数来触发严重的断言错误。你可以传入一个错误信息提示。如果只是想报告一个错误,但不希望打断整个程序,你可以使用 'ensure()’。这会记录一个带有完整调用堆栈的错误信息,但程序会继续执行。如果当前附加了调试器,那么这两个函数都会暂定并进入调试器。
这里主要介绍UE4编辑器的基本构成,操作,以及自己搭建一个简单的场景。
材质编辑器的使用
快速编程入门:这里简单介绍了AActor的使用
关照快速入门:这里介绍了使用点光源、聚光灯、方向光的使用操作
地形快速入门
UMG快速入门:该Demo使用Widget制作了人物血量的显示,值得注意的是,在该案例中,使用了将控件UI绑定到子对象引用的属性上的方法。
创建主菜单
行为树入门
常用的装饰器
蓝图在UE4中承担了举足轻重的地位,蓝图相比于C++编程来说,具有以下优点:
但同样也要认识到,蓝图只是UE4内置的脚本语言,并非技术,无法直接解决具体问题。因此针对蓝图的学习不应过于钻研,只要能掌握常用的操作即可。
先熟悉重要的快捷键:Alt + Shift + O:定位文件。项目文件太多时,这个会帮上大忙,当然,你的文件名命名最好有个比较好的规范。
暴露游戏元素给蓝图
UPROPERTY
//1. 声明多播委托DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnKingDeathSignature, AKing*, DeadKing);//2. 定义多播委托变量UPROPERTY(BlueprintAssignable) // 蓝图可以动态地为委托赋予事件 FOnKingDeathSignature OnKingDeath;// 多播委托实例,在Broadcast之后调用该委托上的方法//3. 调用多播委托OnKingDeath.Broadcast(this);//4. 在蓝图中绑定多播委托Bind Event to OnKingDeath
UFUNCTION
当使用BlueprintImplementableEvent或BlueprintNativeEvent没有返回值时,在蓝图中以事件的方式使用,当有返回值时,以函数的方式使用。
一些建议
ExpandEnumAsExecs:可以实现多引脚的函数。
UENUM(BlueprintType)enum class BranchOutput : uint8{ Branch0, Branch1, Branch2,};UFUNCTION(BlueprintCallable, Category = "methods", Meta = (ExpandEnumAsExecs = "Branches")) void FunExpandEnumAsExecs(int32 Input, BranchOutput& Branches); void AMyActor::FunExpandEnumAsExecs(int32 Input, BranchOutput& Branches){ if (Input == 0) { Branches = BranchOutput::Branch0; } else if(Input == 1) { Branches = BranchOutput::Branch1; } else { Branches = BranchOutput::Branch2; }}
FLatentActionInfo可以实现一些需要等待的操作:底层原理还是每一帧去查询状态,等到可以执行后续操作了再去进一步执行。
/** * Perform a latent action with a delay (specified in seconds). Calling again while it is counting down will be ignored. * * @param WorldContext World context. * @param Duration length of delay (in seconds). * @param LatentInfo The latent action. */ UFUNCTION(BlueprintCallable, Category="Utilities|FlowControl", meta=(Latent, WorldContext="WorldContextObject", LatentInfo="LatentInfo", Duration="0.2", Keywords="sleep")) static void Delay(UObject* WorldContextObject, float Duration, struct FLatentActionInfo LatentInfo );void UKismetSystemLibrary::Delay(UObject* WorldContextObject, float Duration, FLatentActionInfo LatentInfo ){ if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull)) { FLatentActionManager& LatentActionManager = World->GetLatentActionManager(); if (LatentActionManager.FindExistingAction(LatentInfo.CallbackTarget, LatentInfo.UUID) == NULL) { LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, new FDelayAction(Duration, LatentInfo)); } }}// FDelayAction// A simple delay action; counts down and triggers it's output link when the time remaining falls to zeroclass FDelayAction : public FPendingLatentAction{public: float TimeRemaining; FName ExecutionFunction; int32 OutputLink; FWeakObjectPtr CallbackTarget; FDelayAction(float Duration, const FLatentActionInfo& LatentInfo) : TimeRemaining(Duration) , ExecutionFunction(LatentInfo.ExecutionFunction) , OutputLink(LatentInfo.Linkage) , CallbackTarget(LatentInfo.CallbackTarget) { } virtual void UpdateOperation(FLatentResponse& Response) override { TimeRemaining -= Response.ElapsedTime(); Response.FinishAndTriggerIf(TimeRemaining <= 0.0f, ExecutionFunction, OutputLink, CallbackTarget); } #if WITH_EDITOR // Returns a human readable description of the latent operation's current state virtual FString GetDescription() const override { static const FNumberFormattingOptions DelayTimeFormatOptions = FNumberFormattingOptions() .SetMinimumFractionalDigits(3) .SetMaximumFractionalDigits(3); return FText::Format(NSLOCTEXT("DelayAction", "DelayActionTimeFmt", "Delay ({0} seconds left)"), FText::AsNumber(TimeRemaining, &DelayTimeFormatOptions)).ToString(); }#endif};
如果有可能,尽量将函数放入共享库:继承自UBluepintFunctionLibrary
将函数标记为const也可以让蓝图结点不带引脚。
新版本支持蓝图原生化,以减少蓝图虚拟机的调用。
动画在游戏中非常重要,但UE4的一些动画比较缺少一些重点,目前只对常用的动画技术做一些了解。后面当需要实际调研的时候再进一步学习。
首先说明以下课程视频中的一些操作和意义
编辑动画层:说明可以自己认为修改动画帧中的骨骼。
曲线驱动动画:可以在动画帧中选取其中某些帧自己组成一个补间动画。
使用子动画实例:目前新版本无法创建子动画蓝图,不做研究
创建动态动画:使用Anim Dynamics结点可以实现部分骨骼的物理震动。
创建混合空间动画:常用,3D中创建人物动作的过度,例如从走到跑的过程
创建分层动画:一些枪战或战斗类型游戏会用到,使用了Layered blend per bone,从设置的那块骨骼开始,包括它的子骨骼都开始使用 “Blend Pose 0” 的动画来替代 “Base Pose ” 的动画。因为UE4默认的骨骼布置较为特殊,可以使用该方法很简单的就实现了脊柱上的骨骼被另外一个姿势控制,从而实现了普通枪战的动画。
创建过场动画:常用在游戏开始、结束、或某些叙事片段中
具体在项目中常用到的有: