This sounds eerily like a problem I had when I was designing some of my AI stuff for use through the editor by my artists/designers.
In doing so they wanted to be able to have AI units target positions, gameobjects, components, or any which weary ‘Transform’ related object that might result from some sequence of commands they wired together using our T&I system (sort of like UnityEvent).
So I created them the ‘ComplexTarget’:
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
using com.spacepuppy;
using com.spacepuppy.AI.Sensors;
using com.spacepuppy.Geom;
using com.spacepuppy.Utils;
namespace com.spacepuppy.AI
{
public enum ComplexTargetType
{
Null = 0,
Aspect = 1,
Transform = 2,
Vector2 = 3,
Vector3 = 4
}
public struct ComplexTarget
{
public static IPlanarSurface DefualtSurface;
#region Fields
public readonly ComplexTargetType TargetType;
private readonly object _target;
private readonly Vector3 _vector;
private readonly IPlanarSurface _surface;
#endregion
#region CONSTRUCTOR
public ComplexTarget(IAspect aspect)
{
if (aspect != null)
{
TargetType = ComplexTargetType.Aspect;
_target = aspect;
}
else
{
TargetType = ComplexTargetType.Null;
_target = null;
}
_vector = Vector3.zero;
_surface = DefualtSurface;
}
public ComplexTarget(Transform target)
{
if (target != null)
{
TargetType = ComplexTargetType.Transform;
_target = target;
}
else
{
TargetType = ComplexTargetType.Null;
_target = null;
}
_vector = Vector3.zero;
_surface = DefualtSurface;
}
public ComplexTarget(Vector2 location)
{
TargetType = ComplexTargetType.Vector2;
_target = null;
_vector = (Vector3)location;
_surface = DefualtSurface;
}
public ComplexTarget(Vector3 location)
{
TargetType = ComplexTargetType.Vector3;
_target = null;
_vector = location;
_surface = DefualtSurface;
}
public ComplexTarget(IAspect aspect, IPlanarSurface surface)
{
if (aspect != null)
{
TargetType = ComplexTargetType.Aspect;
_target = aspect;
}
else
{
TargetType = ComplexTargetType.Null;
_target = null;
}
_vector = Vector3.zero;
_surface = surface;
}
public ComplexTarget(Transform target, IPlanarSurface surface)
{
if (target != null)
{
TargetType = ComplexTargetType.Transform;
_target = target;
}
else
{
TargetType = ComplexTargetType.Null;
_target = null;
}
_vector = Vector3.zero;
_surface = surface;
}
public ComplexTarget(Vector2 location, IPlanarSurface surface)
{
TargetType = ComplexTargetType.Vector2;
_target = null;
_vector = (Vector3)location;
_surface = surface;
}
public ComplexTarget(Vector3 location, IPlanarSurface surface)
{
TargetType = ComplexTargetType.Vector2;
_target = null;
_vector = location;
_surface = surface;
}
#endregion
#region Properties
public Vector2 Position2D
{
get
{
switch (this.TargetType)
{
case ComplexTargetType.Null:
return Vector2.zero;
case ComplexTargetType.Aspect:
var a = _target as IAspect;
if (a.IsNullOrDestroyed()) return Vector2.zero;
else if (_surface == null) return ConvertUtil.ToVector2(a.transform.position);
else return _surface.ProjectPosition2D(a.transform.position);
case ComplexTargetType.Transform:
var t = _target as Transform;
if (t.IsNullOrDestroyed()) return Vector2.zero;
else if (_surface == null) return ConvertUtil.ToVector2(t.position);
else return _surface.ProjectPosition2D(t.position);
case ComplexTargetType.Vector2:
return ConvertUtil.ToVector2(_vector);
case ComplexTargetType.Vector3:
if (_surface == null) return ConvertUtil.ToVector2(_vector);
else return _surface.ProjectPosition2D(_vector);
default:
return Vector2.zero;
}
}
}
public Vector3 Position
{
get
{
switch (this.TargetType)
{
case ComplexTargetType.Null:
return Vector3.zero;
case ComplexTargetType.Aspect:
var a = _target as IAspect;
if (a == null) return Vector3.zero;
else return a.transform.position;
case ComplexTargetType.Transform:
var t = _target as Transform;
if (t == null) return Vector3.zero;
else return t.position;
case ComplexTargetType.Vector2:
if (_surface == null) return _vector.SetZ(0f);
else return _surface.ProjectPosition3D(ConvertUtil.ToVector2(_vector));
case ComplexTargetType.Vector3:
return _vector;
default:
return Vector3.zero;
}
}
}
public IAspect TargetAspect { get { return _target as IAspect; } }
public Transform Transform
{
get
{
switch (TargetType)
{
case ComplexTargetType.Aspect:
var a = _target as IAspect;
if (a == null) return null;
else return a.transform;
case ComplexTargetType.Transform:
return _target as Transform;
default:
return null;
}
}
}
public bool IsNull
{
get
{
switch (this.TargetType)
{
case ComplexTargetType.Null:
return true;
case ComplexTargetType.Aspect:
case ComplexTargetType.Transform:
return _target.IsNullOrDestroyed();
case ComplexTargetType.Vector2:
case ComplexTargetType.Vector3:
default:
return false;
}
}
}
#endregion
#region Static Methods
public static ComplexTarget FromObject(object targ)
{
if (targ == null) return new ComplexTarget();
if (targ is Component)
{
if (targ is IAspect)
return new ComplexTarget(targ as IAspect);
else if (targ is Transform)
return new ComplexTarget(targ as Transform);
else
return new ComplexTarget((targ as Component).transform);
}
else if (targ is GameObject)
return new ComplexTarget((targ as GameObject).transform);
else if (targ is Vector2)
return new ComplexTarget((Vector2)targ);
else if (targ is Vector3)
return new ComplexTarget((Vector3)targ);
else
return new ComplexTarget();
}
public static ComplexTarget Null { get { return new ComplexTarget(); } }
//public static implicit operator ComplexTarget(IAspect o)
//{
// return new ComplexTarget(o);
//}
public static implicit operator ComplexTarget(Transform o)
{
return new ComplexTarget(o);
}
public static implicit operator ComplexTarget(Vector2 v)
{
return new ComplexTarget(v);
}
public static implicit operator ComplexTarget(Vector3 v)
{
return new ComplexTarget(v);
}
public static implicit operator ComplexTarget(GameObject go)
{
if (go == null) return new ComplexTarget();
else return new ComplexTarget(go.transform);
}
public static implicit operator ComplexTarget(Component c)
{
if (c == null) return new ComplexTarget();
if (c is IAspect)
return new ComplexTarget(c as IAspect);
else if (c is Transform)
return new ComplexTarget(c as Transform);
else
return new ComplexTarget(c.transform);
}
#endregion
}
}
All with implicit conversion built in.
It’s a dirty dirty trick… but it gets the job done within the system they desired.
And you can see my accessing it here in this slapped together movement style we did in a game jam:
using UnityEngine;
using System.Collections.Generic;
using com.spacepuppy;
using com.spacepuppy.AI;
using com.spacepuppy.Cameras;
using com.spacepuppy.Movement;
using com.spacepuppy.UserInput;
using com.spacepuppy.Utils;
namespace com.mansion.Entities.Actors.Zombie
{
public class ZombieWalkMovementStyle : SPComponent, IMovementStyle
{
#region Fields
[SerializeField()]
private float _speed = 1.0f;
[SerializeField()]
[Tooltip("Turn speed in degrees per second")]
private float _turnSpeed = 90f;
[SerializeField()]
private float _speedDuringTurn = 0.5f;
[SerializeField()]
[DefaultFromSelf(UseEntity = true)]
private ZombieAnimator _animator;
[System.NonSerialized()]
private IEntity _entity;
[System.NonSerialized()]
private MovementMotor _motor;
[System.NonSerialized()]
private ComplexTarget _target;
#endregion
#region CONSTRUCTOR
protected override void Awake()
{
base.Awake();
_entity = SPEntity.Pool.GetFromSource<IEntity>(this);
_motor = this.GetComponent<MovementMotor>();
_target = ComplexTarget.Null;
}
#endregion
#region Properties
public float Speed
{
get { return _speed; }
set { _speed = value; }
}
public ComplexTarget Target
{
get { return _target; }
set { _target = value; }
}
#endregion
#region Methods
#endregion
#region IMovementStyle Interface
void IMovementStyle.OnActivate(IMovementStyle lastStyle, bool stateIsStacking)
{
}
void IMovementStyle.OnDeactivate(IMovementStyle nextStyle, bool stateIsStacking)
{
}
void IMovementStyle.OnPurgedFromStack()
{
}
void IMovementStyle.UpdateMovement()
{
if (Game.Paused) return;
if(_entity.Stalled || _target.IsNull || _entity.HealthMeter.Health <= 0f)
{
//idle
if(_entity.HealthMeter.Health > 0f) _motor.Controller.Move(Vector3.up * Game.GRAV * Time.deltaTime);
_animator.DefaultMovementAnimations.PlayIdle();
return;
}
Vector3 dir = (_target.Position - _motor.Controller.transform.position).SetY(0f);
float dist = dir.magnitude;
//TODO - determine if distance is close, and if said close thing is player, to do attack
if (dist > 0.3f)
{
dir.Normalize();
Vector3 forw = _motor.Controller.transform.forward;
float a = VectorUtil.AngleBetween(forw, dir);
forw = Vector3.RotateTowards(forw, dir, _turnSpeed * Mathf.Deg2Rad * Time.deltaTime, 0.0f);
var speed = (a < _turnSpeed) ? _speed : _speedDuringTurn;
Vector3 mv = forw * speed + Vector3.up * Game.GRAV;
_motor.Controller.Move(mv * Time.deltaTime);
_motor.Controller.transform.rotation = Quaternion.LookRotation(forw, Vector3.up);
_animator.DefaultMovementAnimations.PlayWalk(speed);
}
else
{
//TODO - change this out for attack
var mv = Vector3.up * Game.GRAV;
_motor.Controller.Move(mv * Time.deltaTime);
_animator.DefaultMovementAnimations.PlayIdle();
}
}
void IMovementStyle.OnUpdateMovementComplete()
{
}
#endregion
}
}
With this, someone in the editor using our T&I system (like UnityEvent) could tell the zombie to target just about anything no matter how they have it referenced and not have to manhandle it into the correct type. Rather the implicit conversion does it for us.