2023-06-29 11:55:02 +08:00

8.2 KiB
Raw Blame History

ActionRPG项目的问题

联机

攻击判定问题

Overlap无法返回PhysicsBody的BodyIndex

因为当前物理系统使用Physx的关系所以Overlap事件中SweepResult的HitBoneName返回是的None。

如果取得对应的骨骼

  1. 当前版本4.26,射线是可以取得对应的骨骼的。
  2. 如果是角色自己跑进 Overlap空间里时返回的BodyIndex是-1但Overlap空间自己移动碰到角色时是可以正确返回BodyIndex

武器逻辑

LCMCharacter中的实现

LCMCharacter里通过在构造函数里创建Mesh并且连接到角色模型的插槽

WeaponMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Weapon"));
WeaponMesh->SetupAttachment(GetMesh(), TEXT("Sword_2"));

ActionRPG中的逻辑

创建一个WeaponActor变量之后通过调用DetachFromActor移除当前武器再调用AttachActorToComponent 在AnimNotifyState的Begin与End事件中取得Character基类取得WeaponActor变量再开启与关闭攻击判定。

使用GameplayTask监听输入事件

InputComponent定义于Actor.h中。

初始化输入组件

void UUserWidget::InitializeInputComponent()
{
	if ( APlayerController* Controller = GetOwningPlayer() )
	{
		InputComponent = NewObject< UInputComponent >( this, UInputSettings::GetDefaultInputComponentClass(), NAME_None, RF_Transient );
		InputComponent->bBlockInput = bStopAction;
		InputComponent->Priority = Priority;
		Controller->PushInputComponent( InputComponent );
	}
	else
	{
		FMessageLog("PIE").Info(FText::Format(LOCTEXT("NoInputListeningWithoutPlayerController", "Unable to listen to input actions without a player controller in {0}."), FText::FromName(GetClass()->GetFName())));
	}
}
void UUserWidget::ListenForInputAction( FName ActionName, TEnumAsByte< EInputEvent > EventType, bool bConsume, FOnInputAction Callback )
{
	if ( !InputComponent )
	{
		InitializeInputComponent();
	}

	if ( InputComponent )
	{
		FInputActionBinding NewBinding( ActionName, EventType.GetValue() );
		NewBinding.bConsumeInput = bConsume;
		NewBinding.ActionDelegate.GetDelegateForManualSet().BindUObject( this, &ThisClass::OnInputAction, Callback );

		InputComponent->AddActionBinding( NewBinding );
	}
}

void UUserWidget::StopListeningForInputAction( FName ActionName, TEnumAsByte< EInputEvent > EventType )
{
	if ( InputComponent )
	{
		for ( int32 ExistingIndex = InputComponent->GetNumActionBindings() - 1; ExistingIndex >= 0; --ExistingIndex )
		{
			const FInputActionBinding& ExistingBind = InputComponent->GetActionBinding( ExistingIndex );
			if ( ExistingBind.GetActionName() == ActionName && ExistingBind.KeyEvent == EventType )
			{
				InputComponent->RemoveActionBinding( ExistingIndex );
			}
		}
	}
}

void UUserWidget::StopListeningForAllInputActions()
{
	if ( InputComponent )
	{
		for ( int32 ExistingIndex = InputComponent->GetNumActionBindings() - 1; ExistingIndex >= 0; --ExistingIndex )
		{
			InputComponent->RemoveActionBinding( ExistingIndex );
		}

		UnregisterInputComponent();

		InputComponent->ClearActionBindings();
		InputComponent->MarkPendingKill();
		InputComponent = nullptr;
	}
}

  • bConsumeInput是否应该消耗掉这次输入控制是否传递到下一个InputStack中的对象

UE4对玩家输入的处理规则

有关FScopedPredictionWindowAbilityTask的网络预测逻辑

FScopedPredictionWindow ScopedPrediction(AbilitySystemComponent, IsPredictingClient());

if (IsPredictingClient())
{
	// Tell the server about this
	AbilitySystemComponent->ServerSetReplicatedEvent(EAbilityGenericReplicatedEvent::InputPressed, GetAbilitySpecHandle(), GetActivationPredictionKey(), AbilitySystemComponent->ScopedPredictionKey);
}
else
{
	AbilitySystemComponent->ConsumeGenericReplicatedEvent(EAbilityGenericReplicatedEvent::InputPressed, GetAbilitySpecHandle(), GetActivationPredictionKey());
}

// We are done. Kill us so we don't keep getting broadcast messages
if (ShouldBroadcastAbilityTaskDelegates())
{
	OnPress.Broadcast(ElapsedTime);
}
  • IsPredictingClient判断这个Ability的类型是否为locally predicted本地预测形如果是的就需要告诉Server。

UE4 GameplayAbilitySystem Prediction https://zhuanlan.zhihu.com/p/143637846

InstancingPolicy

技能执行是后通常会有一个生成技能的新对象用于定位追踪该技能。但是大多数时候技能需要频繁的创建使用可能需要会出现快速实例技能对象对性能产生一定的性能影响。AbilitySystem提供给了三种实例化技能的策略

  • 按执行实例化Instanced per Execution这就是前面提到的每执行一个技能时候都会实例化一个技能对象但是如果技能被频繁的调用时候该技能就会有一定的运行开销。但是优点在于由于这个技能重新运行时候会重新生成一个实例对象因而该技能中的变量也会初始化结束技能时候不必考虑重置变量、状态等问题。如果你的技能不是很频繁使用的化可以考虑使用这种的执行策略。
  • 按Actor实例化Instanced per Actor当技能首次被执行后后面每次执行这个技能时候都不会被实例化会重复使用这一个对象。这个和上面的Instanced per Execution策略相反每次执行这个技能后需要清理这个技能的变量和状态工作。这种策略适用于频繁使用某个技能使用使用可能提要执行效率。并且因为技能具有可处理变量和RPC的复制对象而不是浪费网络带宽和CPU时间在每次运行时产生新对象。
  • 非实例化Non-Instanced故名思意该策略的技能被执行时候不会实例化技能对象而是技能的CDO对象。这种策略是优越于上面的两种策略的但是也有一个很大限制---这个策略要求这个技能完全都是由C++编写的因为蓝图创建图标时候需要对象实例并且即使是C++编写这个技能该技能也不能更改其成员变量、不能绑定代理、不能复制变量、不能RPC。因此这个技能在游戏中应用的相对较少但是一些简单的AI技能可以用到。

NetExecutionPolicy

  • 本地预测Local Predicted本地预测是指技能将立即在客户端执行执行过程中不会询问服务端的正确与否但是同时服务端也起着决定性的作用如果服务端的技能执行失败了客户端的技能也会随之停止并“回滚”。如果服务端和客户端执行的结果不矛盾客户端会执行的非常流畅无延时。如果技能需要实时响应可以考虑用这种预测模式。下文会着重介绍这个策略。
  • 仅限本地Local Only仅在本地客户端运行的技能。这个策略不适用于专用服务器游戏本地客户端执行了该技能服务端任然没有被执行在有些情况下可能出现拉扯情况原因在于服务端技能没有被执行技能中的某个状态和客户端不一致。
  • 服务器启动Server Initiated技能将在服务器上启动并PRC到客户端也执行。可以更准确地复制服务器上实际发生的情况但是客户端会因缺少本地预测而发生短暂延迟。这种延迟相对来说是比较低的但是相对于Local Predicted来说还是会牺牲一点流畅性。之前我遇到一种情况一个没有预测的技能A和一个带有预测的技能BA执行了后B不能执行 现在同时执行技能A和技能B 由于A需要等待服务器端做验证B是本地预测技能所以B的客户端会瞬间执行导致B的客户端被执行过了服务端失败出现了一些不可预料的问题了这种情况需要将技能B的网络策略修改为Server Initiated这样就会以服务端为权威运行虽然会牺牲点延迟但是也是可以在游戏接收的范围内有时候需要在此权衡。
  • 仅限服务器Server Only技能将在只在服务器上运行客户端不会。服务端的技能被执行后技能修改的任何变量都将被复制并会将状态传递给客户端。缺点是比较服务端的每个影响都会由延迟同步到客户端端Server Initiated只在运行时候会有一点滞后。

使用GameplayTasks制作Combo技能武器的碰撞判定必须使用同步函数也就是在AnimNotify中调用