六一的部落格


关关难过关关过,前路漫漫亦灿灿。




概览

Line Trace

  • 获取Socket变换信息
  • 获取游戏角色Camera组件的变换信息
  • 射击游戏对发射子弹的建模
  • 完成轨迹计算并获取碰撞信息
  • 绘制子弹轨迹和交点
  • 为轨迹计算添加屏蔽Actor

Socket

  1. 之前我们使用Socket作为锚来附加武器
  2. 现在我们使用它来标记枪口, 获取变换信息

为网格体Rifle添加Socket标记枪口, 获取变换信息

虚幻编辑器

  1. 双击打开 Content > ExternalContent > Weapon > Weapons > Rifle

  2. 选中 Skeleton Tree > RifleRoot , 右键, 选择 Add Socket , 命名为MuzzleSocket

  3. 在细节面板设置MuzzleSocket的位置 Relative Location

    默认在武器坐标系的0坐标, 调整到枪口位置


  4. 更换观察视角, 检查MuzzleSocket变换参数


    • Top视角



模拟射击: 绘制子弹轨迹

C++

STUBaseWeapon

-
起点 枪口 MuzzleSocket的位置分量
偏移 放大的武器朝向 MuzzleSocket的前进方向: 通过MuzzleSocket的旋转分量, 获取前进向量
终点 起点 + 偏移

获取MuzzleSocket变换以得到线段所需信息

Stub
MuzzleSocketName 保存Socket名称
TraceMaxDistance 放大系数, 亦是子弹射击距离; 1 unreal unit = 1 cm

获取Socket变换

  • 加载WeaponMeshComponent时检查其有效性

    在BeginPlay中调用; 使用时不必检查

    1check(WeaponMeshComponent);
  • 获取变换

    1. 参数: Socket名
    2. 返回相对于指定座标系的Socket变换, 默认是世界座标系
    1const FTransform SocketTransform = WeaponMeshComponent->GetSocketTransform(MuzzleSocketName); // 默认世界坐标系
    

获取起点

1const FVector TraceStart = SocketTransform.GetLocation();

获取武器的朝向

1const FVector ShootDirection = SocketTransform.GetRotation().GetForwardVector(); // 单位向量
2// GetRotation返回类型FQuat
3// 在此处FQuat比FRotator更方便, 因为GetForwardVector的存在, 可以获取X轴分量

计算终点

1const FVector TraceEnd = TraceStart + ShootDirection * TraceMaxDistance;         

绘制线段


获取World对象

1#include "Engine/World.h"         
2
3if(!GetWorld()) return;

绘制线段

参数
bPersistentLines 是否一直存在 false
LifeTime 持续时间, 单位s 3
DepthPriority 绘制优先级 0
1#include "DrawDebugHelpers.h"
2
3DrawDebugLine(GetWorld(), TraceStart, TraceEnd, FColor::Red, false, 3.0f, 0, 3.0f);          

完整代码


添加数据成员

ShootThemUp: Weapon/STUBaseWeapon.h

1UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
2FName MuzzleSocketName = "MuzzleSocket";
3
4UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
5float TraceMaxDistance = 1500.0f; // 1 unreal unit = 1cm > 15m

检查WeaponMeshComponent

ShootThemUp: Weapon/STUBaseWeapon.cpp

1// BeginPlay
2check(WeaponMeshComponent);

MakeShot

包含发射子弹的所有逻辑; 如温切斯特连发步枪, 扣一下扳机, 连发数枚子弹, 所以和Fire分开

  1. ShootThemUp: Weapon/STUBaseWeapon.h

    protected
    1void MakeShot();
  2. ShootThemUp: Weapon/STUBaseWeapon.cpp
     1#include "Engine/World.h"
     2#include "DrawDebugHelpers.h"
     3
     4void ASTUBaseWeapon::MakeShot() {
     5    if(!GetWorld()) return;
     6
     7    const FTransform SocketTransform = WeaponMeshComponent->GetSocketTransform(MuzzleSocketName);
     8    const FVector TraceStart = SocketTransform.GetLocation();
     9    const FVector ShootDirection = SocketTransform.GetRotation().GetForwardVector();
    10    const FVector TraceEnd = TraceStart + ShootDirection * TraceMaxDistance;
    11    DrawDebugLine(GetWorld(), TraceStart, TraceEnd, FColor::Red, false, 3.0f, 0, 3.0f);
    12}
  3. 在Fire中调用

在虚幻编辑器中查看

  1. 点击鼠标左键, 触发Fire

  2. 轨迹与枪口垂直: 未设置MuzzleSocket旋转信息, 需要与世界座标系对应轴方向一致



配置MuzzleSocket旋转信息

Rifle > Details

  1. 当前


  2. 前进方向对应X轴方向, 使MuzzleSocket座标系绕自己的Y轴逆时针旋转90度

    Relative Rotation > Pitch = 90


  3. 向上方向对应Z轴方向, 使MuzzleSocket座标系绕自己的X轴顺时针旋转90度

    Relative Rotation > Roll = -90


  4. 效果图



模拟射击: 绘制相交点

C++

给出子弹轨迹的起点和终点, 计算场景中与轨迹碰撞的物体, 保存到FHitResult对象中


HitResult存放碰撞结果

  • 包含以下信息
    - 数据成员
    碰撞时间
    交点 ImpactPoint
    法向量
    指向碰撞Actor的指针
    碰撞标志 bBlockingHit
  • 需要交点信息

计算与轨迹发生碰撞的Actor

  • Engine提供多个碰撞计算函数, 我们使用UWorld::LineTraceSingleByChannel
  • LineTraceSingleByChannel返回true则有碰撞, false则无; 而HitResult中亦有bBlockingHit
  • 如果发生碰撞, LineTraceSingleByChannel通过函数参数返回与线段第一个发生碰撞的物体信息
-
ECollisionChannel::ECC_Visibility 阻挡了Visibility的物体才参与轨迹计算
1FHitResult HitResult;
2GetWorld()->LineTraceSingleByChannel(HitResult, TraceStart, TraceEnd, ECollisionChannel::ECC_Visibility);

绘制相交点

1if (HitResult.bBlockingHit)
2{
3    DrawDebugSphere(GetWorld(), HitResult.ImpactPoint, 10.0f, 24, FColor::Red, false, 5.0f);
4}

完整代码

ShootThemUp: Weapon/STUBaseWeapon.cpp

1// MakeShot
2
3FHitResult HitResult;
4GetWorld()->LineTraceSingleByChannel(HitResult, TraceStart, TraceEnd, ECollisionChannel::ECC_Visibility);
5if (HitResult.bBlockingHit)
6{
7    DrawDebugSphere(GetWorld(), HitResult.ImpactPoint, 10.0f, 24, FColor::Red, false, 5.0f);
8}

查看相交点

虚幻编辑器


查看游戏角色Capsule组件的碰撞选项

BP_STUBaseCharacter > CapsuleComponent > Collision

  1. Collision Presets为Custom
  2. Trace Responses > Visibility为Block

    为Overlap或Ignore时, 不会参与相交计算



射中时, 以交点为圆心绘制球



注意到, 瞄准十字与子弹轨迹不重合


射击游戏中发射子弹建模

C++

  • 子弹从枪口射出, 是对真实世界中的射击进行模拟. 射击游戏中, 子弹从Camera组件位置射出. 该建模也适用于VR Virtual Reality
  • 需要将子弹轨迹的起点改为Camera组件的位置

在武器类获取游戏角色Camera组件的变换信息

  1. 之前旋转游戏角色视角那里, 有提到是通过游戏角色控制器旋转Camera组件完成的; 获取游戏角色Camera组件的变换信息, 亦是通过游戏角色控制器得到
  2. 可以通过游戏角色获取其所属控制器
  3. 可以通过武器组件获取其所属游戏角色
  4. 武器组件生成武器时, 可以为武器设置所属, 即设置武器属于游戏角色
武器 > 游戏角色 > 游戏角色控制器 > 游戏角色Camera组件的变换信息

武器类生成武器时, 设置其属于游戏角色

STUWeaponComponent

  • SpawnActor时, 可以通过FActorSpawnParameters设置其所属; 或者调用SetOwner, 武器的Owner和WeaponComponent的Owner相同
  • 使用GetOwner函数和Character数据成员均可获取武器类所属游戏角色
1// CurrentWeapon->SetOwner(GetOwner());
2CurrentWeapon->SetOwner(Character);

获取游戏角色控制器

STUBaseWeapon

1#include "GameFramework/Character.h"
2#include "GameFramework/PlayerController.h"
3
4const auto Player = Cast<ACharacter>(GetOwner());
5if (!Player) return;
6
7const auto Controller = Player->GetController<APlayerController>();
8if (!Controller) return;

通过游戏角色控制器获取Camera组件的变换信息: 位置和旋转

  • PlayerCameraManager是全局类, 负责管理Camera, 可以直接获取Character的Camera组件
  • GetPlayerViewPoint访问PlayerCameraManager类
1FVector ViewLocation;
2FRotator ViewRotation;
3Controller->GetPlayerViewPoint(ViewLocation, ViewRotation);

重新计算子弹轨迹和碰撞信息

-
起点 Camera组件位置
偏移 Camera组件旋转
1const FVector TraceStart = ViewLocation;
2const FVector ShootDirection = ViewRotation.Vector();
3const FVector TraceEnd = TraceStart + ShootDirection * TraceMaxDistance;    
4
5FHitResult HitResult;
6GetWorld()->LineTraceSingleByChannel(HitResult, TraceStart, TraceEnd, ECollisionChannel::ECC_Visibility);    

绘制子弹轨迹和交点

  1. 子弹从Camera组件所在射出; 若击中, 到击中物体结束, 否则, 绘制射程
  2. 绘制子弹轨迹时, 起点仍是枪口, 终点为击中物体或射程终点
  3. 若击中物体, 绘制交点
1if (HitResult.bBlockingHit)
2{
3    DrawDebugLine(GetWorld(), SocketTransform.GetLocation(), HitResult.ImpactPoint, FColor::Red, false, 3.0f, 0, 3.0f);
4    DrawDebugSphere(GetWorld(), HitResult.ImpactPoint, 10.0f, 24, FColor::Red, false, 5.0f);
5}
6else
7{
8    DrawDebugLine(GetWorld(), SocketTransform.GetLocation(), TraceEnd, FColor::Red, false, 3.0f, 0, 3.0f);
9}

完整代码

ShootThemUp: Components/STUWeaponComponent.cpp

1// SpawnWeapon
2CurrentWeapon->SetOwner(Character);        

ShootThemUp: Weapon/STUBaseWeapon.cpp

 1#include "GameFramework/Character.h"
 2#include "GameFramework/PlayerController.h"
 3
 4// MakeShot
 5void ASTUBaseWeapon::MakeShot()
 6{
 7    if(!GetWorld()) return;
 8
 9    const auto Player = Cast<ACharacter>(GetOwner());
10    if (!Player) return;
11
12    const auto Controller = Player->GetController<APlayerController>();
13    if (!Controller) return;
14
15    FVector ViewLocation;
16    FRotator ViewRotation;
17    Controller->GetPlayerViewPoint(ViewLocation, ViewRotation);
18
19    const FVector TraceStart = ViewLocation;
20    const FVector ShootDirection = ViewRotation.Vector();
21    const FVector TraceEnd = TraceStart + ShootDirection * TraceMaxDistance;    
22
23    FHitResult HitResult;
24    GetWorld()->LineTraceSingleByChannel(HitResult, TraceStart, TraceEnd, ECollisionChannel::ECC_Visibility);    
25
26    if (HitResult.bBlockingHit)
27    {
28        DrawDebugLine(GetWorld(), SocketTransform.GetLocation(), HitResult.ImpactPoint, FColor::Red, false, 3.0f, 0, 3.0f);
29        DrawDebugSphere(GetWorld(), HitResult.ImpactPoint, 10.0f, 24, FColor::Red, false, 5.0f);
30    }
31    else
32    {
33        DrawDebugLine(GetWorld(), SocketTransform.GetLocation(), TraceEnd, FColor::Red, false, 3.0f, 0, 3.0f);
34    }
35}

查看

  1. 击中


  2. 未击中: 真实起点是Camera组件位置


  3. 终点和瞄准十字重合


击中游戏角色时, 交点在骨骼网格体上

虚幻编辑器


当前击中游戏角色时, 交点在Capsule组件上


放大Capsule组件显示

打开BP_STUBaseCharacter, 选择CapsuleComponent, 去到Details > Shape

  • 当前


  • 设置

    -
    Capsule Half Height 88
    Capsule Radius 88

显示Capsule组件

  1. 打开Console
  2. 执行命令
    1show collision           

效果图



轨迹计算时, 忽略游戏角色Capsule组件, 将骨骼网格体考虑在内


设置Capsule组件的碰撞选项

BP_STUBaseCharacter > CapsuleComponent > Details > Collision > Collision Presets

  • 当前

    TraceResponses > Visibility为Block, Capsule组件参与轨迹计算


  • 设置碰撞预设为Pawn

    TraceResponses > Visibility为Ignore, Capsule组件不参与轨迹计算



设置骨骼网格体的碰撞选项

BP_STUBaseCharacter > Mesh > Details > Collision > Collision Presets

  • 碰撞预设默认为CharacterMesh, TraceResponses > Visibility为Ignore, 骨骼网格体不参与轨迹计算


  • 将碰撞预设设置为Custom, 将TraceResponses > Visibility设置为Block, 使骨骼网格体参与轨迹计算



查看

  1. 可以击中骨骼网格体


  2. 子弹可以穿过游戏角色两腿之间而不被Capsule组件阻挡



恢复CapsuleComponent的Shape


当前的问题: 射程的起点为Camera组件位置, 理论上, 我们可以打到枪口无法打到的位置

  1. 和敌人背对背时击中敌人
    • 没调出来
    • 解决思路: 要求武器朝向和子弹真实轨迹的夹角为锐角
  2. 打到自己
    • BP_STUBaseCharacter > Camera组件 > Details > Transform > Location, 将XYZ置为0
    • 解决思路: 轨迹计算时, 屏蔽游戏角色本身

轨迹计算时屏蔽游戏角色本身

C++


设置轨迹计算参数 FCollisionQueryParams

添加屏蔽Actor

ShootThemUp: Weapon/STUBaseWeapon.cpp

1// MakeShot
2
3FCollisionQueryParams CollisionParams;
4// CollisionParams.AddIgnoredActor(Player);
5CollisionParams.AddIgnoredActor(GetOwner());

计算与轨迹发生碰撞的Actor: 修改参数

ShootThemUp: Weapon/STUBaseWeapon.cpp

1// MakeShot
2GetWorld()->LineTraceSingleByChannel(HitResult, TraceStart, TraceEnd, ECollisionChannel::ECC_Visibility, CollisionParams);

击中时, 输出骨骼信息

对不同部位造成伤害时, 游戏角色减少的生命值不同; 比如爆头可以导致游戏角色死亡


存放在FHitResult中

C++

ShootThemUp: Weapon/STUBaseWeapon.cpp

1// MakeShot         
2UE_LOG(LogBaseWeapon, Display, TEXT("Bone: %s"), *HitResult.BoneName.ToString());

查看日志

虚幻编辑器


射击轨迹



概览

Line Trace

  • 获取Socket变换信息
  • 获取游戏角色Camera组件的变换信息
  • 射击游戏对发射子弹的建模
  • 完成轨迹计算并获取碰撞信息
  • 绘制子弹轨迹和交点
  • 为轨迹计算添加屏蔽Actor

Socket

  1. 之前我们使用Socket作为锚来附加武器
  2. 现在我们使用它来标记枪口, 获取变换信息

为网格体Rifle添加Socket标记枪口, 获取变换信息

虚幻编辑器

  1. 双击打开 Content > ExternalContent > Weapon > Weapons > Rifle

  2. 选中 Skeleton Tree > RifleRoot , 右键, 选择 Add Socket , 命名为MuzzleSocket

  3. 在细节面板设置MuzzleSocket的位置 Relative Location

    默认在武器坐标系的0坐标, 调整到枪口位置


  4. 更换观察视角, 检查MuzzleSocket变换参数


    • Top视角



模拟射击: 绘制子弹轨迹

C++

STUBaseWeapon

-
起点 枪口 MuzzleSocket的位置分量
偏移 放大的武器朝向 MuzzleSocket的前进方向: 通过MuzzleSocket的旋转分量, 获取前进向量
终点 起点 + 偏移

获取MuzzleSocket变换以得到线段所需信息

Stub
MuzzleSocketName 保存Socket名称
TraceMaxDistance 放大系数, 亦是子弹射击距离; 1 unreal unit = 1 cm

获取Socket变换

  • 加载WeaponMeshComponent时检查其有效性

    在BeginPlay中调用; 使用时不必检查

    1check(WeaponMeshComponent);
  • 获取变换

    1. 参数: Socket名
    2. 返回相对于指定座标系的Socket变换, 默认是世界座标系
    1const FTransform SocketTransform = WeaponMeshComponent->GetSocketTransform(MuzzleSocketName); // 默认世界坐标系
    

获取起点

1const FVector TraceStart = SocketTransform.GetLocation();

获取武器的朝向

1const FVector ShootDirection = SocketTransform.GetRotation().GetForwardVector(); // 单位向量
2// GetRotation返回类型FQuat
3// 在此处FQuat比FRotator更方便, 因为GetForwardVector的存在, 可以获取X轴分量

计算终点

1const FVector TraceEnd = TraceStart + ShootDirection * TraceMaxDistance;         

绘制线段


获取World对象

1#include "Engine/World.h"         
2
3if(!GetWorld()) return;

绘制线段

参数
bPersistentLines 是否一直存在 false
LifeTime 持续时间, 单位s 3
DepthPriority 绘制优先级 0
1#include "DrawDebugHelpers.h"
2
3DrawDebugLine(GetWorld(), TraceStart, TraceEnd, FColor::Red, false, 3.0f, 0, 3.0f);          

完整代码


添加数据成员

ShootThemUp: Weapon/STUBaseWeapon.h

1UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
2FName MuzzleSocketName = "MuzzleSocket";
3
4UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
5float TraceMaxDistance = 1500.0f; // 1 unreal unit = 1cm > 15m

检查WeaponMeshComponent

ShootThemUp: Weapon/STUBaseWeapon.cpp

1// BeginPlay
2check(WeaponMeshComponent);

MakeShot

包含发射子弹的所有逻辑; 如温切斯特连发步枪, 扣一下扳机, 连发数枚子弹, 所以和Fire分开

  1. ShootThemUp: Weapon/STUBaseWeapon.h

    protected
    1void MakeShot();
  2. ShootThemUp: Weapon/STUBaseWeapon.cpp
     1#include "Engine/World.h"
     2#include "DrawDebugHelpers.h"
     3
     4void ASTUBaseWeapon::MakeShot() {
     5    if(!GetWorld()) return;
     6
     7    const FTransform SocketTransform = WeaponMeshComponent->GetSocketTransform(MuzzleSocketName);
     8    const FVector TraceStart = SocketTransform.GetLocation();
     9    const FVector ShootDirection = SocketTransform.GetRotation().GetForwardVector();
    10    const FVector TraceEnd = TraceStart + ShootDirection * TraceMaxDistance;
    11    DrawDebugLine(GetWorld(), TraceStart, TraceEnd, FColor::Red, false, 3.0f, 0, 3.0f);
    12}
  3. 在Fire中调用

在虚幻编辑器中查看

  1. 点击鼠标左键, 触发Fire

  2. 轨迹与枪口垂直: 未设置MuzzleSocket旋转信息, 需要与世界座标系对应轴方向一致



配置MuzzleSocket旋转信息

Rifle > Details

  1. 当前


  2. 前进方向对应X轴方向, 使MuzzleSocket座标系绕自己的Y轴逆时针旋转90度

    Relative Rotation > Pitch = 90


  3. 向上方向对应Z轴方向, 使MuzzleSocket座标系绕自己的X轴顺时针旋转90度

    Relative Rotation > Roll = -90


  4. 效果图



模拟射击: 绘制相交点

C++

给出子弹轨迹的起点和终点, 计算场景中与轨迹碰撞的物体, 保存到FHitResult对象中


HitResult存放碰撞结果

  • 包含以下信息
    - 数据成员
    碰撞时间
    交点 ImpactPoint
    法向量
    指向碰撞Actor的指针
    碰撞标志 bBlockingHit
  • 需要交点信息

计算与轨迹发生碰撞的Actor

  • Engine提供多个碰撞计算函数, 我们使用UWorld::LineTraceSingleByChannel
  • LineTraceSingleByChannel返回true则有碰撞, false则无; 而HitResult中亦有bBlockingHit
  • 如果发生碰撞, LineTraceSingleByChannel通过函数参数返回与线段第一个发生碰撞的物体信息
-
ECollisionChannel::ECC_Visibility 阻挡了Visibility的物体才参与轨迹计算
1FHitResult HitResult;
2GetWorld()->LineTraceSingleByChannel(HitResult, TraceStart, TraceEnd, ECollisionChannel::ECC_Visibility);

绘制相交点

1if (HitResult.bBlockingHit)
2{
3    DrawDebugSphere(GetWorld(), HitResult.ImpactPoint, 10.0f, 24, FColor::Red, false, 5.0f);
4}

完整代码

ShootThemUp: Weapon/STUBaseWeapon.cpp

1// MakeShot
2
3FHitResult HitResult;
4GetWorld()->LineTraceSingleByChannel(HitResult, TraceStart, TraceEnd, ECollisionChannel::ECC_Visibility);
5if (HitResult.bBlockingHit)
6{
7    DrawDebugSphere(GetWorld(), HitResult.ImpactPoint, 10.0f, 24, FColor::Red, false, 5.0f);
8}

查看相交点

虚幻编辑器


查看游戏角色Capsule组件的碰撞选项

BP_STUBaseCharacter > CapsuleComponent > Collision

  1. Collision Presets为Custom
  2. Trace Responses > Visibility为Block

    为Overlap或Ignore时, 不会参与相交计算



射中时, 以交点为圆心绘制球



注意到, 瞄准十字与子弹轨迹不重合


射击游戏中发射子弹建模

C++

  • 子弹从枪口射出, 是对真实世界中的射击进行模拟. 射击游戏中, 子弹从Camera组件位置射出. 该建模也适用于VR Virtual Reality
  • 需要将子弹轨迹的起点改为Camera组件的位置

在武器类获取游戏角色Camera组件的变换信息

  1. 之前旋转游戏角色视角那里, 有提到是通过游戏角色控制器旋转Camera组件完成的; 获取游戏角色Camera组件的变换信息, 亦是通过游戏角色控制器得到
  2. 可以通过游戏角色获取其所属控制器
  3. 可以通过武器组件获取其所属游戏角色
  4. 武器组件生成武器时, 可以为武器设置所属, 即设置武器属于游戏角色
武器 > 游戏角色 > 游戏角色控制器 > 游戏角色Camera组件的变换信息

武器类生成武器时, 设置其属于游戏角色

STUWeaponComponent

  • SpawnActor时, 可以通过FActorSpawnParameters设置其所属; 或者调用SetOwner, 武器的Owner和WeaponComponent的Owner相同
  • 使用GetOwner函数和Character数据成员均可获取武器类所属游戏角色
1// CurrentWeapon->SetOwner(GetOwner());
2CurrentWeapon->SetOwner(Character);

获取游戏角色控制器

STUBaseWeapon

1#include "GameFramework/Character.h"
2#include "GameFramework/PlayerController.h"
3
4const auto Player = Cast<ACharacter>(GetOwner());
5if (!Player) return;
6
7const auto Controller = Player->GetController<APlayerController>();
8if (!Controller) return;

通过游戏角色控制器获取Camera组件的变换信息: 位置和旋转

  • PlayerCameraManager是全局类, 负责管理Camera, 可以直接获取Character的Camera组件
  • GetPlayerViewPoint访问PlayerCameraManager类
1FVector ViewLocation;
2FRotator ViewRotation;
3Controller->GetPlayerViewPoint(ViewLocation, ViewRotation);

重新计算子弹轨迹和碰撞信息

-
起点 Camera组件位置
偏移 Camera组件旋转
1const FVector TraceStart = ViewLocation;
2const FVector ShootDirection = ViewRotation.Vector();
3const FVector TraceEnd = TraceStart + ShootDirection * TraceMaxDistance;    
4
5FHitResult HitResult;
6GetWorld()->LineTraceSingleByChannel(HitResult, TraceStart, TraceEnd, ECollisionChannel::ECC_Visibility);    

绘制子弹轨迹和交点

  1. 子弹从Camera组件所在射出; 若击中, 到击中物体结束, 否则, 绘制射程
  2. 绘制子弹轨迹时, 起点仍是枪口, 终点为击中物体或射程终点
  3. 若击中物体, 绘制交点
1if (HitResult.bBlockingHit)
2{
3    DrawDebugLine(GetWorld(), SocketTransform.GetLocation(), HitResult.ImpactPoint, FColor::Red, false, 3.0f, 0, 3.0f);
4    DrawDebugSphere(GetWorld(), HitResult.ImpactPoint, 10.0f, 24, FColor::Red, false, 5.0f);
5}
6else
7{
8    DrawDebugLine(GetWorld(), SocketTransform.GetLocation(), TraceEnd, FColor::Red, false, 3.0f, 0, 3.0f);
9}

完整代码

ShootThemUp: Components/STUWeaponComponent.cpp

1// SpawnWeapon
2CurrentWeapon->SetOwner(Character);        

ShootThemUp: Weapon/STUBaseWeapon.cpp

 1#include "GameFramework/Character.h"
 2#include "GameFramework/PlayerController.h"
 3
 4// MakeShot
 5void ASTUBaseWeapon::MakeShot()
 6{
 7    if(!GetWorld()) return;
 8
 9    const auto Player = Cast<ACharacter>(GetOwner());
10    if (!Player) return;
11
12    const auto Controller = Player->GetController<APlayerController>();
13    if (!Controller) return;
14
15    FVector ViewLocation;
16    FRotator ViewRotation;
17    Controller->GetPlayerViewPoint(ViewLocation, ViewRotation);
18
19    const FVector TraceStart = ViewLocation;
20    const FVector ShootDirection = ViewRotation.Vector();
21    const FVector TraceEnd = TraceStart + ShootDirection * TraceMaxDistance;    
22
23    FHitResult HitResult;
24    GetWorld()->LineTraceSingleByChannel(HitResult, TraceStart, TraceEnd, ECollisionChannel::ECC_Visibility);    
25
26    if (HitResult.bBlockingHit)
27    {
28        DrawDebugLine(GetWorld(), SocketTransform.GetLocation(), HitResult.ImpactPoint, FColor::Red, false, 3.0f, 0, 3.0f);
29        DrawDebugSphere(GetWorld(), HitResult.ImpactPoint, 10.0f, 24, FColor::Red, false, 5.0f);
30    }
31    else
32    {
33        DrawDebugLine(GetWorld(), SocketTransform.GetLocation(), TraceEnd, FColor::Red, false, 3.0f, 0, 3.0f);
34    }
35}

查看

  1. 击中


  2. 未击中: 真实起点是Camera组件位置


  3. 终点和瞄准十字重合


击中游戏角色时, 交点在骨骼网格体上

虚幻编辑器


当前击中游戏角色时, 交点在Capsule组件上


放大Capsule组件显示

打开BP_STUBaseCharacter, 选择CapsuleComponent, 去到Details > Shape

  • 当前


  • 设置

    -
    Capsule Half Height 88
    Capsule Radius 88

显示Capsule组件

  1. 打开Console
  2. 执行命令
    1show collision           

效果图



轨迹计算时, 忽略游戏角色Capsule组件, 将骨骼网格体考虑在内


设置Capsule组件的碰撞选项

BP_STUBaseCharacter > CapsuleComponent > Details > Collision > Collision Presets

  • 当前

    TraceResponses > Visibility为Block, Capsule组件参与轨迹计算


  • 设置碰撞预设为Pawn

    TraceResponses > Visibility为Ignore, Capsule组件不参与轨迹计算



设置骨骼网格体的碰撞选项

BP_STUBaseCharacter > Mesh > Details > Collision > Collision Presets

  • 碰撞预设默认为CharacterMesh, TraceResponses > Visibility为Ignore, 骨骼网格体不参与轨迹计算


  • 将碰撞预设设置为Custom, 将TraceResponses > Visibility设置为Block, 使骨骼网格体参与轨迹计算



查看

  1. 可以击中骨骼网格体


  2. 子弹可以穿过游戏角色两腿之间而不被Capsule组件阻挡



恢复CapsuleComponent的Shape


当前的问题: 射程的起点为Camera组件位置, 理论上, 我们可以打到枪口无法打到的位置

  1. 和敌人背对背时击中敌人
    • 没调出来
    • 解决思路: 要求武器朝向和子弹真实轨迹的夹角为锐角
  2. 打到自己
    • BP_STUBaseCharacter > Camera组件 > Details > Transform > Location, 将XYZ置为0
    • 解决思路: 轨迹计算时, 屏蔽游戏角色本身

轨迹计算时屏蔽游戏角色本身

C++


设置轨迹计算参数 FCollisionQueryParams

添加屏蔽Actor

ShootThemUp: Weapon/STUBaseWeapon.cpp

1// MakeShot
2
3FCollisionQueryParams CollisionParams;
4// CollisionParams.AddIgnoredActor(Player);
5CollisionParams.AddIgnoredActor(GetOwner());

计算与轨迹发生碰撞的Actor: 修改参数

ShootThemUp: Weapon/STUBaseWeapon.cpp

1// MakeShot
2GetWorld()->LineTraceSingleByChannel(HitResult, TraceStart, TraceEnd, ECollisionChannel::ECC_Visibility, CollisionParams);

击中时, 输出骨骼信息

对不同部位造成伤害时, 游戏角色减少的生命值不同; 比如爆头可以导致游戏角色死亡


存放在FHitResult中

C++

ShootThemUp: Weapon/STUBaseWeapon.cpp

1// MakeShot         
2UE_LOG(LogBaseWeapon, Display, TEXT("Bone: %s"), *HitResult.BoneName.ToString());

查看日志

虚幻编辑器