[UE4入门笔记(18)] 53.敌人攻击AI 54.游戏暂停菜单 --梁迪老师UE4纯C++&Slate开发沙盒游戏

前言:

笔者目前在校本科大三,目标方向是人工智能、计算机视觉。上一个OpenCV学习笔记专栏已完结,在学习完OpenCV后,我继续学习C++,并用纯C++做UE4项目的方式继续提升自己的水平。

梁迪老师的水平非常高,他的课程本来也无需笔记:课程本身即为最好的笔记。但由于我天赋有限,还是边看边记,以防遗忘——知识点太多,步骤太繁杂了。在学习过程中,我也偶有思考,思索为什么某个方法老师要这样做。所以,一是为了记录,二是为了分享,才有了这个专栏。

内容方面,由于我在开启这个专栏时,此项目已经做完很多了。所以,前期的一些大篇幅叙述的知识,可能在后期应用中一带而过。以及,前期的一些知识,后期会重新剖析,并加上我的个人理解。

另外,若有学术交流/学业交流意愿,可以邮件联系1246210283@qq.com,希望一齐进步。


本篇学习内容:

53.敌人攻击AI
54.游戏暂停菜单


53.敌人攻击AI

(1)动作修改
敌人的攻击动作中,有些动作是伴随移动的。而UE4提供的Root Motion下的EnableRootMotion并不能在固定根骨骼的同时不固定它的缩放等属性,所以需要在代码里修改。

if (Montage_IsPlaying(AnimAttack_III))
	{
		CurrentPlayTime += DeltaSeconds;
		CurrentPlayTime = FMath::Clamp<float>(CurrentPlayTime, 0.f, AnimAttackSeq_III->GetPlayLength());
		FTransform OutputTrans;
		AnimAttackSeq_III->GetBoneTransform(OutputTrans, 0.f, CurrentPlayTime, true);
		RootBonePos = FVector(OutputTrans.GetLocation().X, StartYPos, OutputTrans.GetLocation().Z);
	}

(2)在动作蓝图里设置动作

(3)创建文件TaskAttackSwitch、TaskAttackDash、TaskAttackFollow、TaskAttackNormal、TaskAttackPursuit、TaskRotate,均继承于TaskBase

(4)写TaskAttackSwitch、TaskAttackDash、TaskAttackNormal

主要都是写执行函数。但是TaskAttackDash要额外多一些:

//重写执行函数
		virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;

		//重写终止任务函数 跳出此任务,到上一个父节点时执行
		virtual	EBTNodeResult::Type AbortTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;

protected:

	//动作结束后事件
	void OnAnimationTimerDone();

protected:

	UPROPERTY(EditAnywhere, Category = "Blackboard")
		struct FBlackboardKeySelector Destination;

	UPROPERTY(EditAnywhere, Category = "Blackboard")
		struct FBlackboardKeySelector WaitTime;

	//攻击动作结束后的委托
	FTimerHandle TimerHandle;

注:实际上通过动画的时长来判定运行时间是不合理的,因为运行游戏时帧数低就会导致运行时间很长。最好的方法是在动画播放的最后一帧添加一个通知,让这个通知来告诉行为树:这个动画已经播放完了。

EBTNodeResult::Type USlAiEnemyTaskAttackDash::ExecuteTask(UBehaviorTreeComponent & OwnerComp, uint8 * NodeMemory)
{
	//如果初始化敌人参数不成功,直接返回失败
	if (!InitEnemyElement(OwnerComp)) return EBTNodeResult::Failed;

	//播放冲刺动画 实际上通过动画的时长来判定运行时间是不合理的,因为运行游戏时帧数低就会导致运行时间很长。
	//最好的方法是在动画播放的最后一帧添加一个通知,让这个通知来告诉行为树:这个动画已经播放完了。
	float AttackDuration = SECharacter->PlayAttackAction(EEnemyAttackType::EA_Dash);
	//范围是0
	const float ChaseRadius = 5.f;
	//获取玩家到敌人之间的单位向量
	FVector SPToSE = SEController->GetPlayerLocation() - SECharacter->GetActorLocation();
	SPToSE.Normalize();
	//探索起点:玩家位置减去与敌人之间距离的一点点
	const FVector ChaseOrigin = SEController->GetPlayerLocation() - 20.f * SPToSE;
	//保存随机位置
	FVector DesLoc(0.f);
	//使用导航系统获取随机点
	UNavigationSystem::K2_GetRandomReachablePointInRadius(SEController, ChaseOrigin, DesLoc, ChaseRadius);
	//角色速度
	float Speed = (FVector::Distance(SECharacter->GetActorLocation(), DesLoc)) / AttackDuration + 30.f;
	//修改敌人的速度
	SECharacter->SetMaxSpeed(Speed);

	//修改参数
	OwnerComp.GetBlackboardComponent()->SetValueAsVector(Destination.SelectedKeyName, DesLoc);
	OwnerComp.GetBlackboardComponent()->SetValueAsFloat(WaitTime.SelectedKeyName, AttackDuration);

	//添加事件委托 修改速度为300
	FTimerDelegate TimerDele = FTimerDelegate::CreateUObject(this, &USlAiEnemyTaskAttackDash::OnAnimationTimerDone);
	//注册到事件管理器
	SEController->GetWorld()->GetTimerManager().SetTimer(TimerHandle, TimerDele, AttackDuration, false);

	return EBTNodeResult::Succeeded;
}

EBTNodeResult::Type USlAiEnemyTaskAttackDash::AbortTask(UBehaviorTreeComponent & OwnerComp, uint8 * NodeMemory)
{
	//如果初始化敌人参数不成功,或者事件句柄没有激活,直接返回失败
	if (!InitEnemyElement(OwnerComp) || !TimerHandle.IsValid()) return EBTNodeResult::Aborted;
	//卸载事件委托
	SEController->GetWorld()->GetTimerManager().ClearTimer(TimerHandle);

	return EBTNodeResult::Aborted;
}

void USlAiEnemyTaskAttackDash::OnAnimationTimerDone()
{
	//重新设置速度为300
	if (SECharacter)SECharacter->SetMaxSpeed(300.f);
}

(5)写TaskAttackPursuit,基本逻辑和TaskAttackDash类似

(6)写TaskAttackFollow和TaskAttackRotate

(7)设置行为树

注:UE4自带的Rotate to face BB entry 是很生硬的旋转,所以我们在TaskPursuit下使用TaskRotate写的旋转方法:在EnemyCharacter的帧函数下用插值的方式旋转。

注:Time Limit + Move To 可以用Service里的帧函数实现。但是那个帧函数的帧率是不稳定的,所以这里还是用Time Limit + Move To。

54.游戏暂停菜单

(1)根组件是SBox,其下创建一个SVerticalBox,再创建

TArray<TSharedPtr<SCompoundWidget>> MenuItemList;
TArray<TSharedPtr<SCompoundWidget>>	OptionItemList;

保存组件信息。

(2)用一个函数InitializeWidget()来初始化MenuItemList和OptionItemList。初始化之后,给VertBox添加MenuItemList的组件。

(3)写4个按钮对应的事件。有些事件需要重置菜单按钮。如:

FReply SSlAiGameMenuWidget::GoBackEvent()
{
	//清空VertBox
	VertBox->ClearChildren();

	//填充菜单按钮
	for (TArray<TSharedPtr<SCompoundWidget>>::TIterator It(MenuItemList); It; ++It) {
		VertBox->AddSlot()
			.HAlign(HAlign_Fill)
			.VAlign(VAlign_Fill)
			.Padding(10.f)
			.FillHeight(1.f)
			[
				(*It)->AsShared()
			];
	}
	//设置RootBox高度
	RootBox->SetHeightOverride(400.f);
	return FReply::Handled();
}

(4)准备ChangeCulture和ChangeVolume,绑定到SNew的SSlAiGameOptionWidget下。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值