Używanie Line Traces w C++

Ślady linii służą do testowania kolizji przy użyciu zdefiniowanego segmentu linii. Istnieje kilka rodzajów śladów linii. Śledzenie linii może być oparte na kanale (widoczność lub kamera) lub na typie obiektu (WorldStatic, WorldDynamic, Pawn, …). 

Funkcje śledzenia linii są zdefiniowane w klasie UWorld . Istnieją funkcje, które zwracają tylko pierwszy znaleziony obiekt i inne funkcje, które zwracają wiele obiektów, które zderzyły się z segmentem linii.

W tym artykule skupimy się na jednej z funkcji śledzenia linii. Funkcja to LineTraceSingleByChannel(), która jest oparta na kanale i zwraca pierwszy znaleziony obiekt. Wywołanie funkcji w przykładowym kodzie wygląda następująco:

bool bHitSomething = GetWorld()->LineTraceSingleByChannel(OutHitResult,
                                                          StartLocation,
                                                          EndLocation,
                                                          ECC_Visibility,
                                                          LineTraceParams);

Spójrzmy na każdą część tego wywołania funkcji:

  • bHitSomething :  zmienna logiczna, która przechowuje wartość zwracaną przez funkcję. Otrzymuje wartość true, jeśli śledzenie linii zderzyło się z obiektem.
  • GetWorld () -> LineTraceSingleByChannel () :  Funkcja GetWorld () zwraca wskaźnik typu UWorld, który reprezentuje bieżący poziom. Funkcja Line Trace jest wywoływana za pomocą tego wskaźnika.
  • OutHitResult :  zmienna typu FHitResult . Zostanie wypełniony różnymi informacjami związanymi ze znalezionym obiektem.
  • StartLocation :  FVector  definiujący początek odcinka linii używanego w teście kolizji.
  • EndLocation :  FVector  definiujący koniec segmentu linii.
  • ECC_Visibility :  Ta stała wskazuje, że kanał Visibility będzie używany do testu zderzenia.
  • LineTraceParams :  zmienna typu FCollisionQueryParams, którą można wypełnić wartościami parametrów, które zostaną przekazane do funkcji śledzenia linii. 

Przykładowe użycie:

W tym przykładzie będziesz potrzebować projektu C ++, który ma szablon First Person z Starter Conten . Stworzyłem projekt o nazwie TutoFirstPerson .

Zmodyfikujmy postać gracza, aby używał linii śledzenia do interakcji z kinkietem ściennym. Postać Gracza musi patrzeć w kierunku kinkietu, który musi być mniej niż 3 metry dalej. Po naciśnięciu klawisza E oświetlenie kinkietu włącza się lub wyłącza.

Utwórz klasę C ++ o nazwie WallSconce, używając klasy Actor jako klasy nadrzędnej. Ta klasa zawiera StaticMeshComponent i PointLightComponent . Posiada również funkcję PressSwitch (), która będzie używana przez odtwarzacz do włączania i wyłączania światła. Plik WallSconce.h ma następującą zawartość:

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "WallSconce.generated.h"

UCLASS()
class AWallSconce : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AWallSconce();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	UPROPERTY(VisibleAnywhere)
	USceneComponent* RootScene;

	UPROPERTY(VisibleAnywhere)
	UStaticMeshComponent* StaticMeshComponent;
	
	UPROPERTY(VisibleAnywhere)	
	class UPointLightComponent* PointLightComponent;
	
	void PressSwitch();
};

WallSconce klasa używa Static Mesh SM_Lamp_Wall . W konstruktorze mamy konfigurację komponentów Static Mesh i Point Light. Funkcja PressSwitch () wywołuje funkcję ToggleVisibility () PointLightComponent . Oto zawartość pliku WallSconce.cpp:

#include "WallSconce.h"
#include "Components/PointLightComponent.h"

// Sets default values
AWallSconce::AWallSconce()
{
  // Set this actor to call Tick() every frame.
  PrimaryActorTick.bCanEverTick = true;

  RootScene = CreateDefaultSubobject<USceneComponent>(TEXT("RootScene"));
  RootComponent = RootScene;

  StaticMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(
                                              TEXT("StaticMeshComponent"));
  StaticMeshComponent->SetupAttachment(RootScene);
  
  ConstructorHelpers::FObjectFinder<UStaticMesh> MeshFile(
    TEXT("/Game/StarterContent/Props/SM_Lamp_Wall.SM_Lamp_Wall"));

  if (MeshFile.Succeeded())
  {
    StaticMeshComponent->SetStaticMesh(MeshFile.Object);
  }
  
  // PointLightComponent initialization
  PointLightComponent = CreateDefaultSubobject<UPointLightComponent>(
                                              TEXT("PointLightComponent"));
  PointLightComponent->SetIntensity(1000.f);
  PointLightComponent->SetLightColor(FLinearColor(1.f, 1.f, 1.f));
  PointLightComponent->SetupAttachment(RootScene);
  PointLightComponent->SetRelativeLocation(FVector(0.0f, 0.0f, 30.0f));
}

void AWallSconce::PressSwitch()
{
  PointLightComponent->ToggleVisibility();
}

// Called when the game starts or when spawned
void AWallSconce::BeginPlay()
{
  Super::BeginPlay();	
}

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

Teraz utworzymy mapowanie wejściowe o nazwie Interakcja, które zostanie uruchomione, gdy gracz naciśnie klawisz E.

W edytorze poziomów wejdź do menu Edit-> Project Settings … i w kategorii Engine wybierz opcję Input. Kliknij symbol + obok pozycji Odwzorowania akcji , wprowadź nazwę Interakcja dla nowego mapowania akcji i wybierz klawisz E. Do mapowania akcji dla projektu będzie wyglądać następująco:

Otwórz plik nagłówkowy klasy Character utworzonej przez szablon First Person . Nazwa pliku w moim przykładowym projekcie to TutoFirstPersonCharacter.h. Dodaj deklarację funkcji InteractWithWorld() poniżej funkcji SetupPlayerInputComponent():

 // APawn interface
  virtual void SetupPlayerInputComponent(UInputComponent* InputComponent) 
                                                                 override;
  // End of APawn interface

  void InteractWithWorld();

W pliku Character cpp musisz dodać #include na początku pliku:

#include "WallSconce.h"

W SetupPlayerInputComponent () funkcji, Interact Wejście będzie związany z InteractWithWorld () funkcji. Zmień nazwę ATutoFirstPersonCharacter na nazwę klasy Character twojego projektu i nie zapomnij umieścić operatora & przed nazwą. Służy do zwracania adresu pamięci funkcji.

PlayerInputComponent->BindAction("Interact", IE_Pressed, this,
                                 &ATutoFirstPersonCharacter::InteractWithWorld);

Na końcu pliku cpp utwórz funkcję InteractWithWorld () z poniższym kodem. Nie zapomnij zmienić nazwy klasy, która znajduje się przed operatorem „ :: ”.

void ATutoFirstPersonCharacter::InteractWithWorld()
{
  float LengthOfTrace = 300.f;
	
  FVector StartLocation;
  FVector EndLocation;
  
  StartLocation = FirstPersonCameraComponent->GetComponentLocation();
  
  EndLocation = StartLocation + 
    (FirstPersonCameraComponent->GetForwardVector() * LengthOfTrace);
	
  FHitResult OutHitResult;
  FCollisionQueryParams LineTraceParams;  
   
  bool bHitSomething = GetWorld()->LineTraceSingleByChannel(OutHitResult, 
                       StartLocation, EndLocation, ECC_Visibility, LineTraceParams);

  if(bHitSomething)
  {
    
    AWallSconce* WallSconceInstance = Cast<AWallSconce>( OutHitResult.GetActor() );
	
    if(WallSconceInstance)
    {
      WallSconceInstance->PressSwitch();
    }
  }
}

Wykonano rzutowanie <AWallSconce> w celu sprawdzenia, czy aktor znaleziony przez śledzenie linii należy do klasy AWallSconce . Jeśli tak, to zostanie wywołana funkcja PressSwitch () klasy AWallSconce .

Skompiluj kod C ++. Umieść wystąpienie WallSconce na jednej ze ścian. Uruchom grę, przejdź do WallSconce i spójrz w jego stronę. Naciśnij klawisz E , aby włączyć lub wyłączyć światło.


Źródło: https://romeroblueprints.blogspot.com/2021/01/using-line-traces-in-c.html