Selecting random positions from array without duplicates - c#

In my game, there are many enemies and have this script on it, where I am set their navmesh destination by randomly selecting an element from an array of positions. However, sometimes all enemies go at same position, so I am trying to skip previously generated array indices and find a new index. Any suggestions for this? I don't want to use the goto statement.
Here is code I have done so far:
void move_on_fired_positions()
{
navmesh.Resume ();
again:
new_position = Random.Range (0, firedpoints.Length);
if (prev_num != new_position) {
navmesh.SetDestination (firedpoints [new_position].position);
prev_num = new_position;
} else
{
goto again;
}
}

One thing you can try is keeping a dynamic list of available positions, rather than an array. (Don't worry - they're both serialized and modified the same way in the Unity editor interface.) This way, you can remove positions from the list as they are assigned to enemies, ensuring you never assign duplicates. Here's an idea of how that might look:
public List<Transform> firedpoints;
// Returns available firing position, if any
public Transform GetRandomFiringPosition()
{
if (firedpoints.Count > 0)
{
// Get reference to transform, then remove it from list
int newPositionIndex = Random.Range (0, firedpoints.Length);
Transform returnedPosition = firedpoints[newPositionIndex];
firedpoints.RemoveAt(newPositionIndex);
return returnedPosition;
}
else
{
// Or you can specify some default transform
return null;
}
}
// Makes firing position available for use
public void RegisterFiringPosition(Transform newPosition)
{
firedpoints.Add(newPosition);
}
This code should be in a script on a single object in the scene, which the enemies should have a reference to (or, you can change the code a little and make the class into a singleton, so its methods can be called without a direct reference). Whenever an enemy needs a new position, it can call GetRandomFiringPosition() and store the returned Transform in a variable for future reference.
You haven't determined what conditions make a position available for use again, but when you have, you can call RegisterFiringPosition() to get it back into the list. As a nice side effect, this also makes it possible to assign brand new positions to enemies, for example in response to player-triggered events.
Hope this helps! Let me know if you have any questions.

Related

What is the best way to return all objects within a 3D area?

I'm currently working on an FPS and trying to create a "Gamemode" controller that gives the team with the most team members within a defined 3d area a point for their team. My first attempt looks something like:
public class TDM : Gamemode
{
private Collider pointArea;
private Dictionary<Player, int> teamA;
private Dictionary<Player, int> teamB;
private int methodNumberOfPlayersOnPoint(List<Player> players)
{
int count = 0;
foreach (Player player in players)
{
if (pointArea.bounds.Contains(player.transform.position))
{
count++;
}
}
return count;
}
}
is there a better way to do this?
You can call OverlapSphere which gets all the elements within a sphere. Or a box using OverlapBox or capsule using OverlapCapsule.
void GettAll(Vector3 center, float radius)
{
Collider[] objectsHit = Physics.OverlapSphere(center, radius);
foreach (var obj in objectsHit )
{
// do something for each object
}
}
I don't have much experience in Unity yet so I'll let someone else provide code, but I believe what you're looking for are Trigger Colliders. You basically want to let the engine itself do the heavy lifting for checking for intersection of your player with the zone.
Then in the method that gets called when the player enters the area you can add them to the count that is being used by your scorekeeping component for calculating point accrual. When the player leaves you remove them from the count.
If the position alone (ignoring any shape of objects) is enough precise then this is probably already a good approach.
You could/should cache the pointArea.bounds though before the loop so it is fetched only once.
And you could use Linq to simplify the code like
private int methodNumberOfPlayersOnPoint(List<Player> players)
{
var bounds = pointArea.bounds;
return players.Count(player => bounds.Contains(player.transform.position));
}
Note though that the Bounds in Unity are always world axis aligned! That means if your area is rotated in any way the bounds are not accurate.
In such case you might wan to rather use a PhysicsOverlapBox which allows you to check a specific rotation as well. This requires all your objects having colliders of course
private static int methodNumberOfPlayersOnPoint(BoxCollider area)
{
return Physics.OverlapBox(area.transform.TransformPoint(area.center), area.size / 2f, area.transform.rotation).Length;
}
Note for now this returns ALL objects within this area. you might want to filter for a certain LayerMask and include a filter for the Team (e.g. tag or some ID stored in Player)

Play in sequence of 4 game objects - animators (Not single animator with multiple states) in a queue

I'm new to unity. I have 4 GameObjects say Red, Green, Blue, Yellow - with index mapped 0, 1, 2 and 3 respectively. Also, I kept index sequence in the list List<int> systemTrack.
Now, I'm calling the sequence of animator with respect to list { 0, 1, 2, 3 } (this can be random 0-3).
I can perform one animator loop at a given time.
void Start() {
animator.Play("blue_animation", 0, 0);
}
How can I call in sequence based on list ? Perhaps Update() is right place to call.
So far I found this thread - But it has single Animator object with multiple state called in sequence which is not my case.
Few others discussions also available for Animation component Not for the new Animator component.
In your current class you could use a Coroutine like
List<AnimatorQueueController> animatorQueues;
private void Start()
{
StartCoroutine(RunAllControllersSequencial());
}
IEnumerator RunAllControllersSequencial()
{
foreach (var queue in animatorQueues)
{
// This runs the routine and waits until it finishes
yield return queue.RunAnimationQueueAndWait();
}
}
Now the only thing to do is define/implement how each if these controllers "knows", that its own animation queue has finished.
There are probably many possible ways to go. Straight away: None of them will be beautiful ;) Since the Animator uses a lot of string based stuff you always will end up either having to make sure all animation/trigger names are written correctly, or you will have to reference the animationClip and either hope the state is called the same or you have to expensively find the states via the according clip, you'd have to know how much time to wait for the queue to finish, etc.
This said, also this solution won't be perfect but in my opinion for your setup it would be the most convenient ;)
You could e.g. use an Animation Event and do something like
public class AnimationQueueController : MonoBehaviour
{
[SerializeField] private Animator _animator;
// Here set the first state name for each instance via the Inspector
[SerializeField] private string firstStateName = "first_state";
private void Awake ()
{
if(!_animator) _animator = GetComponent<Animator>();
}
// Simply have another routine you can yield so it is executed and at the same time waits until it is done
public IEnumerator RunAnimationQueueAndWaitUntilEnd()
{
hasReachedEnd = false;
_animator.Play(firstStateName, 0 ,0);
yield return new WaitUntil(() => hasReachedEnd);
}
// This is the method you will invoke via the Animation Event
// See https://docs.unity3d.com/Manual/script-AnimationWindowEvent.html
public void AnimationEnd()
{
hasReachedEnd = true;
}
}
This at least reduces your implementation overhead to
make sure you fire the Animation Event at the end of the last state
make sure you provide the name of the first state in the AnimatorQueueController
in your original script instead of Animator references rather store the AnimatorQueueController in your list

assign class objects instead of the actual script class to unity game objects in unity c#

I want to create a game in Unity C# in which the player will add as much as drones he needs as game objects to the game world. Therefore, I made a drone prefab and later in runtime I ask the number of drones the player wants to have (e.x. n numbers) and instantiate the prefab n times.
The script which is attached to the drone prefab is called drone_script.
Therefore, I am having a drone_script class as a general class. This class has an attribute (let's call it subscriber) which should have different unique values for each drone. So, it is created as a null reference in drone_script general class and later in runtime, I will initialize it.
During runtime, I create n (the same numbers as the drone game objects) objects from this class and assign their subscriber attribute different values.
This is how it looks:
some_class
{
for (int i = 0; i < number_of_Drones; ++i)
{
drones_script[i] = new drone_script();
\\ here I am creating n objects of my general drone_script class
drones_script[i].drone_subscriber = a unique value;
\\ here I am assigning to each of the drone objects' drone_subscriber attribute a different value.
drco = new Vector3((float)0, (float)1, (float)0);
drones[i] = Instantiate(Resources.Load("drone"), drco,
Quaternion.Euler(-90, 0, 90)) as GameObject;
\\I instatiate the drone prefab in unity game world here.
}
}
drone_script
{
public ROSBridgeSubscriber drone_subscriber;
void Start() { \\some code }
void FixedUpdate () { \\some code }
}
My problem is when the drone game objects are created in unity, the general class drone_script is attached to them and because of this their subscriber attribute is null. However, I need the drone_script[i] object to be assigned to them as their script, not the general drone_script itself. Because only then each drone game object has its corresponding subscriber attribute value from before even the start function is being called.
Am I thinking correctly?
If yes, how can I do it?
First of all, I suggest you to assign dronePrefab using editor or in Start method of "some_class" because Resources.Load("drone") might be resource consuming, since you load a drone resource "n" times.
Now your specific problem, you don't need to create new instances of "drone_script" because as you mentioned yourself, each drone that you create has his own "drone_script". So basically what we need to do is:
Instantiate drone and save his reference (as GameObject)
Get that drone's "drone_script" (using GetComponent<>(); )
Save that script reference in our array so we can use it later
Adjust some variables of that script now or later in the game
Something like that should work. In code everything should be something like this (didn't test, might be some miss types)
some_class
{
public GameObject dronePrefab;
for (int i = 0; i < number_of_Drones; ++i)
{
// Step 1.
drco = new Vector3((float)0, (float)1, (float)0);
drones[i] = Instantiate(dronePrefab, drco,
Quaternion.Euler(-90, 0, 90)) as GameObject;
// Steps 2 & 3
drones_script[i] = drones[i].GetComponent<drone_script>();
// Step 4
drones_script[i].drone_subscriber = a unique value;
}
}
EDIT:
Just to point it out: drone_script has to be attached to drone prefab

Enabling Game objects

So I've been trying to access game objects in my scene (which are disabled), to enable them. But I'm getting an error: "Object reference not set to an instance of an object"
private List<Character> characters = new List<Character>();
private List<GameObject> characterGameObjects = new List<GameObject> ();
public void loadSceneCharacters(){
if (characters != null) {
for(int i = 0; i < characters.Count; i++){
characterGameObjects.Add(GameObject.Find(characters[i].CharacterName));
characterGameObjects[i].SetActive(true);
}
}
}
You can't find disabled gameobjects.
A solution is to either reference them in inspector or find them all first when they are enabled, then disable those you don't need.
I think your characters list is empty.
If you don't Instantiate GameObjects you can fill characters list with drag and drop so you can change code like this.
public List<Character> characters = new List<Character>(); ///Thanks to public
//you can see list in Unity Editor
private List<GameObject> characterGameObjects = new List<GameObject> ();
public void loadSceneCharacters(){
if (characters != null) {
for(int i = 0; i < characters.Count; i++){
characterGameObjects.Add(characters[i])); //get and add gameobject
characterGameObjects[i].SetActive(true);
}
}
}
If you have dynamically created GameObjects you can fill the list with GameObject.Find("CharacterName");
However, i dont suggest that find every gameobject with name.Instead of that, During the Instantiate state you can add new gameObject instance to your character list.
Even if the character list would be empty this code would not throw an exception.
The problem is probably that you can not find disabled gameobject using the find methods (at least that was my experience, correct me if i am wrong guys).
What i usually do as a workaround instead of searching is to add the gameobjects via drag and drop. If this is not possible you can either, search for the gameobjects in Awake or Start, add them to your list and disable them. Or do some sort of adding when you instanciate them... Basicly you have to somehow get the reference to the gameobjects before you disable them.
Hope this helps.
Something that can help you is to create an empty object, then put all your characters inside the empty object.. then by code do something like:
foreach(Character c in MyEmptyObject.GetComponentsInChildren(Character, true))//the true in this
//line indicates that it should search for inactive objects
{
c.gameObject.SetActive(true);
}
this assuming that your characters have an script called "Character"

How to have a Game Object remove itself from a list? (Unity3d)

OK so basically I have a list that keeps track of all the enemies in the scene. I am using the list primarily so I can have each enemy check other enemies positions so they don't end up on the same spot. When ever an enemy is created it will add itself to a list, but I need the enemy to remove itself once it's killed. I tried what I have in the code below but it throws me an error (ArgumentOutOfRangeException: Argument is out of range).
void Start ()
{
Manager.EnemyList.Add(this.gameObject);
ListSpot = Manager.EnemyList.Count;
}
//Kills the enemy
public void Kill()
{
Manager.EnemyList.RemoveAt(ListSpot);
Destroy(this.gameObject);
}
Use a list from the System.Collections.Generic namespace, then you can just call Add and Remove with the instance of the object. It will use the hash functions of the instance to do its thing.
List<GameObject> enemies = new List<GameObject>();
enemies.Add(enemy1);
enemies.Remove(enemy1);
Edit: your solution throws the exception because the index starts by 0 and count will always begin to count at 1. if you add the first element its position is 0, but the count is 1.

Categories

Resources