Implementing a mine (help)

I have implemented a mine in the toon tanks to try and practice and make the game my own. The mine works as intended when I drive over it, it will damage the tank, even destroy it.

The problem occurs when I shoot a projectile and it lands on the mine and crashes the engine. I assume this is a nullptr but probably wrong because I am doing the null checks I need.

The code my not be the best or the smartest way to do it, but I do want to destroy the mine if shot at, any help to point me in the right direction to solve this would be great.

LMine.cpp

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


#include "LMine.h"
#include "Components/StaticMeshComponent.h"
#include "Components/SphereComponent.h"
#include "Kismet/GameplayStatics.h"
#include "Tank.h"
#include "HealthComponent.h"

// Sets default values
ALMine::ALMine()
{
 	 PrimaryActorTick.bCanEverTick = false;

    MineMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MineMesh"));
    RootComponent = MineMesh;

    CollisionSphere = CreateDefaultSubobject<USphereComponent>(TEXT("CollisionSphere"));
    CollisionSphere->SetupAttachment(RootComponent);
    CollisionSphere->InitSphereRadius(50.f);
    CollisionSphere->OnComponentBeginOverlap.AddDynamic(this, &ALMine::OnOverlapBegin);
}

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

void ALMine::OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult)
{
	 if (OtherActor && OtherActor != this)
    {
        ATank* Tank = Cast<ATank>(OtherActor);
        UHealthComponent* HealthComp = Cast<UHealthComponent>(Tank->GetComponentByClass(UHealthComponent::StaticClass()));
            if (HealthComp)
            {
                HealthComp->DamageTaken(Tank, DamageAmount, nullptr, nullptr, this);
                Destroy();
            }
    }
}

LMine.h

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

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/StaticMeshComponent.h"
#include "Components/SphereComponent.h"
#include "LMine.generated.h"

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

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

private:    
    UPROPERTY(VisibleAnywhere)
    UStaticMeshComponent* MineMesh;

    UPROPERTY(VisibleAnywhere)
	 USphereComponent* CollisionSphere;

    UPROPERTY(EditAnywhere)
    float DamageAmount = 50.f;

    UFUNCTION()
    void OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult);

};

Error:

Unhandled Exception: EXCEPTION_ACCESS_VIOLATION reading address 0x0000000000000000

UnrealEditor_Engine
UnrealEditor_ToonTanks!ALMine::OnOverlapBegin() [E:\LaniganDEV\Projects\LittleTank\Source\ToonTanks\LMine.cpp:37]
UnrealEditor_ToonTanks!ALMine::execOnOverlapBegin() [E:\LaniganDEV\Projects\LittleTank\Intermediate\Build\Win64\UnrealEditor\Inc\ToonTanks\LMine.gen.cpp:34]
UnrealEditor_CoreUObject
UnrealEditor_CoreUObject
UnrealEditor_Engine
UnrealEditor_Engine
UnrealEditor_Engine
UnrealEditor_Engine
UnrealEditor_Engine
UnrealEditor_Engine
UnrealEditor_Engine
UnrealEditor_Engine
UnrealEditor_Engine
UnrealEditor_Engine
UnrealEditor_Engine
UnrealEditor_Engine
UnrealEditor_Core
UnrealEditor_Core
UnrealEditor_Core
UnrealEditor_Engine
UnrealEditor_Engine
UnrealEditor_Engine
UnrealEditor_Engine
UnrealEditor_UnrealEd
UnrealEditor_UnrealEd
UnrealEditor
UnrealEditor
UnrealEditor
UnrealEditor
UnrealEditor
UnrealEditor
kernel32
ntdll

Is it accurate ti say that you are applying damage to the tank on collision, but Tim this case the other is a shell, and not a tank. You have no damage to apply in this case but you would just destroy both unless you are trying to plan for a near tank to take splash damage or something

You are potentially dereferencing a null pointer here (-> first dereferences the pointer). So what happens if the cast failed thus Tank is nullptr?

1 Like

You are correct. I Added and if check for the tank before the health component, because if the tank component is null it can not get the health component.

void ALMine::OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult)
{
	 if (OtherActor && OtherActor != this)
    {
        ATank* Tank = Cast<ATank>(OtherActor);
        if(Tank){

        UHealthComponent* HealthComp = Cast<UHealthComponent>(Tank->GetComponentByClass(UHealthComponent::StaticClass()));
            if (HealthComp)
            {
                HealthComp->DamageTaken(Tank, DamageAmount, nullptr, nullptr, this);
                Destroy();
        }   }
    }
}

After Updating the code for a handle destruction in the LMine class and updating my GameMode.cpp it seems to be operating the way i like.

void ALMine::HandleDestruction()
{
    Destroy();
}

LittleTanksGameMode:

void ALittleTanksGameMode::ActorDied(AActor* DeadActor)
{
    if(DeadActor == Tank)
    {
        Tank->HandleDestruction();
        if(LittleTanksPlayerController)
        {
            LittleTanksPlayerController->SetPlayerEnabledState(false);
        }
        
    }
    else if (ATower* DestroyedTower = Cast<ATower>(DeadActor))
    {
        DestroyedTower->HandleDestruction();
    }
    else if(ALMine* DestroyedMine = Cast<ALMine>(DeadActor))
    {
        DestroyedMine->HandleDestruction();
    }
    

With that said, why are you casting? The point of casting to a derived type is to access the derived class’ members (and to return nullptr if the object isn’t of that type) but you aren’t doing that here. You should be and to just call GetComponentByClass on the actor (or better yet use FindComponent to save typing)

// No need to cast
// ATank* Tank = Cast<ATank>(OtherActor);
// if (Tank)
UHealthComponent* HealthComp = OtherActor->FindComponentByClass<UHealthComponent>();
if (HealthComp)

Like I said I am new to the unreal c++ and this is the way i thought about going about it,

void ALMine::OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult)
{
	 if (OtherActor && OtherActor != this)
    {

        UHealthComponent* HealthComp = Cast<UHealthComponent>(FindComponentByClass<UHealthComponent>());
        {
            if (HealthComp)
            {
                HealthComp->DamageTaken(OtherActor, DamageAmount, nullptr, nullptr, this);
                Destroy();
        }   }
    }
}

so for damageTaken i would want to pass in OtherActor this will be any actor that enters the collision correct?
Also then the problem does exsit that it can damage the tank, my thoughts about this would be just destroying them both


void ALMine::OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult)

{

     if (OtherActor && OtherActor != this)

    {

        UHealthComponent* HealthComp = Cast<UHealthComponent>(FindComponentByClass<UHealthComponent>());

        {

             if (HealthComp)

            {

                HealthComp->DamageTaken(OtherActor, DamageAmount, nullptr, nullptr, this);

                Destroy();

            }

        }

     

        Destroy();

         

    }

}

Sorry I must not understand the concepts so far and was trying something maybe I need to push on through the course and come back then, Just trying to learn by doing.

The Cast there isn’t needed either. There are two versions (overloads) of FindComponentByClass.

One is

UActorComponent* FindComponentByClass(TSubclassOf<UActorComponent>)

The other is a function template. Templates are bit of an advanced feature of C++ but it enables generic programming; you write a “recipe” of a sorts, for a function or class and the compiler will use that to generate a function or class.

The definition of that template is

template<typename T>
T* FindComponentByClass()
{
    // Call the other overload
    return Cast<T>(FindComponentByClass(T::StaticClass()));
}

And when called like

FindComponent<UHealthComponent>();

The compiler will generate a function

UHealthComponent* FindComponentByClass()
{
    return Cast<UHealthComponent>(FindComponentByClass(UHealthComponent::StaticClass());
}

So it’s already casting to the type you specified.

1 Like

After spending all day, I still never really got my head over how function templets and such work. Since i was having problems with being able to destroy the mine and drive over it(because of collision) so if i did overlap then the onHit from the projectile wouldnt work. so I worked around by doing the calls directly in the overlapevent for the mine taking in the projectile damage as a peramtor, so even if the projectile damage amount changes , it will also apply the same to the mine.

The mine damage is seperate so if i want to make a mine weaker or stronger that is individually better.

I know my code is probably the best way to do it, and there will be a lot of refactoring which will come down the line when I progress more but i am just happy I was able to problem solve it, Even if it took me all day.

Here is the new code:

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


#include "Mine.h"
#include "Tank.h"
#include "HealthComponent.h"
#include "Projectile.h"
#include "Kismet/GameplayStatics.h" 

// Sets default values
AMine::AMine()
{
 	// 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;
	CollisionSphere = CreateDefaultSubobject<USphereComponent>(TEXT("CollisionSphere"));
    CollisionSphere->InitSphereRadius(50.f);
	RootComponent = CollisionSphere;
    CollisionSphere->OnComponentBeginOverlap.AddDynamic(this, &AMine::OnOverlapBegin);

	MineMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MineMesh"));
    
	MineMesh->SetupAttachment(RootComponent);



}

void AMine::HandleDestruction()
{
	Destroy();
}

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

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

}

void AMine::OnOverlapBegin(UPrimitiveComponent *OverlappedComp, AActor *OtherActor, UPrimitiveComponent *OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult &SweepResult)
{
	if ( OtherActor && OtherActor != this)
    {
        AProjectile* Projectile = Cast<AProjectile>(OtherActor);
        if (Projectile)
        {

            // Apply damage to the mine
            UHealthComponent* HealthComp = FindComponentByClass<UHealthComponent>();
            if (HealthComp)
            {
                UGameplayStatics::ApplyDamage(this, Projectile->Damage, nullptr, this, nullptr);
                
			}
            // Destroy the projectile
            Projectile->Destroy();
        }
			//Applies Damage to the actor who goes into the mine
			if (OtherActor && OtherActor != this && !Projectile)
			{
				UGameplayStatics::ApplyDamage(OtherActor, DamageAmount, nullptr, this, nullptr);
				Destroy();

			}

    }
}


Well just happy my short paragraph was correct at least. :grinning:

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms