[CODE SNIPPET - C++] Detecting if Pawn (or any actor) is in light

Hello all! This is in C++, and the code you see below is actually setup in my Character class in which I derive all my characters from, but you can move it other classes (I recommend creating a base class with this code in it that way, you can check the lighting condition of any object derived from that base class).

H:



virtual void Tick(float DeltaSeconds) override;

/* Handle Light Detection */
void HandleLightDetection();

/* Does this character look for light? */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Character")
bool bCheckLightingCondition;

/* Is this character in light? */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Character")
bool bIsInLight;


CPP:



void AABCharacter::Tick(float DeltaSeconds)
{
	// Call Super::Tick,
	Super::Tick(DeltaSeconds);

	// Handle Light Detection.
	HandleLightDetection();
}

/* Handle Light Detection */
void AABCharacter::HandleLightDetection()
{
	// If "CheckLightingCondition" is true,
	if (bCheckLightingCondition)
	{
		// Write "bIsInLight" to the screen as a message,
		GEngine->AddOnScreenDebugMessage(-1, 5, FColor::Yellow, FString::FromInt(bIsInLight));

		// Create a bool that will tell if any light is actually affecting the player or if the player is in any of the lights,
		bool lit = false;

		// [THANKS TO  FOR THE ARTICLE ABOUT THIS! :D] For every light component,
		for (TObjectIterator<ULightComponent> Itr; Itr; ++Itr)
		{
			// THANKS TO N00854180T FOR MAKING THE CODE SO MUCH NEATER :D //

			// If this light is enabled (or visible),
			if (!Itr->IsVisible())
				continue;

			// If the light type is not directional,
			if (Itr->GetLightType() == LightType_Directional)
				continue;

			// If the light affects this character's capsule component,
			if (!Itr->AffectsPrimitive(CapsuleComponent))
				continue;

			// If a line trace test from the light's position to the character's position does not hit any actor,
			if (GetWorld()->LineTraceTest(Itr->GetComponentLocation(), GetActorLocation(), ECC_Visibility, FCollisionQueryParams(true)))
				continue;

			// Draw a Debug Line to show how the light is hitting the player,
			DrawDebugLine(GetWorld(), Itr->GetComponentLocation(), GetActorLocation(), FColor::Yellow);

			// The player is in light, so set "lit" to true.
			lit = true;
		}

		// If "lit",
		if (lit)
		{
			// Set "bIsInLight" to true,
			bIsInLight = true;

			// And prevent "bIsInLight" from being set to false by returning.
			return;
		}

		// Only set to false if "lit" is not equal to true.
		bIsInLight = false;
	}
}


You can remove the line where I check if the light is not a directional light if needed. I only have that there because I don’t want it to check directional lights. I have not really looked to see if there was a way to do this in blueprint (because you can’t really iterate through every light component unless you use “Get All Actors of Type” which can be slow when called every frame), but you can try it if you want. You can remove the lines with the stars, although I recommend that you try the code out first before doing so. I only had those there to make sure it was working correctly. And there you go! When the actor being checked is in light, “bIsInLight” will be equal to true. One more thing! In my constructor for my playable character class (which is derived from my base character class), I set “bCheckLightingCondition” to true. I created that variable so I can control which types of characters should check if they are in light. Here are some images of the result (NOTE: I have not built lighting again yet so the shadows look weird):

http://imageshack.com/scaled/large/661/4M3IeV.png

http://imageshack.com/scaled/large/539/AXmBQf.png

http://imageshack.com/scaled/large/905/udyoMT.png

That little spotlight right there is actually a flashlight, lol

http://imageshack.com/scaled/large/910/0GTJuL.png

Now I’m running in front of the flashlight :smiley:

http://imageshack.com/scaled/large/743/MXAmXI.png

Anyways, I just thought this was pretty cool and I hope this will be useful to someone! :slight_smile: Later! :smiley:

Thanks ! That will help those who want to create a stealth game :smiley:

Great addition!

One suggestion for the code is to change this part so that the conditions are reversed



// For every light component,
		for (TObjectIterator<ULightComponent> Itr; Itr; ++Itr)
		{
			// If this light is enabled (or visible),
			if (Itr->IsVisible())
			{
				// If the light type is not directional,
				if (Itr->GetLightType() != LightType_Directional)
				{
					// If the light affects this character's capsule component,
					if (Itr->AffectsPrimitive(CapsuleComponent))
					{
						// If a line trace test from the light's position to the character's position does not hit any actor,
						if (!GetWorld()->LineTraceTest(Itr->GetComponentLocation(), GetActorLocation(), ECC_Visibility, FCollisionQueryParams(true)))
						{
							// *** Draw a Debug Line to show how the light is hitting the player,
							DrawDebugLine(GetWorld(), Itr->GetComponentLocation(), GetActorLocation(), FColor::Yellow);

							// The player is in light, so set "lit" to true.
							lit = true;
						}
					}
				}
			}
		}


Like this:



		// For every light component,
		for (TObjectIterator<ULightComponent> Itr; Itr; ++Itr)
		{
			// If this light is not enabled (or visible) go to next,
			if (!Itr->IsVisible())
				continue;

			// If the light type IS directional, go to next,
			if (Itr->GetLightType() == LightType_Directional)
				continue;

			// If the light does not affectsthis character's capsule component, go to next,
			if (!Itr->AffectsPrimitive(CapsuleComponent))
				continue;

			// If a line trace test from the light's position to the character's position does not hit any actor,
			if (!GetWorld()->LineTraceTest(Itr->GetComponentLocation(), GetActorLocation(), ECC_Visibility, FCollisionQueryParams(true)))
			{
				// *** Draw a Debug Line to show how the light is hitting the player,
				DrawDebugLine(GetWorld(), Itr->GetComponentLocation(), GetActorLocation(), FColor::Yellow);

				// The player is in light, so set "lit" to true.
				lit = true;
			}
		}


And so on. Doing it that way you can also un-nest some of the checks and just put them serial. I like to do this for all logic of this type as it ends up making it so you can quickly skim through and see all the conditions it needs to succeed without having to take into account the scoping 2+ times etc. I left the last condition as it is since it’s more readable that way anyway, and changing it wouldn’t really affect readability.

Makes the code quite a bit more readable.

Nice work!

Thanks guys! And thanks for the suggestion! It looks a trillion times neater. I’ve updated the post with the neater version. I will start using this for other mechanics that require a loop! :smiley:

Over the years I’ve fallen in love with this trick, as it makes it many times easier to go back and add a new check down the line for additional functionality. :smiley:

Hey! I was rewriting my system so that it calculates the amount of light on the player accurately, but I don’t think my math is right. Could any of you check it out or give any suggestions?



bool ABaseCharacter::GetLightingCondition()
{
	// Define a variable for the final result to be stored in,
	bool bLit = false;

	float averageLight = 0;
	int32 lightsFound = 0;

	// Loop through all lights,
	for (TObjectIterator<ULightComponent> Itr; Itr; ++Itr)
	{
		// If this light is valid,
		if (!IsValid(Itr->GetOwner()))
			continue;

		// If this light is not a default object,
		if (Itr->GetOwner()->HasAnyFlags(RF_ClassDefaultObject))
			continue;

		// If this light is enabled (or visible),
		if (!Itr->IsVisible())
			continue;

		// If the light affects this character's capsule component,
		if (!Itr->AffectsPrimitive(GetCapsuleComponent()))
			continue;

		// If a line trace test from the light's position to the character's position does not hit any actor,
		if (GetWorld()->LineTraceTest(Itr->GetComponentLocation(), GetActorLocation(), ECC_Visibility, FCollisionQueryParams(NAME_None, true, Itr->GetOwner())))
			continue;

		lightsFound += 1;
		
		switch (Itr->GetLightType())
		{
			case ELightComponentType::LightType_Directional:
			{
				UDirectionalLightComponent* directionalLight = Cast<UDirectionalLightComponent>(*Itr);

				averageLight += directionalLight->Intensity / 17;

				break;
			}
			
			case ELightComponentType::LightType_Point:
			{
				UPointLightComponent* pointLight = Cast<UPointLightComponent>(*Itr);

				float wattage = pointLight->Intensity / 17;
				float distance = FVector::Dist(GetActorLocation(), Itr->GetComponentLocation()) / 100;
				float surfaceArea = 4 * PI * FMath::Square(distance);
				float light = FMath::Clamp<float>(wattage / surfaceArea, 0, 1);

				averageLight += light;

				break;
			}

			case ELightComponentType::LightType_Spot:
			{
				USpotLightComponent* spotLight = Cast<USpotLightComponent>(*Itr);

				float wattage = spotLight->Intensity / 17;
				float distance = FVector::Dist(GetActorLocation(), Itr->GetComponentLocation()) / 1000;
				float slantLength = FMath::Sqrt(FMath::Square(spotLight->AttenuationRadius) + FMath::Square(spotLight->AttenuationRadius));
				float surfaceArea = (PI * FMath::Square(distance)) + (PI * distance * slantLength);
				float light = FMath::Clamp<float>(wattage / surfaceArea, 0, 1);

				averageLight += light;

				break;
			}
		}
		
		/* Draw a Debug Line to show how the light is hitting the character,*/
		DrawDebugLine(GetWorld(), Itr->GetComponentLocation(), GetActorLocation(), FColor::Yellow);
		
		// The player is in light, so set "lit" to true.
		bLit = true;
	}

	// Average,
	averageLight /= lightsFound;

	// Round average light to nearest tenth,
	if (averageLight != 0.0f)
		averageLight = FMath::FloorToFloat(averageLight * 100 + 0.5f) / 100;

	GEngine->AddOnScreenDebugMessage(-1, 0.01f, FColor::White, FString::SanitizeFloat(averageLight));

	// Return final result.
	return bLit;
}


Something is not right…Thanks! :slight_smile:

Hmm, for me the code fires twice per component.
So it finds each light component twice thus breaking the math? Not too sure, will have a deeper look later! Very interesting post! Not fantastic at physics but would the answer be closer to

light = 1 / (distance*distance)*wattage;
??

http://www.portraitlighting.net/inversesquare_law.htm

edit: as i said no expert! My suggestion is clearly wrong! will keep working on this.
Check the component is in world to make it not grab two of each light. Math seems good to me for doing what your doing, i modified it to show ‘total light’ as i found this more useful.