访问自定义 class 的 IEnumerable<T> 时出现 NullReferenceException
NullReferenceException when accessing an IEnumerable<T> of a custom class
目标
我正在使用 Unity 设计一个 boid 系统。我通过在 "Swarm" 列表进入对撞机时添加一个 boid 来处理感知半径。为了找到每个 boid 的力,我需要循环遍历 swarm 列表,访问 "Boid" class,并检索速度和位置。
问题
来自每个群体实体的 Boid classes 被添加到一个新列表中,并传递给物理控制器。但是,在第 96 行抛出 NullReferenceException,我不明白为什么该变量会为 null。据我所知,使用 foreach 访问填充的 Enumerable<Boid>
应该有变量。
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)
经测试,似乎在访问新的Boids列表的任何部分时都会抛出。
为什么我的新列表中没有数据?有没有更好的方法来处理 3D space 中 boid 的 2D 实现?有没有我可以用来更好地理解 Linq 的资源?
P.S。我对使用 Linq 系统很陌生,大部分代码来自 this video and this Unity project script
代码
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;
}
}
Boid_Settings模块:
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;
}
上下文图片
证明boid classes有数据要读取
Unity 环境和 Boid.cs 附件
Unity Boid Boid.body 组件
当游戏 运行 时,每个 Boid 找到两个 swarm 伙伴
原来我分配的是空数据,因为第 40 行的 GetComponent 函数找到了没有附加 Boid 脚本的 boid 的 "Body"。使用 GetComponentInChildren 进行搜索时,会填充数组并给出值。
旧线:
IEnumerable<Boid> boids = swarm.Select(o => o.GetComponent<Boid>()).ToList();
新线:
IEnumerable<Boid> boids = swarm.Select(o => o.GetComponentInChildren<Boid>()).ToList();
目标
我正在使用 Unity 设计一个 boid 系统。我通过在 "Swarm" 列表进入对撞机时添加一个 boid 来处理感知半径。为了找到每个 boid 的力,我需要循环遍历 swarm 列表,访问 "Boid" class,并检索速度和位置。
问题
来自每个群体实体的 Boid classes 被添加到一个新列表中,并传递给物理控制器。但是,在第 96 行抛出 NullReferenceException,我不明白为什么该变量会为 null。据我所知,使用 foreach 访问填充的 Enumerable<Boid>
应该有变量。
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)
经测试,似乎在访问新的Boids列表的任何部分时都会抛出。
为什么我的新列表中没有数据?有没有更好的方法来处理 3D space 中 boid 的 2D 实现?有没有我可以用来更好地理解 Linq 的资源?
P.S。我对使用 Linq 系统很陌生,大部分代码来自 this video and this Unity project script
代码
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;
}
}
Boid_Settings模块:
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;
}
上下文图片
证明boid classes有数据要读取
原来我分配的是空数据,因为第 40 行的 GetComponent 函数找到了没有附加 Boid 脚本的 boid 的 "Body"。使用 GetComponentInChildren 进行搜索时,会填充数组并给出值。
旧线:
IEnumerable<Boid> boids = swarm.Select(o => o.GetComponent<Boid>()).ToList();
新线:
IEnumerable<Boid> boids = swarm.Select(o => o.GetComponentInChildren<Boid>()).ToList();