Bootstrap

[UE]碰撞和Trace检测

基础概念

UE物理引擎原理分析(一):碰撞检测World层

碰撞响应参考

UE4物理精粹Part 3:Collision

碰撞相关概念

碰撞概述
在这里插入图片描述
几个概念:

Simulate Physics:物理模拟,如果要触发Hit事件,这个属性需要设置True,
对应的C++函数为UPrimitiveComponent::SetSimulatePhysics(bool bSimulate)

Simulation Generates Hit Events:在开启物理模拟时,物体被撞击时是否触发事件,Hit事件和开启物理模拟强绑定,所以如果只想注册碰撞相关的事件,而不需要物理模拟时,不要用Hit Event,用Overlap吧。
对应的C++属性为FBodyInstance::bNotifyRigidBodyCollision,C++函数为:UPrimitiveComponent::SetNotifyRigidBodyCollision(bool bNewNotifyRigidBodyCollision)

Generate Overlap Events:开启才会有重叠事件产生

Collision Preset:一个碰撞预设会指定一个默认的Object Type和Collision Enabled类型

Collision Enabled:
No Collision-无碰撞
Physics Only-仅用于物理模拟
QueryOnly(No Physics Collision):仅开启查询
CollisionEnabled(Query and Physics):开启查询和物理模拟

Object Type:对象类型
Object Responses: 对象响应类型,不同对象类型之间可以设定ignore、Overlap、Block三种响应类型
只要有一个物体将碰撞对象设置为Ignore,碰撞都不会触发任何事件或者物理现象。
只有两个物体之间都是Block时,碰撞时才会阻挡
如果一个物体Overlap,另外一个Block,则是Overlap

Overlap和Hit事件

概念和条件

[UE4]C++创建BoxCollision(BoxComponent)并注册Overlap和Hit事件回调函数

1.所有UPrimitiveComponent及其子类都可以产生Overlap和Hit事件
UPrimitiveComponent :拥有几何表示的场景组件,用于渲染视觉元素或与物理对象发生碰撞或重叠。

2.除了在每个UPrimitiveComponent及其子类对象上可以产生Overlap和Hit事件,还有直接在Actor上产生Overlap和Hit事件,
What is the difference between OnActorHit and OnComponentHit?

当一个Actor有多个PrimitiveComponent组件时,每个PrimitiveComponent组件都有自己的碰撞体积,它们可以单独检测碰撞。
但是,当Actor整体发生碰撞时,引擎会根据Actor的所有PrimitiveComponent组件的碰撞体积来计算整个Actor的碰撞。

  1. 产生Overlap 的条件

1.1 首先 两个物体都要选中 Generate Overlap Events
1.2 其次 最少有一个物体的碰撞设置为Overlap 另一个物体需要为Overlap 或者 Block 都可以触发

  1. 产生Hit 的条件

2.1 首先 需要响应Hit事件的物体 必须选中了Simulation GeneratesHitEvents,对应的C++属性叫做FBodyInstance::bNotifyRigidBodyCollision,C++函数叫:UPrimitiveComponent::SetNotifyRigidBodyCollision(bool bNewNotifyRigidBodyCollision)
2.2 有一个物体必须选中SimulatePhysis,对应的C++函数:UPrimitiveComponent::SetSimulatePhysics(bool bSimulate)
2.3 两个物体的CollisionEnable 必须有Physics Collisiion
2.4 两个物体的碰撞属性必须都互相为Block

使用示例

1.UPrimitiveComponent及其子类的Overlap和Hit事件示例

// UPrimitiveComponent及其子类的Overlap和Hit事件示例
OnComponentBeginOverlap
OnComponentEndOverlap
OnComponentHit

AMyActor::AMyActor()
{
    PrimaryActorTick.bCanEverTick = true;
    CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent"));
    CollisionComponent->BodyInstance.SetCollisionProfileName(TEXT("Projectile"));
    CollisionComponent->OnComponentBeginOverlap.AddDynamic(this, &AMyActor ::OnMyCompBegionOverlap);
	CollisionComponent->OnComponentEndOverlap.AddDynamic(this, &AMyActor ::OnMyCompEndOverlap);
    CollisionComponent->OnComponentHit.AddDynamic(this, &AMyActor::OnMyCompHit);
}

void AMyActor ::OnMyCompBegionOverlap(UPrimitiveComponent* OverlappedComponent,
									  AActor* OtherActor,    //触发时间的主体,通常是控制的人
		                              UPrimitiveComponent* OterComp,
		                              int32 OtherBodyIndex,
		                              bool bFromSweep,
		                              const FHitResult& SweepResult)
{
}

void AMyActor ::OnMyCompEndOverlap(UPrimitiveComponent* OverlappedComponent,
		                           AActor* OtherActor,
                                   UPrimitiveComponent* OtherComp,
                                   int32 OtherBodyIndex)
{
}

void AMyActor ::OnMyCompHit(UPrimitiveComponent* HitComponent,
                            AActor* OtherActor,
                            UPrimitiveComponent* OtherComponent,
                            FVector NormalImpulse,
                            const FHitResult& Hit)
{

}
或者
AMyOnComponentHit::AMyOnComponentHit()
{
	MyComp = CreateDefaultSubobject<UBoxComponent>(TEXT("BoxComp"));
	MyComp->SetSimulatePhysics(true);
    MyComp->SetNotifyRigidBodyCollision(true);
	MyComp->BodyInstance.SetCollisionProfileName("BlockAllDynamic");
	MyComp->OnComponentHit.AddDynamic(this, &AOnMyComponentHit::OnMyCompHit);
}
void AOnMyComponentHit::OnMyCompHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{
}

2.Actor的Overlap和Hit事件示例

// Actor的Overlap和Hit事件示例
OnActorBeginOverlap
OnActorEndOverlap
OnActorHit

AMyActor::AMyActor()
{
    OnActorBeginOverlap.AddDynamic(this, &AMyActor::OnOverlap);
    OnActorHit.AddDynamic(this, &AMyActor::OnMyActorHit);
}
void AMyActor::OnOverlap(AActor* OverlappedActor, AActor* OtherActor)
{ 

}


void AMyActor::OnMyActorHit(AActor* SelfActor,
                            AActor* OtherActor,
                            FVector NormalImpulse,
                            const FHitResult& Hit)
{
	UE_LOG(LogTemp, Warning, TEXT("Actor %s hit %s at location %s with normal %s"), *SelfActor->GetName(), *OtherActor->GetName(), *Hit.ImpactPoint.ToString(), *Hit.ImpactNormal.ToString());
    //遍历了当前Actor的所有PrimitiveComponent组件,并与碰撞信息中的Component进行比较,以确定哪个组件发生了碰撞
    for (UPrimitiveComponent* Component : GetComponentsByClass(UPrimitiveComponent::StaticClass()))
    {
        if (Hit.Component == Component)
        {
            UE_LOG(LogTemp, Warning, TEXT("Hit component %s of actor %s"), *Component->GetName(), *SelfActor->GetName());
        }
    }
}



问题

1.发生Overlap时,const FHitResult& SweepResult参数中的位置信息没有
Component Overlap hit position always returns 0,0,0
on-component-begin-overlap-sweep-result-not-populated

解决办法:对于BoxComponent,CapsuleComponent, SphereComponent这三个形状,可以在发生Overlap时,在做一次Trace检测,得到精确的位置信息;对于其他形状的Overlap,暂时没找到其他方法。

2.处理BoxComponent,CapsuleComponent, SphereComponent这三种形状的触发器之外,还可能需要一些特定形状的触发器Overlap来做区域检测
解决办法:
1.使用UE提供的TriggerVolume来实现自定义形状的触发器Overlap检测 Is volume trigger useful?
2.利用一个不渲染的mesh做特殊形状的触发器Overlap检测
例如,圆柱体Cylinder,圆锥Cone
关闭渲染,只留下碰撞查询,就可以实现一个任意形状的overlap检测

Trace检测

【UE4】LineTrace 功能详解
UE4的移动碰撞

引擎 World.h 中的Trace

Trace 类型有两种:LineTrace、SweepTrace
Trace 数量有两种: TraceSingle、TraceMulti
Trace 方式有三种:ByChannel、ByObjectType、ByProfile

【UE4 C++】 射线检测 LineTrace 及 BoxTrace、SphereTrace、CapsuleTrace API

(UE4 4.20)UE4 碰撞(Collision)之光线检测(RayTrace ) [详细测试了TraceSingle和TraceMulti的区别]

连续碰撞检测(CCD)介绍

UKismetSystemLibrary 中的 Trace

UKismetSystemLibrary 中的 Trace 有 LineTrace、BoxTrace、SphereTrace、CapsuleTrace 四种类型,每种又分为 xxxTraceSingle、xxxTraceMulti 两种。

UKismetSystemLibrary 里的 Trace 都是调用的 World 里的 Trace。调用对应关系如下:
在这里插入图片描述

也就是 UKismetSystemLibrary 里默认是 ByChannel,也有对应其他方式的,比如 UKismetSystemLibrary::LineTraceSingleForObjects 对应 World->LineTraceSingleByObjectType,其他同理。
  即 Line 对应 Line,其他对应 Sweep。

HitResult

UE4 微笔记 之 HitResult (持续更新)
射线检测的结果会存储在一个名为HitResult的结构体中。该结构体包含了与射线相交的物体、碰撞点、法向量等信息

USTRUCT(BlueprintType, meta = (HasNativeBreak = "Engine.GameplayStatics.BreakHitResult", HasNativeMake = "Engine.GameplayStatics.MakeHitResult"))
struct ENGINE_API FHitResult
{
    GENERATED_BODY()

    /** Face index we hit (for complex hits with triangle meshes). */
    //被击中物体表面上的三角形索引。当射线与一个静态网格碰撞时,该值将告诉您哪个三角形被命中
    UPROPERTY()
    int32 FaceIndex;

    /**
    * 'Time' of impact along trace direction (ranging from 0.0 to 1.0) if there is a hit, indicating time between TraceStart and TraceEnd.
    * For swept movement (but not queries) this may be pulled back slightly from the actual time of impact, to prevent precision problems with adjacent geometry.
    */
    UPROPERTY()
    float Time;
    
    /** The distance from the TraceStart to the Location in world space. This value is 0 if there was an initial overlap (trace started inside another colliding object). */
    // 起始位置到碰撞点之间的距离
    UPROPERTY()
    float Distance; 
    
    /**
    * The location in world space where the moving shape would end up against the impacted object, if there is a hit. Equal to the point of impact for line tests.
    * Example: for a sphere trace test, this is the point where the center of the sphere would be located when it touched the other object.
    * For swept movement (but not queries) this may not equal the final location of the shape since hits are pulled back slightly to prevent precision issues from overlapping another surface.
    */
    //被击中物体表面上的位置
    UPROPERTY()
    FVector_NetQuantize Location;

    /**
    * Location in world space of the actual contact of the trace shape (box, sphere, ray, etc) with the impacted object.
    * Example: for a sphere trace test, this is the point where the surface of the sphere touches the other object.
    * @note: In the case of initial overlap (bStartPenetrating=true), ImpactPoint will be the same as Location because there is no meaningful single impact point to report.
    */
    //碰撞点在世界坐标系下的位置
    UPROPERTY()
    FVector_NetQuantize ImpactPoint;
    
    /**
    * Normal of the hit in world space, for the object that was swept. Equal to ImpactNormal for line tests.
    * This is computed for capsules and spheres, otherwise it will be the same as ImpactNormal.
    * Example: for a sphere trace test, this is a normalized vector pointing in towards the center of the sphere at the point of impact.
    */
    // 碰撞表面法向量方向
    UPROPERTY()
    FVector_NetQuantizeNormal Normal;
    
    /**
    * Normal of the hit in world space, for the object that was hit by the sweep, if any.
    * For example if a sphere hits a flat plane, this is a normalized vector pointing out from the plane.
    * In the case of impact with a corner or edge of a surface, usually the "most opposing" normal (opposed to the query direction) is chosen.
    */
    UPROPERTY()
    FVector_NetQuantizeNormal ImpactNormal;
    
    /**
    * Start location of the trace.
    * For example if a sphere is swept against the world, this is the starting location of the center of the sphere.
    */
    // 追踪起点 
    UPROPERTY()
    FVector_NetQuantize TraceStart;

    /**
    * End location of the trace; this is NOT where the impact occurred (if any), but the furthest point in the attempted sweep.
    * For example if a sphere is swept against the world, this would be the center of the sphere if there was no blocking hit.
    */
    // 追踪终点  
    UPROPERTY()
    FVector_NetQuantize TraceEnd;
    
    /**
    * If this test started in penetration (bStartPenetrating is true) and a depenetration vector can be computed,
    * this value is the distance along Normal that will result in moving out of penetration.
    * If the distance cannot be computed, this distance will be zero.
    */
    //射线与被击中物体之间的穿透深度:指的是从射线命中点到物体表面最近点的距离
    UPROPERTY()
    float PenetrationDepth;
    
    /** If the hit result is from a collision this will have extra info about the item that hit the second item. */
    UPROPERTY()
    int32 MyItem;
    
    /** Extra data about item that was hit (hit primitive specific). */
    UPROPERTY()
    int32 Item;
    
    /** Index to item that was hit, also hit primitive specific. */
    UPROPERTY()
    uint8 ElementIndex;
    
    /** Indicates if this hit was a result of blocking collision. If false, there was no hit or it was an overlap/touch instead. */
    // True: 发生了碰撞,并且是blocking 
    // False: 发生了碰撞,但是overlap只重叠,不碰撞
    UPROPERTY()
    uint8 bBlockingHit : 1;

    /**
    * Whether the trace started in penetration, i.e. with an initial blocking overlap.
    * In the case of penetration, if PenetrationDepth > 0.f, then it will represent the distance along the Normal vector that will result in
    * minimal contact between the swept shape and the object that was hit. In this case, ImpactNormal will be the normal opposed to movement at that location
    * (ie, Normal may not equal ImpactNormal). ImpactPoint will be the same as Location, since there is no single impact point to report.
    */
    //表示射线是否从物体内部开始穿透。如果该值为true,则表示射线在物体内部,并且已经开始穿透;如果该值为false,则表示射线从物体外部击中
    UPROPERTY()
    uint8 bStartPenetrating : 1;
    
    /**
    * Physical material that was hit.
    * @note Must set bReturnPhysicalMaterial on the swept PrimitiveComponent or in the query params for this to be returned.
    */
    UPROPERTY()
    TWeakObjectPtr<class UPhysicalMaterial> PhysMaterial;
    
    /** Handle to the object hit by the trace. */
    UPROPERTY()
    FActorInstanceHandle HitObjectHandle;
    
    /** PrimitiveComponent hit by the trace. */
    UPROPERTY()
    TWeakObjectPtr<class UPrimitiveComponent> Component;
    
    /** Name of bone we hit (for skeletal meshes). */
    UPROPERTY()
    FName BoneName;
    
    /** Name of the _my_ bone which took part in hit event (in case of two skeletal meshes colliding). */
    UPROPERTY()
    FName MyBoneName;

...
}

Trace示例

注意:
如果自定义的TraceChannel枚举类型为A,在调用SphereTraceSingle指定TraceChannel参数为A,则需要确保要检测的Actor已经设置为允许被扫描通道A所包含。

LineTrace

//Called every frame
void AUnrealCPPCharacter::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	FHitResult OutHit;
	FVector Start = FP_Gun->GetComponentLocation();
	// alternatively you can get the camera location
	// FVector Start = FirstPersonCameraComponent->GetComponentLocation();

	FVector ForwardVector = FirstPersonCameraComponent->GetForwardVector();
	FVector End = ((ForwardVector * 1000.f) + Start);
	FCollisionQueryParams CollisionParams;

	DrawDebugLine(GetWorld(), Start, End, FColor::Green, false, 1, 0, 1);
	if(GetWorld()->LineTraceSingleByChannel(OutHit, Start, End, ECC_Visibility, CollisionParams)) 
	{
		if(OutHit.bBlockingHit)
		{
			GEngine->AddOnScreenDebugMessage(-1, 1.f, FColor::Red, FString::Printf(TEXT("You are hitting: %s"), *OutHit.GetActor()->GetName()));
			GEngine->AddOnScreenDebugMessage(-1, 1.f, FColor::Red, FString::Printf(TEXT("Impact Point: %s"), *OutHit.ImpactPoint.ToString()));
			GEngine->AddOnScreenDebugMessage(-1, 1.f, FColor::Red, FString::Printf(TEXT("Normal Point: %s"), *OutHit.ImpactNormal.ToString()));
		}
	}

}

SphereTraceSingle

SphereTraceSingle是一个用于在游戏中进行球形sweep检测的函数。它可以检测从指定位置发出的球形射线是否与场景中的任何物体相交,并返回最近相交点和相交物体信息。

TArray<FHitResult> OutHits;
FVector MyLocation = GetActorLocation();

// create a collision sphere
FCollisionShape MyColSphere = FCollisionShape::MakeSphere(500.0f);
// draw collision sphere
DrawDebugSphere(GetWorld(), GetActorLocation(), MyColSphere.GetSphereRadius(), 50, FColor::Cyan, true);
    
// check if something got hit in the sweep
// Sweep的开始位置和结束位置可以相同。。。
bool isHit = GetWorld()->SweepMultiByChannel(OutHits, MyLocation, MyLocation, FQuat::Identity, ECC_WorldStatic, MyColSphere);

BoxSweep

/*首先
创建了一个FCollisionQueryParams对象BoxParams,用于设置射线查询参数
SCENE_QUERY_STAT(FreeCam)表示使用FreeCam作为查询名称(用于统计性能数据),
false表示不需要复杂的碰撞检测(只需判断是否有阻挡即可),
this表示忽略当前Actor对象。
*/
CollisionQueryParams BoxParams(SCENE_QUERY_STAT(FreeCam), false, this);
/*OutVT.Target这个Actor被添加到忽略列表中,避免对结果产生影响*/
BoxParams.AddIgnoredActor(OutVT.Target);
FHitResult Result;

/*然后
调用GetWorld()->SweepSingleByChannel()方法进行射线查询
从Loc点向Pos点发射一条以ECC_Camera通道为类型、形状为12x12x12盒子的射线,并返回第一个被击中的物体信息(如果没有则返回空)
*/
GetWorld()->SweepSingleByChannel(Result, Loc, Pos, FQuat::Identity, ECC_Camera,
    FCollisionShape::MakeBox(FVector(12.f)), BoxParams);

/*最后
根据查询结果来更新OutVT.POV.Location和OutVT.POV.Rotation属性。
如果没有击中任何物体,则直接使用Pos作为自由相机位置;
否则使用Result.Location作为新位置(第一个被击中的物体信息)。
*/
OutVT.POV.Location = !Result.bBlockingHit ? Pos : Result.Location;
OutVT.POV.Rotation = Rotator;

SphereTrace

同BoxTrace

调用World->SweepSingleByChannel
参数换成FCollisionShape::MakeSphere

CapsuleTrace

同BoxTrace

调用World->SweepSingleByChannel
参数换成FCollisionShape::MakeCapsule

记录

SetActorLocation中的Sweep和Teleport

Sweep英文释义:Whether we sweep to the destination location,triggering overlaps along the way and stopping short of the target if blocked by something.
Only the root component is swept and checked for blocking collision,child components move without sweeping,if collision is off,this has no effect.

Sweep中文翻译:我们是否扫瞄到目标位置,触发沿途的重叠,如果被什么东西挡住就会停在目标附近。
只有根组件被清除和检查阻塞碰撞,子组件移动而不清除,如果Collision是关闭的,这没有影响。

Teleport英文释义:Whether we teleport the physics state(if physics collision is enabled for this object). if true,physics velocity for this object is unchanged(so ragdoll parts are not affected by change in location). If false,physics velocity is updated based on the change in position(affecting ragdoll parts). If CCD is on and not teleporting, this will affect objects along the entire swept volume.

Teleport中文翻译:我们是否传送物理状态(如果这个对象启用了物理碰撞)。
如果为true,这个物体的物理速度是不变的(所以布偶部件不会受到位置变化的影响)。
如果为false,物理速度将根据位置的变化进行更新(影响布偶部件)。
如果CCD是开启的,而不是传送,这将影响沿整个扫描体积的物体。

Sweep与Teleport作用说明
Sweep用来阻止运动穿透,这个应该就是charactor不会穿透cube的原因
Teleport开启时,物体瞬间从一个位置移动到另一个位置,自身会产生一个很大的速度(瞬间移动时间很短,物理引擎正常会计算物体速度s/t,因为t很小,所以导致物体会有个很大的速度,teleport开启就不会计算这个自身的速度,物体瞬间移动后速度保持为0)

为啥打开物理模拟后 Actor会穿过地面

# 发现两个CapsuleComponent组成直接的父子关系时,最终会影响整体和其他物体的碰撞关系
# 下面的根组件和地面是Block, 但是不小心挂了一个CapsuleComponent子组件,和地面是ignore,会导致物体穿过地面。。。。
CapsuleComponent(根组件,和地面Block)
    Capsule Collision(也是CapsuleComponent组件,受击盒,和地面是ignore)

# 修改成下面的方式,就不会穿过地面了
CapsuleComponent(根组件,和地面Block)
    SkeletalMeshComponent
        Capsule Collision(受击盒,和地面是重叠)

注意:
UCLASS(ClassGroup="Collision", editinlinenew, hidecategories=(Object,LOD,Lighting,TextureStreaming), meta=(DisplayName="Capsule Collision", BlueprintSpawnableComponent))
class ENGINE_API UCapsuleComponent : public UShapeComponent
{
    GENERATED_UCLASS_BODY()
    ...
}
其中包含了多个类标记,具体含义如下:
ClassGroup="Collision":
	指定该组件属于“Collision”类别
hidecategories=(Object,LOD,Lighting,TextureStreaming):
	指定在编辑器中隐藏了“Object”、“LOD”、“Lighting”和“TextureStreaming”这几个类别。
meta=(DisplayName="Capsule Collision", BlueprintSpawnableComponent):
	指定一些额外的信息。其中,DisplayName用于指定在编辑器中显示的名称为“Capsule Collision”; BlueprintSpawnableComponent用于指定该组件可以在蓝图中创建。
;