iT邦幫忙

2025 iThome 鐵人賽

DAY 7
0
Software Development

30 天用 Unreal Engine 5 C++ 開發遊戲系列 第 7

# Day 7|實作目標鎖定系統(Target Lock System)

  • 分享至 

  • xImage
  •  

今天來製作視角目標鎖定 (Target Locking)
從相機位置往前方發射一個 碰撞檢測
如果命中指定的物件類型,則會將目標鎖定在第一個被擊中的物件上。
接著角色會 朝向目標,並且將相機稍微提高一些,以獲得更好的視角。
(有GIF需要載入)

0. 前置作業

iThome30daysCharacter.h 設置標頭:

變數:

UPROPERTY(Category="Character: Camera", EditAnywhere, BlueprintReadOnly)
AActor* TargetActor;

UPROPERTY(Category="Character: Camera", EditAnywhere, BlueprintReadWrite)
bool bIsTargetLocking;

函式:

UFUNCTION(BlueprintCallable, Category="Input")
virtual void DoTargeting(float DeltaTime, float TargetZOffset=100.f, float CamHeight = 140.f, float RInterpSpeed = 10.f, float VInterpSpeed = 10.f);

UFUNCTION(BlueprintCallable, Category="Input", meta=(bIgnoreSelf="true", WorldContext="WorldContextObject", AutoCreateRefTerm="ActorsToIgnore"))
virtual void TargetLockCam(float TraceStartXOffset, float TraceLength, float TraceRadius, TArray<TEnumAsByte<EObjectTypeQuery>> ObjectTypes, TArray<AActor*> ActorsToIgnore, EDrawDebugTrace::Type DrawDebugType, float DebugDuration);

1. 由相機往前方發射 Sphere Trace

首先,我們需要一個檢測方式來找出角色前方是否有可鎖定的目標。這裡使用 Sphere Trace For Objects,由相機位置往角色面朝方向延伸一段距離。

偵測邏輯如下:

  1. 取得相機的位置與前方方向。
  2. 在這個方向上發射一個 Sphere Trace
  3. 設定檢測的 Object Type(例如 Pawn、Enemy)。
FHitResult OutHit;
FVector Start = FollowCamera->GetComponentLocation() + FollowCamera->GetForwardVector() * TraceStartXOffset;
FVector End = FollowCamera->GetComponentLocation() + FollowCamera->GetForwardVector() * TraceLength;
const bool bHit = UKismetSystemLibrary::SphereTraceSingleForObjects(
        this,                                    // WorldContext
        Start,                                   // Start
        End,                                     // End(不移動,只是球體)
        TraceRadius,                             // 半徑
        ObjectTypes,                             // 偵測類型
        false,                                   // bTraceComplex
        ActorsToIgnore,                          // 忽略清單
        DrawDebugType,                           // Debug 顯示
        OutHit,                                  // 結果
        true,                                    // 忽略自己
        FLinearColor::Red,                       // Trace 顏色
        FLinearColor::Green,                     // Hit 顏色
        DebugDuration                            // Debug 持續時間
    );

示範GIF載入失敗

2. 取得第一個命中的物件

當 Sphere Trace 擊中任何符合條件的物件時,會返回第一個命中的 Object。
Out Hit 可拿到命中 Object 的資料,像是 Actor、Location、Distance。

if (bHit) TargetActor = OutHit.GetActor();

示範GIF載入失敗

3. 角色平滑旋轉朝向目標

在 Tick 裡檢查是否有有效的 TargetActor

  • 計算角色到目標的方向向量。
  • 使用 插值(FInterpTo / RInterpTo) 讓角色平滑旋轉,而不是瞬間轉向。
// Target Rotation
FVector TargetDir = TargetActor->GetActorLocation() - GetActorLocation() + FVector(0,0,TargetZOffset);
FRotator TargetR = FMath::RInterpTo(Controller->GetControlRotation() , TargetDir.Rotation(), DeltaTime, 10.f);
Controller->SetControlRotation(TargetR);

示範GIF載入失敗

4. 相機高度微調

為了提供更好的視角,我們可以在鎖定目標時:

  • 將 CameraBoom 的 相對 Z 偏移 平滑提高一點(例如 +150)。
  • 當解除鎖定時,平滑恢復到原本的高度 (0)。
if (bIsTargetLocking)
{
    // Target Cam Height
    float CurLoc1 = CameraBoom->GetRelativeLocation().Z;
    float TartgetZ1 = FMath::FInterpTo(CurLoc1, CamHeight, DeltaTime, 10.f);
    CameraBoom->SetRelativeLocation(FVector(0,0,TartgetZ1));
}else
{
    // OG Cam Height
    float CurLoc0 = CameraBoom->GetRelativeLocation().Z;
    float TargetZ0 = FMath::FInterpTo(CurLoc0, 0, DeltaTime, 10.f);
    CameraBoom->SetRelativeLocation(FVector(0,0,TargetZ0));
}

示範GIF載入失敗

完整程式碼:

void AiThome30daysCharacter::TargetLockCam(float TraceStartXOffset, float TraceLength, float TraceRadius, TArray<TEnumAsByte<EObjectTypeQuery>> ObjectTypes, TArray <AActor*> ActorsToIgnore, EDrawDebugTrace::Type DrawDebugType, float DebugDuration)
{
	if (TargetActor != nullptr)
	{
		TargetActor = nullptr;
		bIsTargetLocking = false;
		return;
	}
	FHitResult OutHit;
	FVector Start = FollowCamera->GetComponentLocation() + FollowCamera->GetForwardVector() * TraceStartXOffset;
	FVector End = FollowCamera->GetComponentLocation() + FollowCamera->GetForwardVector() * TraceLength;
	const bool bHit = UKismetSystemLibrary::SphereTraceSingleForObjects(
			this,                                    // WorldContext
			Start,                                   // Start
			End,                                     // End(不移動,只是球體)
			TraceRadius,                             // 半徑
			ObjectTypes,                             // 偵測類型
			false,                                   // bTraceComplex
			ActorsToIgnore,                          // 忽略清單
			DrawDebugType,                           // Debug 顯示
			OutHit,                               // 結果
			true,                                    // 忽略自己
			FLinearColor::Red,                     // Trace 顏色
			FLinearColor::Green,                       // Hit 顏色
			DebugDuration                            // Debug 持續時間
		);
	if (bHit) TargetActor = OutHit.GetActor();
	bIsTargetLocking = true;
}
void AiThome30daysCharacter::DoTargeting(float DeltaTime, float TargetZOffset, float CamHeight, float RInterpSpeed, float VInterpSpeed)
{
	if (bIsTargetLocking && TargetActor && Controller)
	{
		// Target Rotation
		FVector TargetDir = TargetActor->GetActorLocation() - GetActorLocation() + FVector(0,0,TargetZOffset);
		FRotator TargetR = FMath::RInterpTo(Controller->GetControlRotation() , TargetDir.Rotation(), DeltaTime, 10.f);
		Controller->SetControlRotation(TargetR);

		// Target Cam Height
		float CurLoc1 = CameraBoom->GetRelativeLocation().Z;
		float TartgetZ1 = FMath::FInterpTo(CurLoc1, CamHeight, DeltaTime, 10.f);
		CameraBoom->SetRelativeLocation(FVector(0,0,TartgetZ1));
	}else
	{
		// OG Cam Height
		float CurLoc0 = CameraBoom->GetRelativeLocation().Z;
		float TargetZ0 = FMath::FInterpTo(CurLoc0, 0, DeltaTime, 10.f);
		CameraBoom->SetRelativeLocation(FVector(0,0,TargetZ0));
	}
}

Blueprint 裡接的方式:
https://ithelp.ithome.com.tw/upload/images/20250921/20171036OoNJr2HwME.png
https://ithelp.ithome.com.tw/upload/images/20250921/201710364tFqdLctM5.png

喔對前面忘記說了,要新增一個 IA_TargetLock,然後在 IMC_Defualt 設置按鍵。
另外,我 GIF 裡面使用的綠色沙包是 /Variant_Combat/Blueprints/Interactables/BP_CombatDummy,然後 Object Type 建議改成 Pawn,預設是 World Dynamic。
https://ithelp.ithome.com.tw/upload/images/20250921/20171036M8sz2zEZ5z.png
也可以自訂,在 Target Lock Cam 功能那再 Add pin 就可以了。


那下一篇就會開始加入近戰攻擊邏輯囉!


上一篇
# Day 6|加入 Root Motion 閃避動畫
系列文
30 天用 Unreal Engine 5 C++ 開發遊戲7
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言