Typecast a Generic T to Vector3

Hi there!

I started using Generics for the first time, and I got an issue.

here an example of my function :

void test( T parTarget)
{
if (parTarget as Vector3)
Vector3 TempTarget = parTarget as Vector3;
}

As you probably know, I cant use as Vector3 as Vector3 is a structure. I searched around the web for an answer, but didn’t find anything for my specific case.

If you have an idea, go for it! :slight_smile:

Cast it:
if (parTarget.type == typeof(Vector3))
Vector3 TempTarget = (Vector3)parTarget;

1 Like

Generally speaking if you’re doing these kinds of type checks to a generic argument then a generic argument is not an appropriate solution to your problem. It looks like you want multiple overloads of the test method that take different arguments. What else could you pass into test and what would you do with it on the other end?

5 Likes

@gorbit99

I tried and it didn’t work.

Well I only tried with the (Vector3)partarget part. Does the conditionnal typeof(Vector3) change anything for the following code?

@KelsoMRK

It will ether be a GameObject or a Vector3.

I’m using Generic because well, I thought it was this kind of situation that I needed to do that.

Also, the Generic Parameter goes tru like 4 function before getting there, and I didn’t want to duplicate each function for a different overload.

Is it literally a logical branch based on whether it’s a GameObject or a Vector3? There’s not really any commonality between them. What if you pass in something else?

Again, sounds like you need 2 test methods - one that takes a GameObject and one that takes a Vector3.

3 Likes

sounds very much like you’re after overloading not generics as mentioned above

did you have a look through: https://msdn.microsoft.com/en-us/library/twcad0zb.aspx

1 Like

Another vote for method overloading. Another clue that generics isn’t the right paradigm is that the type constraint syntax wouldn’t allow you to constrain to both a struct and a class (either a class in general or a specific base class). Both of these signatures would cause an error:

void test<T>() where T : struct, GameObject { ... }

void test<T>() where T: struct, class { ... }

I’m guessing if your two use-cases are a Vector3 and a GameObject, you want to test against the GameObject’s position. Overloading would look like this:

void test(Vector3 target)
{
    // do some kind of test against the target
}

void test(GameObject target)
{
    test(target.position); // call the Vector3 version
}
3 Likes

I see your points, it does make sense.

It just annoys me that I will have to make 2 function for the same call.

I had it working with the generic, but it wasn’t optimal nor respecting any kind of best practice.

If it wasn’t a gameobject in the generic, I would do a parObject.ToString(), giving me something like (1,1,1). Then take this string and substring it to create a vector3 with it.

@MV10 the reason why I didn’t want to overload the function, was because the function goes tru like 3 classes, and some of them are derived classes of a base class. So I would have to duplicate the function at all those place.
(Ex : CombatManager → WeaponManager → Weapon → Skill ect…)

might be worth a look

1 Like

But it’s not the same call. It can’t be because you’re performing different operations :slight_smile:

That sounds scary and horrible. Please don’t do that.

1 Like

If it’s duplicated, just put it in the base class:

public abstract class BaseCombatManager
{

    public virtual void test(Vector3 target)
    {
        // implementation that all derived classes can use, or can override if necessary
    }

}
1 Like

@LeftyRighty
Yeah I will read your link tonight, seems interresting. Is it a different take on inheritance?

@KelsoMRK

It’s horrible, that’s why I’m looking for a solution. :slight_smile:

You are right that it’s not the same call. I’m tryhard to keep it working my way, but you have a good point. My function does something different for each type of object, so it doesn’t make sens to keep them in the same call. Stop shoving the truth in my face! :slight_smile:

@MV10
CombatManager → WeaponManager → Weapon → Skill aren’t derived classes , they call each other and check if everything is okay then call the next one. Like Combat check if the player alive, weaponmanager check if weapon is equiped ect…

I have some derived classe like Weapon is the parent classes of every weapon, and skill of every skill. I’m using the base classes function when I can.

But Maybe I should change my structure.

@LeftyRighty @KelsoMRK @MV10

I don’t know if it’s good practice in OOP :

I will give you an example of what i have right now :

In combatManager.cs:
Attack<T>(T parTarget)
{
[...] Some check up[...]
  WeaponManager.Attack(parTarget);
}

WeaponManager.cs:
Attack<T>(T parTarget)
{
  [...] Some check up[...]
  EquippedWeapon.Attack(parTarget);
}

Weapon.cs
Attack<T>(T parTarget)
{
  [...] Some check up[...]
  if (parTarget as Gameobject)
    Do this
  else
     Get Vector3 and do this
}

TL;DR : Each classes call the next one.

That’s a short/gross example of what I’m doing right now. I don’t know if it’s good practice, but I followed a guide that was doing it like this.

Should I just :

In combatManager.cs:
Attack<T>(T parTarget)
{
[...] Some check up[...]
  WeaponManager.EquipedWeapon.Skill.Attack(parTarget);
}

And do my check up in a previous call?

Is it good practice?

Thank you!

Sorry to abuse you guys, I learned from scratch and I have no idea about good habit of coding in C#.

Use code tags…

Oh I couldn’t find the tag, didnt know this forum was using BBcode.

It’s pseudo code, so I’m not sure if it will be clear.

Why?

Step back and ask yourself why you are doing this. What situations are there that you are going to call this with a GameObject or a Vector3, and do you really need it to maintain that identity throughout the function stack?

Ideally you would abandon one or the other, and only hand one type through the whole stack. In other words overload the first method, and not any of the others.

If you really do need to maintain both parameter throughout, I would actually box the parameters together. Create a TargetData class with both a Vector3 and a GameObject. Then you can simply do your operations on that. It’s still ugly, but not as ugly as using generics.

2 Likes

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.

3 Likes

@Kiwasi

It’s a case of derived class and skills not working the same way. All skills use the base Skills class, but some require gameobject for targeting, others don’t, and some accept both.

@lordofduct

At first I thought it looked crazy complicated, then I read your code and it’s actually a pretty good idea, and makes a lot of sense.(Is misspelling Defualt a kind of trademark? :P)

I might try your idea in my code, thanks for the example! :slight_smile:

In which case go for the boxing solution. Which is pretty much what @lordofduct suggested.

1 Like

And that worked!

Did I simpler version of @lordofduct script, and does the job!

Thank you everyone, special thanks to @Kiwasi and @lordofduct for the final solution!

I hadn’t even noticed that… lol.

That’s just my bad typing skills.

Sure I type 100 word like series of characters per minute. Doesn’t mean they’re actual words. :stuck_out_tongue:

Happy it worked for you. Yeah, it could definitely be simplified. The IPlanarSurface is definitely not needed (it’s used in 2.5d games where the gameworld may actual swerve to and frow in 3d, but the gameplay remains 2d).