Not getting component's location as expected

I just completed the toon tanks section, and I am trying to make some changes in the code to make the game better.

The Change I want to make:-
I want to let the enemy tower fire, only when the tank is in its range and no object is between tower and tank so that when the tower fires the projectile, it doesn’t collide with any wall.

The problem:-

  • Actually I am not getting the correct location of the UStaticMeshComponent* Turret.
  • And even If I manually set the location somewhat up it is not detecting my tank as a Word_Dynamic.

My Code

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

#pragma once

#include "CoreMinimal.h"
#include "BasePawn.h"
#include "Tower.generated.h"

/**
 * 
 */
UCLASS()
class TOONTANKS_API ATower : public ABasePawn
{
	GENERATED_BODY()
	
public:
	// Called every frame
	virtual void Tick(float DeltaTime) override;

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

private:
	
	// class APawn* TankRef = nullptr;
	class ATank* TankRef = nullptr;

	FTimerHandle FireRateTimeHandle;

	UPROPERTY(EditDefaultsOnly, Category = "Combat")
	float FireRange = 700.f;

	UPROPERTY(EditDefaultsOnly, Category = "Combat")
	float FireRate = 1.0f;

	bool CheckTankRange();
	void CheckFireCondition();

	UStaticMeshComponent* TurretRef;

	void SettingUp();
};

Turret.cpp:-

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


#include "Tower.h"
#include "Kismet/GameplayStatics.h"
#include "Tank.h"
#include "TimerManager.h"
#include "DrawDebugHelpers.h"

#define OUT

void ATower::BeginPlay()
{
    Super::BeginPlay();

    SettingUp();
}

void ATower::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

    if(CheckTankRange())  //Rotating the tower's turret every frame if the tank is in range
    {
        RotateTurret(TankRef->GetActorLocation()); //Using RotateTurret Function derived form BasePawn class and passing the TankLocation as target location
    }
}

void ATower::CheckFireCondition()  //Checks the condition for firing
{
    if (!TankRef) {return;}

    FHitResult HitResult;
    FVector TurretLocation = TurretRef->GetComponentTransform().GetLocation();
    FVector TurretRangeRocation = TurretRef->GetComponentTransform().GetRotation().Vector() * FireRange;

    bool HasHit = GetWorld()->LineTraceSingleByChannel(OUT HitResult, TurretLocation, TurretRangeRocation, ECC_WorldDynamic);
    DrawDebugLine(GetWorld(), TurretLocation, TurretRangeRocation, FColor::Red, true, 0.f, 0.f, 5.f);

    if(HasHit)
    {
        UE_LOG(LogTemp, Warning, TEXT("%s"), *(HitResult.GetActor())->GetName());
    }

    if (Cast<ATank>(HitResult.GetActor()) == TankRef)
    {
        if (CheckTankRange() & TankRef->bTankAlive)  //Checks if the tank is in range or not and tank is alive or not. If  teh tank would be dead then tower will not fire any more
        {
            FireProjectile(); //after checking the above condition tower fires the projectile by using this function which is derived from BasePawn class
        }
    }
    
}

bool ATower::CheckTankRange()  //This function checks if the tank is in range or not
{
    if(!TankRef) {return false;}
    
    float Distance = FVector::Dist(GetActorLocation(), TankRef->GetActorLocation()); //stores the distance between tower and tank

    if(Distance <= FireRange) //If the distance is equal or less than the Fire Range then this function will return true
    {
        return true;
    }

    return false;
}

void ATower::HandleDestruction()  //HandleDestruction for tower
{
    Super::HandleDestruction();  //Calls every part of the fuction named same as this in parent class
    Destroy();  //Destroy the tower from the level
}

void ATower::SettingUp()
{
    // TankRef = UGameplayStatics::GetPlayerPawn(this, 0);
    TankRef = Cast<ATank>(UGameplayStatics::GetPlayerPawn(this, 0));  //Getting pointer to Pawn by casting it to ATank

    GetWorldTimerManager().SetTimer(FireRateTimeHandle, this, &ATower::CheckFireCondition, FireRate, true);  //Setting to check the fire condition every FireRate Seconds


    TArray<UActorComponent*> StaticMeshComponents = GetComponentsByClass(UStaticMeshComponent::StaticClass());
    
    for (UActorComponent* StaticMesh : StaticMeshComponents)
    {
        if (StaticMesh->GetName() == TEXT("Turret") & StaticMesh->ComponentHasTag(TEXT("Turret")))
        {
            TurretRef = Cast<UStaticMeshComponent>(StaticMesh);
            UE_LOG(LogTemp, Warning, TEXT("%s"), *TurretRef->GetName());
        }
    }
}

NOTE:- I am not providing the code of other files because according to me it won’t help you solve the problem.

Turret Blueprint:-

The visual representation of my ray tracing, which I am getting right now:-

Some important points about my code:-

  • I assigned a tag to my turret component to verify in the code that we are taking correct 100% correct reference of the turret
  • I Log out at every step to check the result of the code, and I am getting the correct reference to the turret.
  • Even if you somehow solve to get the correct location of the turret, remember that ray tracing is not detecting tank as a world dynamic.

There’s no need for you to find this component; you already have it in ABasePawn. Simply make it protected and then you can access it from the derived class.

I also tried that but I am getting the same output, the ray lines are made in the wrong location.

My code after taking reference of the turret from BasePawn:-

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


#include "Tower.h"
#include "Kismet/GameplayStatics.h"
#include "Tank.h"
#include "TimerManager.h"
#include "DrawDebugHelpers.h"

#define OUT

void ATower::BeginPlay()
{
    Super::BeginPlay();

    SettingUp();
}

void ATower::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

    if(CheckTankRange())  //Rotating the tower's turret every frame if the tank is in range
    {
        RotateTurret(TankRef->GetActorLocation()); //Using RotateTurret Function derived form BasePawn class and passing the TankLocation as target location
    }
}

void ATower::CheckFireCondition()  //Checks the condition for firing
{
    if (!TankRef) {return;}

    FHitResult HitResult;
    FVector TurretLocation = Turret->GetComponentTransform().GetLocation();
    FVector TurretRangeRocation = Turret->GetForwardVector() * FireRange;

    bool HasHit = GetWorld()->LineTraceSingleByChannel(OUT HitResult, TurretLocation, TurretRangeRocation, ECC_WorldDynamic);
    DrawDebugLine(GetWorld(), TurretLocation, TurretRangeRocation, FColor::Red, true, 0.f, 0.f, 5.f);

    if(HasHit)
    {
        UE_LOG(LogTemp, Warning, TEXT("%s"), *(HitResult.GetActor())->GetName());
    }

    // if (Cast<ATank>(HitResult.GetActor()) == TankRef)
    // {
        if (CheckTankRange() & TankRef->bTankAlive)  //Checks if the tank is in range or not and tank is alive or not. If  teh tank would be dead then tower will not fire any more
        {
            FireProjectile(); //after checking the above condition tower fires the projectile by using this function which is derived from BasePawn class
        }
    // }
    
}

bool ATower::CheckTankRange()  //This function checks if the tank is in range or not
{
    if(!TankRef) {return false;}
    
    float Distance = FVector::Dist(GetActorLocation(), TankRef->GetActorLocation()); //stores the distance between tower and tank

    if(Distance <= FireRange) //If the distance is equal or less than the Fire Range then this function will return true
    {
        return true;
    }

    return false;
}

void ATower::HandleDestruction()  //HandleDestruction for tower
{
    Super::HandleDestruction();  //Calls every part of the fuction named same as this in parent class
    Destroy();  //Destroy the tower from the level
}

void ATower::SettingUp()
{
    // TankRef = UGameplayStatics::GetPlayerPawn(this, 0);
    TankRef = Cast<ATank>(UGameplayStatics::GetPlayerPawn(this, 0));  //Getting pointer to Pawn by casting it to ATank

    GetWorldTimerManager().SetTimer(FireRateTimeHandle, this, &ATower::CheckFireCondition, FireRate, true);  //Setting to check the fire condition every FireRate Seconds

}

Oh right, derp. That is the location of the turret for the… Turret. You should use the projectile spawn point instead as that should be where you want to do the line trace.

‘Yes’, Now it is working. But I am still getting the wrong rotation.

My Code After taking the location of projectile spawn point:-

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


#include "Tower.h"
#include "Kismet/GameplayStatics.h"
#include "Tank.h"
#include "TimerManager.h"
#include "DrawDebugHelpers.h"

#define OUT

void ATower::BeginPlay()
{
    Super::BeginPlay();

    SettingUp();
}

void ATower::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

    if(CheckTankRange())  //Rotating the tower's turret every frame if the tank is in range
    {
        RotateTurret(TankRef->GetActorLocation()); //Using RotateTurret Function derived form BasePawn class and passing the TankLocation as target location
    }
}

void ATower::CheckFireCondition()  //Checks the condition for firing
{
    if (!TankRef) {return;}

    FHitResult HitResult;
    FVector ProjectileSpawnPointLocation = ProjectileSpawnPoint->GetComponentTransform().GetLocation();
    FVector ProjectileSpawnPointRangeRocation = ProjectileSpawnPoint->GetForwardVector() * FireRange + ProjectileSpawnPointLocation.Z;

    bool HasHit = GetWorld()->LineTraceSingleByChannel(OUT HitResult, ProjectileSpawnPointLocation, ProjectileSpawnPointRangeRocation, ECC_WorldDynamic);
    DrawDebugLine(GetWorld(), ProjectileSpawnPointLocation, ProjectileSpawnPointRangeRocation, FColor::Red, true, 0.f, 0.f, 5.f);

    if(HasHit)
    {
        UE_LOG(LogTemp, Warning, TEXT("%s"), *(HitResult.GetActor())->GetName());
    }

    if (Cast<ATank>(HitResult.GetActor()) == TankRef)
    {
        if (CheckTankRange() & TankRef->bTankAlive)  //Checks if the tank is in range or not and tank is alive or not. If  teh tank would be dead then tower will not fire any more
        {
            FireProjectile(); //after checking the above condition tower fires the projectile by using this function which is derived from BasePawn class
        }
    }
    
}

bool ATower::CheckTankRange()  //This function checks if the tank is in range or not
{
    if(!TankRef) {return false;}
    
    float Distance = FVector::Dist(GetActorLocation(), TankRef->GetActorLocation()); //stores the distance between tower and tank

    if(Distance <= FireRange) //If the distance is equal or less than the Fire Range then this function will return true
    {
        return true;
    }

    return false;
}

void ATower::HandleDestruction()  //HandleDestruction for tower
{
    Super::HandleDestruction();  //Calls every part of the fuction named same as this in parent class
    Destroy();  //Destroy the tower from the level
}

void ATower::SettingUp()
{
    // TankRef = UGameplayStatics::GetPlayerPawn(this, 0);
    TankRef = Cast<ATank>(UGameplayStatics::GetPlayerPawn(this, 0));  //Getting pointer to Pawn by casting it to ATank

    GetWorldTimerManager().SetTimer(FireRateTimeHandle, this, &ATower::CheckFireCondition, FireRate, true);  //Setting to check the fire condition every FireRate Seconds

}

This calculation is wrong. The calculation is start + forward unit vector * distance

const FVector Start = ProjectileSpawnPoint->GetComponentLocation();
const FVector End = Start + ProjectileSpawnPoint->GetForwardVector() * FireRange;
1 Like

Thanks a lot for your help. Now everything is working fine.

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

Privacy & Terms