I have a drone player set up that has two cameras on it: a first person and a topdown camera, and i want to be able able to "scan" particular zones in the world by using the camera as a scanner. Currently i have a script set up that holds on the game objects to be scanned and handles the ray-casting and gradual color change of the objects. Currently, when i switch to first-person camera, all of my objects start turning red even though they each have different materials assigned to them. My current code is attached below. I only want to be able to turn one zone at a time to red while either the FPS or topdown cameras is looking directly at it.
`
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ZoneController : MonoBehaviour
{
private Camera frontViewCamera;
private Camera topDownCamera;
public Material[] zones;
public float speed = 0.05f;
private Color startColor, endColor = Color.red;
// Start is called before the first frame update
void Start()
{
frontViewCamera = GameObject.FindGameObjectWithTag("FrontFaceCam").GetComponent<Camera>() as Camera;
topDownCamera = GameObject.FindGameObjectWithTag("TopDownCam").GetComponent<Camera>() as Camera;
foreach (var zone in zones)
{
zone.color = Color.white;
}
startColor = Color.white;
}
private IEnumerator ChangeColour()
{
float tick = 0f;
foreach (var zone in zones)
{
while (zone.color != endColor)
{
tick = Time.deltaTime * speed;
zone.color = Color.Lerp(startColor, endColor, tick);
yield return null;
}
}
}
private void StartFadeToRed()
{
Ray frontCameraRayCast;
Ray topDownCameraRayCast;
RaycastHit fHit;
RaycastHit tHit;
frontCameraRayCast = frontViewCamera.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0));
topDownCameraRayCast = topDownCamera.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0));
if (frontViewCamera.enabled)
{
if (Physics.Raycast(frontCameraRayCast, out fHit))
{
Debug.DrawRay(frontCameraRayCast.origin, frontCameraRayCast.direction * 10, Color.red);
StartCoroutine("ChangeColour");
}
}
if (topDownCamera.enabled)
{
if (Physics.Raycast(topDownCameraRayCast, out tHit) && tHit.collider.CompareTag("Zone"))
{
Debug.Log("Raycast topdwon");
StartCoroutine("ChangeColour");
}
}
}
// Update is called once per frame
void Update()
{
StartFadeToRed();
}
}
`
CodePudding user response:
First, doing that color change on Update is horrible!!!! do it only once per camera switch.
Probably is because is getting stuck with so many coroutines due to the Update and float pointers never getting to a full Color.red, try doing once per object, abstract your code better.
I recommend you use DOTween, in a. few lines you can fix this.
CodePudding user response:
You are currently
- potentially starting multiple concurrent Coroutines
- Do exactly what you say you don't want to - fade all zone materials one after another
- Which might potentially mean multiple objects use the same material as well
What you would rather want to do is
- keep track of what you are hitting actually
- get the material instance of according object(s)
- start to fade those to red
- also reset them when not being hit anymore
Now where and how you implement which part is up to you of course - I would probably have a Zone
component on each of your zone objects like e.g.
public class Zone : MonoBehaviour
{
// the renderers to fade to red - if only one you could simplify this of course
[Header("References")]
[SerializeField] private Renderer[] _renderersToFade;
// How long should a full fading take?
[Header("Settings")]
[SerializeField] private float _fadeDuration = 1f;
// is this zone currently being hit?
private bool isHit;
// current color interpolation factor
private float _currentFactor;
// currently running fade routine
private Coroutine _currentRoutine;
public bool IsHit
{
get => isHit;
set
{
// value is the same as current -> do nothing
if (value == isHit) return;
// set the field
isHit = value;
// if was fading interrupt the current routine
if (_currentRoutine != null)
{
StopCoroutine(_currentRoutine);
}
// start a new fade routine
_currentRoutine = StartCoroutine(FadeRoutine());
}
}
// keeps track of the original material colors per renderer
private readonly Dictionary<Renderer, Color> _originalColors = new();
private void Awake()
{
// initialize the original color dictionary
foreach (var renderer in _renderersToFade)
{
_originalColors.Add(renderer, renderer.material.color);
}
}
private IEnumerator FadeRoutine()
{
// are we fading in or out?
var targetFactor = isHit ? 1 : 0;
// fade until reaching the target factor
while (!Mathf.Approximately(_currentFactor, targetFactor))
{
// move the factor towards the target factor
_currentFactor = Mathf.MoveTowards(_currentFactor, targetFactor, Time.deltaTime / _fadeDuration);
// update all renderer colors
foreach (var renderer in _renderersToFade)
{
renderer.material.color = Color.Lerp(_originalColors[renderer], Color.red, _currentFactor);
}
// go to next frame and continue
yield return null;
}
}
}
And then control the via your central ZoneController
where you want to store the current hit and also reset it once it is not hit anymore like e.g.
public class ZoneController : MonoBehaviour
{
// if possible rather already reference these via the Inspector
[SerializeField] private Camera frontViewCamera;
[SerializeField] private Camera topDownCamera;
// will keep reference to the currently hit zone
private Zone _currentHitZone;
private readonly Vector2 _viewportCenter = Vector2.one * 0.5f;
private void Start()
{
// only as fallback get those on runtime
if (!frontViewCamera) frontViewCamera = GameObject.FindGameObjectWithTag("FrontFaceCam").GetComponent<Camera>();
if (!topDownCamera) topDownCamera = GameObject.FindGameObjectWithTag("TopDownCam").GetComponent<Camera>();
}
private void Update()
{
// Get the currently active camera
Camera activeCamera;
if (frontViewCamera.enabled) activeCamera = frontViewCamera;
else if (topDownCamera.enabled) activeCamera = topDownCamera;
else activeCamera = null;
// no camera active at all? - should actually never happen ;)
if (!activeCamera) return;
// get the ray
var ray = activeCamera.ViewportPointToRay(_viewportCenter);
if (Physics.Raycast(ray, out var frontHit))
{
if (!frontHit.transform.TryGetComponent<Zone>(out var frontHitZone))
{
// Hitting something but it is not a Zone
Debug.DrawLine(ray.origin, frontHit.point, Color.yellow);
HitZone(null);
}
else
{
// Hitting a Zone
Debug.DrawLine(ray.origin, frontHit.point, Color.red);
HitZone(frontHitZone);
}
}
else
{
// Hitting nothing at all
Debug.DrawRay(ray.origin, ray.direction * 10, Color.green);
HitZone(null);
}
}
private void HitZone(Zone hitZone)
{
// hitting still the same zone? -> ignore
if (_currentHitZone == hitZone) return;
// did we hit something before? -> set it to not hit anymore
if (_currentHitZone) _currentHitZone.IsHit = false;
// store the new hit
_currentHitZone = hitZone;
// are currently hitting something? -> set to hit
if (_currentHitZone) _currentHitZone.IsHit = true;
}
}