精华内容
下载资源
问答
  • UE4 移动物体的几种方法
    千次阅读
    2021-01-14 05:05:01

    1,Actor->SetActorLocation

    Actor->SetActorLocation()

    2,AActor::AddActorWorldOffset(), AActor::AddActorLocalOffset()

    AddActorWorldOffset与AddActorLocalOffset区别:如果期望Actor沿着某个世界坐标系方向移动,那么使用AddActorWorldOffset并且参数为世界坐标系的DeltaLocation;如果期望Actor沿着当前Actor局部坐标系方向移动,那么使用AddActorLocalOffset并且参数为相对当前Actor的DeltaLocation Offset(比如想做螺旋轨迹但又不想计算其世界空间坐标,那么就用LocalOffset)。

    如果使用AddActorWorldOffset或者AddActorLocalOffset移动Character,那么MovementMode必须设置为fly,否则当DeltaLocation较小时,角色会始终往下掉(即使禁用物理模拟),UCharacterMovementComponent::SetMovementMode(EMovementMode::MOVE_Flying);。或者Unpossess Controller。

    3,Velocity

    ACharacter->GetCharacterMovement()->Velocity += FVector(5.f, 5.f, 0.f);

    4,将一个Controller(PlayerController或者AIController)possess到一个Actor上,然后调用:

    Controller->MoveTo();

    5,将一个Controller(PlayerController或者AIController)possess到一个Actor上,然后调用

    GetWorld()->GetNavigationSystem()->SimpleMoveToLocation(Controller, DestLocation);

    注意:如果使用Controller->MoveTo或者使用NavigationSystem的Move函数,前提条件是你使用了Navigation组件并build了地形,否则无效。

    6,APawn->AddMovementInput

    APawn->AddMovementInput(FVector WorldDirection, float ScaleValue = 1.0f, bool bForce = false);

    其中WorldDirection是方向,ScaleValue是速率倍速,bForce表示是否忽略Controller中的IgnoreMoveInput属性值,强制移动。

    7,UCharacterMovementComponent::AddImpulse

    void UCharacterMovementComponent::AddImpulse( FVector Impulse, bool bVelocityChange )

    AddImpulse 一般用来做投掷、爆炸、击飞等物理效果。添加的是一个瞬间的力,之后就不需要每帧做处理了。

    注意:AddImpulse 作用对象一般都是 StaticMeshComponent ,而不能是 CollisionComponent,否则无效。且 StaticMeshComponent 要开启物理:SetSimulatePhysics(true) ,否则也无效。

    8,UCharacterMovementComponent::AddForce

    void UCharacterMovementComponent::AddForce( FVector Force )

    如果想让物体保持移动,需要每帧都执行AddForce()函数,也就说如果加速度是实时变化的,那么就可以用AddForce。 两者的区别:

    AddForce accounts for delta time and should be used for applying force over more than one frame, AddImpulse does not account for delta time and should be used for single ‘pushes‘, like from an explosion or being thrown by a player. The reason is that if you use AddForce for throwing or an explosion, how far the object moves depends on the framerate at the exact frame the force was applied, rather than being independent of framerate like AddImpulse is.

    9,UKismetSystemLibrary::MoveComponentTo

    FLatentActionInfo ActionInfo;

    ActionInfo.CallbackTarget= this;

    UKismetSystemLibrary::MoveComponentTo(TopDownCameraComponent, Location, Rotation,false, false, 1.f, true, EMoveComponentAction::Move, ActionInfo);

    一般用来移动Actor身上的Component,例如CameraComponent等。支持平滑移动,可以设置移动到目标Location、Rotation过程的时长。

    原文:https://www.cnblogs.com/sevenyuan/p/11850076.html

    更多相关内容
  • UE4:当前加速度由来

    2021-04-06 17:17:24
    UE4 加速的的获取

    UE4加速度

    获取最大的加速度(2048)或者当前的加速度
    加速度

    如何计算加速度

    通过调用移动组件AddInputVector进行累加到ControlInputVector变量,这个变量将会在TickComponent的ConsumeInputVector消耗掉并变成加速度。这个加速度的Z方向会根据当前模式变成0,比如行走或者跳跃,最后乘上最大加速度变成最终加速度。
    加速度由来

    展开全文
  • 前言关于UE4的移动组件,我写了一篇非常详细的分析文档。由于篇幅比较大,我将其拆分成三个部分。分别从移动框架与实现原理,移动的网络同步,移动组件的优化与改造三个方面来写。这三篇文档中难免有问题和漏洞,...

    前言关于UE4的移动组件,我写了一篇非常详细的分析文档。由于篇幅比较大,我将其拆分成三个部分。分别从移动框架与实现原理,移动的网络同步,移动组件的优化与改造三个方面来写。这三篇文档中难免有问题和漏洞,所以我也会在发现问题时及时更新和修改,也希望大家能给出一些建议。

    一.深刻理解移动组件的意义

    在大部分游戏中,玩家的移动是最最核心的一个基本操作。UE提供的GamePlay框架就给开发者提供了一个比较完美的移动解决方案。由于UE采用了组件化的设计思路,所以这个移动解决方案的核心功能就都交给了移动组件来完成。移动可能根据游戏的复杂程度有不同的处理,如果是一个简单的俯视视角RTS类型的游戏,可能只提供基本的坐标移动就可以了;而对于第一人称的RPG游戏,玩家可能上天入地,潜水飞行,那需要的移动就要更复杂一些。但是不管是哪一种,UE都基本上帮我们实现了,这也得益于其早期的FPS游戏的开发经验。

    然而,引擎提供的基本移动并不一定能完成我们的目标,我们也不应该因此局限我们的设计。比如轻功的飞檐走壁,魔法飞船的超重力,弹簧鞋,喷气背包飞行控制,这些效果都需要我们自己去进一步的处理移动逻辑,我们可以在其基础上修改,也可以自定义自己的移动模式。不管怎么样,这些操作都需要对移动组件进行细致入微的调整,所以我们就必须要深刻理解移动组件的实现原理。

    再者,在一个网络游戏中,我们对移动的处理会更加的复杂。如何让不同客户端的玩家都体验到流畅的移动表现?如何保证角色不会由于一点点的延迟而产生“瞬移”?UE对这方面的处理都值得我们去学习与思考。

    移动组件看起来只是一个和移动相关的组件,但其本身涉及到状态机,同步解决方案,物理模块,不同移动状态的细节处理,动画以及与其他组件(Actor)之间的调用关系等相关内容,足够花上一段时间去好好研究。这篇文章会从移动的基本原理,移动状态的细节处理,移动同步的解决方案几个角度尽可能详细的分析其实现原理,然后帮助大家快速理解并更好的使用移动组件。最后,给出几个特殊移动模式的实现思路供大家参考。

    二.移动实现的基本原理

    2.1 移动组件与玩家角色

    角色的移动本质上就是合理的改变坐标位置,在UE里面角色移动的本质就是修改某个特定组件的坐标位置。图2-1是我们常见的一个Character的组件构成情况,可以看到我们通常将CapsuleComponent(胶囊体)作为自己的根组件,而Character的坐标本质上就是其RootComponent的坐标,Mesh网格等其他组件都会跟随胶囊体而移动。移动组件在初始化的时候会把胶囊体设置为移动基础组件UpdateComponent,随后的操作都是在计算UpdateComponent的位置。

    图2-1 一个默认Character的组件构成

    当然,我们也并不是一定要设置胶囊体为UpdateComponent,对于DefaultPawn(观察者)会把他的SphereComponent作为UpdateComponent,对于交通工具对象AWheeledVehicle会默认把他的Mesh网格组件作为UpdateComponent。你可以自己定义你的UpdateComponent,但是你的自定义组件必须要继承USceneComponent(换句话说就是组件得有世界坐标信息),这样他才能正常的实现其移动的逻辑。

    2.2 移动组件继承树

    移动组件类并不是只有一个,他通过一个继承树,逐渐扩展了移动组件的能力。从最简单的提供移动功能,到可以正确模拟不同移动状态的移动效果。如图2-2所示

    图2-2 移动组件继承关系类图

    移动组件类一共四个。首先是UMovementComponent,作为移动组件的基类实现了基本的移动接口SafeMovementUpdatedComponent(),可以调用UpdateComponent组件的接口函数来更新其位置。

    bool UMovementComponent::MoveUpdatedComponentImpl( const FVector& Delta, const FQuat& NewRotation, bool bSweep, FHitResult* OutHit, ETeleportType Teleport)

    if (UpdatedComponent)

    const FVector NewDelta = ConstrainDirectionToPlane(Delta);

    return UpdatedComponent->MoveComponent(NewDelta, NewRotation, bSweep, OutHit, MoveComponentFlags, Teleport);

    return false;

    通过上图可以看到UpdateComponent的类型是UScenceComponent,UScenceComponent类型的组件提供了基本的位置信息——ComponentToWorld,同时也提供了改变自身以及其子组件的位置的接口InternalSetWorldLocationAndRotation()。而UPrimitiveComponent又继承于UScenceComponent,增加了渲染以及物理方面的信息。我们常见的Mesh组件以及胶囊体都是继承自UPrimitiveComponent,因为想要实现一个真实的移动效果,我们时刻都可能与物理世界的某一个Actor接触着,而且移动的同时还需要渲染出我们移动的动画来表现给玩家看。

    下一个组件是UNavMovementComponent,该组件更多的是提供给AI寻路的能力,同时包括基本的移动状态,比如是否能游泳,是否能飞行等。

    UPawnMovementComponent组件开始变得可以和玩家交互了,前面都是基本的移动接口,不手动调用根本无法实现玩家操作。UPawnMovementComponent提供了AddInputVector(),可以实现接收玩家的输入并根据输入值修改所控制Pawn的位置。要注意的是,在UE中,Pawn是一个可控制的游戏角色(也可以是被AI控制),他的移动必须与UPawnMovementComponent配合才行,所以这也是名字的由来吧。一般的操作流程是,玩家通过InputComponent组件绑定一个按键操作,然后在按键响应时调用Pawn的AddMovementInput接口,进而调用移动组件的AddInputVector(),调用结束后会通过ConsumeMovementInputVector()接口消耗掉该次操作的输入数值,完成一次移动操作。

    最后到了移动组件的重头了UCharacterMovementComponent,该组件可以说是Epic做了多年游戏的经验集成了,里面非常精确的处理了各种常见的移动状态细节,实现了比较流畅的同步解决方案。各种位置校正,平滑处理才达到了目前的移动效果,而且我们不需要自己写代码就会使用这个完成度的相当高的移动组件,可以说确实很适合做第一,第三人称的RPG游戏了。

    其实还有一个比较常用的移动组件,UProjectileMovementComponent ,一般用来模拟弓箭,子弹等抛射物的运动状态。不过,这篇文档不将重点放在这里。

    2.3 移动组件相关类关系简析

    前面主要针对移动组件本身进行了分析,这里更全面的概括一下移动的整个框架。(参考图2-3)

    图2-3 移动框架相关类图

    在一个普通的三维空间里,最简单的移动就是直接修改角色的坐标。所以,我们的角色只要有一个包含坐标信息的组件,就可以通过基本的移动组件完成移动。但是随着游戏世界的复杂程度加深,我们在游戏里面添加了可行走的地面,可以探索的海洋。我们发现移动就变得复杂起来,玩家的脚下有地面才能行走,那就需要不停的检测地面碰撞信息(FFindFloorResult,FBasedMovementInfo);玩家想进入水中游泳,那就需要检测到水的体积(GetPhysicsVolume(),Overlap事件,同样需要物理);水中的速度与效果与陆地上差别很大,那就把两个状态分开写(PhysSwimming,PhysWalking);移动的时候动画动作得匹配上啊,那就在更新位置的时候,更新动画(TickCharacterPose);移动的时候碰到障碍物怎么办,被其他玩家推怎么处理(MoveAlongFloor里有相关处理);游戏内容太少,想增加一些可以自己寻路的NPC,又需要设置导航网格(涉及到FNavAgentProperties);一个玩家太无聊,那就让大家一起联机玩(模拟移动同步FRepMovement,客户端移动修正ClientUpdatePositionAfterServerUpdate)。

    这么一看,做一个优秀移动组件还真不简单。但是不管怎么样,UE基本上都帮你实现了。通过上面的描述,你现在也大体上了解了移动组件在各个方面的处理,不过遇到具体的问题也许还是无从下手,所以咱们继续往下分析。

    三.各个移动状态的细节处理

    这一节我们把焦点集中在UCharacterMovementComponent组件上,来详细的分析一下他是如何处理各种移动状态下的玩家角色的。首先肯定是从Tick开始,每帧都要进行状态的检测与处理,状态通过一个移动模式MovementMode来区分,在合适的时候修改为正确的移动模式。移动模式默认有6种,基本常用的模式有行走、游泳、下落、飞行四种,有一种给AI代理提供的行走模式,最后还有一个自定义移动模式。

    图3-1 单机模式下的移动处理流程

    3.1 Walking

    行走模式可以说是所有移动模式的基础,也是各个移动模式里面最为复杂的一个。为了模拟出出真实世界的移动效果,玩家的脚下必须要有一个可以支撑不会掉落的物理对象,就好像地面一样。在移动组件里面,这个地面通过成员变量FFindFloorResult CurrentFloor来记录。在游戏一开始的时候,移动组件就会根据配置设置默认的MovementMode,如果是Walking,就会通过FindFloor操作来找到当前的地面,CurrentFloor的初始化堆栈如下图3-2(Character Restart()的会覆盖Pawn的Restart()):

    图3-2

    下面先分析一下FindFloor的流程,FindFloor本质上就是通过胶囊体的Sweep检测来找到脚下的地面,所以地面必须要有物理数据,而且通道类型要设置与玩家的Pawn有Block响应。这里还有一些小的细节,比如我们在寻找地面的时候,只考虑脚下位置附近的,而忽略掉腰部附近的物体;Sweep用的是胶囊体而不是射线检测,方便处理斜面移动,计算可站立半径等(参考图3-3,HitResult里面的Normal与ImpactNormal在胶囊体Sweep检测时不一定相同)。另外,目前Character的移动是基于胶囊体实现的,所以一个不带胶囊体组件的Actor是无法正常使用UCharacterMovementComponent的。

    图3-3

    找到了地面玩家就可以站立住么?不一定。这里又涉及到一个新的概念PerchRadiusThreshold,我称他为可栖息范围半径,也就是可站立半径。默认这个值为0,移动组件会忽略这个可站立半径的相关计算,一旦这个值大于0.15,就会做进一步的判断看看当前的地面空间是否足够让玩家站立在上面。

    前面的准备工作完成了,现在正式进入Walking的位移计算,这一段代码都是在PhysWalking里面计算的。为了表现的更为平滑流畅,UE4把一个Tick的移动分成了N段处理(每段的时间不能超过MaxSimulationTimeStep)。在处理每段时,首先把当前的位置信息,地面信息记录下来。在TickComponent的时候根据玩家的按键时长,计算出当前的加速度。随后在CalcVelocity()根据加速度计算速度,同时还会考虑地面摩擦,是否在水中等情况。

    // apply input to acceleration

    Acceleration = ScaleInputAcceleration(ConstrainInputAcceleration(InputVector));

    算出速度之后,调用函数MoveAlongFloor()改变当前对象的坐标位置。在真正调用移动接口SafeMoveUpdatedComponent()前还会简单处理一种特殊的情况——玩家沿着斜面行走。正常在walking状态下,玩家只会前后左右移动,不会有Z方向的移动速度。如果遇到斜坡怎么办?如果这个斜坡可以行走,就会调用ComputeGroundMovementDelta()函数去根据当前的水平速度计算出一个新的平行与斜面的速度,这样可以简单模拟一个沿着斜面行走的效果,而且一般来说上坡的时候玩家的水平速度应该减小,通过设置bMaintainHorizontalGroundVelocity为false可以自动处理这种情况。

    现在看起来我们已经可以比较完美的模拟一个移动的流程了,不过仔细想一下还有一种情况没考虑到。那就是遇到障碍的情况怎么处理?根据我们平时游戏经验,遇到障碍肯定是移动失败,还可能沿着墙面滑动一点。UE里面确实也就是这么处理的,在角色移动的过程中(SafeMoveUpdatedComponent),会有一个碰撞检测流程。由于UPrimitiveComponent组件才拥有物理数据,所以这个操作是在函数UPrimitiveComponent::MoveComponentImpl里面处理的。下面的代码会检测移动过程中是否遇到了障碍,如果遇到了障碍会把HitResult返回。

    FComponentQueryParams Params(PrimitiveComponentStatics::MoveComponentName, Actor);

    FCollisionResponseParams ResponseParam;

    InitSweepCollisionParams(Params, ResponseParam);

    bool const bHadBlockingHit = MyWorld->ComponentSweepMulti(Hits, this, TraceStart, TraceEnd, InitialRotationQuat, Params);

    在接收到SafeMoveUpdatedComponent()返回的HitResult后,会在下面的代码里面处理碰撞障碍的情况。 1. 如果Hit.Normal在Z方向上有值而且还可以行走,那说明这是一个可以移动上去的斜面,随后让玩家沿着斜面移动 2. 判断当前的碰撞体是否可以踩上去,如果可以的话就试着踩上去,如果过程中发现没有踩上去,也会调用SlideAlongSurface()沿着碰撞滑动。

    // UCharacterMovementComponent::PhysWalking

    else if (Hit.IsValidBlockingHit())

    // We impacted something (most likely another ramp, but possibly a barrier).

    float PercentTimeApplied = Hit.Time;

    if ((Hit.Time > 0.f) && (Hit.Normal.Z > KINDA_SMALL_NUMBER) && IsWalkable(Hit))

    // Another walkable ramp.

    const float InitialPercentRemaining = 1.f - PercentTimeApplied;

    RampVector = ComputeGroundMovementDelta(Delta * InitialPercentRemaining, Hit, false);

    LastMoveTimeSlice = InitialPercentRemaining * LastMoveTimeSlice;

    SafeMoveUpdatedComponent(RampVector, UpdatedComponent->GetComponentQuat(), true, Hit);

    const float SecondHitPercent = Hit.Time * InitialPercentRemaining;

    PercentTimeApplied = FMath::Clamp(PercentTimeApplied + SecondHitPercent, 0.f, 1.f);

    if (Hit.IsValidBlockingHit())

    if (CanStepUp(Hit) || (CharacterOwner->GetMovementBase() != NULL && CharacterOwner->GetMovementBase()->GetOwner() == Hit.GetActor()))

    // hit a barrier, try to step up

    const FVector GravDir(0.f, 0.f, -1.f);

    if (!StepUp(GravDir, Delta * (1.f - PercentTimeApplied), Hit, OutStepDownResult))

    UE_LOG(LogCharacterMovement, Verbose, TEXT("- StepUp (ImpactNormal %s, Normal %s"), *Hit.ImpactNormal.ToString(), *Hit.Normal.ToString());

    HandleImpact(Hit, LastMoveTimeSlice, RampVector);

    SlideAlongSurface(Delta, 1.f - PercentTimeApplied, Hit.Normal, Hit, true);

    else

    // Don't recalculate velocity based on this height adjustment, if considering vertical adjustments.

    UE_LOG(LogCharacterMovement, Verbose, TEXT("+ StepUp (ImpactNormal %s, Normal %s"), *Hit.ImpactNormal.ToString(), *Hit.Normal.ToString());

    bJustTeleported |= !bMaintainHorizontalGroundVelocity;

    else if ( Hit.Component.IsValid() && !Hit.Component.Get()->CanCharacterStepUp(CharacterOwner) )

    HandleImpact(Hit, LastMoveTimeSlice, RampVector);

    SlideAlongSurface(Delta, 1.f - PercentTimeApplied, Hit.Normal, Hit, true);

    基本上的移动处理就完成了,移动后还会立刻判断玩家是否进入水中,或者进入Falling状态,如果是的话立刻切换到新的状态。 由于玩家在一帧里面可能会从Walking,Swiming,Falling的等状态不断的切换,所以在每次执行移动前都会有一个iteration记录当前帧的移动次数,如果超过限制就会取消本次的移动模拟行为。

    3.2 Falling

    Falling状态也算是处理Walking以外最常见的状态,只要玩家在空中(无论是跳起还是下落),玩家都会处于Falling状态。与Walking相似,为了表现的更为平滑流畅,Falling的计算也把一个Tick的移动分成了N段处理(每段的时间不能超过MaxSimulationTimeStep)。在处理每段时,首先计算玩家通过输入控制的水平速度,因为玩家在空中也可以受到玩家控制的影响。随后,获取重力计算速度。重力的获取有点意思,你会发现他是通过Volume体积获取的,

    float UMovementComponent::GetGravityZ() const

    return GetPhysicsVolume()->GetGravityZ();

    APhysicsVolume* UMovementComponent::GetPhysicsVolume() const

    if (UpdatedComponent)

    return UpdatedComponent->GetPhysicsVolume();

    return GetWorld()->GetDefaultPhysicsVolume();

    Volume里面会取WorldSetting里面的GlobalGravityZ,这里给我们一个提示,我们可以通过修改代码实现不同Volume的重力不同,实现自定义的玩法。注意,即使我们没有处在任何一个体积里面,他也会给我们的UpdateComponent绑定一个默认的DefaultVolume。那为什么要有一个DefaultVolume?因为在很多逻辑处理上都需要获取DefaultVolume以及里面的相关的数据。比如,DefaultVolume有一个TerminalLimit,在通过重力计算下降速度的时候不可以超过这个设置的速度,我们可以通过修改该值来改变速度的限制。默认情况下,DefaultVolume里面的很多属性都是通过ProjectSetting里面的Physics相关配置来初始化的。参考图3-4

    图3-4

    通过获取到的Gravity计算出当前新的FallSpeed(NewFallVelocity里面计算,计算规则很简单,就是单纯的用当前速度-Gravity*deltaTime)。随后再根据当前以及上一帧的速度计算出位移并进行移动,公式如下

    FVector Adjusted = 0.5f*(OldVelocity + Velocity) * timeTick; SafeMoveUpdatedComponent( Adjusted, PawnRotation, true, Hit);

    前面我们计算完速度并移动玩家后,也一样要考虑到移动碰撞问题。 第一种情况就是正常落地,如果玩家计算后发现碰撞到一个可以站立的地形,那直接调用ProcessLanded进行落地操作(这个判断主要是根据碰撞点的高度来的,可以筛选掉墙面)。

    第二种情况就是跳的过程中遇到一个平台,然后检测玩家的坐标与当前碰撞点是否在一个可接受的范围(IsWithinEdgeTolerance),是的话就执行FindFloor重新检测一遍地面,检测到的话就执行落地流程。

    第三种情况是就是墙面等一些不可踩上去的,下落过程如果碰到障碍,首先会执行HandleImpact给碰到的对象一个力。随后调用ComputeSlideVector计算一下滑动的位移,由于碰撞到障碍后,玩家的速度会有变化,这时候重新计算一下速度,再次调整玩家的位置与方向。如果玩家这时候有水平方向上的位移,还会通过LimitAirControl来限制玩家的速度,毕竟玩家在空中是无法自由控制角色的。对第三种情况做进一步的延伸,可能会出现碰撞调整后又碰到了另一个墙面,这里Falling的处理可以让玩家在两个墙面找到一个合适的位置。但是仍然不能解决玩家被夹在两个斜面但是却无法落地的情况(或者在Waling和Falling中不断切换)。如果有时间,我们后面可以尝试解决这个问题,解决思路可以从FindFloor下的ComputeFloorDist函数入手,目的就是让这个情况下玩家可以找到一个可行走的地面。

     图3-5 夹在缝隙导致不停的切换状态

    3.2.1 Jump

    提到Falling,不得不提跳跃这一基本操作。下面大致描述了跳跃响应的基本流程, 1. 绑定触发响应事件

    void APrimalCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)

    // Set up gameplay key bindings

    check(PlayerInputComponent);

    PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump);

    PlayerInputComponent->BindAction("Jump", IE_Released, this, &ACharacter::StopJumping);

    void ACharacter::Jump()

    bPressedJump = true;

    JumpKeyHoldTime = 0.0f;

    void ACharacter::StopJumping()

    bPressedJump = false;

    ResetJumpState();

    2.一旦按键响应立刻设置bPressedJump为true。TickComponent的帧循环调用ACharacter::CheckJumpInput来立刻检测到是否执行跳跃操作①执行CanJump()函数,处理蓝图里面的相关限制逻辑。如果蓝图里面不重写该函数,就会默认执行ACharacter::CanJumpInternal_Implementation()。这里面是控制玩家能否跳跃的依据,比如蹲伏状态不能跳跃,游泳状态不能跳跃。另外,有一个JumpMaxHoldTime表示玩家按键超过这个值后不会触发跳跃。JumpMaxCount表示玩家可以执行跳跃的段数。(比如二段跳)

    ②执行CharacterMovement->DoJump(bClientUpdating)函数,执行跳跃操作,进入Falling,设置跳跃速度为JumpZVelocity,这个值不能小于0。

    ③ 判断const bool bDidJump = canJump && CharacterMovement && DoJump;是否为真。做一些其他的相关操作。

    const bool bDidJump = CanJump() && CharacterMovement->DoJump(bClientUpdating);

    if (!bWasJumping && bDidJump)

    JumpCurrentCount++;

    OnJumped();

    3.在一次PerformMovement结束后,就会执行ClearJumpInput,设置设置bPressedJump为false。但是不会清除JumpCurrentCount这样可以继续处理多段跳。

    4.玩家松开按键p也会设置bPressedJump为false,清空相关状态。如果玩家仍在空中,那也不会清除JumpCurrentCount。一旦bPressedJump为false,就不会处理任何跳跃操作了。

    5.如果玩家在空中按下跳跃键,他也会进入ACharacter::CheckJumpInput,如果JumpCurrentCount小于JumpMaxCount,玩家就可以继续执行跳跃操作了。

    图3-6

    3.3 Swiming

    各个状态的差异本质有三个点:

    1.速度的不同

    2.受重力影响的程度

    3.惯性大小

    游泳状态表现上来看是一个有移动惯性(松手后不会立刻停止),受重力影响小(在水中会慢慢下落或者不动),移动速度比平时慢(表现水有阻力)的状态。而玩家是否在水中的默认检测逻辑也比较简单,就是判断当前的updateComponent所在的Volume是否是WaterVolume。(在编辑器里面拉一个PhysicsVolume,修改属性WaterVolume即可)

    CharacterMovement组件里面有浮力大小配置Buoyancy,根据玩家潜入水中的程度(ImmersionDepth返回0-1)可计算最终的浮力。随后,开始要计算速度了,这时候我们需要获取Volume里面的摩擦力Friction,然后传入CalcVelocity里面,这体现出玩家在水中移动变慢的效果。随后在Z方向通过计算浮力大小该计算该方向的速度,随着玩家潜水的程度,你会发现玩家在Z方向的速度越来越小,一旦全身都浸入了水中,在Z轴方向的重力速度就会被完全忽略。

    // UCharacterMovementComponent::PhysSwimming

    const float Friction = 0.5f * GetPhysicsVolume()->FluidFriction * Depth;

    CalcVelocity(deltaTime, Friction, true, BrakingDecelerationSwimming);

    Velocity.Z += GetGravityZ() * deltaTime * (1.f - NetBuoyancy);

    // UCharacterMovementComponent::CalcVelocity Apply fluid friction

    if (bFluid)

    Velocity = Velocity * (1.f - FMath::Min(Friction * DeltaTime, 1.f));

    图3-7 角色在水体积中飘浮

    速度计算后,玩家就可以移动了。这里UE单独写了一个接口Swim来执行移动操作,同时他考虑到如果移动后玩家离开了水体积而且超出水面过大,他机会强制把玩家调整到水面位置,表现会更好一些。

    接下来还要什么,那大家可能也猜出来了,就是处理移动中检测到碰撞障碍的情况。基本上和之前的逻辑差不多,如果可以踩上去(StepUp())就调整玩家位置踩上去,如果踩不上去就给障碍一个力,然后顺着障碍表面滑动一段距离(HandleImpact,SlideAlongSurface)。

    那水中移动的惯性表现是怎么处理的呢?其实并不是水中做了什么特殊处理,而是计算速度时有两个传入的参数与Walking不同。一个是Friction表示摩擦力,另一个是BrakingDeceleration表示刹车的反向速度。 在加速度为0的时候(表示玩家的输入已经被清空),水中的传入的摩擦力要远比地面摩擦里小(0.15:8),而刹车速度为0(Walking为2048),所以ApplyVelocityBraking在处理的时候在Walking表现的好像立刻刹车一样,而在Swim和fly等情况下就好像有移动惯性一样。

    // Only apply braking if there is no acceleration, or we are over our max speed and need to slow down to it.

    if ((bZeroAcceleration && bZeroRequestedAcceleration) || bVelocityOverMax)

    const FVector OldVelocity = Velocity;

    const float ActualBrakingFriction = (bUseSeparateBrakingFriction ? BrakingFriction : Friction);

    ApplyVelocityBraking(DeltaTime, ActualBrakingFriction, BrakingDeceleration);

    //Don't allow braking to lower us below max speed if we started above it.

    if (bVelocityOverMax && Velocity.SizeSquared() < FMath::Square(MaxSpeed) && FVector::DotProduct(Acceleration, OldVelocity) > 0.0f)

    Velocity = OldVelocity.GetSafeNormal() * MaxSpeed;

    3.4 Flying

    终于讲到了最后一个移动状态了,如果你想调试该状态的话,在角色的移动组件里面修改DefaultLandMovementMode为Flying即可。 Flying和其他状态套路差不多,而且相对更简单一些,首先根据前面输入计算Acceleration,然后根据摩擦力开始计算当前的速度。速度计算后调用SafeMoveUpdatedComponent进行移动。如果碰到障碍,就先看能不能踩上去,不能的话处理碰撞,沿着障碍表面滑动。

    //UCharacterMovementComponent::PhysFlying

    //RootMotion Relative

    RestorePreAdditiveRootMotionVelocity();

    if( !HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity() )

    if( bCheatFlying && Acceleration.IsZero() )

    Velocity = FVector::ZeroVector;

    const float Friction = 0.5f * GetPhysicsVolume()->FluidFriction;

    CalcVelocity(deltaTime, Friction, true, BrakingDecelerationFlying);

    //RootMotion Relative

    ApplyRootMotionToVelocity(deltaTime);

    有一个关于Flying状态的现象大家可能会产生疑问,当我设置默认移动方式为Flying的时候,玩家可以在松开键盘后进行滑行一段距离(有惯性)。但是使用GM命令的时候,为什么就像Walking状态一样,松开按键后立刻停止? 其实时代码对cheat Flying做了特殊处理,玩家松开按键后,加速度变为0,这时候强制设置玩家速度为0。所以使用GM的表现与实际上的不太一样。

    3.5 FScopedMovementUpdate延迟更新

    FScopedMovementUpdate并不是一种状态,而是一种优化移动方案。因为大家在查看引擎代码时,可能会看到在执行移动前会有下面这样的代码:

    // Scoped updates can improve performance of multiple MoveComponent calls.

    FScopedMovementUpdate ScopedMovementUpdate(UpdatedComponent, bEnableScopedMovementUpdates ? EScopedUpdate::DeferredUpdates : EScopedUpdate::ImmediateUpdates);

    MaybeUpdateBasedMovement(DeltaSeconds);

    //......其他逻辑处理,这里不给出具体代码

    // Clear jump input now, to allow movement events to trigger it for next update.

    CharacterOwner->ClearJumpInput();

    // change position

    StartNewPhysics(DeltaSeconds, 0);

    //......其他逻辑处理,这里不给出具体代码

    OnMovementUpdated(DeltaSeconds, OldLocation, OldVelocity);

    } // End scoped movement update

    为什么要把移动的代码放到这个大括号里面,FScopedMovementUpdate又是什么东西?仔细回想一下我们前面具体的移动处理逻辑,在一个帧里面,我们由于移动的不合法,碰到障碍等可能会多次重置或者修改我们的移动。如果只是简单修改胶囊体的位置,其实没什么,不过实际上我们还要同时修改子组件的位置,更新物理体积,更新物理位置等等,而计算过程中的那些移动数据其实是没有用的,我们只需要最后的那个移动数据。

    因此使用FScopedMovementUpdate可以在其作用域范围内,先锁定不更新物理等对象的移动,等这次移动真正的完成后再去更新。(等到FScopedMovementUpdate析构的时候再处理)

    来自http://blog.csdn.net/u012999985/article/details/78669508

    展开全文
  • 1.Particle System(粒子系统)粒子系统如同贴图、模型一样属于UE4的驻留资产,不能单独存在于场景中,必须使用载体依托才能在场景中渲染出来,粒子系统包含粒子特效的所有配置。粒子系统在UE4中表现为如下样子:创建...

    1.Particle System(粒子系统)

    粒子系统如同贴图、模型一样属于UE4的驻留资产,不能单独存在于场景中,必须使用载体依托才能在场景中渲染出来,粒子系统包含粒子特效的所有配置。粒子系统在UE4中表现为如下样子:

    4ef26d46e9c240d326e1fee38c29846f.png

    创建粒子系统

    在内容浏览器中右键选择Particle System可以直接创建粒子系统。

    1b1265644c2809a38e5d706095dd0896.png

    2.Emitter Actor(粒子发射器)

    粒子发射器就是粒子系统的载体,粒子发射器承载粒子系统确定粒子系统在场景中的位置坐标等信息。但是似乎UE4没办法直接创建空的粒子发射器,UE4的粒子发射器在将粒子系统拖入场景中时,UE4会自动为该粒子系统创建一个粒子发射器。

    3.Particle System Component(粒子系统组件)

    粒子系统组件是一个包含了粒子系统的蓝图,粒子系统组件可以使用代码逻辑来控制粒子系统。

    二、Cascade(粒子编辑器)

    粒子系统的核心就是粒子编辑器,在粒子编辑器里我们可以创造出琳琅满目的粒子特效。

    粒子编辑器包含Toolbar、Emitters、Details、Viewport和Curve Editor等视图。

    1.ToolBar(工具栏)

    c0ebe6806ea138857fd9eb67acdafd9e.png

    2.Emitters

    Emitters是用于控制粒子属性的视图,Emitters的基本组成单元就是Emitter(发射器),这里的Emitter和前面提到的Emitter Actor有一些差异,二者都是粒子发射器,但是Emitter是属于粒子系统内部的发射器,主要用于对粒子系统的某一部分的粒子发射,如爆炸的粒子特效中可以由烟、火光和火花等粒子部分组成,Emitter就是用于发射这些粒子部分的发射器,而Emitter Actor则是用于发射整个粒子系统的发射器。

    3.Emitter(发射器)

    在Emitters视图中每一列就是一个Emitter,一个Emitter控制一个粒子部分。一个Emitter拥有Required和Spawn两个固有组成模块,即每个Emitter被创建出来就拥有Required和Spawn模块,且这两个模块不能删除。

    Emitter的执行顺序是自左而右的。

    0d4b93369ec985f5838666b260ce7547.png

    创建Emitter

    直接在Emitters视图的空白出右键选择New Particle Sprite Emitter即可。

    赋值Emitter

    选中需要复制的Emitter右键/Emiter/Duplicate Emitter即可。

    删除Emitter

    选中需要删除的Emitter右键/Emitter/Delete Emitter,或直接按delete键。

    4.Emitter的分类

    直接创建的Emitter默认为普通Emitter即没有任何特殊属性的Emitter,要创建具有特殊属性的特殊的Emitter需要修改Emitter的TypeData属性,右键/TypeData选择需要的类型即可修改Emitter的类型。

    8c1ad6afa0c18905b0a53fcfa277b2c6.png

    AnimTrail Data

    骨骼动画发射器,AnimTrail可以使粒子跟随人物骨骼运动而运动,如下图的跟随人物手臂运动的彩虹带

    6cab297f0447a14218cbd18b7fc7d53d.png

    AnimTrail是配合UE4的动画系统一起使用的,使用AnimTrail我们需要选择一段动画片段如下图

    ef9cd50796b5ee123fdbf603748c3612.png

    发射器会在选定的时间片段内发射粒子,在右侧的Trail栏中选定粒子需要跟随的骨骼和骨骼需要绑定的粒子系统,就可以使粒子跟随着骨骼运动了。

    Beam Data

    光束发射器,Beam有一个源点和一个目标点,粒子由源点发射运动到目标点销毁,Beam在粒子由源点运动到目标点的过程中可以设置粒子抖动,使粒子的运动具有抖动效果,如下图的闪电。

    fd65f8d3c64d77a44c644f6a7b932c96.png

    Beam拥有一个专属Module–Beam,在这个模块里可以为Beam添加Source(源点)、Target(目标点)和Noise(噪声),Noise就是控制粒子抖动的Module,要想粒子出现抖动有几点是必须要做的,即勾选Noise模块的Low Freq/Low Freq Enabled、设置Frequency(噪声点的频率,影响粒子抖动的平滑程度);Source和Target模块的Suorce Tangent/Distribution/Constant和Target Tangent/Distribution/Constant(影响源点到目标点的粒子抖动的曲率),要想这两个设置起作用还必须设置Source/Source Tangent Method为User Set,目标点也是一样的设置;还有一点就是要想粒子从源点向目标点运动还需要设置Beam Data模块的Beam/Beam Method为Target。

    GPU Sprites

    GPU Sprites是普通粒子的加强版,可以使粒子具有更完美的物理效果,但当对于普通粒子发射器来说更消耗GPU性能。

    矢量场:

    矢量场是GPU粒子独有的属性,GPU粒子在矢量成的控制下可以作出任意轨迹运用,如:

    9843e90a899b41fba4c7ad5c9364f7fe.png

    矢量场有多个属性控制模块

    58970ba75e4e9119402c044af96a9d0c.png

    矢量场默认是看不见的,我们需要在Viewport里勾选View/Vector Fields,使矢量场可见。

    Mesh Data

    网格发射器,Mesh可以使粒子使用模型网格,这样发射出来的粒子就是一个个具体的模型,如下图所示。

    d4ae1fc946b210565d75729b1bcbfe93.png

    Ribbon Data

    光带发射器,Ribbon可以记录粒子的运行轨迹,并可以对其属性进行编辑,如下图使用粒子运行轨迹制作的光带。

    69332caa544f3cf78abb2b2d5f115fc6.png

    Ribbon的粒子生成模块不是使用Spawn,而是Spawn PerUnit,由于Spawn是Emitter的固有Module所以无法移除,我们要启用Spawn PerUnit则需要将Spawn Module的Spawn/Distribution/Constant设置为0,达到间接禁用Spawn的效果。这样彩带粒子就是用Spawn PerUnit生成粒子了,想要彩带粒子跟随其他粒子运动我们还需要一个Module–Trail/Source,将Source Module里的Source/Source Mothod设置为PET2SRCM Particle,并且Source Name设置为需要跟随粒子部分的Emitter的名字,这样就可以产生一个跟随其他粒子的彩带了,当然这个彩带粒子是需要设置贴图的,否则尽管生成了,但是却没有渲染。

    4.Module(模块)

    模块用于控制粒子某一方面的属性,在Emitter中每一行就是一个Module,Module的种类很多,这里只列举自己使用过的Module。

    UE4Module的执行顺序是自上而下的所以当一个Emitter中有两个相同的Module时,下面的Module会覆盖上面的Module。

    添加Module

    选中需要添加Module的Emitter,右键选择需要的Module即可

    移除Module

    选中需要移除的Module右键/Delete Module,或直接按Delete键。

    拷贝Module

    Shift+拖动即可拷贝一个Module到另一个Emitter中。

    Required

    Required控制粒子的显示方面的属性,如粒子贴图,显示模式等。Sub UV栏控制贴图的逐帧融合。

    Spawn

    Spawn控制粒子的发射方面的属性,如粒子发射量,发射模式等。Spawn栏控制粒子持续性的发射;

    Burst栏控制粒子间歇性的爆炸发射。

    LifeTime

    LifeTime控制粒子的声明周期方面的属性。

    Initial Size

    Initial Size控制粒子初始尺寸方面的属性。

    Initial Velocity

    Initial Velocity控制粒子初始速度方面的属性。

    Color Over Life

    Color Over Life控制粒子生命周期内的颜色变化方面的属性。

    生命周期的初始颜色是Color Over Life/Distribution/Constant Curve/Points/0/Out Val,

    生命周期的结束颜色是Color Over Life/Distribution/Constant Curve/Points/1/Out Val。

    Initial Location

    Initial Location控制粒子发射的初始位置方面的属性,通过Start Location/Distribution/Max和Min可以控制粒子发射时的位置分布,如Max,Min都为0时粒子只从一个点发射出来,当设置了值,粒子会在设置的范围内随机发射出来。

    Const Acceleration

    Const Acceleration控制粒子的运动的加速度。

    Size By Speed

    Size By Speed控制粒子的尺寸随速度的变化,要想粒子的尺寸可以随速度变化需要将Required模块的Emitter/Screen Alignment设置为PSA Velocity。

    Light

    Light控制粒子发光,但是似乎只有发生碰撞之后才会发光。

    Actor Collision

    Actor Collision控制粒子的物理碰撞。

    Orbit

    Obirt控制粒子的随机运动。

    三、LOD(细节层次)

    默认情况下任何创建出来的粒子系统默认为LOD0等级的细节层次,我们可以在Toolbar中的Add LOD添加细节层次,在LOD中切换细节层次。

    2591a09e837212e5a731b3453462417b.png

    Details/LOD栏可以设置LOD切换与距离的关系,可以在不同的细节层次下修改Emitter不会对其他的细节层次产生影响,这样就可以很精确的控制各个细节层次了。

    展开全文
  • UE4 烟花模型

    2019-01-22 11:27:41
    Const Acceleration模块为粒子应用了初始加速度,但Const Acceleration模块不接受Distribution属性。 Const Acceleration是GPU Sprite粒子唯一可用的加速度类型。 该模块将为粒子荷载数据 UsedAcceleration 添加...
  • Ue4载具模板的重要参数解析,包括速度与加速度

    千次阅读 多人点赞 2020-09-22 18:59:16
    当然,你完全可以继承自UE4自带的WheelVehicle来创建一个载具,但是我这里给出更为自由的一个方法,使用Pawn创建载具。 新建一个Pawn,我将其命名为BP_Root_Car,为其添加骨骼网络体SkeletalMesh,将骨骼网络体移动到...
  • 摘要:UE4世界设置中的属性介绍_资源库在UE4的编辑器中点击“设置”(Settings),在里面就可以看到“世界设置”(World Settings)了,那么你知道UE4中的世界设置有哪些属性与功能吗?UE4世界设置:WorldEnable World ...
  • 视角控制没什么特殊的 只不过转身需要用动画蓝图的修改骨骼实现,不然不好实现“带有力的旋转” 至于前后左右输入,我是每次输入给当前速度加上DeltaTime*加速度,暂时还没考虑防止斜向加速度会更大的处理 当然每帧...
  • UE4材质编辑器SpeedByScale大小随速度

    千次阅读 2022-01-21 09:42:12
    UE4材质编辑器SpeedByScale大小随速度 最近在学习粒子特效,学的是系统的粒子编辑器,非Niagra,然后遇到了这个速度随大小的属性设置。一开始没有理解,后来自己测试了一下。理解如下 理解: 整个的作用就是让粒子...
  • 补了一下初中物理,其中物理单位如下:​​​ ...在UE4中,物理单位有不少区别: 长度:厘米 cm 质量:千克 kg 加速度:厘米每秒平方 cm/s^2 力:千克厘米每秒平方 kg * cm/s^2 用的地方注意。 ...
  • ue4_角色加速

    2021-08-12 10:14:13
    1.项目设置-输入-添加键(sprintj加速:鼠标左键+shift zoom开镜 :鼠标右键) 2.打开人物蓝图,添加蓝图。max walk speed为character movement中一个参数 结果:w+shfit+鼠标左键 加速
  • 后续文章更新移步→微信公众号“虚幻社区”(mantra...曲线是动画中非常重要的概念,可以表示物体的移动速度,加速度;可以表示物体颜色的变化;还可以表示物体的位置、旋转和缩放等等信息。曲线从本质上表示某些值随...
  • UE4UE4GamePlay架构

    千次阅读 2021-12-08 14:50:53
    这篇博文主要记录一些自己在学习GamePlay的过程中一些心得记录,最开始使用的是UE5源码学习,后来不知道不小心改了啥,UE5源码崩了,就换回了UE4.26所以源码部分可能会有一部分来自UE5有一部分来自UE4,会有点出入。...
  • UE4UE4文件系统

    千次阅读 2022-01-27 12:05:15
    title: 【UE4UE4 文件系统 date: 2021-06-16 22:24:22 tags: UE4 categories: 知识记录 password: abstract: message: 先来看一下UE4文件系统的类组成情况: 我们一个个类来看。 这里面类组成大致可以分为三大类...
  • 原标题:UE4实时渲染,不用合成。第一部完全UE4渲染动画片是这样炼成的~今天,跟大家分享的这个图文是:第一部完全用UE4渲染输出的动画片,名为《Zafari》。是,是动画片,不是动画电影哈~那今天要介绍它的原因也是...
  • ue4性能优化_个人总结

    千次阅读 2021-01-12 10:35:58
    有些节点是比较耗的,比如sin、 pow、cos、divide、Noise等,比较省的就是减乘除clamp。 (3).opauqe>mask>transulant.透明的消耗巨大,同理指数级雾也少用。 4. 后期框 后期处理是第二个GPU消耗过高的元凶,SSR...
  • 俯仰滚动航向滑移或滑移速度和加速度Altiutude和爬升速度飞行路径 飞机HUD基于蓝图和材料 Hud本身被实现为对每个指示器使用纹理和纹理蒙版的材料。通过使用Material实例,您可以控制HUD的设计或显示模式 它可以用作...
  • 环境:UE4 4.16新建一个 蓝图Actor, 增加 组件 sphere collision 作为 root 节点 附加一个 网格 球体 用来观察把 sphere collision 拖到 构建脚本中 拖出引脚,使用函数 Set Physics Linear Velocity 为其设置...
  • UE4 渲染性能设置

    千次阅读 2020-12-20 06:34:19
    本篇文章主要跟大家介绍的是UE4虚幻引擎的渲染性能设置,不清楚方法的可以看看。渲染设置(Rendering Setting)本文介绍 ue4 4.14的世界设置在编辑器最上端点击“设置”(Settings)按钮, 选择“项目设置”(Project ...
  • UE4 优化

    千次阅读 2022-01-06 22:16:25
    UE4 优化
  • 跳事件不能写在轴事件,只能写在动作事件,轴...Jump Z Velocity就是你跳跃的初始加速度,也可以简称为跳跃的力量,数值越大,跳的越高。 Air Control为0就是跳跃中不能做任何其他事,为1就可以在跳跃中做任意事情 ...
  • UE4+Cesium

    2022-01-14 15:29:58
    同样,Cesium的FloatingPawn扩展了内置的FloatingPawn,使其能在地球上任意移动,并允许使用鼠标滚轮控制移动速度,特别是距离地面很远时非常有用(用键盘WSAD前后左右移动,鼠标控制方向,滚轮控制移动速度)。...
  • [SD plugin] 快捷键创建节点重新排布插件(nuke UE4风格)从这个工具就能看出来,我对快捷键的执着,对工作效率极致提升的吹毛求疵。这篇文章总结了一下我个人工作过程中,感觉非常有用,但又相对偏门的一些奇技淫巧。...
  • UE4UE4框选

    千次阅读 2021-03-01 17:56:04
    自适应视窗缩放速度 解除对选中物体旋转 对屏幕外物体进行位移旋转缩放 物体吸附到模型顶点 屏幕编队(记录当前相机位置和旋转) 落到正下方物体 显示与隐藏 资源操作篇 场景中选择相关物体 ...
  • UE4源码加密及资源加密

    千次阅读 2018-04-27 05:57:32
    虚幻引擎4是由游戏开发者为开发游戏而制作的、完整的游戏开发工具套件。从二维的移动平台游戏到主机平台的大作,虚幻引擎是一个...UE4的主要代码逻辑位于项目中EXE以及资源文件apk中,深思virbox Protector可以保护...
  • [UE4]性能优化指南(程序向)

    千次阅读 2020-12-20 06:34:17
    封面来源:A Cold Stop(Mixer 制作材质、UE4渲染 )[UE4]性能优化指南(美术向)玄冬Wong:[UE4]性能优化指南(美术向)​zhuanlan.zhihu.com内容都是处理项目问题的相关笔记,留给自己做备忘录,也分享出来让别人少走...
  • UE4大幅提升编译速度的技巧

    万次阅读 2019-10-15 13:48:05
    1.首先在Unreal Engine的安装文件夹下找到Win64这个文件,每个人的安装位置不太一样这是我的安装文件路径:E:\UE\UE_4.20\Engine\Binaries\Win64 在下面找到Win64这个文件。 2.在Win64这个文件中找到一个UnrealHead....
  • 之前做项目的过程中,有一部分工作是在UE4里制作输出小短片。由于要完成的量比较大,所以研究了一些批渲染的方法。逻辑上跟以前在maya里用batch render差不多,不过UE4这边的设置相对繁琐一点点。本文讲解了在不打开...
  • 教程内容将同步免费发布于 开发游戏的老王(知乎|CSDN)的专栏《玩转UE4/UE5动画系统》。教程中使用的资源及工程将以开源项目的形式更新到GitHub:玩转UE4上。 工程文件: 玩转UE4(GitHub) 文章目录1. 状态机概要...
  • 制动时的最大减速度 UnrotateVector将世界坐标系变成局部坐标系,再来一次 RotateVector 将 会把局部坐标系变成世界坐标系。 UnRotateVector作用是让Vector A绕着原点逆时针旋转 B。 RotateVec

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,553
精华内容 1,421
关键字:

ue4加速度