8.2 KiB
8.2 KiB
ActionRPG项目的问题
联机
攻击判定问题
Overlap无法返回PhysicsBody的BodyIndex
因为当前物理系统使用Physx的关系,所以Overlap事件中SweepResult的HitBoneName返回是的None。
如果取得对应的骨骼
- 当前版本4.26,射线是可以取得对应的骨骼的。
- 如果是角色自己跑进 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对玩家输入的处理规则
有关FScopedPredictionWindow(AbilityTask的网络预测逻辑)
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,和一个带有预测的技能B,A执行了后B不能执行, 现在同时执行技能A和技能B, 由于A需要等待服务器端做验证,B是本地预测技能所以B的客户端会瞬间执行,导致B的客户端被执行过了,服务端失败,出现了一些不可预料的问题了,这种情况需要将技能B的网络策略修改为Server Initiated,这样就会以服务端为权威运行,虽然会牺牲点延迟,但是也是可以在游戏接收的范围内,有时候需要在此权衡。
- 仅限服务器:(Server Only)技能将在只在服务器上运行,客户端不会。服务端的技能被执行后,技能修改的任何变量都将被复制,并会将状态传递给客户端。缺点是比较服务端的每个影响都会由延迟同步到客户端端,Server Initiated只在运行时候会有一点滞后。