This is a follow on from my last question now I'm looking to extend it's functionality but I'm hitting some major road blocks.
My goal is as follows:
I have a camera that is projecting a ray to hit objects with a specific tag. When the ray hits a tag for a set period of time I'm looking for something to happen, just now all I'm looking for is a debug message or something simple. When the ray doesn't hit any object with a tag I'm wanting my timer to not update. If the user looks away from the object and comes back, I'm wanting the timer to start again from 0 and I want similar functionality should the user look another one of the tags i.e the timer starts again.
How ever, my timer isn't working as intended and it just keeps counting no matter what I'm looking. I've spent 3 hours on this and I've gotten tunnel vision where I keep attempting the same lines of code over and over.
Could someone with a fresh pair of eyes take a look at what I've done so far and point out to me what it is I'm missing / doing wrong?
public float end_time;
public float start_time;
private float running_time;
public Texture2D progress_bar_empty;
public Texture2D progress_bar_full;
public Material highlight_material;
public float progress = 0;
public bool hited = false;
public List<string> _tags = new List<string>();
private GameObject hit_object;
private Material oldMat;
RaycastHit hit;
// Use this for initialization
void Start ()
{
start_time = Time.deltaTime;
running_time = 0;
}
// Update is called once per frame
void Update ()
{
EyeTarget();
Check(hited);
}
void EyeTarget()
{
try
{
Vector3 fwd = transform.TransformDirection(Vector3.forward);
if (Physics.Raycast(transform.position, fwd, out hit))
{
foreach(string t in _tags)
{
if(hit.collider.gameObject.tag == t)
{
HighLight();
}
}
}
Debug.DrawRay(transform.position, fwd, Color.red);
}
catch(Exception e)
{
Debug.Log(e.Message);
}
}
void ResetTimer()
{
start_time = Time.time;
running_time = 0f;
//Debug.Log("resetting timer");
}
void HighLight()
{
if(hit_object == null)
{
ResetTime();
oldMat = hit.transform.renderer.material;
hit.collider.gameObject.renderer.material = highlight_material;
hit_object = hit.collider.gameObject;
hited = true;
}
else if( hit.transform.tag != hit_object.tag)
{
//hit.collider.gameObject.renderer.material = oldMat;
hit_object = null;
hit_object.renderer.material = oldMat;
progress = 0;
Debug.Log("hi");
hited = false;
}
}
// see if ray has hit object
void Check(bool hit)
{
if(hit)
{
start_time = Time.time - end_time;
running_time += Time.deltaTime;
if ( running_time >= end_time )
{
hited = false;
}
}
else if( hited == false)
ResetTime();
}
void ResetTime()
{
start_time = Time.time;
running_time = 0f;
Debug.Log("restting timer");
}
I tried to leave your version alone as much as possible. This version will stop the timer when the cursor moves off a tagged object and will start again, from 0, when another tagged object is under the raycast.
My code is a little verbose but it makes it easier to see what's going on.
public float end_time;
public float start_time;
public float running_time;
public Texture2D progress_bar_empty;
public Texture2D progress_bar_full;
public Material highlight_material;
public float progress = 0;
public bool trackTimer = false;
public List<string> _tags = new List<string>();
public GameObject lastHitObject = null;
private Material oldMat;
// Use this for initialization
void Start ()
{
ResetTimer();
}
// Update is called once per frame
void Update ()
{
EyeTarget();
// Update the timer if and only if we are tracking time AND
// the last ray cast hit something.
bool updateTimer = (trackTimer && lastHitObject != null);
Check(updateTimer);
}
void EyeTarget()
{
RaycastHit hit;
bool hitTaggedObject = false;
Vector3 fwd = transform.TransformDirection(Vector3.forward);
if (Physics.Raycast(transform.position, fwd, out hit))
{
foreach(string t in _tags)
{
if(hit.collider.gameObject.tag == t)
{
HighLight(hit.collider.gameObject);
hitTaggedObject = true;
}
}
}
// ** Make sure to clean up highlighting if nothing new was hit
if (!hitTaggedObject){
HighLight(null);
}
}
void ResetTimer()
{
start_time = Time.time;
running_time = 0f;
}
void HighLight(GameObject nextHitObject)
{
// Case1: Last ray and new ray both hit objects
if(lastHitObject != null && nextHitObject != null){
//1a: same objects, do nothing
if(lastHitObject.tag == nextHitObject.tag)return;
{ //1b: different objects, swap highlight texture
lastHitObject.renderer.material = oldMat;
lastHitObject = nextHitObject;
oldMat = lastHitObject.renderer.material;
lastHitObject.renderer.material = highlight_material;
trackTimer = true;
return;
}
}
// Case2: Last ray hit nothing, new ray hit object.
if(lastHitObject == null && nextHitObject != null){
ResetTimer();
lastHitObject = nextHitObject;
oldMat = lastHitObject.renderer.material;
lastHitObject.renderer.material = highlight_material;
trackTimer = true;
return;
}
// Case3: Last ray hit something, new ray hit nothing
if(lastHitObject != null && nextHitObject == null){
lastHitObject.renderer.material = oldMat;
lastHitObject = null;
trackTimer = false;
return;
}
}
// see if ray has hit object
void Check(bool updateTimer)
{
if(updateTimer)
{
start_time = Time.time - end_time;
running_time += Time.deltaTime;
if ( running_time >= end_time )
{
trackTimer = false;
}
}
}
trackTimer is a state flag, separate from the state of your selected object, that tracks when running_time reached end_time. Once those 2 are equal, trackTimer flips to false and you need to highlight a new object or rehighlight the current object before the timer will start again.
Related
The post is a bit long but both scripts are connected to each other. I tried to reduce the amount of code.
The Waypoints script is attached to empty GameObject and I added to it a rotation part :
[Header("Rotation")]
public Quaternion rotationTothinkWhatToDoHere;
but I'm not sure how to use it here in the Waypoints script and in the WaypointsFollower script.
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Cinemachine;
public class Waypoints : MonoBehaviour
{
[Header("Objects To Move")]
public Transform objectToMovePrefab;
public int numberOfObjectsToMove = 1;
public bool moveInReverse = false;
[Header("Speed")]
public float speed;
public bool randomSpeed = false;
public float minRandomSpeed = 1;
public float maxRandomSpeed = 100;
private bool changeSpeedOnce = false;
[Header("Rotation")]
public Quaternion rotationTothinkWhatToDoHere;
[Header("Waypoints")]
[SerializeField] private List<Transform> waypoints;
public bool moveOnWaypoints = false;
[Header("Delay")]
public bool useDelay = false;
public float delay = 3;
public bool randomDelay = false;
public float minRandomDelay = 0.3f;
public float maxRandomDelay = 5;
[Header("LineRenderer")]
public LineRenderer lineRenderer;
public bool moveOnLineRenderer = false;
public List<Vector3> lineRendererPositions = new List<Vector3>();
[Header("Cinemachine Cameras")]
public CinemachineVirtualCamera virtualCamera;
private List<WaypointsFollower> waypointsFollowers = new List<WaypointsFollower>();
private void Start()
{
for (int i = 0; i < numberOfObjectsToMove; i++)
{
var parent = GameObject.Find("Moving Object Parent");
var objectToMove = Instantiate(objectToMovePrefab, parent.transform);
objectToMove.name = "Platfrom";
waypointsFollowers.Add(objectToMove.GetComponent<WaypointsFollower>());
}
virtualCamera.Follow = waypointsFollowers[0].gameObject.transform;
virtualCamera.LookAt = waypointsFollowers[0].gameObject.transform;
foreach (WaypointsFollower wpf in waypointsFollowers)
{
wpf.goForward = moveInReverse;
}
WaypointsMovementStates();
SpeedUpdater();
if (useDelay)
StartCoroutine(SendObjectstomoveWithDelay());
}
private void Update()
{
lineRendererPositions.Clear();
lineRendererPositions.AddRange(GetLinePointsInWorldSpace());
SpeedUpdater();
}
IEnumerator SendObjectstomoveWithDelay()
{
{
foreach (WaypointsFollower follower in waypointsFollowers)
{
if (randomDelay)
{
delay = Random.Range(minRandomDelay, maxRandomDelay);
}
yield return new WaitForSeconds(delay);
follower.go = true;
}
}
}
private void SpeedUpdater()
{
if (changeSpeedOnce == false)
{
foreach (WaypointsFollower follower in waypointsFollowers)
{
if (randomSpeed)
{
follower.speed = Random.Range(minRandomSpeed, maxRandomSpeed);
}
else
{
follower.speed = speed;
}
}
changeSpeedOnce = true;
}
}
Vector3[] GetLinePointsInWorldSpace()
{
var positions = new Vector3[lineRenderer.positionCount];
//Get the positions which are shown in the inspector
lineRenderer.GetPositions(positions);
//the points returned are in world space
return positions;
}
private void WaypointsMovementStates()
{
// If moving on both linerenderer positions and waypoints objects
if (moveOnLineRenderer && moveOnWaypoints && waypoints.Count > 0)
{
if (useDelay == false)
{
foreach (WaypointsFollower wpf in waypointsFollowers)
{
wpf.go = true;
}
}
}
// If moving on linerenderer positions only without moving on waypoints objects
if (moveOnLineRenderer && moveOnWaypoints == false)
{
if (waypoints.Count > 0)
waypoints.Clear();
if (useDelay == false)
{
foreach (WaypointsFollower wpf in waypointsFollowers)
{
wpf.go = true;
}
}
}
// If only to move on waypoints objects without moving on linerenderer positions
if (moveOnWaypoints && waypoints.Count > 0 && moveOnLineRenderer == false)
{
lineRendererPositions.Clear();
foreach (Transform wp in waypoints)
{
lineRendererPositions.Add(wp.position);
}
if (useDelay == false)
{
foreach (WaypointsFollower wpf in waypointsFollowers)
{
wpf.go = true;
}
}
}
if(moveInReverse)
{
foreach (WaypointsFollower wpf in waypointsFollowers)
{
wpf.go = true;
}
}
}
}
In the WaypointsFollower script, this script is attached to each object that moves along the waypoints.
It's working fine if goForward is true but when goFoward is false for some reason the index value is -1 and I'm getting exception out of index was out of range on line 73 :
newPos = Vector3.MoveTowards(oldPos, waypoints.lineRendererPositions[index], distanceToTravel);
The idea when goForward is false to move the object from the last waypoint to the first waypoint and then when it's reaching the first waypoint then switch the goForward to true and move forward from the first waypoint to the last.
It's working when goFoward is first time true then it's moving from the first waypoint to the last waypoint and then it's moving from the last waypoint to the first one but it's not working when goForward is first time false.
I can't figure out why it's -1
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class WaypointsFollower : MonoBehaviour
{
public float speed;
public Waypoints waypoints;
public bool go;
public bool goForward;
private int index = 0;
private int counter = 0;
private int c = 0;
private List<GameObject> curvedLinePoints = new List<GameObject>();
public int numofposbetweenpoints;
private bool getonce;
private void Start()
{
waypoints = GameObject.Find("Waypoints").GetComponent<Waypoints>();
curvedLinePoints = GameObject.FindGameObjectsWithTag("Curved Line Point").ToList();
if(waypoints.moveInReverse == false)
{
goForward = true;
}
else
{
goForward = false;
}
if(goForward)
{
index = 0;
}
else
{
index = waypoints.lineRendererPositions.Count - 1;
}
}
private void Update()
{
if (getonce == false)
{
numofposbetweenpoints = curvedLinePoints.Count;
getonce = true;
}
if (go == true && waypoints.lineRendererPositions.Count > 0)
{
Move();
}
}
private void Move()
{
Vector3 newPos = transform.position;
float distanceToTravel = speed * Time.deltaTime;
bool stillTraveling = true;
while (stillTraveling)
{
Vector3 oldPos = newPos;
// error exception out of bound on line 55 to check !!!!!
newPos = Vector3.MoveTowards(oldPos, waypoints.lineRendererPositions[index], distanceToTravel);
distanceToTravel -= Vector3.Distance(newPos, oldPos);
if (newPos == waypoints.lineRendererPositions[index]) // Vector3 comparison is approximate so this is ok
{
// when you hit a waypoint:
if (goForward)
{
bool atLastOne = index >= waypoints.lineRendererPositions.Count - 1;
if (!atLastOne)
{
index++;
counter++;
if (counter == numofposbetweenpoints)
{
c++;
counter = 0;
}
if (c == curvedLinePoints.Count - 1)
{
c = 0;
}
}
else { index--; goForward = false; }
}
else
{ // going backwards:
bool atFirstOne = index <= 0;
if (!atFirstOne)
{
index--;
counter++;
if (counter == numofposbetweenpoints)
{
c++;
counter = 0;
}
if (c == curvedLinePoints.Count - 1)
{
c = 0;
}
}
else { index++; goForward = true; }
}
}
else
{
stillTraveling = false;
}
}
transform.position = newPos;
}
}
I saw your previous post before it was deleted, so here is the answer I had for your original question of moving between waypoints by both rotating and movement with the option of what occurs at the end of the motion. I can answer your current question if answering your last deleted question has still not solved your issue.
Instead of using the Update function to handle the rotation and movement between a series of waypoints in a list, I would recommend using a Coroutine. If you are unfamiliar, think of them as a process that handles small increments of work overtime and will jump back where it leaves off. It should simplify the issue of rotation and movement into smaller bite-sized pieces of logic, allowing an easier time to understand your issue.
// new enum - outside of your class
public enum WaypointMovementType
{
REPEAT_START, // will repeat to the start waypoint when end is reached
REPEAT_REVERSE, // will reverse the waypoint list when end is reached
STOP // will terminate when end is reached
};
// new variables - this is inside your class
// keep a reference of our coroutine to not run duplicates
Coroutine movingWayPoints = null;
// time it takes to rotate to our goal waypoint
private float rotateTime = 0.5f;
// time it takes to move to our goal waypoint
private float movementTime = 1.5f;
// Start is called before the first frame update
void Start()
{
parent = GameObject.Find("Waypoints");
// generate the waypoints
GenerateWaypoints();
// run our process
if (movingWayPoints == null)
movingWayPoints = StartCoroutine(MoveBetweenWaypoints(tmpList, WaypointMovementType.STOP));
}
private IEnumerator MoveBetweenWaypoints(List<Vector3> waypoints, WaypointMovementType movementType)
{
int currentWaypointIdx = 0;
// continue our loop until we have reached our end goal waypoint
while (currentWaypointIdx < waypoints.Count)
{
// rotate towards out goal point
yield return StartCoroutine(RotateTowardsGoalWaypoint(waypoints[currentWaypointIdx]));
// move towards our goal point
yield return StartCoroutine(MoveTowardsGoalWaypoint(waypoints[currentWaypointIdx]));
// increment our index count or wait for further time if you would like a delay between rotation / movement
++currentWaypointIdx;
}
// coroutine is done, so set the motion to null
movingWayPoints = null;
// now that we have reached the end, determine what we want to do
if (movementType != WaypointMovementType.STOP)
{
// if we want to reverse, then reverse our list
if (movementType == WaypointMovementType.REPEAT_REVERSE)
waypoints.Reverse();
// now call the coroutine again
movingWayPoints = StartCoroutine(MoveBetweenWaypoints(waypoints, movementType));
}
}
private IEnumerator RotateTowardsGoalWaypoint(Vector3 goalWaypoint)
{
// store our current rotation
Quaternion initialRotation = transform.rotation;
// find our direction to the goal
Vector3 dir = goalWaypoint - transform.position;
// calculate the final / goal rotation
Quaternion finalRotation = Quaternion.LookRotation(dir);
// store our current time
float currentTime = 0.0f;
// rotate until we reach our goal
while (currentTime <= rotateTime)
{
currentTime += Time.deltaTime;
transform.rotation = Quaternion.Lerp(initialRotation, finalRotation, currentTime / rotateTime);
yield return null;
}
// set our rotation in case there are floating point precision errors
transform.rotation = finalRotation;
}
private IEnumerator MoveTowardsGoalWaypoint(Vector3 goalWaypoint)
{
// store our current position
Vector3 initialPostion = transform.position;
// store our current time
float currentTime = 0.0f;
while (currentTime <= movementTime)
{
currentTime += Time.deltaTime;
transform.position = Vector3.Lerp(initialPostion, goalWaypoint, currentTime / movementTime);
yield return null;
}
// set our position in case there are floating point precision errors
transform.position = goalWaypoint;
}
The current code uses time instead of speed which can be changed by changing how the Lerp steps are inputted. One other issue currently is the repeat backwards will move and rotate to the first element in the reversed list, but this can be fixed by passing in an index parameter for where to start in the list.
If you would rather use your current implementation, I can help debug it, but in the future do not delete questions you expect an answer to unless you have a good reason to do so. I should also mention, as your original question had only 1 script, the current script is expecting the object that generates the waypoints is the same object that is moving between them. It would be very easy to fix by creating a new public field that references some other Transform that should move between the waypoints, then replace all of the transform.rotation and transform.position with yourObject.position and yourobject.rotation.
I should also add, to change how the waypoint movement will function after a single pass is finished, simply change the WaypointMovementType parameter to a different value before making the initial call.
Edit: As derHugo mentioned that a speed variant of the answer would better suit the needs of the use case, here is an additional snippet with speed instead of time.
private IEnumerator MoveBetweenWaypoints(List<Vector3> waypoints, WaypointMovementType movementType)
{
int currentWaypointIdx = 0;
// continue our loop until we have reached our end goal waypoint
while (currentWaypointIdx < waypoints.Count)
{
// rotate towards out goal point
yield return StartCoroutine(RotateTowardsGoalWaypoint(waypoints[currentWaypointIdx]));
// move towards our goal point
yield return StartCoroutine(MoveTowardsGoalWaypoint(waypoints[currentWaypointIdx]));
// increment our index count or wait for further time if you would like a delay between rotation / movement
++currentWaypointIdx;
}
// coroutine is done, so set the motion to null
movingWayPoints = null;
// now that we have reached the end, determine what we want to do
if (movementType != WaypointMovementType.STOP)
{
// if we want to reverse, then reverse our list
if (movementType == WaypointMovementType.REPEAT_REVERSE)
waypoints.Reverse();
// now call the coroutine again
movingWayPoints = StartCoroutine(MoveBetweenWaypoints(waypoints, movementType));
}
}
private IEnumerator RotateTowardsGoalWaypoint(Vector3 goalWaypoint)
{
// find our direction to the goal
Vector3 dir = goalWaypoint - transform.position;
// calculate the final / goal rotation
Quaternion finalRotation = Quaternion.LookRotation(dir);
// continue until our angles match
while(Vector3.Angle(transform.forward, dir) > ROTATION_CHECK_EPSILON)
{
transform.rotation = Quaternion.RotateTowards(transform.rotation, finalRotation, Time.deltaTime * rotateSpeed);
yield return null;
}
// set our rotation in case there are floating point precision errors
transform.rotation = finalRotation;
}
private IEnumerator MoveTowardsGoalWaypoint(Vector3 goalWaypoint)
{
// continue until our distance is close to our goal
while(Vector3.Distance(transform.position, goalWaypoint) > DISTANCE_CHECK_EPSILON)
{
transform.position = Vector3.MoveTowards(transform.position, goalWaypoint, Time.deltaTime * movementSpeed);
yield return null;
}
// set our position in case there are floating point precision errors
transform.position = goalWaypoint;
}
Suppose maximum brightness of an item is 100% and vice versa. When I glance at one of them, the item should be gradually brightening up from 0% to 100%. So when I look away halfway of the brightening, the object will slowly dim back to 0%. When I glance back again, the brightening will just resume. I am using Color.Lerp for my brightening and dimming back down.
Each item has their own individual pace of brightening and dimming back down even when they are gathered in a very tight space.
Assume that Item A, B, C, D and E arranged tightly beside each other in order. Hence, I glance at Item A which its brightness has reached to 80% and I immediately shift through B,C and D then stop and glance at E to reach its maximum brightness. In this scenario, A should be dimming back down, while B, C and D brightens up a little bit. Then E will be brightening to the max. How do I achieve this no matter how many items I have on the spot? I am having problem with updating the info of current item and keeping the info of previous item.
The code works well on a single item AND ALSO glancing back and forth between two items. But when there are three or more objects involved, the codes just doesn't work anymore.
Scripts are as below:
Raycast to detect items
public class RaycastSelection : MonoBehaviour
{
RaycastHit hit;
Ray ray;
public GameObject currentObject;
public GameObject previousObject;
public GameObject lastPreviousObject;
public GameObject lastObject;
private bool saveObjectOnce = false;
private bool isFading = false;
private bool fadeOnce = false;
private ColorFade colorFading;
private ColorFade colorFading1;
private ColorFade colorFading2;
public void Start()
{
colorFading = GameObject.FindWithTag("Puzzle").GetComponent<ColorFade>();
colorFading1 = GameObject.FindWithTag("Clue").GetComponent<ColorFade>();
colorFading2 = GameObject.FindWithTag("Selectable").GetComponent<ColorFade>();
}
public void Update()
{
ray = Camera.main.ScreenPointToRay(Input.mousePosition);
Vector3 forward = Camera.main.transform.TransformDirection(Vector3.forward) * 10;
Debug.DrawRay(Camera.main.transform.position, forward, Color.red);
//if player is looking
if (Physics.Raycast(ray, out hit))
{
var selection = hit.transform;
if (selection.CompareTag("Puzzle") || selection.CompareTag("Clue") || selection.CompareTag("Selectable"))
{
//if it is first time looking at object
if(currentObject == null)
{
currentObject = hit.transform.gameObject;
}
//if it is second time looking at object
else
{
if (saveObjectOnce == false)
{
previousObject = currentObject;
saveObjectOnce = true;
}
currentObject = hit.transform.gameObject;
}
//if previous and current object are same
if(string.Equals(currentObject, previousObject))
{
isFading = true;
ColorFade fadingColor = currentObject.GetComponent<ColorFade>();
fadingColor.FadeCheck(isFading);
if (lastPreviousObject != null)
{
isFading = false;
ColorFade fadingcolor = lastPreviousObject.GetComponent<ColorFade>();
fadingcolor.FadeCheck(isFading);
}
}
//if previous and current object are different
else
{
lastPreviousObject = previousObject;
previousObject = currentObject;
}
}
//if detects other objects than the items
else
{
lastObject = currentObject;
isFading = false;
if (lastObject != null)
{
ColorFade fadingColor = lastObject.GetComponent<ColorFade>();
fadingColor.FadeCheck(isFading);
}
}
}
//if the player is not looking
else
{
lastObject = currentObject;
if (lastObject != null)
{
ColorFade fadingColor = lastObject.GetComponent<ColorFade>();
fadingColor.FadeCheck(isFading);
}
}
}
}
Changing brightness of the item (This script is attached to each item individually)
public class ColorFade : MonoBehaviour
{
[SerializeField] private bool saveColorOnce = false;
[SerializeField] private Transform thisObject;
[SerializeField] private Color startColor;
[SerializeField] private Color EndColor;
[SerializeField] private float lerpFadeTime = 0f;
private void Start()
{
startColor = this.transform.GetComponent<Renderer>().material.color;
thisObject = this.transform;
}
public void Update()
{
}
public void FadeCheck(bool fadingStatus)
{
if(fadingStatus == true)
{
FadeIn();
}
else
{
FadeOut();
}
}
private void FadeIn()
{
Debug.Log(this.transform.gameObject.name);
Debug.Log("FadeIn");
lerpFadeTime += Time.deltaTime / 3f;
thisObject.GetComponent<Renderer>().material.color = Color.Lerp(startColor, EndColor, lerpFadeTime);
if(lerpFadeTime >= 1f)
{
lerpFadeTime = 1f;
}
}
private void FadeOut()
{
Debug.Log(this.transform.gameObject.name);
Debug.Log("FadeOut");
lerpFadeTime -= Time.deltaTime / 3f;
thisObject.GetComponent<Renderer>().material.color = Color.Lerp(startColor, EndColor, lerpFadeTime);
if (lerpFadeTime <= 0f)
{
lerpFadeTime = 0f;
}
}
}
I would suggest Coroutines which are most of the times better to control than using complex Update methods with timers, states and bools.
public class RaycastSelection : MonoBehaviour
{
[Header("Components")]
// reference this via the Inspector already if possible
[SerializeField] private Camera camera;
[Header("Debug")]
[SerializeField] private ColorFade lastFaderHit;
private void Awake()
{
if (!camera) camera = Camera.main;
}
private void Update()
{
RaycastHit hit;
if (Physics.Raycast(camera.ScreenPointToRay(Input.mousePosition), out hit))
{
// if hitting same object do nothing
if (lastFaderHit && hit.transform == lastFaderHit.transform) return;
// if looking on different object start fade out previous if exists
lastFaderHit?.FadeOut();
// start fade in current
var current = hit.transform.GetComponent<ColorFade>();
current?.FadeIn();
// update previous hit
lastFaderHit = current;
}
else
{
// if looking at nothing start fadeout previous hit if exists
lastFaderHit?.FadeOut();
// reset previous hit
lastFaderHit = null;
}
}
}
and in ColorFade use coroutines like
public class ColorFade : MonoBehaviour
{
[Header("Components")]
// reference this in the Inspector if possible
[SerializeField] private Renderer renderer;
[Header("Settings")]
[SerializeField] private Color startColor;
[SerializeField] private Color EndColor;
// fade duration for a complete 0-100 fade
// fading will be shorter if only fading parts
[SerializeField] private float fadeDuration = 1f;
[Header("Debug")]
[SerializeField] [Range(0f, 1f)] private float currentFade;
private void Awake()
{
if (!renderer) renderer = GetComponent<Renderer>();
startColor = renderer.material.color;
}
public void FadeIn()
{
// avoid concurrent routines
StopAllCoroutines();
StartCoroutine(FadeTowards(1));
}
public void FadeOut()
{
// avoid concurrent routines
StopAllCoroutines();
StartCoroutine(FadeTowards(0));
}
private IEnumerator FadeTowards(float targetFade)
{
while (!Mathf.Approximately(currentFade, targetFade))
{
// increase or decrease the currentFade according to the fade speed
if (currentFade < targetFade)
currentFade += Time.deltaTime / fadeDuration;
else
currentFade -= Time.deltaTime / fadeDuration;
// if you like you could even add some ease-in and ease-out here
//var lerpFactor = Mathf.SmoothStep(0, 1, currentFade);
renderer.material.color = Color.Lerp(startColor, EndColor, currentFade /*or lerpFactor for eased fading*/);
// let this frame be rendered and continue from this point
// in the next frame
yield return null;
}
}
}
Fade an object out if it's not currently being viewed
Don't worry about which objects are fading out and not selected. That is the job of the fader. If it's not being viewed, it's fading out. It's that simple.
Fader.cs - Put this component on your objects that can be faded
public class Fader : MonoBehaiour
{
// Flag to know if the object is currently being viewed
public bool IsCurrentlyViewed = false;
// Time in seconds it takes to fade from 100% brightness to 0% brightness. Default is 3 seconds
[SerializeField] private float SecondsTimeToFade = 3.0f;
// Ratio from 0 to 1 to indicate how bright an object should be
[SerializeField] private float PercentRatioBright = 0.0f;
// Start and end colors
[SerializeField] private Color startingColor;
[SerializeField] private Color endingColor;
// Cache the renderer
private Renderer renderer;
public void Start()
{
renderer = GetComponent<Renderer>();
startingColor = renderer.material.color;
if (layer < 8)
Debug.LogWarning("Fader layer should be set to a player-defined layer");
}
public void Update()
{
// Fade in when being viewed, fade out when not
if (PercentRatioBright > 0.0f && PercentRatioBright <= 1.0f)
Fade();
// Reset the IsCurrentlyViewed bool
IsCurrentlyViewed = false;
}
private void Fade()
{
float brightnessRatioUnsignedDifference = Time.deltaTime / SecondsTimeToFade;
PercentRatioBright += brightnessRatioUnsignedDifference * (IsCurrentlyViewed ? 1 : -1);
renderer.material.color = Color.Lerp(startingColor, endingColor, PercentRatioBright);
}
}
Example call:
public class ThroughTheLookingClass : MonoBehaiour
{
// Set this so you only hit what you want with the ray
// Make sure your selectable objects have this layer
[SerializeField] private LayerMask SelectableLayerMask;
public Update()
{
// Cast screen point to ray, select objects with layermask and have the Fader component
if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition, out RaycastHit hit, layerMask: SelectableLayerMask)))
{
hit.transform.gameObject.GetComponent<Fader>()?.IsCurrentlyViewed = true;
}
}
}
I revisit my own script and made some changes, so the code works out fine now.
RaycastHit hit;
Ray ray;
public GameObject nextObject;
public GameObject currentObject;
public GameObject previousObject;
public GameObject lastPreviousObject;
public GameObject lastObject;
private bool saveObjectOnce = false;
private bool isFading = false;
private bool fadeOnce = false;
public void Start()
{
}
public void Update()
{
ray = Camera.main.ScreenPointToRay(Input.mousePosition);
Vector3 forward = Camera.main.transform.TransformDirection(Vector3.forward) * 10;
Debug.DrawRay(Camera.main.transform.position, forward, Color.red);
//if player is looking
if (Physics.Raycast(ray, out hit))
{
var selection = hit.transform;
if (selection.CompareTag("Puzzle") || selection.CompareTag("Clue") || selection.CompareTag("Selectable"))
{
nextObject = selection.gameObject;
//if previous and current object are same
if (GameObject.Equals(nextObject, currentObject))
{
isFading = true;
ColorFade fadingColor = currentObject.GetComponent<ColorFade>();
fadingColor.FadeCheck(isFading);
if(previousObject != null)
{
isFading = false;
ColorFade fadingcolor1 = previousObject.GetComponent<ColorFade>();
fadingcolor1.FadeCheck(isFading);
}
if(GameObject.Equals(currentObject, lastPreviousObject))
{
lastPreviousObject = null;
}
else {
if (lastPreviousObject != null)
{
isFading = false;
ColorFade fadingcolor = lastPreviousObject.GetComponent<ColorFade>();
fadingcolor.FadeCheck(isFading);
}
}
}
//if previous and current object are different
else
{
lastPreviousObject = previousObject;
previousObject = currentObject;
currentObject = nextObject;
}
}
//if detects other objects than the items
else
{
isFading = false;
lastObject = nextObject;
if (lastObject != null)
{
ColorFade fadingColor = lastObject.GetComponent<ColorFade>();
fadingColor.FadeCheck(isFading);
}
}
}
//if the player is not looking
else
{
isFading = false;
lastObject = nextObject;
if (lastObject != null)
{
ColorFade fadingColor = lastObject.GetComponent<ColorFade>();
fadingColor.FadeCheck(isFading);
}
}
As you can see, I started with an object in a scene and attached a script to it along with other components. https://imgur.com/z9Tooh9
It looks barren due to there not actually being a model in there. However, when the game is started, it is created by another script in an object in the hierarchy. https://imgur.com/guQQlJO
You can also see that the script works as expected and detects all the skinned mesh renderers and allows you to adjust the colors accordingly.
However, this is where the problem comes in.
When I duplicate this object the first object does what is expected of it but, the second one doesn't.
As you can see, there are no skinned mesh renderers in the list for the second object. https://imgur.com/zTRHL9F
Naturally, I put debug logs at the point where it detects the skinned mesh renderers to see what the issue is:
void OnMouseEnter()
{
Debug.Log("Mouse Entered");
foreach (SkinnedMeshRenderer element in skinnedMeshRenderersScan) //For
every object it finds
{
Debug.Log("Detected: " + element);
Debug.Log("Detected Color: " + selectedColor);
element.material.color = selectedColor;
}
}
void OnMouseExit()
{
Debug.Log("Mouse Left");
foreach (SkinnedMeshRenderer element in skinnedMeshRenderersScan) //For
every object it finds
{
//Debug.Log("Detected: " + element);
//Debug.Log("Detected Color: " + deselectedColor);
element.material.color = deselectedColor;
}
}
The first object notes that they're detected. https://imgur.com/fBhXjKj
The second one simply pretends as if the debug log is not even there. https://imgur.com/alE76aY
I'm more than happy to elaborate in the event that you don't quite understand what it is I'm asking.
Many thanks in advance and sorry if my formatting is terrible I'm not the best at it.
I've tried searching for answers online but, I could not locate a solution to my rather unique problem.
The whole script is as follows:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TankController : MonoBehaviour
{
Vector3 targetPosition;
Vector3 lookAtTarget;
public Vector4 selectedColor;
public Vector4 deselectedColor;
Quaternion playerRot;
float rotSpeed = 2;
float speed = 3;
bool moving = false;
public bool Building = false;
public bool selected = false;
public bool CoolingDown = false;
public double CoolDown = .2;
public double original = .2;
MeshRenderer RenderMesh;
MeshRenderer RenderMeshParent;
SkinnedMeshRenderer[] skinnedMeshRenderersScan;
public List<SkinnedMeshRenderer> skinnedMeshRenderersList = new
List<SkinnedMeshRenderer>();
// Use this for initialization
void Start()
{
RenderMesh = GetComponentInChildren<MeshRenderer>();
RenderMeshParent = GetComponentInParent<MeshRenderer>();
skinnedMeshRenderersScan = GetComponentsInChildren<SkinnedMeshRenderer> ();
foreach (SkinnedMeshRenderer element in skinnedMeshRenderersScan) //For every object it finds
{
if (!skinnedMeshRenderersList.Contains(element)) //If it isn't already in this list
{
skinnedMeshRenderersList.Add(element); //Add to the list
}
}
}
// Update is called once per frame
void Update()
{
if (Input.GetMouseButton(0))
{
if (CoolingDown == false) //If not cooling down
{
SetTargetPosition();
CoolingDown = true; //Set cooling down to true
}
}
if (CoolingDown == true)
{
CoolDown -= Time.deltaTime; //Takes some time away
if (CoolDown <= 0) //Checks if the cooldown is done yet
{
CoolingDown = false; //Sets cooling down to false
CoolDown = original; //Cooldown timer is reset by equalling its original value
}
}
if (moving)
Move();
}
void SetTargetPosition()
{
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit, Mathf.Infinity))
{
if (hit.collider.CompareTag("Hittable") == true && selected == true)
{
targetPosition = hit.point;
lookAtTarget = new Vector3(targetPosition.x - transform.position.x,
transform.position.y,
targetPosition.z - transform.position.z);
playerRot = Quaternion.LookRotation(lookAtTarget);
moving = true;
}
if (hit.collider.CompareTag("Unit") == true)
{
Fighting self = GetComponentInChildren<Fighting>();
Fighting other = hit.collider.gameObject.GetComponentInChildren<Fighting>();
PlayerMaster playcheck = GetComponent<PlayerMaster>();
if (CoolingDown == false) //If not cooling down
{
if (gameObject.name == hit.collider.name)
{
if (selected == false)
{
selected = true;
RenderMesh.enabled = !RenderMesh.enabled;
}
else if (selected == true)
{
selected = false;
RenderMesh.enabled = !RenderMesh.enabled;
}
}
CoolingDown = true; //Set cooling down to true
}
}
}
}
}
void Move()
{
if (Building == false)
{
transform.rotation = Quaternion.Slerp(transform.rotation,
playerRot,
rotSpeed * Time.deltaTime);
transform.position = Vector3.MoveTowards(transform.position,
targetPosition,
speed * Time.deltaTime);
if (transform.position == targetPosition)
moving = false;
}
}
void OnMouseEnter()
{
Debug.Log("Mouse Entered");
foreach (SkinnedMeshRenderer element in skinnedMeshRenderersScan) //For every object it finds
{
Debug.Log("Detected: " + element);
Debug.Log("Detected Color: " + selectedColor);
element.material.color = selectedColor;
}
}
void OnMouseExit()
{
Debug.Log("Mouse Left");
foreach (SkinnedMeshRenderer element in skinnedMeshRenderersScan) //For every object it finds
{
//Debug.Log("Detected: " + element);
//Debug.Log("Detected Color: " + deselectedColor);
element.material.color = deselectedColor;
}
}
}
To recap, the expected result is that the second object (Or more) would work the same way the first object does.
In practice, it does not.
I was able to fix this issue by putting the scans in the awake() method.
I'm having difficulty getting my gold pickups to respawn after they've been destroyed on death. The idea is, if the player fails to pick up the 5 gold bars, activates a checkpoint, and dies, the current gold is destroyed and it resets once the screen has faded from black.
I currently have a Coroutine in my Health Manager that runs correctly if the player dies and resets them. I have a Gold Pickup script that destroys the gold if they haven't been picked up. I just can't seem to get them to re-instantiate. I've tried adding the instantiate code within the Health Manager's coroutine and within the Gold Pickup script. Nothing seems to work. If I'm not getting errors saying 'Array index is out of range' it's 'object reference not set to an instance of an object' etc.
public class GoldPickup : MonoBehaviour{
public int value;
public GameObject pickupEffect;
public GameObject[] goldBarArray;
public HealthManager healthManager;
public Checkpoint checkpoint;
private Vector3 goldRespawnPoint;
private Quaternion goldStartPosition;
void Start()
{
//To destroy multiple objects at once, use FindGameObjectsWithTag.
//GetComponent is considered more efficient than FindObjectOfType, but the latter avoids any errors saying an object reference hasn't been set.
goldBarArray = GameObject.FindGameObjectsWithTag("Gold");
healthManager = FindObjectOfType<HealthManager>();
//FindObjectOfType<Checkpoint>();
checkpoint = FindObjectOfType<Checkpoint>();
goldRespawnPoint = transform.position;
goldStartPosition = transform.rotation;
}
public void OnTriggerEnter(Collider other)
{
if (other.gameObject.CompareTag("Player"))
{
FindObjectOfType<GameManager>().AddGold(value);
Instantiate(pickupEffect, transform.position, transform.rotation);
Destroy(gameObject);
}
}
public void DestroyGold()
{
//For Statics, an object reference isn't necessary. Use the FindObjectOfType to find the appropriate script and reference the Type, such as HealthManager.
if (checkpoint.checkpoint1On == false)
{
foreach (GameObject Gold in goldBarArray)
{
Destroy(Gold);
Instantiate(goldBarArray[5], goldRespawnPoint, goldStartPosition);
goldRespawnPoint = transform.position;
goldStartPosition = transform.rotation;
//healthManager.RespawnCo();
}
}
}
/*public void GoldReset()
{
if (healthManager.isRespawning == true)
{
if (checkpoint.checkpoint1On == false)
{
StartCoroutine("GoldRespawnCo");
}
}
else if (_respawnCoroutine != null)
{
StopCoroutine(_respawnCoroutine);
_respawnCoroutine = StartCoroutine("GoldRespawnCo");
}*/
/*public IEnumerator GoldRespawnCo()
{
if (checkpoint.checkpoint1On == false)
{
Instantiate(goldPrefab, goldRespawnPoint, goldStartPosition);
transform.position = goldRespawnPoint;
transform.rotation = goldStartPosition;
}
else
{
yield return null;
}
}*/
/*if (thePlayer.gameObject.activeInHierarchy == false)
{
Destroy(gameObject);
Instantiate(goldBar, transform.position, transform.rotation);
}
else
{
if (thePlayer.gameObject.activeInHierarchy == true)
{
transform.position = respawnPoint;
transform.rotation = startPosition;
}
}*/
}
public class HealthManager : MonoBehaviour
//The counters will count down and will keep counting down based on the length variables
public int maxHealth;
public int currentHealth;
public PlayerController thePlayer;
//public GoldPickup goldPickup;
//public GoldPickup[] goldPickup;
public float invincibilityLength;
public Renderer playerRenderer;
public float flashLength;
public float respawnLength;
public GameObject deathEffect;
public Image blackScreen;
public float fadeSpeed;
public float waitForFade;
public bool isRespawning;
//public GameObject goldBar;
//To reference another script's function, such as in the DeathTrigger script, make a public DeathTrigger, give it a reference name, and put it into the Start function. Use the reference name and assign it using GetComponent. Call another script's method by using the reference name, followed by a dot and the name of the method. Eg: deathTrigger.DestroyGold().
private Quaternion startPosition;
//private Quaternion goldPosition;
private float flashCounter;
private float invincibilityCounter;
private Vector3 respawnPoint;
//private Vector3 goldRespawnPoint;
private bool isFadetoBlack;
private bool isFadefromBlack;
//private Coroutine _respawnCoroutine;
//private Vector3 goldRespawnPoint;
//private Quaternion goldStartPosition;
void Start()
{
currentHealth = maxHealth;
respawnPoint = thePlayer.transform.position;
startPosition = thePlayer.transform.rotation;
//goldPickup = GetComponent<GoldPickup>();
//goldRespawnPoint = goldBar.transform.position;
//goldStartPosition = goldBar.transform.rotation;
//goldRespawnPoint = transform.position;
//goldStartPosition = transform.rotation;
//goldPickup = FindObjectOfType<GoldPickup>();
//goldRespawnPoint = goldBar.transform.position;
//goldPosition = goldBar.transform.rotation;
}
void Update()
{
//These functions are checked every frame until the player takes damage
if (invincibilityCounter > 0)
{
invincibilityCounter -= Time.deltaTime;
flashCounter -= Time.deltaTime;
if (flashCounter <= 0)
//The Flash Counter is currently set at 0.1 and will be within the 0 region as it counts down. During this period, the playerRenderer will alternate between on and off
{
playerRenderer.enabled = !playerRenderer.enabled;
//The Flash Counter will keep counting down and reloop depending on the Flash Length time
flashCounter = flashLength;
}
//This makes sure after the flashing and invincibility has worn off that the player renderer is always turned back on so you can see the player
if (invincibilityCounter <= 0)
{
playerRenderer.enabled = true;
}
}
if (isFadetoBlack)
{
blackScreen.color = new Color(blackScreen.color.r, blackScreen.color.g, blackScreen.color.b, Mathf.MoveTowards(blackScreen.color.a, 1f, fadeSpeed * Time.deltaTime));
if (blackScreen.color.a == 1f)
{
isFadetoBlack = false;
}
}
if (isFadefromBlack)
{
blackScreen.color = new Color(blackScreen.color.r, blackScreen.color.g, blackScreen.color.b, Mathf.MoveTowards(blackScreen.color.a, 0f, fadeSpeed * Time.deltaTime));
if (blackScreen.color.a == 0f)
{
isFadefromBlack = false;
}
}
}
public void HurtPlayer(int damage, Vector3 direction)
{
//If the invincibility countdown reaches zero it stops, making you no longer invincible and prone to taking damage again
if (invincibilityCounter <= 0)
{
currentHealth -= damage;
if (currentHealth <= 0)
{
Respawn();
}
else
{
thePlayer.Knockback(direction);
invincibilityCounter = invincibilityLength;
playerRenderer.enabled = false;
flashCounter = flashLength;
}
}
}
public void Respawn()
{
//A StartCoroutine must be set up before the IEnumerator can begin
if (!isRespawning)
{
StartCoroutine("RespawnCo");
}
}
//IEnumerators or Coroutines will execute the code separately at specified times while the rest of the code in a codeblock will carry on executing as normal.
//To prevent an error appearing below the name of the Coroutine, be sure to place a yield return somewhere within the code block. Either yield return null or a new WaitForSeconds.
public IEnumerator RespawnCo()
{
if (GameManager.currentGold < 5)
{
isRespawning = true;
thePlayer.gameObject.SetActive(false);
Instantiate(deathEffect, respawnPoint, startPosition);
yield return new WaitForSeconds(respawnLength);
isFadetoBlack = true;
yield return new WaitForSeconds(waitForFade);
//To reference another script's function quickly and just the once, use the FindObjectOfType function. This is considered to be slow however.
FindObjectOfType<GoldPickup>().DestroyGold();
//GetComponent<GoldPickup>().DestroyGold();
//Instantiate(goldBar, goldRespawnPoint, Quaternion.identity);
isFadefromBlack = true;
//goldRespawnPoint = goldBar.transform.position;
//goldStartPosition = goldBar.transform.rotation;
isRespawning = false;
thePlayer.gameObject.SetActive(true);
thePlayer.transform.position = respawnPoint;
thePlayer.transform.rotation = startPosition;
currentHealth = maxHealth;
invincibilityCounter = invincibilityLength;
playerRenderer.enabled = false;
flashCounter = flashLength;
GameManager.currentGold = 0;
GetComponent<GameManager>().SetCountText();
StopCoroutine("RespawnCo");
/*isRespawning = true;
thePlayer.gameObject.SetActive(false);
yield return new WaitForSeconds(respawnLength);
isFadetoBlack = true;
yield return new WaitForSeconds(waitForFade);
isFadefromBlack = true;
invincibilityCounter = invincibilityLength;
playerRenderer.enabled = false;
flashCounter = flashLength;
SceneManager.LoadScene("Level 1");
GameManager.currentGold = 0;*/
}
else if(GameManager.currentGold >= 5)
{
isRespawning = true;
thePlayer.gameObject.SetActive(false);
Instantiate(deathEffect, respawnPoint, startPosition);
yield return new WaitForSeconds(respawnLength);
isFadetoBlack = true;
yield return new WaitForSeconds(waitForFade);
isFadefromBlack = true;
isRespawning = false;
thePlayer.gameObject.SetActive(true);
thePlayer.transform.position = respawnPoint;
thePlayer.transform.rotation = startPosition;
currentHealth = maxHealth;
invincibilityCounter = invincibilityLength;
playerRenderer.enabled = false;
flashCounter = flashLength;
}
}
/*public void HealPlayer(int healAmount)
{
currentHealth += healAmount;
if(currentHealth > maxHealth)
{
currentHealth = maxHealth;
}
}*/
public void SetSpawnPoint(Vector3 newPosition)
{
respawnPoint = newPosition;
}
public class Checkpoint : MonoBehaviour
public HealthManager theHealthManager;
public Renderer cpRenderer;
public Renderer postRenderer;
public SpriteRenderer pcRenderer;
public Material cpOff;
public Material cpOn;
public Material postOff;
public Material postOn;
public GameObject[] infoPanels;
public bool checkpoint1On;
//Make sure to assign a value to a bool with '=' and in an 'if' statement somewhere in the code to prevent warnings.
//private bool checkpoint1IsActivated;
private bool infoPanel1Activated;
void Start()
{
theHealthManager = FindObjectOfType<HealthManager>();
}
void Update()
//Key presses are better handled in the Update function and will recognise keys being pressed once every frame.
{
if (checkpoint1On == true)
{
if (infoPanel1Activated == false)
{
if (Input.GetKeyDown(KeyCode.Space))
{
infoPanels[0].SetActive(true);
infoPanel1Activated = true;
}
}
else
{
if (infoPanel1Activated == true)
{
if (Input.GetKeyDown(KeyCode.Space))
{
infoPanels[0].SetActive(false);
infoPanel1Activated = false;
}
}
}
}
}
public void Checkpoint1On()
{
cpRenderer.material = cpOn;
postRenderer.material = postOn;
pcRenderer.color = new Color(1f, 1f, 1f, 1f);
checkpoint1On = true;
}
//[] makes a variable an Array (a list). The 'foreach' loop will check through all the Checkpoint objects
//Checkpoint[] checkpoints = FindObjectsOfType<Checkpoint>();
//For each Checkpoint Array called 'checkpoints', look for 'cp' and turn the others in the list off
/*foreach (Checkpoint cp in checkpoints)
{
cp.CheckpointOff();
}
theRenderer.material = cpOn;*/
public void Checkpoint1Off()
{
cpRenderer.material = cpOff;
postRenderer.material = postOff;
pcRenderer.color = new Color(1f, 1f, 1f, 5f);
checkpoint1On = false;
}
public void OnTriggerStay(Collider other)
{
if (other.gameObject.CompareTag("Player"))
{
if (GameManager.currentGold >= 5)
{
if (Input.GetKeyDown(KeyCode.Return))
{
theHealthManager.SetSpawnPoint(transform.position);
Checkpoint1On();
checkpoint1On = true;
}
}
else if (GameManager.currentGold <= 5)
{
checkpoint1On = false;
}
}
}
In your DestroyGold() function, you instantiate the gold like this:
foreach (GameObject Gold in goldBarArray)
{
Destroy(Gold);
Instantiate(goldBarArray[5], goldRespawnPoint, goldStartPosition);
goldRespawnPoint = transform.position;
goldStartPosition = transform.rotation;
//healthManager.RespawnCo();
}
But transform.position and transform.rotation only get the position and rotation of the current object (i.e. whatever your script is attached to). So not only are you spawning all the gold in the same spot, it's spawning the gold at the location of the object that holds your script, not where you actually want it to go!
Without knowing much about the objects in your scene, here's what I can tell you: try creating a Transform[] to store the locations where you want to respawn the gold. Also, make sure you assign the goldRespawnPoint and goldStartPosition BEFORE you call Instantiate() in your foreach loop. Finally, just a general tip: you should never use variable == true or variable == false in an if statement. You can just use if(variable) or if(!variable), respectively. It will work just the same while being more readable and reducing the amount of code you need to write.
EDIT 1: In response to comments, I've added specific code examples for implementing these suggestions.
To start, you're probably getting the out of range error because of goldBarArray[5]. Since arrays start at index 0, you can only access up to element n-1 in a size n array. More on how to fix this in the next step.
Now for the Transform array. In the area where you declare your public variables (at the top of the script), add the line
public Transform[] spawnPoints;
Then, back in Unity you will be able to assign those spawn points in the Inspector.
EDIT 2: Additionally, in the foreach loop you're trying to instantiate one of the gold bars from the scene, but those are getting deleted with the Destroy(Gold); statement. Instead, you should be instantiating from the prefab which won't get destroyed. To do this, add
public GameObject goldPrefab;
up with the rest of your public variables. Then, in the Editor create a prefab by dragging one of the gold bars from the Hierarchy into your Assets folder. Finally, set that prefab to be the value of goldPrefab in the Inspector.
Now, you actually can clean up your foreach loop a little bit. You can get rid of the goldRespawnPoint and goldStartPosition lines because the respawn locations will be contained in the Transform array we just created. Again, without knowing how your scene is structured I've needed to just make an educated guess about what will work. Give this loop a try:
int spawnPointCounter = 0;
foreach(GameObject Gold in goldBarArray){
Destroy(Gold);
Transform currentSP = spawnPoints[spawnPointCounter];
Instantiate(goldPrefab, currentSP.position, currentSP.rotation);
spawnPointCounter++;
}
Some times my code runs but then without changing any of the code it stops working and says I have the error argument is out of range exception?
it says it is on the line, "if (gameObject.transform.localPosition.y == finP[1].transform.localPosition.y)".
here is my code:
public class enemy : MonoBehaviour
{
List<GameObject> finP = new List<GameObject>();
public Node nodeScript;
Vector2 direction = Vector2.zero;
bool trigger = false;
public float Speed = 1.0f;
// Use this for initialization
void Start()
{
nodeScript = GameObject.Find("P16").GetComponent<Node>();
}
// Update is called once per frame
void Update()
{
apple();
MovePosition();
//nodeScript.FinalPath.ForEach(x => Debug.Log(x));
//Debug.Log(nodeScript.FinalPath.Count);
}
void apple()
{
if (gameObject.transform.localPosition.y == finP[1].transform.localPosition.y)
{
if (gameObject.transform.localPosition.x > finP[1].transform.localPosition.x)
{
Debug.Log("left");
direction = Vector2.left;
}
else
{
Debug.Log("right");
direction = Vector2.right;
}
}
else
{
if (gameObject.transform.localPosition.y > finP[1].transform.localPosition.y)
{
direction = Vector2.down;
}
if (gameObject.transform.localPosition.y < finP[0].transform.localPosition.y)
{
direction = Vector2.up;
}
}
}
public void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.tag == "pallet")
{
finP.Clear();
foreach(string var in nodeScript.FinalPath)
{
Debug.Log("en script " + var);
finP.Add(GameObject.Find(var));
}
Debug.Log(finP[1]);
}
}
void MovePosition()
{
transform.localPosition += (Vector3)(direction * Speed) * Time.deltaTime;
}
}
When you hit the "pallet" gameobject you're clearing the list, while also trying to access elements of that list every frame. Although you're adding to the list immediately after, there is probably a frame or two where the list is lacking the required elements. In your apple() function, I'd throw a if (finP.Count < 1) return; and that should fix your problem.