ToonTanks - Help me make a Land Mine - Problem Solved

I have finished all the lessons for ToonTanks in this course:

Unreal Engine C++ Developer: Learn C++ and Make Video Games

Now i am trying to make a new pawn class that is a land mine. The idea is that if the tank touches it, it will deal damage to the tank and explode. But it is not doing anything when the tank touches it. Can someone help me?

PawnLandMine.h:

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"

#include "GameFramework/Pawn.h"

#include "PawnLandMine.generated.h"

class UCapsuleComponent;

class UHealthComponent;

UCLASS()

class TOONTANKS_API APawnLandMine : public APawn

{

    GENERATED_BODY()

private:

    // COMPONENTS

    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true"))

    UCapsuleComponent* CapsuleComp;

    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true"))

    UStaticMeshComponent* BaseMesh;

    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true"))

    UHealthComponent* HealthComponent;

    // VARIABLES

    UPROPERTY(EditAnywhere, Category = "Effects")

    UParticleSystem* DeathParticle;

    UPROPERTY(EditAnywhere, Category = "Effects")

    USoundBase* DeathSound;

    UPROPERTY(EditAnywhere, Category = "Effects")

    TSubclassOf<UMatineeCameraShake> DeathShake;

    UPROPERTY(EditDefaultsOnly, Category = "Damage")

    TSubclassOf<UDamageType> DamageType;

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Damage", meta = (AllowPrivateAccess = "true"))

    float Damage = 50;

    

// FUNCTIONS

    UFUNCTION()

    void OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit);

public:

    // Sets default values for this pawn's properties

    APawnLandMine();

    void PawnDestroyed();

    virtual void HandleDestruction();

protected:

    // Called when the game starts or when spawned

    virtual void BeginPlay() override;

};

PawnLandMine.cpp:

// Fill out your copyright notice in the Description page of Project Settings.


#include "PawnLandMine.h"
#include "Components/CapsuleComponent.h"
#include "ToonTanks/Components/HealthComponent.h"
#include "Kismet/GameplayStatics.h"

// Sets default values
APawnLandMine::APawnLandMine()
{
 	// Set this pawn to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = false;
	
	CapsuleComp = CreateDefaultSubobject<UCapsuleComponent>(TEXT("Capsule Collider"));
	CapsuleComp->OnComponentHit.AddDynamic(this, &APawnLandMine::OnHit);
	RootComponent = CapsuleComp;

	BaseMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Base Mesh"));
	BaseMesh->SetupAttachment(RootComponent);
	HealthComponent = CreateDefaultSubobject<UHealthComponent>(TEXT("Health Component"));
	UE_LOG(LogTemp, Warning, TEXT("APawnLandMine::APawnLandMine() has been run"));
}

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

void APawnLandMine::HandleDestruction() 
{
	// Play death effects particle, sound and camera shake.

	UGameplayStatics::SpawnEmitterAtLocation(this, DeathParticle, GetActorLocation());
	UGameplayStatics::SpawnSoundAtLocation(this, DeathSound, GetActorLocation());
	GetWorld()->GetFirstPlayerController()->ClientPlayCameraShake(DeathShake);
}

void APawnLandMine::PawnDestroyed() 
{
	HandleDestruction();
	Destroy();	
}

void APawnLandMine::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit) 
{
	UE_LOG(LogTemp, Warning, TEXT("APawnLandMine::OnHit has been run"));
	// If the other actor ISN'T self AND exists, then apply damage
	if (OtherActor && OtherActor != this)
	{
		UGameplayStatics::ApplyDamage(OtherActor, Damage, GetOwner()->GetInstigatorController(), this, DamageType);
		PawnDestroyed();
	}
}

===========================================================================
Update 2021-03-07:

DanM said:

  1. Do you intend to being able to control this land mine? If the answer is no then this should be derived from AActor not APawn.

No, I do not need to control the land mine as a player. I have made an Actor for the Land Mine before this making this Pawn. It caused the game to crash due to the code:

GetOwner()->GetInstigatorController()

Here’s its code:

LandMineBase.h:

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

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

UCLASS()
class TOONTANKS_API ALandMineBase : public AActor
{
	GENERATED_BODY()

private:
	// COMPONENTS
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true"))
	UStaticMeshComponent* LandMineMesh;

	// VARIABLES
	UPROPERTY(EditDefaultsOnly, Category = "Damage")
	TSubclassOf<UDamageType> DamageType;
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Damage", meta = (AllowPrivateAccess = "true"))
	float Damage = 50;
	UPROPERTY(EditAnywhere, Category = "Effects")
	UParticleSystem* ExplodeParticle;
	UPROPERTY(EditAnywhere, Category = "Effects")
	USoundBase* ExplodeSound;
	UPROPERTY(EditAnywhere, Category = "Effects")
	TSubclassOf<UMatineeCameraShake> HitShake;

	// FUNCTIONS
	UFUNCTION()
	void OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit);

public:	
	// Sets default values for this actor's properties
	ALandMineBase();

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

LandMineBase.cpp:

// Fill out your copyright notice in the Description page of Project Settings.


#include "LandMineBase.h"
#include "Components/StaticMeshComponent.h"
#include "Kismet/GameplayStatics.h"
#include "Particles/ParticleSystemComponent.h"

// Sets default values
ALandMineBase::ALandMineBase()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = false;

	LandMineMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Land Mine Mesh"));
	LandMineMesh->OnComponentHit.AddDynamic(this, &ALandMineBase::OnHit);
	RootComponent = LandMineMesh;
}

// Called when the game starts or when spawned
void ALandMineBase::BeginPlay()
{
	Super::BeginPlay();
	// DEBUG
	// GetOwner()->GetInstigatorController() seems to always make the game crash!
	if (GetOwner()->GetInstigatorController() == nullptr)
	{
		UE_LOG(LogTemp, Error, TEXT("GetOwner()->GetInstigatorController() is invalid or has a nullptr!"));
		return;
	}
}

void ALandMineBase::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit) 
{
	// Try to get a reference to the owning class.
	AActor* MyOwner = GetOwner();
	// If for some reason we can't get a valid reference, return as we need to check against the owner.
	if (!MyOwner)
	{
		return;
	}
	// If the other actor ISN'T self OR Owner AND exists, then apply damage
	if (OtherActor && OtherActor != this/* && OtherActor != MyOwner*/)
	{
		UGameplayStatics::ApplyDamage(OtherActor, Damage, MyOwner->GetInstigatorController(), this, DamageType);
		UGameplayStatics::SpawnEmitterAtLocation(this, ExplodeParticle, GetActorLocation());
		UGameplayStatics::PlaySoundAtLocation(this, ExplodeSound, GetActorLocation());
		GetWorld()->GetFirstPlayerController()->ClientPlayCameraShake(HitShake);
		Destroy();
	}
}

Here’s Blueprints:
BP_LandMineBase:


  1. Do you intend to being able to control this land mine? If the answer is no then this should be derived from AActor not APawn.
  2. Are you saying that log isn’t coming up?

This log does not come up. The landmine does not seem to detect when the tank’s Capsule Collider touches its own.

By any chance did you create the blueprint before you added and compiled this line of code?

LandMineMesh->OnComponentHit.AddDynamic(this, &ALandMineBase::OnHit);

I probably did.
I just deleted and remade the blueprint for LandMineBase, but it still caused a crash:

Crash Report:

LoginId:99ca5f6d414e9a5c4a59bb909f115cc0
EpicAccountId:adb257f957b54506b13b5ee2ef54f2b1

Unhandled Exception: EXCEPTION_ACCESS_VIOLATION reading address 0x0000000000000130

UE4Editor_Engine
UE4Editor_ToonTanks_6295!ALandMineBase::BeginPlay() [D:\Coding Projects\Unreal games\ToonTanks\Source\ToonTanks\Actors\LandMineBase.cpp:26]
UE4Editor_Engine
UE4Editor_Engine
UE4Editor_Engine
UE4Editor_Engine
UE4Editor_Engine
UE4Editor_UnrealEd
UE4Editor_UnrealEd
UE4Editor_UnrealEd
UE4Editor_UnrealEd
UE4Editor_UnrealEd
UE4Editor_UnrealEd
UE4Editor_UnrealEd
UE4Editor_UnrealEd
UE4Editor
UE4Editor
UE4Editor
UE4Editor
UE4Editor
kernel32
ntdll

Line 26 of LandMineBase.cpp:

	if (GetOwner()->GetInstigatorController() == nullptr)

Well GetOwner is going to return a nullptr as nothing owns it. You are immediately dereferencing that nullptr which causes the crash.

So that would be the issue of OnHit not doing (sorry I didn’t notice before). Since this land mine isn’t (presumably) going to be possessed by a controller you can just pass nullptr to the argument needed for Instigator.

Putting in nullptr made the game not crash, but the BP_LandMineBase still does nothing when the tank touches it. I even deleted and remade the blueprint after compilling the code.

If you still have this then this is going to return from the function as the owner is null.

I have commented the lines of code you mentioned, and now it works! I just have to make sure to delete the commented code and make sure the land mine does not touch the level.

void ALandMineBase::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit) 
{
	// Try to get a reference to the owning class.
	// AActor* MyOwner = GetOwner();

	// If for some reason we can't get a valid reference, return as we need to check against the owner.
	// if (!MyOwner)
	// {
	// 	return;
	// }

	// If the other actor ISN'T self OR Owner AND exists, then apply damage
	if (OtherActor && OtherActor != this/* && OtherActor != MyOwner*/)
	{
		UGameplayStatics::ApplyDamage(OtherActor, Damage, nullptr, this, DamageType);
		UGameplayStatics::SpawnEmitterAtLocation(this, ExplodeParticle, GetActorLocation());
		UGameplayStatics::PlaySoundAtLocation(this, ExplodeSound, GetActorLocation());
		GetWorld()->GetFirstPlayerController()->ClientPlayCameraShake(HitShake);
		Destroy();
	}
}

You can add a check OtherActor->IsA<APawnTank>() or the base class if you have moving turrets you want to also be able to trigger it.

Privacy & Terms