Coroutine not working from inside an instantiated object - c#

I'm creating some lines of many diffent rectangles. Both lines and rectangles are created by code.
I do some logic with the rectangle's vertices and update that information in my instantiated rectangles. This works really well.
Then on each rectangle I have 2 scripts, one storing data and the other storing a corutine to make the rectangle flash or stop flashing when certain conditions are met after lines and rectangles move possitions.
It does not work, it might have to be done with some other outside scripts and even diccionaries or lists, but as I'm new to coroutines I would like to know if it's possible to do it from each instantiated object by simply checking those bool and int variables as they change during the game.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class rectanguloAnim : MonoBehaviour{
rectangulo rect; //script containing data from this rectangle
Color32 colOrig;
Material material;
IEnumerator currentFlashCoroutine;
void Start()
{
rect = GetComponent<rectangulo>();
colOrig = GetComponent<rectangulo>().colorOriginal;
material = GetComponent<Renderer>().material;
}
void Update()
{
// this bool and int change when the line and rect are interacted with
if (rect.isIntermediate == true || rect.activeDirection != 0)
{
if (currentFlashCoroutine != null)
{
StopCoroutine(currentFlashCoroutine);
}
currentFlashCoroutine = ChangeColor(material, colOrig, Color.white);
StartCoroutine(currentFlashCoroutine);
}
else
{
if (currentFlashCoroutine != null)
{
StopCoroutine(currentFlashCoroutine);
}
}
}
IEnumerator ChangeColor(Material toChange, Color32 startColor, Color32 endColor)
{
float t = 0;
float colorDuration = Random.Range(2.49f, 6f);
while (t < colorDuration)
{
float timeDuration = Random.Range(.55f, .95f);
if (t > timeDuration)
{
t = 0;
}
t += Time.deltaTime;
toChange.color = Color.Lerp(startColor, endColor, t / colorDuration);
yield return null;
}
}
}

I think you should rather make your field
private Coroutine currentFlashCoroutine;
and then use
currentcurrentFlashCoroutine = StartCoroutine(ChangeColor(material, colOrig, Color.white);
and then do
if(currentFlashCoroutine != null)
{
StopCoroutine (currentFlashCoroutine);
currentFlashCoroutine = null;
}
Btw in doubt you could also use StopAllCoroutines since in your use case you seem to anyway only have a single routine in your class ;)
The second issue here is that you are stopping and starting a new routine every frame as long the condition is true.
You would rather do somthing like
void Update()
{
// this bool and int change when the line and rect are interacted with
if (rect.isIntermediate || rect.activeDirection != 0)
{
// only start a new routine if there is not already one running
if(currentFlashCoroutine == null)
{
currentFlashCoroutine = StartCoroutine(ChangeColor(material, colOrig, Color.white);
}
}
else
{
if(currentFlashCoroutine != null)
{
StopCoroutine (currentFlashCoroutine);
currentFlashCoroutine = null;
}
}
}
IEnumerator ChangeColor(Material toChange, Color32 startColor, Color32 endColor)
{
var t = 0f;
var colorDuration = Random.Range(2.49f, 6f);
while (t < colorDuration)
{
var timeDuration = Random.Range(.55f, .95f);
if (t > timeDuration)
{
t = 0;
}
t += Time.deltaTime;
toChange.color = Color.Lerp(startColor, endColor, t / colorDuration);
yield return null;
}
currentFlashRoutine = null;
}

Related

Unity cant figure out how to fix the "still trying to access gameobject" error

So i was writing code to simulate how animals would eat food and drink water around the map, but i keep getting an error. I believe its because multiple animals are trying to access one lake or one fruit, but i don't know how to avoid this from happening.
So here is my code, please help (i know my coding skills are quite bad, but i don't care)
public float hunger = 100;
public float speed;
public float thirst = 100;
// Start is called before the first frame update
void Start()
{
StartCoroutine(hungerdrop());
}
// Update is called once per frame
void Update()
{
if (hunger <= 20 && hunger < thirst)
{
gotoBush();
}
if (thirst <= 20 && thirst < hunger)
{
gotoLake();
}
if (hunger <= 0)
{
Destroy(gameObject);
}
}
IEnumerator hungerdrop()
{
yield return new WaitForSeconds(0.2f);
hunger -= 1;
thirst -= 2;
StartCoroutine(hungerdrop());
}
public void gotoBush()
{
GetClosestFruit();
}
public void gotoLake()
{
GetClosestLake();
}
GameObject GetClosestFruit()
{
GameObject tMin = null;
float minDist = Mathf.Infinity;
Vector3 currentPos = transform.position;
foreach (GameObject t in GameObject.FindGameObjectsWithTag("fruit"))
{
float dist = Vector3.Distance(t.transform.position, currentPos);
if (dist < minDist)
{
tMin = t;
minDist = dist;
}
}
if (tMin != null)
{
float step = speed * Time.deltaTime;
StartCoroutine(walkToBush());
IEnumerator walkToBush()
{
if (tMin != null)
{
while (transform.position != tMin.transform.position && tMin != null)
{
yield return new WaitForSeconds(0.1f);
transform.position = Vector3.MoveTowards(transform.position, tMin.transform.position, step);
}
hunger = 100;
StartCoroutine(bushEat());
}
IEnumerator bushEat()
{
yield return new WaitForSeconds(0.1f);
destroyBush();
}
}
void destroyBush()
{
Destroy(tMin);
}
}
return tMin;
}
GameObject GetClosestLake()
{
GameObject tMin = null;
float minDist = Mathf.Infinity;
Vector3 currentPos = transform.position;
foreach (GameObject t in GameObject.FindGameObjectsWithTag("lake"))
{
float dist = Vector3.Distance(t.transform.position, currentPos);
if (dist < minDist)
{
tMin = t;
minDist = dist;
}
}
if (tMin != null )
{
float step = speed * Time.deltaTime;
StartCoroutine(walkToBush());
IEnumerator walkToBush()
{
if (tMin != null)
{
while (transform.position != tMin.transform.position && tMin != null)
{
yield return new WaitForSeconds(0.1f);
if(tMin != null)
{
transform.position = Vector3.MoveTowards(transform.position, tMin.transform.position, step);
}
}
thirst = 100;
StartCoroutine(bushEat());
}
IEnumerator bushEat()
{
yield return new WaitForSeconds(0.1f);
destroyBush();
}
}
void destroyBush()
{
Destroy(tMin);
}
}
return tMin;
}
}
First: You should use while loops in coroutines instead of recursion like this:
IEnumerator hungerdrop()
{
while(true)
{
hunger -= 1;
thirst -= 2;
yield return new WaitForSeconds(0.2f);
}
}
Second: To check for gameObjects that have already ben destroyed, you can simply use an if statement like:
if(gameObject != null)
{
//do something
}
If I understand correctly you have multiple animal instances all running this component.
I see two huge issues here:
You might have concurrent Coroutines since you start a new routine every frame while your conditions are true! This might even mean that you try to move in two opposed directions => you never reach a target.
Multiple of your animals could target the very same resource => while the one is still moving towards it, the other one might already have eaten it and destroys it.
And then there is most probably this issue that you check the alive state of your resources after getting the position in
while (transform.position != tMin.transform.position && tMin != null)
where you should rather check
while (tMin && transform.position != tMin.transform.position)
However, this would still not prevent the rest of your code after the while from being executed even if tMin doesn't exist anymore!
What I would rather do is
Make sure only one Coroutine is running at a time
Make sure only one animal can target a resource at a time
Therefor instead of using tags I would rather have two actual components attached to the resources with a common base class:
public abstract class ConsumableResource : MonoBehaviour
{
public bool isOccupied;
}
and then these two go onto the according resource GameObjects:
public class Fruit : ConsumableResource
{
// Doesn't have to do anything else .. but could
}
and
public class Lake : ConsumableResource
{
// Doesn't have to do anything else .. but could
// You could e.g. consider to add a counter so that multiple animals can target this at a time
// and/or add another counter so multiple animals can consume this resource before it is destroyed
}
And then I would do
public class Animal : MonoBehaviour
{
public float hunger = 100;
public float speed;
public float thirst = 100;
// the currently executed coroutine
private Coroutine _currentRoutine;
// the currently targeted resource instance
private ConsumableResource _currentTargetResource;
// Update is called once per frame
private void Update()
{
// first of all I would rather drop the hunger and thirst like this
// This now means hunger drops 5 units per second and thirst drops 10 units per second
// What you had before was a complex way for writing basically the same
hunger -= 5f * Time.deltaTime;
thirst -= 10f * Time.deltaTime;
if (hunger <= 0)
{
Destroy(gameObject);
}
// In order to no end up with multiple concurrent routines check if there is already a routine running first
if (_currentRoutine == null)
{
if (hunger <= 20 && hunger < thirst)
{
// start our resource routine targeting the type Fruit and if we succeed to run the entire routine to and
// then call WhenConsumedFruit afterwards to reset the hunger
_currentRoutine = StartCoroutine(GetResourceRoutine<Fruit>(WhenConsumedFruit));
}
// make your cases exclusive!
else if (thirst <= 20 && thirst < hunger)
{
_currentRoutine = StartCoroutine(GetResourceRoutine<Lake>(WhenConsumedLake));
}
}
}
private void WhenConsumedFruit()
{
hunger = 100;
}
private void WhenConsumedLake()
{
thirst = 100;
}
// Use a generic method to not repeat the same implementation
// This now can be used to get the closest of any inherited type from ConsumableResource
private T FindClosestResource<T>() where T : ConsumableResource
{
// First get all instances in the scene of given resource type
var allFruits = FindObjectsOfType<T>();
// filter out those that are already occupied by another animal instance
var onlyNotOccupiedFruits = allFruits.Where(fruit => !fruit.isOccupied);
// sort the remaining instances by distance
var sortedByDistance = onlyNotOccupiedFruits.OrderBy(fruit => (fruit.transform.position - transform.position).sqrMagnitude);
// take the first one (= closest) or null if there was no remaining instance
return sortedByDistance.FirstOrDefault();
}
// Again use the most generic base class so you can reuse the same code for any inherited type of ConsumableResource
IEnumerator MoveTowardsResource(ConsumableResource target)
{
while (transform.position != target.transform.position)
{
transform.position = Vector3.MoveTowards(transform.position, target.transform.position, speed * Time.deltaTime);
yield return null;
}
transform.position = target.transform.position;
}
// and again do his only once
IEnumerator ConsumeResource(ConsumableResource target)
{
yield return new WaitForSeconds(0.1f);
// You might want to consider to rather move this into the resource itself
// so it could extend the behavior as said e.g. using counters
// without the need that your animal class has to be aware of what exactly
// this means for the resource. Maybe it doesn't destroy it but only disable
// for some time so it can grow back => unlimmitted options ;)
Destroy(target.gameObject);
}
private IEnumerator GetResourceRoutine<T>(Action whenConsumed) where T : ConsumableResource
{
_currentTargetResource = FindClosestResource<T>();
// did we find a closest fruit?
if (!_currentTargetResource)
{
// if not terminate this routine and allow the next one to start
_currentRoutine = null;
yield break;
}
// set closest fruit occupied so no animal can take it anymore
_currentTargetResource.isOccupied = true;
// Move towards the closest fruit
yield return MoveTowardsResource(_currentTargetResource);
// "Eat" the fruit
yield return ConsumeResource(_currentTargetResource);
// when all is done without this object dying before simply invoked the passed callback action
whenConsumed?.Invoke();
// Allow the next routine to start
_currentRoutine = null;
}
private void OnDestroy()
{
// in case we die for whatever reason make sure to release the occupied resources if there are any
// so if you die on the way towards it at least from now on another animal can pick it again as target
if (_currentTargetResource) _currentTargetResource.isOccupied = false;
}
}

Why I'm getting MissingReferenceException exception when starting the coroutine over and over again?

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GenerateWalls : MonoBehaviour
{
public GameObject gameObjectToRaise;
public float duration;
public Vector3 raiseAmount;
public bool go = false;
public Color[] colors = new Color[4];
public bool randomColors = false;
private GameObject objtoraise;
private GameObject[] walls;
private bool scaleOver = false;
private void Start()
{
Init();
ColorWalls();
// The z Axis must be minimum 1 or any value above 0 could be also 0.1f
// but it's better to keep it minimum as 1 by default.
if (raiseAmount.z < 1)
{
raiseAmount.z = 1f;
}
if (go)
{
StartCoroutine(ScaleOverSeconds(objtoraise, new Vector3(raiseAmount.x, raiseAmount.y,
raiseAmount.z), duration));
}
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.R))
{
//if (scaleOver)
//{
if (objtoraise != null)
{
if (raiseAmount.z < 1)
{
raiseAmount.z = 1f;
}
Destroy(objtoraise);
Init();
ColorWalls();
StartCoroutine(ScaleOverSeconds(objtoraise, new Vector3(raiseAmount.x, raiseAmount.y,
raiseAmount.z), duration));
scaleOver = false;
//}
}
}
}
private void Init()
{
objtoraise = Instantiate(gameObjectToRaise);
objtoraise.name = "Walls";
walls = GameObject.FindGameObjectsWithTag("Wall");
}
public IEnumerator ScaleOverSeconds(GameObject objectToScale, Vector3 scaleTo, float seconds)
{
float elapsedTime = 0;
Vector3 startingScale = objectToScale.transform.localScale;
while (elapsedTime < seconds)
{
objectToScale.transform.localScale = Vector3.Lerp(startingScale, scaleTo, (elapsedTime / seconds));
elapsedTime += Time.deltaTime;
yield return new WaitForEndOfFrame();
}
objectToScale.transform.localScale = scaleTo;
scaleOver = true;
}
private void ColorWalls()
{
for (int i = 0; i < walls.Length; i++)
{
if (randomColors)
{
walls[i].transform.GetChild(0).GetComponent<Renderer>().material.color
= GetRandomColour32();
}
else
{
walls[i].transform.GetChild(0).GetComponent<Renderer>().material.color = colors[i];
}
}
}
private Color32 GetRandomColour32()
{
//using Color32
return new Color32(
(byte)UnityEngine.Random.Range(0, 255), //Red
(byte)UnityEngine.Random.Range(0, 255), //Green
(byte)UnityEngine.Random.Range(0, 255), //Blue
255 //Alpha (transparency)
);
}
}
Inside the Update() when I press the R key it's destroying the Instantiated object and then Instantiate is again and start the coroutine again. The problem is when I press on the R key many times in a row after two times I'm getting MissingReferenceException exception in the editor :
MissingReferenceException: The object of type 'GameObject' has been destroyed but you are still trying to access it.
Your script should either check if it is null or you should not destroy the object.
GenerateWalls+d__12.MoveNext () (at Assets/Scripts/GenerateWalls.cs:81)
Line 81 is :
objectToScale.transform.localScale = Vector3.Lerp(startingScale, scaleTo, (elapsedTime / seconds));
The goal is to be able to generate the walls each time over again when pressing R it should stop the current coroutine and start over.
Maybe the problem is that it's in the middle of the coroutine and because the old coroutine didn't stop yet then the object is missing in the middle because I destroy it?
How then I should do it to be able to press R over and over again and it will start the coroutine over and over? Not to start multiple coroutines but to start each time the coroutine over again.
The solution is to add : StopCoroutine In the Update()
This is not the solution. I thought stopping the coroutine will stop also the while loop inside but it didn't. It seems that checking for null inside the while loop solved the problem :
public IEnumerator ScaleOverSeconds(GameObject objectToScale, Vector3 scaleTo, float seconds)
{
if (objectToScale != null)
{
float elapsedTime = 0;
Vector3 startingScale = objectToScale.transform.localScale;
while (elapsedTime < seconds)
{
if (objectToScale == null)
{
yield return null;
}
else
{
objectToScale.transform.localScale = Vector3.Lerp(startingScale, scaleTo, (elapsedTime / seconds));
elapsedTime += Time.deltaTime;
yield return new WaitForEndOfFrame();
}
}
objectToScale.transform.localScale = scaleTo;
scaleOver = true;
}
}

Why the reverse is not working ? and how can I add the rotation part?

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;
}

How to get Panel to show when character lands on a specific position on game board after dice roll in Unity

I have a 3D board game in Unity. I would like to move my character without having to press a key, but most importantly I would like to show a dynamic panel in canvas for whatever square the character lands on. So far I have the dice rolling and the character moving (after pressing a key) the correct amount of squares, but I am unable to figure out how to activate the panel based on the square color. Any help would be appreciated.
Here is my CharacterScript:
public class CharacterScript : MonoBehaviour
{
public Path currentPath;
public int squarePosition;
public int squares;
bool isMoving;
public GameObject PinkSquarePanel = GameObject.FindGameObjectWithTag("PinkSquare");
public GameObject CyanSquarePanel = GameObject.FindGameObjectWithTag("CyanSquare");
public GameObject WhiteSquarePanel = GameObject.FindGameObjectWithTag("WhiteSquare");
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.Escape) && !isMoving)
{
squares = DiceNumberTextScript.diceNumber;
}
StartCoroutine(Move());
if (squares == 0)
{
ActivateSquarePanel();
}
}
IEnumerator Move()
{
if (isMoving)
{
yield break;
}
isMoving = true;
while (squares > 0)
{
Vector3 nextPosition = currentPath.squareList[squarePosition + 1].position;
while (MoveToNextSquare(nextPosition))
{
yield return null;
}
yield return new WaitForSeconds(0.1f);
squares--;
squarePosition++;
}
isMoving = false;
}
bool MoveToNextSquare(Vector3 goal)
{
return goal != (transform.position = Vector3.MoveTowards(transform.position, goal, 4f * Time.deltaTime));
}
void ActivateSquarePanel()
{
if (squarePosition.Equals(PinkSquarePanel))
{
PinkSquarePanel.GetComponent<CanvasGroup>().alpha = 1;
}
else if (squarePosition.Equals(CyanSquarePanel))
{
CyanSquarePanel.GetComponent<CanvasGroup>().alpha = 1;
}
else if (squarePosition.Equals(WhiteSquarePanel))
{
WhiteSquarePanel.GetComponent<CanvasGroup>().alpha = 1;
}
}
}
And here is my PathScript:
public class Path : MonoBehaviour
{
Transform[] squareObjects;
public List<Transform> squareList = new List<Transform>();
GameObject[] PinkSquares = GameObject.FindGameObjectsWithTag("PinkSquare");
PinkSquare[] pinkList = new PinkSquare[1];
GameObject[] CyanSquares = GameObject.FindGameObjectsWithTag("CyanSquare");
CyanSquare[] cyanList = new CyanSquare[1];
GameObject[] WhiteSquares = GameObject.FindGameObjectsWithTag("WhiteSquare");
WhiteSquare[] whiteList = new WhiteSquare[1];
private void OnDrawGizmos()
{
Gizmos.color = Color.black;
FillSquares();
for (int i = 0; i < squareList.Count; i++)
{
Vector3 currentPosition = squareList[i].position;
if (i > 0)
{
Vector3 previousPosition = squareList[i - 1].position;
Gizmos.DrawLine(previousPosition, currentPosition);
if(currentPosition.Equals(PinkSquares))
{
pinkList[i] = new PinkSquare();
}
else if (currentPosition.Equals(CyanSquares))
{
cyanList[i] = new CyanSquare();
}
else if (currentPosition.Equals(WhiteSquares))
{
whiteList[i] = new WhiteSquare();
}
}
}
}
void FillSquares()
{
squareList.Clear();
squareObjects = GetComponentsInChildren<Transform>();
foreach (Transform square in squareObjects)
{
if (square != this.transform)
{
squareList.Add(square);
}
}
}
}
I believe your issue is in your comparisons, you are trying to use an Equals to compare your currentPosition which is a Vector3 type to a GameObject[] which is an array of gameObjects. As I mentioned in my comments, this comparison will always fail as an array of gameObjects can not be equal to a vector.
Instead of using these lines, try this line:
if(squareList[i].gameObject.tag.CompareTag("PinkSquare")
The full snippet of if else would look like
if(squareList[i].gameObject.tag.CompareTag("PinkSquare")
{
pinkList[i] = new PinkSquare();
}
else if(squareList[i].gameObject.tag.CompareTag("CyanSquare")
{
cyanList[i] = new CyanSquare();
}
else if(squareList[i].gameObject.tag.CompareTag("WhiteSquare")
{
whiteList[i] = new WhiteSquare();
}
Your CharacterScript is going to need to get the gameObject or Transform from the Path script as it is only keeping track of indexes. Your issue in this script is you are comparing an integer to a GameObject which would never be true. I would also recommend not using OnDrawGizmos() as it is an editor only script and should only be used to render editor debugging tools. The only reference to a Gizmo I see in the function is Gizmos.color = Color.black; which does nothing as you are not rendering a gizmo anywhere. I would move this code to a different function and call it from your CharacterScript. Have the return type be GameObject or Transform of the square you are on, so the CharacterSCript can check which color it lands on. Using an Integer nor Vector3 to compare to a GameObject[] will never work.
I am not sure if there are issues elsewhere in the code, but as this comparison would always fail, none of these statements would get broken into. What this means is your panels would never have the chance to get their alpha set nor get created.

Line Renderer Incorrect display

I have a small dilemma and I can't figure out what the problem is. I have a grid of cells ... I drag the mouse through them and create a connection between them with line renderer. The problem is that Line Renderer does not look ok, I will attach an image below to see what the problem is. Thank you very much.
public Grid grid;
public float connectionDistanceLimit = 3.185f; //0.91*3.5
public float snapDistance = 0.2f;
public LineRenderer connectionRenderer;
private List<Vector3> connectionPositions;
public List<GridCell> cellsOnScreen = new List<GridCell>();
public List<GridCell> connectedCells;
private GridCell lastConnectedCell;
private GridCell currentSelectedCell;
private bool conectionStarted = false;
IEnumerator Start()
{
yield return new WaitForEndOfFrame();
cellsOnScreen = grid.GetGridElements();
foreach (GridCell cell in cellsOnScreen)
cell.CellPointEvent += Cell_CellPointEvent;
}
private void Cell_CellPointEvent(bool enter, GridCell cell)
{
currentSelectedCell = cell;
if (conectionStarted)
return;
connectionPositions = new List<Vector3>();
connectedCells = new List<GridCell>();
connectedCells.Add(cell);
Vector3 positionToAdd = cell.transform.position;
positionToAdd.z = 0f;
connectionPositions.Add(positionToAdd);
cell.SetColor();
conectionStarted = true;
}
void Update()
{
if (Input.GetMouseButton(0))
{
if (connectionPositions != null)
{
if (lastConnectedCell != null)
{
if (lastConnectedCell != currentSelectedCell)
return;
}
List<Vector3> tempPositions = new List<Vector3>(connectionPositions);
Vector3 mPosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector3 lastConnection = tempPositions[tempPositions.Count - 1];
mPosition.z = 0f;
lastConnection.z = 0f;
if (Vector3.Distance(lastConnection, mPosition) > connectionDistanceLimit)
{
Ray r = new Ray(lastConnection, (mPosition - lastConnection));
mPosition = r.GetPoint(connectionDistanceLimit);
}
if (Vector3.Distance(lastConnection, mPosition) < connectionDistanceLimit)
foreach (GridCell cell in cellsOnScreen)
{
if (!connectedCells.Contains(cell))
{
GridCell lastCell = connectedCells[connectedCells.Count - 1];
if ((cell.transform.position.y > lastCell.transform.position.y && cell.transform.position.x != lastCell.transform.position.x) ||
cell.transform.position.y < lastCell.transform.position.y && cell.transform.position.x != lastCell.transform.position.x)
{
}
else
{
if (Vector3.Distance(mPosition, cell.transform.position) <= snapDistance)
{
connectedCells.Add(cell);
connectionPositions.Add(cell.transform.position);
cell.isConnected = true;
cell.SetColor();
break;
}
}
}
}
lastConnection.z = 0f;
mPosition.z = 0f;
tempPositions.Add(mPosition);
connectionRenderer.positionCount = tempPositions.Count;
connectionRenderer.SetPositions(tempPositions.ToArray());
}
}
if (Input.GetMouseButtonUp(0))
{
lastConnectedCell = connectedCells[connectedCells.Count - 1];
}
}
In the inspector I look ok, but in Play Mode it makes this effect that I don't realize why. I thought position Z was a problem, but no, it's 0.
Sorry for bad English.
I'm 7 months late, but it looks like your width is too small. I was just trying to debug this myself and the unity line renderer really doesn't play nice with low widths and closely adjacent positions. you either need to increase the width or find another solution to draw the line you want.
i recommend just computing the vertex positions yourself and rendering a model that way tho you can probably find some stuff on the unity store
That weird behaviour might happen if you have multiple points positions at one place, or width curve's wrong setup.
example

Categories

Resources