0

Goal

I am designing a boid system using Unity. I deal with the awareness radius by adding a boid to the "Swarm" list when it enters a collider. In order to find the force for each boid, I need to cycle through the swarm list, access the "Boid" class, and retrieve velocity and position.

Problem

The Boid classes from each swarm entity are added to a new list, and passed to the physics controller. However, a NullReferenceException is thrown at line 96, and I don't understand why that variable would be null. As far as I know, accessing a populated Enumerable<Boid> using foreach should have variables within.

NullReferenceException: Object reference not set to an instance of an object Boid.Alignment (System.Collections.Generic.IEnumerable`1[T] boids) (at Assets/Scripts/Boid.cs:96) Boid.Update () (at Assets/Scripts/Boid.cs:42)

After testing, it seems it is thrown when accessing any part of the new list of Boids.

Why does my new list contain no data? Is there a better way to handle a 2D implementation of boids in a 3D space? Is there a resource I can use to better understand Linq?

P.S. I am very new to using Linq systems, and most of this code is taken from this video and this Unity project script

Code

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public class Boid : MonoBehaviour
{
// Global Variables
public Boid_Settings settings;

// Local Variables
public Rigidbody body;
public Vector2 acceleration;
public Vector2 velocity
{
    get
    { return new Vector2(body.velocity.x, body.velocity.z); }
    set
    { body.velocity = new Vector3(value.x, body.velocity.y, value.y); }
}
public Vector2 position
{
    get
    { return new Vector2(transform.position.x, transform.position.z); }
}
public List<GameObject> swarm = new List<GameObject>();
public List<GameObject> targets = new List<GameObject>();


// Functions
private void Start()
{
    float angle = Random.Range(0, 2 * Mathf.PI);
    transform.rotation = Quaternion.Euler(new Vector3(0, 0, angle));
    velocity = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle));
}

private void Update()
{
    IEnumerable<Boid> boids = swarm.Select(o => o.GetComponent<Boid>()).ToList(); //Line 40

    Vector2 alignment = Alignment(boids); //LINE 42
    Vector2 separation = Separation(boids);
    Vector2 cohesion = Cohesion(boids);

    acceleration = settings.alignmentWeight * alignment + settings.cohesionWeight * cohesion + settings.seperationWeight * separation;

    UpdatePhysics();
}

// Entity Awareness Assignment
private void OnTriggerEnter(Collider collider)
{
    if (collider.CompareTag("Zombie"))
    { swarm.Add(collider.gameObject); }
    else if (collider.CompareTag("Player") || collider.CompareTag("Lure"))
    { targets.Add(collider.gameObject); }
}

private void OnTriggerExit(Collider collider)
{
    if (collider.CompareTag("Zombie"))
    { swarm.Remove(collider.gameObject); }
    else if (collider.CompareTag("Player") || collider.CompareTag("Lure"))
    {
        targets.Remove(collider.gameObject);
        StartCoroutine(LingerTarget(collider.gameObject));
    }
}

IEnumerator LingerTarget(GameObject target)
{
    targets.Add(target);
    yield return new WaitForSeconds(settings.lingerTime);
    targets.Remove(target);
}


// Core Boid Logic
public void UpdatePhysics()
{
    // Apply the acceleration, and then limit the speed to the maximum.
    Vector2 UncappedVelocity = velocity + acceleration;
    velocity = ApplyLimit(UncappedVelocity, settings.maxSpeed);

    float angle = Mathf.Atan2(velocity.y, velocity.x) * Mathf.Rad2Deg;
    body.transform.rotation = Quaternion.Euler(new Vector3(0, 0, angle));
}

private Vector2 Alignment(IEnumerable<Boid> boids)
{
    Vector2 velocity = Vector2.zero;
    if (!boids.Any()) return velocity;

    foreach (Boid boid in boids)
    { velocity += boid.velocity; } //LINE 96
    velocity /= boids.Count();

    Vector2 steer = Steer(velocity.normalized * settings.maxSpeed);
    return steer;
}

private Vector2 Cohesion(IEnumerable<Boid> boids)
{
    if (!boids.Any()) return Vector2.zero;

    Vector2 sumPositions = Vector2.zero;
    foreach (Boid boid in boids)
    { sumPositions += boid.position; }
    Vector2 average = sumPositions / boids.Count();
    Vector2 direction = average - position;

    Vector2 steer = Steer(direction.normalized * settings.maxSpeed);
    return steer;
}

private Vector2 Separation(IEnumerable<Boid> boids)
{
    Vector2 direction = Vector2.zero;
    boids = boids.Where(o => Vector3.Distance(o.transform.position, position) <= settings.avoidanceRadius);
    if (!boids.Any()) return direction;

    foreach (Boid boid in boids)
    {
        Vector2 difference = position - boid.position;
        direction += difference.normalized / difference.magnitude;
    }
    direction /= boids.Count();

    Vector2 steer = Steer(direction.normalized * settings.maxSpeed);
    return steer;
}

private Vector2 Steer(Vector2 desired)
{
    Vector2 steer = desired - velocity;
    steer = ApplyLimit(steer, settings.maxSteerForce);

    return steer;
}

// Calculation Helpers
private Vector2 ApplyLimit(Vector2 baseVector, float limit)
{
    if (baseVector.sqrMagnitude > limit * limit)
    { baseVector = baseVector.normalized * limit; }
    return baseVector;
}
}

The Boid_Settings Module:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

[CreateAssetMenu]
public class Boid_Settings : ScriptableObject
{
// Boid
public float maxSpeed = 5;
public float avoidanceRadius = 1;
public float maxSteerForce = 3;

public float lingerTime = 2.5f;
public float alignmentWeight = 1;
public float cohesionWeight = 1;
public float seperationWeight = 1;
public float targetWeight = 1;

// Spawner
public float awarenessRadius = 2.5f;
}

Context Pictures

Proof the boid classes have data to be read Proof the boid classes have data to be read Unity Enviroment & Boid.cs Attachment Unity Enviroment & Boid.cs Attachment Unity Boid Boid.body Component Unity Boid Boid.body Component Each Boid finds two swarm mates when the game is run Boid Population Proof

Hamid Yusifli
  • 9,688
  • 2
  • 24
  • 48
Sove67
  • 7
  • 4
  • check Boid.cs line number 96. – Beingnin Apr 29 '20 at 17:38
  • That is where the error occurs, but I don't understand why. – Sove67 Apr 29 '20 at 18:00
  • @Sove67 It looks like from the exception stack trace the error appeasrs in the `Update()` method when you pass variable `boids` to `Alignment(boids);`. Set a break point on this line and check if `boids` is `null` from the previous line of `IEnumerable boids = swarm.Select(o => o.GetComponent()).ToList(); ` – Ryan Wilson Apr 29 '20 at 18:55
  • @RyanWilson I added *if (boids == null) { Debug.Log("No Data Attached"); }* before the Alignment(boids) but it was not called. – Sove67 Apr 29 '20 at 20:18
  • 2
    Does this answer your question? [What is a NullReferenceException, and how do I fix it?](https://stackoverflow.com/questions/4660142/what-is-a-nullreferenceexception-and-how-do-i-fix-it) Also did you check `body` is not null? – CSharpie Apr 29 '20 at 20:34
  • Unfortunately, no. I know what a NullReferenceException is, but don't know why accessing part of this IEnumerable returns null rather than a Vector2 as I would expect. – Sove67 Apr 29 '20 at 20:38
  • Please could you add some comments to your code to highlight lines 96 and 42 in the appropriate files. – Ian Kemp Apr 29 '20 at 20:55
  • Further, I don't understand how `Result of printing self's velocity` has anything to do with the `Alignment()` method. It feels like you're conflating multiple issues here, please consider editing the question to remove the irrelevant bits. – Ian Kemp Apr 29 '20 at 20:59
  • @IanKemp Lines have been commented, Thanks. The velocity is a check to ensure the boids do have data to be read, and the problem is in the IEnumerable not the class itself. Is that obvious enough for me to remove the picture? – Sove67 Apr 29 '20 at 21:03

1 Answers1

-1

Turns out I was assigning null data, because the GetComponent function at line 40 found the "Body" of the boids, which did not have a Boid script attached. When searching with GetComponentInChildren, the array is populated and values are given.

The Old Line:

IEnumerable<Boid> boids = swarm.Select(o => o.GetComponent<Boid>()).ToList();

The New Line:

IEnumerable<Boid> boids = swarm.Select(o => o.GetComponentInChildren<Boid>()).ToList();

Sove67
  • 7
  • 4