Antes de começar
Certifique-se de ter concluído os seguintes objetivos na seção anterior, Equipar seu Personagem:
Criamos um item de coleta de ressurgimento e o adicionamos ao seu nível
Criamos um lançador de dardos equipável para seu personagem segurar e usar
Projéteis Básicos
Seu personagem pode segurar seu lançador de dardos, e sua ferramenta tem vinculações de controle para usá-la, mas não faz jus ao nome dela. Nesta seção, você implementará a lógica de projéteis para fazer com que os dardos sejam lançados a partir do item equipado.
A Unreal Engine tem uma classe de componente de movimento de projétil que você pode adicionar a um ator para transformá-lo em um projétil. Ele inclui muitas variáveis úteis, como velocidade do projétil, ricochete e escala de gravidade.
Para lidar com o cálculo matemático da implementação de projéteis, você precisará pensar em várias coisas:
A transformação inicial, velocidade e direção do projétil.
Escolha se deseja gerar projéteis a partir do centro do personagem ou da ferramenta que ele equipou.
O comportamento que você deseja que o projétil tenha ao colidir com outro objeto.
Criar uma Classe Base de Projétil
Primeiro, você criará uma classe base de projétil e, em seguida, criará uma subclasse a partir para criar projéteis únicos para suas ferramentas.
Para começar a configurar uma classe base de projétil, siga estas etapas:
No Unreal Editor, acesse Ferramentas > Nova classe C++. Selecione Ator como a classe pai e nomeie a classe como
FirstPersonProjectile. Clique em Criar classe.No VS, acesse
FirstPersonProjectile.h. No topo do arquivo, declare para a frente umUProjectileMovementComponente umUSphereComponent.Você usará um componente de esfera simples para simular colisões entre o projétil e outros objetos.
C++// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "FirstPersonProjectile.generated.h" class UProjectileMovementComponent; class USphereComponent;Adicione os especificadores
BlueprintTypee Blueprintablepara expor essa classe a Blueprints:C++UCLASS(BlueprintType, Blueprintable) class FIRSTPERSON_API AFirstPersonProjectile : public AActorAbra
FirstPersonProjectile.cpp, na parte superior do arquivo, adicione a instrução include para"GameFramework/ProjectileMovementComponent.h"e"Components/SphereComponent.h"para incluir as classes de componente de movimento e colisão do projétil.C++#include "FirstPersonProjectile.h" #include "GameFramework/ProjectileMovementComponent.h" #include "Components/SphereComponent.h" // Sets default values AFirstPersonProjectile::AFirstPersonProjectile()
Implementar Comportamento de Projétil ao Atingir Objetos
Para deixar o projétil mais realista, faça-o exercer uma força (um impulso) nos objetos atingidos. Por exemplo, se você atirar em um bloco ativado por física, o projétil empurrará o bloco ao longo do solo. Em seguida, remova o projétil após o impacto em vez de deixá-lo cumprir seu tempo de vida padrão. Crie uma função OnHit() para implementar esse comportamento.
Para implementar o comportamento de impacto do projétil, siga estas etapas:
Em
FirstPersonProjectile.h,na seçãopública, defina uma propriedadefloatdenominadaPhysicsForce.Adicione um macro
UPROPERTY()comEditAnywhere,BlueprintReadOnlyeCategory = "Projectile | Physics".É a quantidade de força que o projétil transmite ao atingir algo.
C++// The amount of force this projectile imparts on objects it collides with UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Projectile | Physics") float PhysicsForce = 100.0f;Defina uma função
voidOnHit(). Esta é uma função de AActor que é chamada quando o ator colide com outro componente ou ator. Ela recebe os seguintes argumentos:HitComp: o componente que foi atingido.OtherActor: o ator que foi atingido.OtherComp: o componente que criou o impacto (neste caso, o CollisionComponent do projétil)."NormalImpulse":o impulso normal do impacto.Acerto: uma referência deFHitResultque contém mais dados sobre o evento Acerto, como hora, distância e posição.
C++// Called when the projectile collides with an object UFUNCTION() void OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit);Em
FirstPersonProjectile.cpp, implemente a funçãoOnHit()definida no arquivo de cabeçalho. Dentro deOnHit(), em uma instruçãoif, verifique se:OtherActornão é nulo e não é o projétil em si.OtherCompnão é nulo e também simula física.
C++void AFirstPersonProjectile::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit) { // Only add impulse and destroy projectile if we hit a physics object if ((OtherActor != nullptr) && (OtherActor != this) && (OtherComp != nullptr) && OtherComp->IsSimulatingPhysics()) { } }Isso verifica se o projétil atingiu um objeto diferente dele e que participa da física.
Dentro da instrução
if, useAddImpulseAtLocation()para adicionar um impulso ao componenteOtherComp.Passe para a função a velocidade do projétil multiplicada por
PhysicsForcee aplique-a na posição do ator do projétil.C++void AFirstPersonProjectile::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit) { // Only add impulse and destroy projectile if we hit a physics object if ((OtherActor != nullptr) && (OtherActor != this) && (OtherComp != nullptr) && OtherComp->IsSimulatingPhysics()) { // --- New Code Start --- OtherComp->AddImpulseAtLocation(GetVelocity() * PhysicsForce, GetActorLocation()); // --- New Code End --- } }AddImpulseAtLocation()é uma função de física na Unreal Engine que aplica uma força instantânea (impulso) a um objeto de física simulado em uma posição específica no espaço do mundo. É útil quando você deseja simular um impacto, como uma explosão arremessando algo, uma bala atingindo um objeto ou uma porta sendo aberta.Como este projétil atingiu outro ator, remova o projétil da cena chamando
Destroy().
A sua função OnHit() completa ficará desta forma:
void AFirstPersonProjectile::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{
// Only add impulse and destroy projectile if we hit a physics
if ((OtherActor != nullptr) && (OtherActor != this) && (OtherComp != nullptr) && OtherComp->IsSimulatingPhysics())
{
OtherComp->AddImpulseAtLocation(GetVelocity() * PhysicsForce, GetActorLocation());
Destroy();
}
}Adicione a malha, o movimento e os componentes de colisão do projétil
Em seguida, adicione uma malha estática, uma lógica de movimento do projétil e uma esfera de colisão ao projétil e defina como o projétil deve se mover.
Para adicionar esses componentes ao seu projétil, siga estas etapas:
Em
FirstPersonProjectile.h, na Na seçãopublic, declare umTObjectPtrpara umUStaticMeshComponentchamadoProjectileMesh. Esta é a malha estática do projétil no mundo.Dê a ele uma macro
UPROPERTY()comEditAnywhere,BlueprintReadOnlyeCategory = "Projectile | Mesh".C++// Mesh of the projectile in the world UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Projectile | Mesh") TObjectPtr<UStaticMeshComponent> ProjectileMesh;Na seção
protegida, declare:Um
TObjectPtra umUSphereComponentdenominadoCollisionComponent.Um
TObjectPtra umUProjectileMovementComponentdenominadoProjectileMovement.
Dê a ambos uma macro
UPROPERTY()comVisibleAnywhere,BlueprintReadOnlyeCategory = "Projectile | Components".C++// Sphere collision component UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Projectile | Components") TObjectPtr<USphereComponent> CollisionComponent; // Projectile movement component UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Projectile | Components") TObjectPtr<UProjectileMovementComponent> ProjectileMovement;O
ProjectileMovementComponentlida com a lógica de movimento para você. Ele calcula aonde seu Ator pai deve ir com base na velocidade, gravidade e outras variáveis. Depois, durantemarca de verificação, aplica o movimento ao projétil.Em
FirstPersonProjectile.cpp, na função de construtorAFirstPersonProjectile(), crie subobjetos padrão para os componentes de malha, colisão e movimento do projétil. Em seguida, anexe a malha do projétil ao componente de colisão.C++// Sets default values AFirstPersonProjectile::AFirstPersonProjectile() { // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; // --- New Code Start --- // Use a simple sphere as the collision representation CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("CollisionComponent")); check(CollisionComponent != nullptr);Chame
InitSphereRadius()para inicializar o tamanho doCollisionComponent.C++// Sets default values AFirstPersonProjectile::AFirstPersonProjectile() { // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; // Use a simple sphere as the collision representation CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("CollisionComponent")); check(CollisionComponent != nullptr);Defina o nome do perfil de colisão do componente de colisão como
ProjétilusandoBodyInstance.SetCollisionProfileName().C++CollisionComponent->InitSphereRadius(5.0f); // --- New Code Start --- // Set the collision profile to the "Projectile" collision preset CollisionComponent->BodyInstance.SetCollisionProfileName("Projectile"); // --- New Code End ---No Unreal Editor, seus perfis de colisão são armazenados em Configurações de Projeto, Engine, Colisão, e você pode definir até 18 perfis de colisão personalizados para usar no código. O comportamento padrão desse perfil de colisão "Projétil" é Bloco e cria colisões em qualquer objeto com o qual colide.
Você definiu uma função
OnHit()anteriormente para ativar quando o projétil atinge algo, mas precisa de uma maneira de notificar quando essa colisão ocorre. Para fazer isso, use a macroAddDynamic()para assinar uma função paraOnComponentHitEventemCollisionComponent. Passe a esta macro a funçãoOnHit().C++CollisionComponent->InitSphereRadius(5.0f); // Set the collision profile to the "Projectile" collision preset CollisionComponent->BodyInstance.SetCollisionProfileName("Projectile"); // --- New Code Start --- // Set up a notification for when this component hits something blocking CollisionComponent->OnComponentHit.AddDynamic(this, &AFirstPersonProjectile::OnHit); // --- New Code End ---Defina o
CollisionComponentcomo oRootComponentdo projétil e como oUpdatedComponentpara o componente de movimento a ser rastreado.C++CollisionComponent->InitSphereRadius(5.0f); // Set the collision profile to the "Projectile" collision preset CollisionComponent->BodyInstance.SetCollisionProfileName("Projectile"); // Set up a notification for when this component hits something blocking CollisionComponent->OnComponentHit.AddDynamic(this, &AFirstPersonProjectile::OnHit); // --- New Code Start ---Inicialize o componente
ProjectileMovementcom os seguintes valores:InitialSpeed: a velocidade inicial do projétil quando ele surge. Defina como3000.0f.MaxSpeed: a velocidade máxima do projétil. Defina como3000.0f.bRotationFollowVelocity: se o objeto deve girar para atingir a direção de sua velocidade. Por exemplo, como um avião de papel sobe e desce enquanto sobe e desce. Defina comoverdadeiro.bShouldBounce: se o projétil deve rebater nos obstáculos. Defina comoverdadeiro.Bounciness: quantidade de velocidade preservada após uma colisão, sendo que valores mais baixos fazem com que o projétil perca mais energia. Defina como0,4f.Atrito: quantidade de velocidade tangencial (lateral) preservada após o impacto. Defina como
0,8.
C++// Set as root component and UpdatedComponent RootComponent = CollisionComponent; ProjectileMovement->UpdatedComponent = CollisionComponent; // --- New Code Start --- ProjectileMovement->InitialSpeed = 3000.f; ProjectileMovement->MaxSpeed = 3000.f; ProjectileMovement->bRotationFollowsVelocity = true; ProjectileMovement->bShouldBounce = true; ProjectileMovement->Bounciness = 0.2f;
Defina a vida útil do projétil
Por padrão, você fará o projétil desaparecer alguns segundos após dispará-lo. No entanto, depois de derivar os Blueprints de projétil no editor, você pode experimentar alterar ou remover esse tempo padrão para tentar encher o nível com dardos de espuma.
Para fazer o projétil desaparecer após alguns segundos, siga estas etapas:
Em
FirstPersonProjectile.h, na seçãopública, declare um float denominadoProjectileLifespan.Dê a ele uma macro
UPROPERTY()comEditAnywhere,BlueprintReadOnlyeCategory = "Projectile | Lifespan".É o tempo de vida do projétil em segundos.
C++// Despawn after 5 seconds by default UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Projectile | Lifespan") float ProjectileLifespan = 5.0f;Em
FirstPersonProjectile.cpp, no final da função de construtorAFirstPersonProjectile(), defina oInitialLifeSpando projétil comoProjectileLifespanpara fazê-lo desaparecer após cinco segundos.C++ProjectileMovement->UpdatedComponent = CollisionComponent; ProjectileMovement->InitialSpeed = 3000.f; ProjectileMovement->MaxSpeed = 3000.f; ProjectileMovement->bRotationFollowsVelocity = true; ProjectileMovement->bShouldBounce = true; ProjectileMovement->Bounciness = 0.2f; ProjectileMovement->Friction = 0.8f; // --- New Code Start --- // Disappear after 5.0 seconds by default.InitialLifeSpané uma propriedade herdada de AActor. É um float que define quanto tempo o ator vive antes de morrer. Um valor0significa que o Ator vive até o jogo parar.
Seu FirstPersonProjectile.h completo deve ter a seguinte aparência:
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "FirstPersonProjectile.generated.h"
class UProjectileMovementComponent;
class USphereComponent;
O AFirstPersonProjectile.cpp completo deve ficar assim:
// Copyright Epic Games, Inc. All Rights Reserved.
#include "FirstPersonProjectile.h"
#include "GameFramework/ProjectileMovementComponent.h"
#include "Components/SphereComponent.h"
// Sets default values
AFirstPersonProjectile::AFirstPersonProjectile()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
Obter a Direção da Câmera do Personagem
Os projéteis devem surgir do próprio lançador de dardos. Portanto, você precisará fazer um cálculo para saber onde o lançador de dardos está e para onde ele está apontando. Como o lançador está anexado ao personagem do jogador, esses valores mudarão de acordo com onde o personagem está e para onde está olhando.
Seu personagem em primeira pessoa contém algumas das informações posicionais necessárias para lançar um dardo. Portanto, comece modificando a classe do personagem para capturar essas informações com um traçado de linha e retornar o resultado.
Para rastrear e obter as informações necessárias do seu personagem, siga estas etapas:
No VS, abra o
.he o.cppdos arquivos do seu personagem.No arquivo
.h, na seçãopública, declare uma nova função denominadaGetCameraTargetLocation()que retorna umFVector. Essa função retornará a posição no mundo para o qual o personagem está olhando.C++// Returns the location in the world the character is looking at UFUNCTION() FVector GetCameraTargetLocation();No arquivo
.cppdo personagem, implemente a funçãoGetCameraTargetLocation(). Comece declarando umFVectordenominadoTargetPositionpara retornar.C++FVector AAdventureCharacter::GetCameraTargetLocation() { // The target position to return FVector TargetPosition; }Crie um ponteiro para o
UWorldchamandoGetWorld().C++FVector AAdventureCharacter::GetCameraTargetLocation() { // The target position to return FVector TargetPosition; // --- New Code Start --- UWorld* const World = GetWorld(); // --- New Code End --- }Adicione uma instrução
ifpara verificar seMundonão é nulo. Na instruçãoif, declare umFHitResultdenominadoAcerto.C++FVector AAdventureCharacter::GetCameraTargetLocation() { // The target position to return FVector TargetPosition; UWorld* const World = GetWorld(); // --- New Code Start --- if (World != nullptr) {FHitResulté uma struct no UE que armazena informações sobre o resultado de uma consulta de colisão, incluindo o Ator ou componente que foi atingido e onde ele foi atingido.Para encontrar o ponto para o qual a personagem está olhando, você vai simular um traçado de linha ao longo do vetor para o qual o personagem está olhando até um ponto distante. Se o traçado de linha colidir com um objeto, você saberá para onde seu personagem está olhando no mundo.
Na instrução if, declare dois valores
const FVectordenominadosTraceStarteTraceEnd:Defina
TraceStartpara a posição deFirstPersonCameraComponent.Defina
TraceEndcomoTraceStartmais o vetor para a frente do componente de câmera multiplicado por um número bem grande. Isso garante que o rastro irá longe o suficiente para colidir com a maioria dos objetos no seu mundo, desde que o personagem não esteja olhando para o céu. (Se o personagem estiver olhando para o céu,TraceEndserá o ponto terminal do traçado de linha.)C++if (World != nullptr) { // The result of the line trace FHitResult Hit; // --- New Code Start --- // Simulate a line trace from the character along the vector they're looking down const FVector TraceStart = FirstPersonCameraComponent->GetComponentLocation(); const FVector TraceEnd = TraceStart + FirstPersonCameraComponent->GetForwardVector() * 10000.0; // --- New Code End ---
Chame
LineTraceSingleByChannel()a partir doUWorldpara simular o rastro. PasseAcerto,TraceStart,TraceEnde umECollisionChannel::ECC_Visibility.Isso simula um traçado de linha de
TraceStartatéTraceEnd,colidindo com qualquer objeto visível e armazenando o resultado do rastro emAcerto.ECollisionChannel::ECC_Visibilityé o canal a ser usado para o rastro, e esses canais definem os tipos de objetos que o rastro deve tentar atingir. UseECC_Visibilitypara verificações de câmera em linha de visão.C++if (World != nullptr) { // The result of the line trace FHitResult Hit; // Simulate a line trace from the character along the vector they're looking down const FVector TraceStart = FirstPersonCameraComponent->GetComponentLocation(); const FVector TraceEnd = TraceStart + FirstPersonCameraComponent->GetForwardVector() * 10000.0; // --- New Code Start ---O valor
Acertoagora contém informações sobre o resultado, como a posição e a normal do impacto. Ele também sabe se o impacto foi resultado de uma colisão de objeto. O local do impacto (ou o ponto final da linha de rastro) é o local alvo da câmera para retorno.Use um operador ternário para definir
TargetPositioncomoHit.ImpactPointse o impacto for um impacto de bloqueio eHit.TraceEndse não for. Em seguida, retorneTargetPosition.C++if (World != nullptr) { // The result of the line trace FHitResult Hit; // Simulate a line trace from the character along the vector they're looking down const FVector TraceStart = FirstPersonCameraComponent->GetComponentLocation(); const FVector TraceEnd = TraceStart + FirstPersonCameraComponent->GetForwardVector() * 10000.0; // Simulate a line trace and save result in Hit
A sua função GetCameraTargetLocation() completa ficará desta forma:
FVector AAdventureCharacter::GetCameraTargetLocation()
{
// The target position to return
FVector TargetPosition;
UWorld* const World = GetWorld();
if (World != nullptr)
{
// The result of the line trace
Gerar projéteis com DartLauncher::Use()
Agora que você sabe para onde o personagem está olhando, implemente o restante da lógica do projétil na função Use() do lançador de dardos. Você obterá a localização e a direção para lançar o projétil e, em seguida, gerará o projétil.
Para obter a localização e a rotação em que o projétil deve surgir, siga estes passos:
Em
DartLauncher.h, no topo do arquivo, adicione uma declaração de encaminhamento paraAFirstPersonProjectile.Na seção
public, declare uma propriedadeTSubclassOf<AFirstPersonProjectile>de nomeProjectileClass. Este será o projétil que o lançador de dardos vai gerar. Atribua a ele a macroUPROPERTY()comEditAnywhereeCategory = "Projectile".DartLauncher.hagora deve ficar assim:C++// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "EquippableToolBase.h" #include "DartLauncher.generated.h" class AFirstPersonProjectile;DartLauncher.cpp, adicione instruções include para:”Kismet/KismetMathLibrary.h”. A matemática de projéteis pode ser complicada, e este arquivo inclui várias funções que você usará para lançar projéteis."FirstPersonProjectile.h""EnhancedInputComponent.h"
C++#include "Tools/DartLauncher.h" #include "FirstPersonProjectile.h" #include "Kismet/KismetMathLibrary.h" #include "EnhancedInputComponent.h" #include "AdventureCharacter.h"Na implementação de
Use()do DartLauncher, depois da mensagem de depuração:Obtenha o
UWorldchamandoGetWorld().Adicione uma instrução
ifpara verificar seMundoeProjectileClassnão são nulos.Na instrução
if, obtenha a posição para a qual o personagem está olhando chamandoOwningCharacter->GetCameraTargetLocation().
C++void ADartLauncher::Use() { GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Yellow, TEXT("Using the dart launcher!")); UWorld* const World = GetWorld(); if (World != nullptr && ProjectileClass != nullptr) { FVector TargetPosition = OwningCharacter->GetCameraTargetLocation(); }Os projéteis devem surgir da ferramenta que o personagem está segurando, mas não do centro do objeto equipado. A malha
SKM_Pistoldo lançador de dardos tem um soquete "Cano" que você pode usar para definir um ponto de surgimento preciso para seus dardos.Na instrução
if, declare um novoFVectordenominadoSocketLocatione defina-o como o resultado da chamada deGetSocketLocation(“Muzzle”)noToolMeshComponent.C++if (World != nullptr && ProjectileClass != nullptr) { // Get the direction of the player camera FVector TargetPosition = OwningCharacter->GetCameraTargetLocation(); // --- New Code Start --- // Get the correct socket to spawn the projectile from FVector SocketLocation = ToolMeshComponent->GetSocketLocation("Muzzle"); // --- New Code End --- }Declare um
FRotatordenominadoSpawnRotation. Esta é a rotação (valores de arfagem, eixo vertical e eixo de inclinação) do projétil conforme ele surge.Defina isso como o resultado da chamada de
FindLookAtRotation()da biblioteca matemática kismet, transmitindoSocketLocationeTargetPositionque você obteve a partir do personagem do jogador.C++if (World != nullptr && ProjectileClass != nullptr) { // Get the direction of the player camera FVector TargetPosition = OwningCharacter->GetCameraTargetLocation(); // Get the correct socket to spawn the projectile from FVector SocketLocation = ToolMeshComponent->GetSocketLocation("Muzzle"); // --- New Code Start --- // Get projectile's rotation as it spawns so we know in what direction to apply an offsetFindLookAtRotationcalcula e retorna a rotação que você precisaria emSocketLocationpara enfrentarTargetPosition.Declare um
FVectordenominadoSpawnLocatione defina-o como o resultado da adição deSocketLocatione do vetor direto deSpawnRotationmultiplicado por10,0.O soquete do cano não fica exatamente na frente do lançador, então você precisará multiplicar o vetor por um deslocamento para que o projétil dispare a partir do local correto.
C++if (World != nullptr && ProjectileClass != nullptr) { // Get the direction of the player camera FVector TargetPosition = OwningCharacter->GetCameraTargetLocation(); // Get the correct socket to spawn the projectile from FVector SocketLocation = ToolMeshComponent->GetSocketLocation("Muzzle"); // Get the rotation of the projectile as it spawns so we know in what direction to apply an offset FRotator SpawnRotation = UKismetMathLibrary::FindLookAtRotation(SocketLocation, TargetPosition);
Agora que você tem um local e uma rotação, está pronto para gerar o projétil.
Para gerar um projétil, siga estes passos:
Ainda na função
Use(), na instruçãoif, declare umFActorSpawnParametersdenominadoActorSpawnParams. Esta classe inclui informações sobre onde e como gerar o ator.C++void ADartLauncher::Use() { GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Yellow, TEXT("Using the dart launcher!")); UWorld* const World = GetWorld(); if (World != nullptr && ProjectileClass != nullptr) { // Get the direction of the player camera FVector TargetPosition = OwningCharacter->GetCameraTargetLocation();Defina o valor
SpawnCollisionHandlingOverrideemActorSpawnParamscomoESpawnActorCollisionHandlingMethod::AdjustIfPossibleButDontSpawnIfColliding.C++//Set Spawn Collision Handling Override FActorSpawnParameters ActorSpawnParams; // --- New Code Start --- ActorSpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButDontSpawnIfColliding; // --- New Code End ---Esta linha de código tenta encontrar um lugar para que o projétil surja num local onde não colida com outro Ator (como em uma parede) e não permitirá surgimentos se não encontrar um local adequado.
Use
SpawnActor()para gerar o projétil no cano do lançador de dardos, transmitindoProjectileClass,SpawnLocation,SpawnRotationeActorSpawnParams.C++ActorSpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButDontSpawnIfColliding; // --- New Code Start --- // Spawn the projectile at the muzzle World->SpawnActor<AFirstPersonProjectile>(ProjectileClass, SpawnLocation, SpawnRotation, ActorSpawnParams); // --- New Code End ---
Sua função Use() completa agora deve ficar assim:
void ADartLauncher::Use()
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Yellow, TEXT("Using the dart launcher!"));
UWorld* const World = GetWorld();
if (World != nullptr && ProjectileClass != nullptr)
{
FVector TargetPosition = OwningCharacter->GetCameraTargetLocation();
// Get the correct socket to spawn the projectile from
Derivar uma classe e Blueprint de dardo de espuma
Agora que toda a lógica de geração está pronta, é hora de construir um projétil de verdade! Sua classe de lançador de dardos precisa de uma subclasse de AFirstPersonProjectile para ser iniciada, então você precisará criar uma em código para usar em seu nível.
Para derivar uma classe de projétil de dardo de espuma para usar no jogo, siga estas etapas:
No Unreal Editor, acesse Ferramentas > Nova classe C++.
Acesse Todas as classes, pesquise e selecione projeto em primeira pessoa como a classe pai e nomeie a classe
FoamDart. Clique em Criar classe.No VS, deixe esses arquivos como estão, salve seu código e compile-o.
Neste tutorial, você não implementará nenhum código de projétil personalizado além do que definiu em FirstPersonProjectile, mas poderá modificar a classe FoamDart por conta própria para atender às necessidades do seu projeto. Por exemplo, você pode tentar fazer com que os projéteis de dardo grudem em objetos em vez de desaparecerem ou quicarem.
Para criar um Blueprint de dardo de espuma, siga estes passos:
No Unreal Editor, crie uma classe de Blueprint baseada em FoamDart e nomeie-a
BP_FoamDart. Salve isso na sua pastaFirstPerson/Blueprints/Ferramentas.Com o Blueprint aberto, selecione o componente Malha do projétil e defina a Malha estática como
SM_FoamBullet.A malha do dardo de espuma aparece na janela de visualização. Aproxime ou pressione F para observar a orientação da malha. A extremidade arredondada é a parte frontal do dardo e deve apontar para a parte superior do eixo X para que os dardos disparem na orientação correta (
ProjectileMovementassume que +X é para frente).Gire o dardo no eixo Z (azul) +90 graus.
Compile e salve o Blueprint.
No Navegador de Conteúdo, abra
BP_DartLauncher.No painel Detalhes , na nova seção Projétil , defina a Classe de Projétil como
BP_FoamDart.Se você não vir
BP_FoamDartna lista, vá até o Navegador de Conteúdo, selecioneBP_FoamDart, depois volte para a propriedade Classe de Projétil e clique em Usar ativo selecionado do Navegador de Conteúdo.Clique em Compile e Save.
É hora da grande revelação. Salve seus ativos e clique em Reproduzir. Quando o jogo começar, você pode correr até o lançador de dardos para pegá-lo. Pressionar o botão esquerdo do mouse faz um projétil sair do cano do lançador de dardos e rebater pelo nível! Esses projéteis devem desaparecer após cinco segundos e transmitir uma pequena força física aos objetos com os quais colidem (incluindo você!).
Se o Inicializador de dardos não disparar e você não vir a mensagem de depuração "Usando o Inicializador de dardos",certifique-se de ter atribuído IA_UseTool à propriedade Usar ação no Blueprint do personagem.
Bônus: ajustando projéteis e ferramentas
Outra opção é implementar os ajustes finais para que os dardos e o inicializador de dardos tenham a melhor aparência possível.
Fazendo projéteis caídos ficarem achatados
Em FirstPersonProjectile.cpp, quando o projétil atingir um alvo, verifique se atingiu o solo (superfície horizontal plana) e, caso tenha atingido, faça com que fique achatado.
No topo de OnHit(), adicione este código:
// If we hit the ground (mostly-up surface normal), lay the dart flat.
if (FVector::DotProduct(Hit.ImpactNormal, FVector::UpVector) > 0.7f)
{
FRotator Flat = GetActorRotation();
Flat.Pitch = 0.f;
Flat.Roll = 0.f;
SetActorRotation(Flat);
return;
}Próxima
Parabéns! Você concluiu o tutorial da Trilha do Programador em Primeira Pessoa e aprendeu muito ao longo do caminho!
Você implementou personagens e movimentos personalizados, criou itens de coleta e dados e até mesmo criou ferramentas equipáveis com seus próprios projéteis. Você tem tudo o que precisa para pegar esse projeto e transformá-lo em algo totalmente seu.
Aqui estão algumas sugestões:
Você pode expandir o inventário do jogador com diferentes tipos de itens? E quanto às pilhas de itens?
Você pode combinar coletas e projéteis para criar munição coletável? Que tal implementar esse sistema de munição no lançador de dardos?
Você pode desenvolver a ideia de consumíveis em consumíveis equipáveis? Que tal um kit de saúde que o jogador segura ou uma bola que ele pode pegar e arremessar?
Código completo
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "FirstPersonProjectile.generated.h"
class UProjectileMovementComponent;
class USphereComponent;
// Copyright Epic Games, Inc. All Rights Reserved.
#include "FirstPersonProjectile.h"
#include "GameFramework/ProjectileMovementComponent.h"
#include "Components/SphereComponent.h"
// Sets default values
AFirstPersonProjectile::AFirstPersonProjectile()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/Character.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "InputActionValue.h"
// Copyright Epic Games, Inc. All Rights Reserved.
#include "AdventureCharacter.h"
#include "InventoryComponent.h"
#include "EquippableToolDefinition.h"
#include "EquippableToolBase.h"
// Sets default values
AAdventureCharacter::AAdventureCharacter()
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "EquippableToolBase.h"
#include "DartLauncher.generated.h"
class AFirstPersonProjectile;
// Copyright Epic Games, Inc. All Rights Reserved.
#include "Tools/DartLauncher.h"
#include "FirstPersonProjectile.h"
#include "Kismet/KismetMathLibrary.h"
#include "EnhancedInputComponent.h"
#include "AdventureCharacter.h"
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "EnhancedInputSubsystems.h" //
#include "Animation/AnimBlueprint.h"
#include "Components/SkeletalMeshComponent.h"
#include "EquippableToolBase.generated.h"