139 lines
8.2 KiB
Markdown
139 lines
8.2 KiB
Markdown
|
## 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中。
|
|||
|
|
|||
|
初始化输入组件
|
|||
|
```c++
|
|||
|
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())));
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
```c++
|
|||
|
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的网络预测逻辑)
|
|||
|
```c++
|
|||
|
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只在运行时候会有一点滞后。
|
|||
|
|
|||
|
|
|||
|
## 使用GameplayTasks制作Combo技能,武器的碰撞判定必须使用同步函数,也就是在AnimNotify中调用
|