Which way is best to Cast to another class?

Currently I’ve been wondering if my code is bad and I just wanted the community’s take on it. Which ways are best to Cast to another class and what are the best practices?

Currently I’m using this in a raycast function:

Player.CPP

ADamageableActorBase* ActorHit = Cast<ADamageableActorBase>(Hit.Actor);

if (ActorHit && ActorHit->isDamageable)
	{
		ActorHit->TakeAttack();
	}

And was wondering if this is better:

if (Cast<ADamageableActorBase>(Hit.Actor) && Cast<ADamageableActorBase>(Hit.Actor)->isDamageable)
	{
		Cast<ADamageableActorBase>(Hit.Actor)->TakeAttack();
	}

Also, it would be nice if you have better ways of doing this and wouldn’t mind showing me.

And thanks in advance.

Hi there,
My understanding of blueprint communication, the best way is through implementing a blueprint interface, so your code is not going to be tied up to any specific actor class. So it would be hit actor > does implement interface ? (Optional) > send interface message. Then, in any actor class that has the interface and was hit by the trace, call the interface event that corresponds to that message. An interface can have inputs and outpus

I am only just learning the engine and haven’t been delving into cpp with it just yet, but have programming experience.
In my opinion the first code snippet is better, as declaring the pointer makes the following code easier to read. As a general rule, if I am going to use something more than once I will try to simplify it as much as possible. For example, rather than reusing the same code, I will make it a function and use a function call instead.
The only other things I can suggest apply to blueprints and I am not sure how they translate to cpp code, but I have been experimenting with different ways of accomplishing the same goal without having to use casting.
One is using event dispatchers, where an event in one blueprint will trigger an event in another class.
Another is using the Get Actor Of Class node, where I can access the variables of that class without casting.
I don’t know if if any are better or more efficient than the other at this stage, and wouldn’t mind finding out more about that, but there may be more options in cpp than doing a cast. It really depends on the use case I think, but for your simple example casting is probably the best way to go about it.

does implement interface ? (Optional)

In C++, you still have to cast to the interface, unlike blueprints.
Yes, if you’re going to have similar functions with a number of different actors, using interfaces is better.
As to the question, first is better, because there’s only one cast instead of three, and casts are said to be a bit expensive.

1 Like

But you still have to have a reference to another class, only in reverse. It’s really a case by case, there’s no universal better way.

Another is using the Get Actor Of Class node

That’s a different story. First of all, Get Actor Of Class is quite expensive since it cycles through all the actors in the level. Second, it’s only in blueprints that you’ll get the reference to actors of that class; in cpp, you’ll get an array of AActor*, and you’ll still have to Cast to the class you need.

2 Likes

Get Actor Of Class is quite expensive since it cycles through all the actors in the level.

So just for clarity, Get Actor Of Class is arguably more expensive than casting directly, because it cycles through all the actors in the level AND then casts to the one you want. Is that correct?

Casting once (first example) is by far the better approach - absolutely no need to do that work multiple times. It also makes the code considerably clearer and readability should always be in ther back of your mind.

Casting between C++ classes is essentially “free” in shipping builds, but casting Blueprint Instances to their C++ parent class incurs a tiny cost since this has to be done through reflection.

An interface doesn’t make any sense in this context so not sure why the discussion went that way, nor GetActorOfClass.

Yes, if you’re going to have similar functions with a number of different actors, using interfaces is better.

This is not strictly true. Interfaces allow classes with no common parent to implement similar functionality - but reflected interfaces are slow, so if you can share a common base it’s a better approach.

There’s also a strong argument for using composition (aka, components) rather than inheritance. Components have the added benefit of being able to have their own internal state/variables etc, work with networking. You can mix both Components and Interfaces to get the best of both worlds, similar to how the ability system works

2 Likes

Thanks for the recommendations! One last thing that I was wondering was is it bad practice to cast to different classes in one function for example having a raycast function shoot a raycast and casting to lets say two classes and having an if statement to see which class was hit and based on the class hit will execute different events.

Like this:

Player.CPP

void APlayer::Shoot()
{
//InstantShot is the raycast function and is being used in the input function Shoot
	FHitResult Hit = InstantShot();

	ADamageableActorBase* ActorHit = Cast<ADamageableActorBase>(Hit.Actor);
	if (ActorHit && ActorHit->isDamageable)
	{
		ActorHit->TakeAttack();
		DrawDebugBox(GetWorld(), Hit.ImpactPoint, FVector(10.0f, 10.0f, 10.0f), FColor::Green, false, 5.0f);
	}

	APlayer* Target = Cast<APlayer>(Hit.Actor);
	if (Target && Target->IsAttackable)
	{
		GEngine->AddOnScreenDebugMessage(-1, 1.0f, FColor::Green, FString::Printf(TEXT("Character hit: %s"), *Hit.GetActor()->GetName()));
		DrawDebugBox(GetWorld(), Hit.ImpactPoint, FVector(10.0f, 10.0f, 10.0f), FColor::Green, false, 5.0f);

		switch (UGameplayStatics::GetSurfaceType(Hit))
		{
		case SurfaceType1:
			UGameplayStatics::ApplyPointDamage(Target, 50.0f, Hit.TraceStart, Hit, GetInstigatorController(), this, UDamageType::StaticClass());
			GEngine->AddOnScreenDebugMessage(-1, 1.0f, FColor::Green, FString::SanitizeFloat(Target->Health->HP));
			GEngine->AddOnScreenDebugMessage(-1, 1.0f, FColor::Green, FString::Printf(TEXT("SurfaceType: HeadShot")));
			break;

		case SurfaceType2:
			UGameplayStatics::ApplyPointDamage(Target, 25.0f, Hit.TraceStart, Hit, GetInstigatorController(), this, UDamageType::StaticClass());
			GEngine->AddOnScreenDebugMessage(-1, 1.0f, FColor::Green, FString::SanitizeFloat(Target->Health->HP));
			GEngine->AddOnScreenDebugMessage(-1, 1.0f, FColor::Green, FString::Printf(TEXT("SurfaceType: FleshShot")));
			break;

		default:
			UGameplayStatics::ApplyPointDamage(Target, 5.0f, Hit.TraceStart, Hit, GetInstigatorController(), this, UDamageType::StaticClass());
			GEngine->AddOnScreenDebugMessage(-1, 1.0f, FColor::Green, FString::SanitizeFloat(Target->Health->HP));
			GEngine->AddOnScreenDebugMessage(-1, 1.0f, FColor::Green, FString::Printf(TEXT("Failed!")));
		}
	}
}