- Home /
All Units Go to Same Waypoint
I create a certain number of Objects (primitive spheres in my test), and I place them at random locations on a terrain. They are instantiated objects in a Custom Class. I have it coded so that if I click the primitive sphere, it turns from Blue to Cyan, and then I click on a location on my terrain it adds that location to a waypoint list (vector3 list that is part of the custom class). The sphere will then move from point to point on the waypoint list. And everything works like magic -- as long as I have only ONE sphere on the terrain. If I have more than one, they all go to the same waypoints. I'm having a hard time figuring out where I'm going wrong. Hmmmm. I'd greatly appreciate it if someone might take a peek and point out to me my error. Thanks.
Here's my Custom Class:
public class UnitSphere
{
public string uName{ get; set; }
public float uStrength{ get; set; }
public List<Vector3> waypoint { get; set; }
public UnitSphere()
{
waypoint = new List<Vector3> ();
}
public UnitSphere(string name, float strength)
{
uName = name;
uStrength = strength;
}
}
Heres' where I intiate the prefabs -- I don't think the problem is here.
void Start ()
{
Random.InitState ((int)System.DateTime.Now.Ticks); // Makes things more random
for (int i = 0; i < totalUnits; i++)
{
GameObject cloneGameUnit = Instantiate (GameUnit);
cloneGameUnit.transform.position = new Vector3 (Random.Range (5f, 45f), .5f, Random.Range (5f, 45f));
cloneGameUnit.gameObject.GetComponent<Renderer> ().material.color = Color.blue;
string tempString = i.ToString ();
string tempName = tempString + tempString + tempString + tempString + tempString;
float tempStrength = Random.Range (80f, 95f);
unit.Add(new UnitSphere(tempName,tempStrength));
}
}
Here's movement, works if there is only one sphere -- I think the problem is here ???
GameObject[] fooThree;
fooThree = GameObject.FindGameObjectsWithTag ("Friendly");
tempIndex = 0;
foreach (GameObject twit in fooThree)
{
if (unit[tempIndex].waypoint==null){unit [tempIndex].waypoint = new List<Vector3> ();}
if (unit[tempIndex].waypoint.Count > 0)
{
agent.destination = unit[tempIndex].waypoint[0];
agent.speed = 3.5f;
}
else
{
agent.speed = 0;
}
if (unit[tempIndex].waypoint.Count > 0 && agent.remainingDistance < 0.5f) {unit [tempIndex].waypoint.RemoveAt(0);}
tempIndex++;
}
And here is adding the waypoints to the list -- Right click for single waypoint --- shift/right for multi-waypoints.
if (Input.GetKey (KeyCode.LeftShift))
{
shiftIsPressed = true;
}
else
{
shiftIsPressed = false;
}
if (Input.GetMouseButtonDown (1) && isSelected == true && shiftIsPressed == true) // left shift, right mouse, object == cyan --- for adding multiple waypoints
{
Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast (ray, out hit))
{
Collider col = hit.collider;
if (col.gameObject.tag == "Terrain")
{
Debug.Log (hit.point);
GameObject[] fooOne;
fooOne = GameObject.FindGameObjectsWithTag ("Friendly");
tempIndex = 0;
foreach (GameObject twit in fooOne)
{
if (twit.GetComponent<Renderer>().material.color==Color.cyan)
{
indexNumber = tempIndex;
unit[indexNumber].waypoint.Add(hit.point);
}
tempIndex++;
}
}
}
}
else if (Input.GetMouseButtonDown (1) && isSelected == true && shiftIsPressed == false) // right click plus unit == cyan, --- clears waypoints and adds one new waypoint
{
Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast (ray, out hit))
{
Collider col = hit.collider;
if (col.gameObject.tag == "Terrain")
{
Debug.Log (hit.point);
GameObject[] fooOne;
fooOne = GameObject.FindGameObjectsWithTag ("Friendly");
tempIndex = 0;
foreach (GameObject twit in fooOne)
{
if (twit.GetComponent<Renderer>().material.color==Color.cyan)
{
indexNumber = tempIndex;
unit[indexNumber].waypoint.Clear();
unit[indexNumber].waypoint.Add(hit.point);
ResetAll ();
}
tempIndex++;
}
}
}
}
if (Input.GetKeyUp(KeyCode.LeftShift))
{
ResetAll ();
}
void ResetAll()
{
GameObject[] fooTwo;
fooTwo = GameObject.FindGameObjectsWithTag ("Friendly");
tempIndex = 0;
foreach (GameObject twit in fooTwo)
{
twit.GetComponent<Renderer> ().material.color = Color.blue;
isSelected = false;
}
}
Well one problem might be that you rely soley on the order of the order returned by FindGameobjectsWithTag to map it to the classes in your list, would propose to use a monobehaviour there and also keep the gameobjects in a list ins$$anonymous$$d of searching them everytime btw.
Also what might be an issue is that you are using material color as an selection indicator, in the past I had mixed results with relying on that (instanced materials) so adding a „isSelected“ property to your unitsphere class (and hopefully monobehaviour soon).
Did you try to Debug how often the condition for adding waypoints in your foreavh loop is executed?
BRP, thanks for your response. I'm a solo programmer and I'm totally self taught and I always appreciate other suggestions. On keeping the gameobjects in a list, this isn't a game that relies on high fps so frequent searches shouldn't be a problem, regardless, I'll consider how I might improve things with your thought. In regard to the color check, that was the only way I could get things to work at the time...I already partly use an "IsSelected" and I'll see if I can more fully utilize that. As for the Debug, uhhh, no I haven't done that and that is an excellent suggestion that I will immediately put to good use. Again, thanks for your response.
Went through the debugging, looks like it's going through properly every place I checked. I'm still looking at it though. I'm going to restructure things a teenie bit and see if I can narrow down where the shortco$$anonymous$$g is located.
Thank you. I will look at your link. I appreciate your help.
The code you posted seems to be correct to me. I think the problem is in code that you didn't post, the part where you mark a unit selected: I think you might turn all of them the selected color. Could you post that code as well, please?
Note that even though this code might work, you would be a lot better off if you made your UnitSphere
class into an actual $$anonymous$$onoBehaviour
, and handled selecting, setting waypoints, and going to waypoints in it. Right now you are controlling everything from "outside" of UnitSphere
, violating what is called in object oriented languages like C# "encapsulation" or "separation of responsibilities". What I'm saying is that you could move most of the blocks that are after foreach (GameObject twit ...)
into functions in SphereUnit
, those loops.
Tell me if you are interested in this refactor, and I'll help you do the transition.
Yes, I would very much be interested in that. I'm an old guy, completely self taught. I always appreciate when someone takes the time to $$anonymous$$ch me something.
Note: I'm not 100% sure that I understand the first sentence in your second comment. How would I make this into an actual "$$anonymous$$onobehaviour" and control it from within? Regardless, I always welcome any sort of suggestions as to how I can make my code cleaner and more effective. Thanks.
I meant to make UnitSphere
derive from $$anonymous$$onoBehaviour
, so that it has its own Update()
call and other variables. But I'm writing a more exhaustive answer, I think it will make more sense there.
Answer by Harinezumi · May 15, 2018 at 03:21 PM
@Topthink, as a response to your interest in learning a different approach, here is what I would do to control the agents.
If I understood it correctly, the functionality you want is to be able to select each unit, assign it one or more waypoints, and let each unit follow through its list until the. As each unit is independent, they can be modelled with independent instances of a class that gets updated - a perfect candidate for a script (that is, a class derived of MonoBehaviour
)!
When I think of classes, I think about what data and behaviour belongs into it, and what kind of "orders" should be possible to give them. For example, UnitSphere
should know what its waypoints are, should update where it should move and move itself, and it should be possible to add a new waypoint or clear all waypoints and set a new one, as well as change its color when it is selected. From this, you can create the following class:
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class UnitSphere : MonoBehaviour {
private List<Vector3> waypoints = new List<Vector3>();
private Renderer ownRenderer;
private NavMeshAgent agent;
private void Awake () {
ownRenderer = GetComponent<Renderer>();
agent = GetComponent<NavMeshAgent>();
SetSelected(false);
}
private void Update () {
// if arrived at first waypoint, remove it
if (waypoints.Count > 0 && !agent.isStopped && agent.remainingDistance < 0.5f) {
waypoints.RemoveAt(0);
}
if (waypoints.Count > 0) {
// if the first waypoint changed...
if (waypoints[0] != agent.destination) {
// set as new destination
agent.destination = waypoints[0];
// make sure to start agent
agent.isStopped = false;
}
}
// if there is no waypoint, stop
else { agent.isStopped = true; }
}
public void AddWaypoint (Vector3 newPoint) {
waypoints.Add(newPoint);
}
public void SetWaypoint (Vector3 newPoint) {
waypoints.Clear();
waypoints.Add(newPoint);
}
public void SetSelected (bool value) {
// this is the same as "if (value) { ...color = Color.cyan; } else { ...color = Color.blue; }"
ownRenderer.material.color = value ? Color.cyan : Color.blue;
}
}
The rest of the functionality simply doesn't belong into UnitSphere
, because it is user input. What is the rest of the functionality? Creating the units, being able to select and unselect them, add a waypoint, and set a new waypoint. Here it goes:
using UnityEngine;
public class UnitInput : MonoBehaviour {
[SerializeField] private int numUnits = 10;
[SerializeField] UnitSphere unitSpherePrefab = null;
private UnitSphere selectedUnit;
private void Start () {
for (int i = 0; i < numUnits; ++i) {
// you can directly assign a position when instantiating
Vector3 position = new Vector3(Random.Range(5f, 45f), .5f, Random.Range(5f, 45f));
// you can directly create a game object and get a reference to a script on it
UnitSphere unitSphereInstance = Instantiate(unitSpherePrefab, position, Quaternion.identity);
// every script has a game object, and every game object has a name
unitSphereInstance.name = i.ToString();
// unitSphereInstance.SetSelected(false); // we could call this here, but this initialization doesn't really belong here
}
}
private void Update () {
// on right click...
if (Input.GetMouseButtonDown(1)) {
RaycastHit hit;
// this is an easier way of creating a ray from mouse click
if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hit)) {
// instead of checking tag, get the script directly
UnitSphere unitClicked = hit.collider.GetComponent<UnitSphere>();
if (unitClicked != null) {
// change selection, wrapped into a function
SelectUnit(unitClicked);
}
// clicked on something else; you might want to check if it is terrain
else {
// if there is a unit selected...
if (selectedUnit != null) {
if (Input.GetKey(KeyCode.LeftShift)) {
selectedUnit.AddWaypoint(hit.point);
}
else {
selectedUnit.SetWaypoint(hit.point);
// unselect as you do in your example;
// however, it would be better to have selection/unselection on a different button!
SelectUnit(null);
}
}
}
}
}
}
private void SelectUnit (UnitSphere newUnitSphere) {
if (selectedUnit != null) { selectedUnit.SetSelected(false); }
selectedUnit = newUnitSphere;
if (selectedUnit != null) { selectedUnit.SetSelected(true); }
}
}
You might also notice that I wrapped things that belong together into separate functions, for example in case of SelectUnit()
. Note how it handles all the possibilities, first unselecting the currently selected unit, no matter what, and then selecting the new one, but only if it wasn't null.
Any question you might have, feel free to ask!
I will look at this immediately. I really appreciate your help.
When I run the program I get a million little spheres and a crash...a couple questions.
I'm a bit confused on one part having to do with the Serializable Field...I'm not that familiar with them. Do I need to add "[System.Serializable]" over where is says "public class UnitInput..." and/or "public class UnitSphere..."
Also, I created the two scripts "UnitSphere" and "UnitInput" and now do I attach both of those to my primitive sphere in the Hierarchy and then drag the sphere into the Project Window and then delete the sphere from the hierarchy? And what do I need to drag to the Inspector?
Thanks.
I know how (and what order) to do this in my original program but I'm a teenie bit confused as to the exact procedure and sequence to use in this situation.
...of course, it could be that I forgot to add the Nav$$anonymous$$esh to the terrain and the Nav$$anonymous$$eshAgent to the sphere. LoL. I'll do that and run the program again. $$anonymous$$aybe it would work if I actually did all the things I need to do.
...I'm working on this off and on today. I really do appreciate the time you are taking to help me. Thanks again.
I'm going down your suggested code line by line and where I don't fully understand it I'm doing a bit of research to help me get there. I still don't have everything working properly but I'm learning a tremendous amount from your examples.
Your answer
Follow this Question
Related Questions
Tetris Unity game structure C# 3 Answers
Classes, MonoBehaviours C# 2 Answers
Distribute terrain in zones 3 Answers
Multiple Cars not working 1 Answer
Issue Instantiating prefab in C# 0 Answers