前言:
笔者目前在校本科大三,目标方向是人工智能、计算机视觉。上一个OpenCV学习笔记专栏已完结,在学习完OpenCV后,我继续学习C++,并用纯C++做UE4项目的方式继续提升自己的水平。
梁迪老师的水平非常高,他的课程本来也无需笔记:课程本身即为最好的笔记。但由于我天赋有限,还是边看边记,以防遗忘——知识点太多,步骤太繁杂了。在学习过程中,我也偶有思考,思索为什么某个方法老师要这样做。所以,一是为了记录,二是为了分享,才有了这个专栏。
内容方面,由于我在开启这个专栏时,此项目已经做完很多了。所以,前期的一些大篇幅叙述的知识,可能在后期应用中一带而过。以及,前期的一些知识,后期会重新剖析,并加上我的个人理解。
另外,若有学术交流/学业交流意愿,可以邮件联系1246210283@qq.com,希望一齐进步。
本篇学习内容:
40.准星
41.射线检测(续第12篇)
42.行为状态机
40.准星
实现目标:在屏幕中央有一个圆形白色准星,识别到物体时变为绿色,开始砍石头时变成一个大的圆形进度条。
(1)在UE4编辑器内新建一个材质,注意设置Material的Material domain为User Interface,Blend Mode设为Masked,这样材质可以附到笔刷上。
(2)设置好这个材质,随后创建C++文件来编辑这个准心
(3)创建一个SlateWidget,完成准备工作。
(4)写SlateWidget的结构:一个大小可变的SBox,里面有SImage绑定了创建的材质实例。同时,定义了一个指针指向材质实例,通过修改材质实例的参数来动态修改准星状态。
(5)修改材质实例的参数要绑定到PlayerController下:先在Playercontroller下声明一个委托并实例化,然后在GameHUD下绑定、调用(即ExcuteIfBound在PlayerController的Tick函数中)
41.射线检测(续第12篇)
此小节接续39小节。
实现目标:在玩家头部发出一道射线,一旦检测到了物品,就在游戏上方的显示框内显示物品的名字。
(1)在PlayerController下创建三个private方法,并实现。
//射线检测结果
FHitResult RayGetHitResult(FVector TraceStart, FVector TraceEnd);
//射线绘制
void DrawRayLine(FVector StartPos, FVector EndPos, float Duration);
//进行射线检测
void RunRayCast();
FHitResult ASlAiPlayerController::RayGetHitResult(FVector TraceStart, FVector TraceEnd)
{
FCollisionQueryParams TraceParams(true); //定义射线检测的一些参数
TraceParams.AddIgnoredActor(SPCharacter);
TraceParams.bTraceAsyncScene = true;
TraceParams.bReturnPhysicalMaterial = false; //是否返回物理材质
TraceParams.bTraceComplex = true; //检测混合的碰撞
FHitResult Hit(ForceInit);
//GameTraceChannel1是我们在UE4编辑器内新建的Trace通道的第一个。
if (GetWorld()->LineTraceSingleByChannel(Hit, TraceStart, TraceEnd, ECollisionChannel::ECC_GameTraceChannel1, TraceParams)) {
DrawRayLine(TraceStart, TraceEnd, 5.f);
}
return Hit;
}
(2)在RunRayCast()内创建保存是否检测到物品的变量,和检测到物品的AActor。
//是否检测到物品
bool IsDetected = false;
RayActor = Hit.GetActor();
(3)关于从检测到的物品获取其文字信息,在PickupObject内实现。在ResourceObject内也实现一个功能一样的同名函数。
//获取物品信息
FText GetInfoText() const;
(4)配合实现PlayerState下的GetRayInfoText()函数,这是之前定义的委托。
在.h中定义RayInfoText:
//射线检测信息Text,由PlayerController进行更新
FText RayInfoText;
FText ASlAiPlayerState::GetRayInfoText() const
{
return RayInfoText;
}
(5)完善RunRayCast()函数,如果RayActor转为ASlAiPickupObject或者ASlAiResourceObject成功,就修改游戏显示框内物品的名字。
void ASlAiPlayerController::RunRayCast()
{
//初始化起始位置和结束位置
FVector StartPos(0.f);
FVector EndPos(0.f);
switch (SPCharacter->GameView)
{
case EGameViewMode::First:
StartPos = SPCharacter->FirstCamera->K2_GetComponentLocation();
EndPos = StartPos + SPCharacter->FirstCamera->GetForwardVector() *2000.f;
break;
case EGameViewMode::Third:
StartPos = SPCharacter->ThirdCamera->K2_GetComponentLocation();
StartPos = StartPos + SPCharacter->ThirdCamera->GetForwardVector() * 300.f;
EndPos = StartPos + SPCharacter->ThirdCamera->GetForwardVector() * 2000.f;
break;
}
//是否检测到物品
bool IsDetected = false;
FHitResult Hit = RayGetHitResult(StartPos, EndPos);
RayActor = Hit.GetActor();
//更好的做法是创建一个基类,PickupObject和ResourceObject都是它的子类,然后实现一个虚函数GetInfoText()
if (Cast<ASlAiPickupObject>(RayActor)) {
//如果Cast成功
IsDetected = true;
SPState->RayInfoText = Cast<ASlAiPickupObject>(RayActor)->GetInfoText();
}
if (Cast<ASlAiResourceObject>(RayActor)) {
IsDetected = true;
SPState->RayInfoText = Cast<ASlAiResourceObject>(RayActor)->GetInfoText();
}
if (Cast<ASlAiEnemyCharacter>(RayActor)) {
IsDetected = true;
SPState->RayInfoText = Cast<ASlAiEnemyCharacter>(RayActor)->GetInfoText();
}
//如果什么都没有检测到那就设置信息为无
if (!IsDetected) {
SPState->RayInfoText = FText();
}
}
42.行为状态机
实现目标:准星在检测到树木、石头时变为绿色,开始砍树时变大且变为进度条状态。在射线识别到可拾取物品时,右手动作要改为拾取。
(1)以上实现目标均由StateMachine()函数完成。为了完成这个函数,需要完善很多东西。
(2)在PlayerState中实现GetAffectRange()方法,用来获取手上物品的攻击距离。
(3)在ResourceObject中定义血量、基础血量,并在BeginPlay()中获取。
(4)在ResourceObject中实现获取资源类型、获取血量百分比、获取伤害函数。
(5)在PlayerState中实现获取伤害值函数,因为伤害值随手上物品类型和资源类型而变化。
(6)在PickupObject中实现被拾取函数。
(7)补充StateMachine()函数:
void ASlAiPlayerController::StateMachine()
{
//如果动作比较多,比较复杂。更好的做法是写几个状态机的类,进行控制。
//普通模式
ChangePreUpperType(EUpperBody::None);
if (!Cast<ASlAiResourceObject>(RayActor) && !Cast<ASlAiHandObject>(RayActor)) {
//如果不是资源物品或可拾取物品,准星显示白色
UpdatePointer.ExecuteIfBound(false, 1.f);
}
//如果检测到资源
if (Cast<ASlAiResourceObject>(RayActor)) {
//如果鼠标左键没有按下,准星锁定模式
if (!IsLeftButtonDown) {
UpdatePointer.ExecuteIfBound(false, 0.f);
}
//如果左键已经按下,并且目标和人物的距离小于攻击范围,就开始打这个目标
if (IsLeftButtonDown && FVector::Distance(RayActor->GetActorLocation(), SPCharacter->GetActorLocation())< SPState->GetAffectRange()) {
//获取实际伤害
int Damage = SPState->GetDamageValue(Cast<ASlAiResourceObject>(RayActor)->GetResourceType());
//让资源受到伤害并且获取剩余血量百分比
float Range = Cast<ASlAiResourceObject>(RayActor)->TakeObjectDamage(Damage)->GetHPRange();
//更新准星
UpdatePointer.ExecuteIfBound(true, Range);
}
}
//如果检测到可拾取物品,并且两者的距离小于300,就可以拾取
if (Cast<ASlAiPickupObject>(RayActor) && FVector::Distance(RayActor->GetActorLocation() , SPCharacter->GetActorLocation()) < 300.f) {
//改变右手预动作为拾取
ChangePreUpperType(EUpperBody::PickUp);
//修改准星锁定模式
UpdatePointer.ExecuteIfBound(false, 0.f);
//如果右键按下
if (IsRightButtonDown) {
//把物品捡起来
Cast<ASlAiPickupObject>(RayActor)->TakePickup();
}
}
}