[19] C++网络通信开发
Day1
通过引入路径找到类型
根据角色进入方向开门(向量运算、几何概念、点乘)
-
向量的减法:
OtherActor->GetActorLocation() - GetActorLocation()
这一部分是计算两个位置向量之间的差向量,即门的位置向量减去角色的位置向量,得到了一个从门指向角色的向量。 -
向量的归一化:
V1.GetSafeNormal()
这一部分是将差向量归一化,即将向量的长度调整为1,这样做的目的是为了得到一个表示方向的单位向量,而不关心具体的长度。 -
点乘运算:
FVector::DotProduct(GetActorRightVector(), V1)
这一部分是计算门的右侧向量与角色进入方向向量的点乘。点乘的结果可以用来判断两个向量之间的夹角关系,从而确定进入角色的位置相对于门的位置是在门的左侧还是右侧。 -
几何概念: 根据点乘的结果,代码确定了门的开启方向。如果点乘结果大于0,则进入角色在门的右侧,门应该向着负方向开启;如果点乘结果小于0,则进入角色在门的左侧,门应该向着正方向开启。这涉及到了几何中向量的方向和夹角的关系。
曲线的应用
向量点乘 计算夹角
2个夹角余弦值
计算角色移动的方向(点乘)
-
获取移动方向: 使用
GetVelocity()
方法获取角色的移动速度向量,并使用GetSafeNormal()
方法将其归一化,以获取移动方向。 -
计算移动方向与角色正方向的夹角: 使用点乘运算
FVector::DotProduct()
计算移动方向向量和角色正方向向量的点乘结果,从而得到它们之间的夹角的余弦值。 -
将余弦值转换为角度: 使用反余弦函数
FMath::Acos()
将余弦值转换为夹角的弧度值,并将其转换为角度制。 -
根据移动方向与角色右侧向量的点乘结果调整角度方向: 如果移动方向向量与角色右侧向量的点乘结果小于0,则将计算得到的角度方向乘以-1,以确保角度方向的正确性。
FVector MoveDir = MyCharacter->GetVelocity().GetSafeNormal();
//移动速度与角色正方向夹角
float DotValue = FVector::DotProduct(MoveDir, MyCharacter->GetBaseAimRotation().Vector());
//反余弦 注意反余弦的单位是弧度,需要将弧度转为角度
Direction = FMath::Acos(DotValue) * 180 / 3.14f;
if (FVector::DotProduct(MoveDir, MyCharacter->GetActorRightVector()) < 0)
{
Direction *= -1;
}
bIronsight = MyCharacter->IsIronsight();
//加速度方向和正方向的夹角 = 余弦
//反余弦
判断角色是否向前奔跑(点乘)
bool ALGCharacterBase::IsSprinting()
{
if (GetHoldWeapon())
{
return bSprint
&& !bIsCrouched
&& FVector::DotProduct(GetVelocity().GetSafeNormal(), GetActorForwardVector()) > .9f;
}
return bSprint && !bIsCrouched;
}
GetVelocity().GetSafeNormal()
:获取角色当前的速度向量,并将其归一化,以得到角色的移动方向。GetActorForwardVector()
:获取角色当前正前方的向量。FVector::DotProduct()
:计算移动方向向量和正前方向向量的点乘结果。
如果这个点乘结果大于0.9(在1附近),那么可以理解为角色的移动方向与正前方向相近。这种检查可能用于一些游戏中的特定功能或者行为,比如判断角色是否在直线上高速移动,或者在进行某些特殊动作时的判断。
节点
API
HUD取角色指针 : GetOwningPawn();
Tips
- 改组件关编译器
- 常量都应该暴露成参数
Day2
大纲
动画融合
同步组
动画叠加
AI
黑板拾取器
内容
动画融合
Tips : 所有动画的融合都是从另一个动画的零点开始的
为什么融合一般推荐0.2秒 :
1. 一般动画最后0.1秒已经没有动作了 , 会导致大多数动画跳帧
2 .一般情况动画播放如果A->B动画 , A动画播放玩过渡到B动画 , 尽量>=你的转换时长的最换规则用的混合时间
同步组
走路动画 : 因为2个动画是不一样状态里面 , 这时候需要加同步组标记 , 如果有同步组有标记的话是按照标记过度
下蹲动画同步组 : 因为是单一蹲下的动画没有衔接且 , 2个动画在一个状态里面
虚幻动画组成方式
分层混合动画 : 无关动画组合 , 如果姿势不一样会不符合常理性 , 行走下半身和奔跑上半身进行融合
相同骨骼之间 : 在给定的百分比之间进行混合
叠加动画 :
理论:
A + B = C; B = 差量
原动画 + 差量动画 = 新动画;
-
叠加动画(Additive Animation):
- 叠加动画允许您将一个或多个动画叠加在角色的基础动画之上,从而实现更加细致的动画控制。例如,您可以在一个基本的行走动画之上叠加一个手部动画,用于持有物品,从而使角色在行走时也可以同时进行手部动作。
- 在虚幻引擎中,您可以通过在动画蒙太奇(Animation Montage)中设置动画轨道(Slot)来创建叠加动画效果。然后在蓝图或代码中通过控制动画轨道的权重来实现动画的叠加效果。
-
分层混合动画(Layered Blend Per Bone Animation):
- 分层混合动画允许您在不同的骨骼层级上混合多个动画,从而实现更加复杂和精细的动画控制。这种技术可以用于实现角色同时进行多种动作的情况,例如角色可以同时行走、跑步和进行手部动作。
- 在虚幻引擎中,您可以通过使用动画蓝图(Animation Blueprint)来设置分层混合动画。在动画蓝图中,您可以使用分层混合节点(Layered Blend Per Bone节点)来将多个动画混合在一起,并通过调整权重和混合参数来控制动画的表现。
叠加动画
//拿到角色的Pitch
MyCharacter->GetBaseAimRotation().GetNormalized().Pitch
批量更改动画
AI
虚幻引擎的AI行为树(Behavior Tree)中,五个主要部分
-
根节点(Root):
- 根节点是行为树的起始点,它定义了行为树的整体结构和执行方式。常见的根节点类型包括选择器(Selector)、序列(Sequence)和并行(Parallel)等,它们决定了子节点的执行顺序和行为树的执行策略。
-
任务节点(Task):
- 任务节点是行为树中最基本的节点类型,负责执行具体的任务或行为。例如,移动、攻击、巡逻等都可以作为任务节点。任务节点执行完任务后,会返回执行结果,通常是成功、失败或运行中。
-
装饰器节点(Decorator):
- 装饰器节点用于修饰其子节点的行为。它可以在子节点执行前或执行后改变其行为,或者根据条件动态决定是否执行子节点。常见的装饰器节点包括条件判断、重复执行、超时等。
-
条件节点(Condition):
- 条件节点是一种特殊的装饰器节点,用于在执行其子节点之前评估一个条件。如果条件成立,子节点会执行;否则,子节点会被跳过。条件节点的结果通常是成功或失败。
-
服务节点(Service):
- 服务节点用于在行为树执行期间提供一种服务,例如更新黑板数据、执行周期性任务等。服务节点通常不返回执行结果,而是在执行过程中影响行为树的其他部分。
行为树具有以下特点:
-
特定的频率执行逻辑:
- 行为树可以根据特定的频率执行其逻辑。这意味着开发者可以控制行为树的更新频率,以平衡性能和响应性能的需求。例如,在每帧更新、每秒更新等不同的频率下执行行为树逻辑。
-
分发权:
- 行为树的根节点负责决定如何分发执行权给其子节点。常见的分发方式包括选择器节点、序列节点和并行节点。选择器节点依次尝试执行其子节点,直到一个子节点成功为止;序列节点依次执行其子节点,直到一个子节点失败为止;并行节点同时执行其所有子节点。
-
执行权:
- 行为树中的节点根据其类型和状态具有不同的执行权。任务节点在执行完成后返回成功、失败或运行中状态;装饰器节点修改其子节点的执行行为,但不改变执行权;条件节点根据评估条件决定是否执行其子节点。
重定义任务节点
ExecuteTask函数
-
接收了两个参数:
UBehaviorTreeComponent& OwnerComp
:指向拥有此任务的行为树组件的引用。通过这个参数,任务可以访问行为树组件中的各种信息,例如黑板数据和行为树节点状态等。uint8* NodeMemory
:指向存储任务节点内部状态的内存缓冲区的指针。这个参数通常用于在任务的多次执行之间保持状态信息。
-
函数返回一个
EBTNodeResult::Type
枚举类型的值,表示任务的执行结果。这个枚举类型包括了任务的成功、失败、运行中等不同的执行结果。 -
在子类中,开发者需要实现具体的任务逻辑。这个函数中的代码将会定义任务在行为树运行时实际要执行的行为。例如,任务可能会根据黑板中的数据执行移动、攻击、检查条件等操作。
-
开发者需要在函数中编写任务的逻辑,并根据执行结果返回相应的
EBTNodeResult::Type
值,以通知行为树系统任务的执行状态。
黑板建值拾取器
初始化行为树任务,并确保任务能够访问到与其关联的黑板数据中的特定键值
射线检测
Tips
Time Remaining : 使用的时候尽量使用固定时间 , 因为好合淡入淡出时长的固定0.2秒混合 , 百分比不推荐
AI :
行为树是共用的 , 黑板不是共用的 100个ai对应1个行为树 , 100个黑板
导入了对应模块才能在C++使用对应的功能
通过文件找UE预设碰撞类型 : DefaultEngine.ini
Day3
大纲
行为树 :
自定义任务节点 : WaitTime
多个AI多份行为树数据问题
拖拽 : 单例数据对象模板
开枪射击
播放蒙太奇 : 播放蒙太奇片段、蒙太奇播放结束触发通知类
内容
自定义任务节点WaitTime
ExecuteTask函数
在UBTTaskNode类中,ExecuteTask函数是用于执行任务节点的主要功能函数。其作用是执行任务节点所定义的具体行为,并返回一个EBTNodeResult类型的值,表示任务执行的结果。
具体来说,ExecuteTask函数可能会执行一系列操作,这些操作通常包括获取必要的数据、执行特定的逻辑或行为,并可能更新相关的黑板信息或任务状态。任务节点的具体行为由开发者在编写行为树时定义,在这个函数中实现。
返回值EBTNodeResult用于表示任务的执行结果,它包括以下几种可能的取值:
EBTNodeResult::Succeeded:表示任务执行成功。
EBTNodeResult::Failed:表示任务执行失败。
EBTNodeResult::Aborted:表示任务执行被中止。
EBTNodeResult::InProgress:表示任务已经开始执行,但尚未完成。如果你希望结束执行中的任务(例如,任务成功执行),你需要调用 FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded)。通常在 TickTask 中调用,即每个 Tick 都会检查任务是否已完成1。
通过返回不同的结果,可以告知行为树系统任务的执行情况,从而控制行为树的流程和逻辑。
怎么解决多个AI多份行为树数据问题
使用 : bCreateNodeInstance = true
给一个单独的变量开辟单独的内存大小
通过 uint8* NodeMemory
这个指针,每个行为树任务节点实例都会单独开辟一块内存空间用于存储特定于该实例的信息。在这种情况下,这块内存空间用于存储等待时间的信息。因此,无论有多少个 AI 实例,每个实例都会有自己独立的等待时间变量,它们不会共享同一个变量,而是各自独立存储。
单例数据对象模板
UClass* 指向类对象的指针 GetClass() 获取蓝图类
格式化文本
WeaponClipTextBlock->SetText(
FText::Format(NSLOCTEXT("gameui", "k1", "{0}/{1}"), FText::AsNumber(CurrentClip), FText::AsNumber(MaxClip)));
播放声音 && 特效
换弹夹动画
选择播放的蒙太奇片段
动画结束 : 类通知
//.h
class LEGOGAME_API UAnimNotify_ReloadWeapon : public UAnimNotify
{
GENERATED_BODY()
protected:
virtual void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation) override;
};
//.cpp
void UAnimNotify_ReloadWeapon::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation)
{
Super::Notify(MeshComp, Animation);
// UE_LOG(LogTemp, Log, TEXT("通知触发"))
if (ALGCharacterBase* CharacterBase = Cast<ALGCharacterBase>(MeshComp->GetOwner()))
{
if (CharacterBase->GetHoldWeapon())
{
CharacterBase->GetHoldWeapon()->MakeFullClip();
}
}
}
叠加动画
附加设置 Local和Mesh的区别
-
Local Space(本地空间):
- 在Local space中设置的叠加动画会相对于当前骨骼或对象的本地坐标空间进行叠加。这意味着动画效果将根据骨骼或对象的自身坐标系来应用,而不受父级或全局坐标系的影响。
- 这种设置通常用于将叠加动画应用于单个骨骼或对象,并保持其相对于自身的运动。
-
Mesh Space(模型空间):
- 在Mesh space中设置的叠加动画会相对于整个模型或角色的坐标空间进行叠加。这意味着动画效果将根据模型或角色的全局坐标系来应用,而不考虑骨骼的层次结构或对象的本地坐标系。
- 这种设置通常用于在模型或角色级别上应用全局效果,例如整体的位移、旋转或缩放。
Tips
面试 : 行为树节点共用有没有遇到过
所有的U类都有::StaticClass(); 函数
蓝图无法使用泛型
API
获取对象所属类 : GetClass()
来获取游戏运行的时间信息 :GetWorld()->GetTimeSeconds();
Day4
大纲
枪械射击
环境问询系统 : EQS
内容
枪械射击
射击的点查找
思路 : 屏幕中心发射一条射线打到人 -> 返回终点 -> 在枪口的位置发送一条到终点的射线
API :
Pc->GetViewportSize(SizeX, SizeY);//获取视口大小
Pc->DeprojectScreenPositionToWorld(CenterX, CenterY, WorldPos, WorldDir)//将屏幕位置投影到世界
DrawDebugLine(GetWorld(), WorldPos, WorldPos + WorldDir * 30000, FColor::Cyan, false, 3);//Debug射线
LineTraceSingleByChannel(Hit, WorldPos, WorldPos + WorldDir * 30000, ECC_WeaponTrace,QueryParams)//射线检测
射线检测 && TaskDamage
//发起射线检测
FHitResult Hit;
//添加忽略
FCollisionQueryParams QueryParams;
QueryParams.AddIgnoredActor(this);
QueryParams.AddIgnoredActor(OwnMaster);
//绘制Debug线
DrawDebugLine(GetWorld(), FirePos, FirePos + FireDir * 10000, FColor::Purple, false, 3);
//发送射线检测 p1:发现第一个阻挡命中 p2:起始点 p3:终止点 p4通道检测
if (GetWorld()->LineTraceSingleByChannel(Hit, FirePos, FirePos + FireDir * 10000, ECC_WeaponTrace))
{
FPointDamageEvent DamageEvent;//完整描述所收到损坏的数据包
//给目标造成伤害 TakeDamage p1:多少伤害 p2:完整描述所收到损坏的数据包 p3:对损坏负责的控制器。p4直接造成伤害的演员(例如爆炸的炮弹,落在你身上的石头)
Hit.GetActor()->TakeDamage(1, DamageEvent, OwnMaster->GetController(), this);
}
环境问询系统-EQS
EQS: 提供合适的位置给行为树使用
测试从上往下测试 , 不通过的则不往下执行
拿到黑板变量
Day5
大纲
内容
UBTTaskNode && UBTDecorator && UBTService共用节点
节点API
//--黑板数据交互--//
if (OriginPositionKey.SelectedKeyType == UBlackboardKeyType_Vector::StaticClass()) //判断类型是否相等
{
//获取黑板数据
OriginPos = OwnerComp.GetBlackboardComponent()->GetValue<UBlackboardKeyType_Vector>(
OriginPositionKey.GetSelectedKeyID());
//设置黑板数据
OwnerComp.GetBlackboardComponent()->SetValue<UBlackboardKeyType_Vector>(NavPositionKey.GetSelectedKeyID(), NavPos);
}
//清除黑板数据
OwnerComp.GetBlackboardComponent()->ClearValue(BlackboardKeySelector.GetSelectedKeyID());
//获取当前 AI 控制器所控制的角色
OwnerComp.GetAIOwner()->GetPawn()
//-- 结束当前的潜在任务--//
void UBTTask_ReloadWeapon::TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
Super::TickTask(OwnerComp, NodeMemory, DeltaSeconds);
if (ALGCharacterBase* CharacterBase = Cast<ALGCharacterBase>(OwnerComp.GetAIOwner()->GetPawn()))
{
//等到武器换弹结束变更武器状态为Normal后为进入Success节点继续走
if (CharacterBase->GetHoldWeapon() && CharacterBase->GetHoldWeapon()->GetCurrentState() == EWeaponState::EWS_Normal)
{
//结束当前的潜在任务
FinishLatentTask(OwnerComp,EBTNodeResult::Succeeded);
}
}
}
//初始化的时候绑定相关黑板数据方法
void UBTTask_FindNavPosition::InitializeFromAsset(UBehaviorTree& Asset)
{
Super::InitializeFromAsset(Asset);
UBlackboardData* BBData = GetBlackboardAsset();
if (BBData)
{
OriginPositionKey.ResolveSelectedKey(*BBData); //解析选定的值
NavPositionKey.ResolveSelectedKey(*BBData);
}
}
虚函数
-
ExecuteTask
: 这个函数用于执行任务节点的主要逻辑。当行为树到达一个任务节点时,会调用该函数来执行具体的任务行为。在这个函数中,你可以编写任务节点的核心逻辑,比如移动、攻击、检查条件等,并通过返回值来表示任务执行的结果(成功、失败、正在执行等)。 -
InitializeFromAsset
: 该函数用于从行为树资产(BehaviorTree Asset)中初始化任务节点的属性和配置。在这个函数中,你可以从行为树资产中读取默认参数、执行条件等信息,并将其应用到任务节点上,以便在行为树运行时正确地执行任务。 -
TickTask
: 这个函数用于处理任务节点的每帧更新逻辑。在行为树执行过程中,如果任务节点需要进行持续性的逻辑更新(比如跟踪目标、定期检查条件等),就可以在这个函数中实现。TickTask
函数在每一帧都会被调用,允许任务节点执行持续性的逻辑。 -
GetInstanceMemorySize
: 这个函数用于指定任务节点需要的实例内存大小。在 Unreal Engine 中,行为树使用实例内存来存储任务节点在运行时所需的数据。通过重写GetInstanceMemorySize
函数,你可以指定任务节点所需的实例内存大小,确保任务节点能够正常运行。
聚焦
OwnerComp.GetAIOwner()->SetFocus(Actor);//设置AI聚焦玩家
OwnerComp.GetAIOwner()->ClearFocus(EAIFocusPriority::Gameplay);//清除AI聚焦玩家
UBTTask_BlackboardBase节点API
更容易地在行为树中使用黑板系统不用通过InitializeFromAsset初始化任务节点数据
UBTDecorator装饰器节点API
虚函数
-
CalculateRawConditionValue
: 这个函数用于计算装饰器节点的原始条件值。装饰器节点通常会根据某些条件来决定是否修改或中断其子节点的执行。在CalculateRawConditionValue
函数中,你可以编写逻辑来计算装饰器节点的原始条件值,以便在运行时正确地影响其子节点的执行。 -
InitializeFromAsset
: 该函数用于从行为树资产(BehaviorTree Asset)中初始化装饰器节点的属性和配置。与任务节点类似,装饰器节点也可以从行为树资产中读取默认参数、执行条件等信息,并在InitializeFromAsset
函数中将其应用到装饰器节点上,以确保在行为树运行时正确地执行装饰逻辑。 -
TickNode
: 这个函数用于处理装饰器节点的每帧更新逻辑。与任务节点的TickTask
函数类似,TickNode
函数允许装饰器节点在每一帧都进行逻辑更新,以便根据需要修改其子节点的执行状态或行为。
UBTService装饰器节点API
OnInstanceCreated
函数是在节点实例创建时被调用的特殊函数。这个函数允许你在节点实例化时执行特定的初始化逻辑或设置节点实例的初始状态。
可导航半径范围内随机位置
-
OwnerComp.GetAIOwner()
:这是获取当前行为树任务拥有者的 AI 控制器的函数调用。AI 控制器是负责控制游戏世界中 AI 行为的对象,通过它可以获取到当前任务的执行者。 -
OriginPos
:这是一个表示原始位置的 FVector 类型的参数。它指定了随机位置的原点,即随机位置将以这个原点为中心进行选择。 -
NavPos
:这是一个 FVector 类型的引用参数。在函数执行完成后,随机选择的位置信息将存储在这个参数中,可以通过它来获取随机位置的具体坐标信息。 -
RandomRange
:这是一个浮点数类型的参数,表示随机位置选择的半径范围。随机位置将在以OriginPos
为中心,半径为RandomRange
的圆形区域内进行选择。
UNavigationSystemV1::K2_GetRandomLocationInNavigableRadius(OwnerComp.GetAIOwner(), OriginPos, NavPos, RandomRange)