Search Unity

Placing a custom class in the editor

Discussion in 'Scripting' started by Afropenguinn, Sep 11, 2014.

  1. Afropenguinn

    Afropenguinn

    Joined:
    May 15, 2013
    Posts:
    305
    I wanted to make all characters in the game the class of "Actor". This actor is then controlled by a brain, a "ActorController" class. There are two types of "ActorController" classes: "PlayerController" and "AIController". So the idea is that I can put the "Actor" script on an object and it becomes a character, which has all the various movement and talking functions built into it. However it won't know how to use these functions, these functions are used by the controller. The next step would be to drop the controller we are using into the actor script in the editor. However, I can't seem to declare a public variable that can hold the "ActorController" scripts, it will only accept an object with the "ActorController" in it. How would I got about making the "ActorController" placeable into a public variable slot in the editor? I tried serializing it already, but as I said that made it only accept objects with the controller in it.

    EDIT: To make it more clear what I want, I want to be able to put that script into that slot in the editor. All the scripts are in my next post.
     
    Last edited: Sep 12, 2014
  2. orb

    orb

    Joined:
    Nov 24, 2010
    Posts:
    3,037
    I'm not having any trouble doing the same. I made classes named just like yours, made a SerializableField for an ActorController, and derived PlayerController and AIController from that. They drop in just fine. Are you sure they're inheriting correctly?

    Code (CSharp):
    1. public class PlayerController : ActorController
    2. {
    Another thing is that it seems more logical to have the controller hold a pointer to an actor. The actor normally doesn't know anything about its controller, and the controller knows all the stuff the actor can do.
     
  3. Afropenguinn

    Afropenguinn

    Joined:
    May 15, 2013
    Posts:
    305
    How do I add a pointer to the controller that points to the Actor?
    And strange, here I will post each code:
    Actor script:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. [RequireComponent (typeof (CharacterController))]
    5. public class Actor : MonoBehaviour
    6. {
    7.     public float speed, jumpForce;
    8.     float hspeed, vspeed, gravity;
    9.     CharacterController characterController;
    10.     public ActorController controller;
    11.  
    12.     void Start ()
    13.     {
    14.         characterController = GetComponent<CharacterController> ();
    15.  
    16.         hspeed = 0f;
    17.         vspeed = 0f;
    18.         gravity = 1f;
    19.  
    20.         controller.ControllerStart ();
    21.     }
    22.  
    23.     public void Update ()
    24.     {
    25.         controller.ControllerUpdate();
    26.  
    27.         //gravity
    28.         if (characterController.isGrounded == true) { vspeed = -1f; } else { vspeed -= gravity; }
    29.  
    30.         //limits
    31.         if (hspeed > 75f) { hspeed = 75f; }
    32.         if (hspeed < -75f) { hspeed = -75f; }
    33.         if (vspeed > 75f) { vspeed = 75f; }
    34.         if (vspeed < -75f) { vspeed = -75f; }
    35.  
    36.         //above collisions
    37.         if (characterController.collisionFlags == CollisionFlags.Above)
    38.         {
    39.             if (vspeed > 0f) { vspeed = 0f; }
    40.         }
    41.  
    42.         //apply forces
    43.         characterController.Move(new Vector3(hspeed * Time.deltaTime, vspeed * Time.deltaTime, 0f));
    44.     }
    45.  
    46.     public void Move(float magnitude)
    47.     {
    48.         magnitude = Mathf.Clamp (magnitude, -1f, 1f);
    49.         hspeed = speed * magnitude;
    50.     }
    51.  
    52.     public void Jump(float magnitude)
    53.     {
    54.         magnitude = Mathf.Clamp (magnitude, -1f, 1f);
    55.         vspeed = speed * magnitude;
    56.     }
    57. }
    ActorController Script:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public abstract class ActorController : MonoBehaviour
    5. {
    6.     public abstract void ControllerStart();
    7.     public abstract void ControllerUpdate();
    8. }
    PlayerController Script:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class PlayerController : ActorController
    5. {
    6.     Actor actor;
    7.  
    8.     public override void ControllerStart ()
    9.     {
    10.         actor = GetComponent<Actor> ();
    11.     }
    12.  
    13.     public override void ControllerUpdate ()
    14.     {
    15.         //walking
    16.         actor.Move(Input.GetAxisRaw ("Move Horizontal"));
    17.  
    18.         if (Input.GetButtonDown ("Jump"))
    19.         {
    20.             actor.Jump(1f);
    21.         }
    22.     }
    23. }
     
  4. BmxGrilled

    BmxGrilled

    Joined:
    Jan 27, 2014
    Posts:
    239
    Code (csharp):
    1. //Actor.cs
    2. //C#
    3. using UnityEngine;
    4. using System.Collections;
    5. [RequireComponent (typeof (CharacterController))]
    6. public class Actor : MonoBehaviour
    7. {
    8.    public float speed, jumpForce;
    9.    float hspeed, vspeed, gravity;
    10.    CharacterController characterController;
    11.    ActorController controller;
    12.    void Start ()
    13.    {
    14.      characterController = GetComponent<CharacterController> ();
    15.      controller = GetComponent<ActorController>();
    16.      hspeed = 0f;
    17.      vspeed = 0f;
    18.      gravity = 1f;
    19.      controller.ControllerStart ();
    20.    }
    21.    public void Update ()
    22.    {
    23.      controller.ControllerUpdate();
    24.      //gravity
    25.      if (characterController.isGrounded == true) { vspeed = -1f; } else { vspeed -= gravity; }
    26.      //limits
    27.      if (hspeed > 75f) { hspeed = 75f; }
    28.      if (hspeed < -75f) { hspeed = -75f; }
    29.      if (vspeed > 75f) { vspeed = 75f; }
    30.      if (vspeed < -75f) { vspeed = -75f; }
    31.      //above collisions
    32.      if (characterController.collisionFlags == CollisionFlags.Above)
    33.      {
    34.        if (vspeed > 0f) { vspeed = 0f; }
    35.      }
    36.      //apply forces
    37.      characterController.Move(new Vector3(hspeed * Time.deltaTime, vspeed * Time.deltaTime, 0f));
    38.    }
    39.    public void Move(float magnitude)
    40.    {
    41.      magnitude = Mathf.Clamp (magnitude, -1f, 1f);
    42.      hspeed = speed * magnitude;
    43.    }
    44.    public void Jump(float magnitude)
    45.    {
    46.      magnitude = Mathf.Clamp (magnitude, -1f, 1f);
    47.      vspeed = speed * magnitude;
    48.    }
    49. }
    50.  
    Code (csharp):
    1. //ActorController.cs
    2. //C#
    3. using UnityEngine;
    4. using System.Collections;
    5. public abstract class ActorController : MonoBehaviour
    6. {
    7.    public abstract void ControllerStart();
    8.    public abstract void ControllerUpdate();
    9. }
    10.  
    Code (csharp):
    1. //PlayerController.cs
    2. //C#
    3. using UnityEngine;
    4. using System.Collections;
    5. public class PlayerController : ActorController
    6. {
    7.    Actor actor;
    8.    public override void ControllerStart ()
    9.    {
    10.      actor = GetComponent<Actor> ();
    11.    }
    12.    public override void ControllerUpdate ()
    13.    {
    14.      //walking
    15.      actor.Move(Input.GetAxisRaw ("Move Horizontal"));
    16.      if (Input.GetButtonDown ("Jump"))
    17.      {
    18.        actor.Jump(1f);
    19.      }
    20.    }
    21. }
    22.  
    I've changed the code so that you only need to drop Actor onto the object, and then drop the controller script (PlayerController or AIController) onto the object also, then the Actor script will detect the controller script in the start event. Hope this helps and makes your life easier.

    ~
    Another walk in the park.
     
  5. orb

    orb

    Joined:
    Nov 24, 2010
    Posts:
    3,037
    It's a bit unclear what you're actually asking for.

    If you're looking for the PlayerController attached to the actor's game object, you simply use GetComponent<PlayerController>() (or whatever) to set it.
     
  6. Afropenguinn

    Afropenguinn

    Joined:
    May 15, 2013
    Posts:
    305
    What exactly did you change?

    And because I am trying to get something like this:

    The PlayerController (which is an ActorController) is within the actor script. How do I let PlayerController referance the Actor it is in? But more importantly, I am looking for a way to do what is in the image above without placing PlayerController in an object, I just want to drop the script in.
     
    Last edited: Sep 11, 2014
  7. BmxGrilled

    BmxGrilled

    Joined:
    Jan 27, 2014
    Posts:
    239
    I changed one line, and added one line, to make it so you only needed to drop the controller in, monobehaviour doesn't like being dropped in generically if you have multiple types inheritting from it. (not sure if I explained that well)

    the lines I changed and added are:

    Actor.cs line 11
    Actor.cs line 15

    ~
    Another walk in the park.
     
  8. Afropenguinn

    Afropenguinn

    Joined:
    May 15, 2013
    Posts:
    305
    That won't allow me to drop it into the editor in the way I was looking for, however. Look at the image in my previous post for reference. ActorController is not a component located in the object.
     
  9. ThermalFusion

    ThermalFusion

    Joined:
    May 1, 2011
    Posts:
    906
    Why do you want a reference to the script file at runtime? I can't think of anything useful you could do with it except getting its name, which you could just use a string instead.
     
  10. Afropenguinn

    Afropenguinn

    Joined:
    May 15, 2013
    Posts:
    305
    I posted the scripts in an earlier post. The "Actor" script contains functions for movement and eventually other actions such as talking, however it does not contain anything that tells it to do those things. The "ActorController" tells them to do these things. Think of the Actor as the body and the Controller as the brain. The Actor is the same for all NPCs and the Player but the ActorController is different.

    I am trying to make the script drop-able into the other script.
     
  11. BmxGrilled

    BmxGrilled

    Joined:
    Jan 27, 2014
    Posts:
    239
    I was unable to get the code working with the ActorController inheritting from MonoBehaviour, when I took MonoBehaviour away I realised it had a dependency because it was using something from MonoBehaviour, so my solution was to keep MonoBehaviour and make it a component you dragged onto the object, however I could look into redesigning it to work the way you want... I'll get back to you soon.

    ~
    Another walk in the park.
     
  12. BmxGrilled

    BmxGrilled

    Joined:
    Jan 27, 2014
    Posts:
    239
    Ok so the only way I could remotely get this working was to predefine the classes in an enumeration and have the actor script create the appropriate class and assign it to the actorController field. This means you choose the class from a dropdown in the inspector, and the actor script handles the rest. Here's the code:

    Code (CSharp):
    1. //Actor.cs
    2. //C#
    3. using UnityEngine;
    4. using System.Collections;
    5. [RequireComponent (typeof (CharacterController))]
    6. public class Actor : MonoBehaviour
    7. {
    8.     public enum ActorType
    9.     {
    10.         TypeA,
    11.         TypeB,
    12.         //...
    13.         TypeN
    14.     }
    15.  
    16.     public float speed, jumpForce;
    17.     float hspeed, vspeed, gravity;
    18.     CharacterController characterController;
    19.     public ActorType actorType;
    20.     public ActorController controller;
    21.     void Start ()
    22.     {
    23.         characterController = GetComponent<CharacterController> ();
    24.         hspeed = 0f;
    25.         vspeed = 0f;
    26.         gravity = 1f;
    27.      
    28.         switch(actorType)
    29.         {
    30.             case ActorType.TypeA:
    31.                 controller = new PlayerController();
    32.                 break;
    33.             case ActorType.TypeB:
    34.                 controller = new AIController();
    35.                 break;
    36.             //...
    37.             case ActorType.TypeN:
    38.                 break;
    39.             default: Debug.LogError("Unknown ActorType Assigned"); return;
    40.         }
    41.         controller.ControllerStart (this);
    42.     }
    43.     public void Update ()
    44.     {
    45.         controller.ControllerUpdate();
    46.         //gravity
    47.         if (characterController.isGrounded == true) { vspeed = -1f; } else { vspeed -= gravity; }
    48.         //limits
    49.         if (hspeed > 75f) { hspeed = 75f; }
    50.         if (hspeed < -75f) { hspeed = -75f; }
    51.         if (vspeed > 75f) { vspeed = 75f; }
    52.         if (vspeed < -75f) { vspeed = -75f; }
    53.         //above collisions
    54.         if (characterController.collisionFlags == CollisionFlags.Above)
    55.         {
    56.             if (vspeed > 0f) { vspeed = 0f; }
    57.         }
    58.         //apply forces
    59.         characterController.Move(new Vector3(hspeed * Time.deltaTime, vspeed * Time.deltaTime, 0f));
    60.     }
    61.     public void Move(float magnitude)
    62.     {
    63.         magnitude = Mathf.Clamp (magnitude, -1f, 1f);
    64.         hspeed = speed * magnitude;
    65.     }
    66.     public void Jump(float magnitude)
    67.     {
    68.         magnitude = Mathf.Clamp (magnitude, -1f, 1f);
    69.         vspeed = speed * magnitude;
    70.     }
    71. }
    72.  
    Code (CSharp):
    1. //ActorController.cs
    2. //C#
    3. using UnityEngine;
    4. using System.Collections;
    5. public abstract class ActorController
    6. {
    7.     public abstract void ControllerStart(Actor actor);
    8.     public abstract void ControllerUpdate();
    9. }
    10.  
    Code (CSharp):
    1. //PlayerController.cs
    2. //C#
    3. using UnityEngine;
    4. using System.Collections;
    5. public class PlayerController : ActorController
    6. {
    7.     Actor actor;
    8.     public override void ControllerStart (Actor actor)
    9.     {
    10.         this.actor = actor;
    11.     }
    12.     public override void ControllerUpdate ()
    13.     {
    14.         //walking
    15.         actor.Move(Input.GetAxisRaw ("Move Horizontal"));
    16.         if (Input.GetButtonDown ("Jump"))
    17.         {
    18.             actor.Jump(1f);
    19.         }
    20.     }
    21. }
    22.  
    Code (CSharp):
    1. //AIController.cs
    2. //C#
    3. using UnityEngine;
    4. using System.Collections;
    5.  
    6. public class AIController : ActorController
    7. {
    8.     Actor actor;
    9.     public override void ControllerStart (Actor actor)
    10.     {
    11.         this.actor = actor;
    12.     }
    13.     public override void ControllerUpdate ()
    14.     {
    15.         //walking
    16.         //...
    17.     }
    18. }
    19.  
    I hope this helps! I honestly tried a lot of things and this is the only way I could remotely get it to work the way you want.

    ~
    Another walk in the park.
     
  13. Afropenguinn

    Afropenguinn

    Joined:
    May 15, 2013
    Posts:
    305
    Getting so close! Is this something that is just not possible within Unity without custom editor coding? I know I made an array of classes before and had it appear in the editor, but I never have been able to drag a class in before.

    Example:
     
    Last edited: Sep 12, 2014
  14. ThermalFusion

    ThermalFusion

    Joined:
    May 1, 2011
    Posts:
    906
    If you create a GameObject for each of your PlayerController and AiController, and attach the components to them, then drag and drop these onto the Actors ActorController variable, isn't that what you want?
    The only script objects you can link references to are those of objects based on either MonoBehaviour or ScriptableObject.
     
  15. orb

    orb

    Joined:
    Nov 24, 2010
    Posts:
    3,037
    You can't add scripts to variables, only game objects with those scripts added as components. Why don't you simply make prefabs, one for each controller type, and instantiate those as needed?
     
  16. Afropenguinn

    Afropenguinn

    Joined:
    May 15, 2013
    Posts:
    305
    I can, and that is what I am using for now. I was just wondering if there was a way to just drop the script right in, since it seemed a bit redundant to have a bunch of prefabs with nothing in them but a single script next to all the scripts. It especially makes me feel like I am doing something redundant when I have "PlayerController (PlayerController)" or "AIController (AIController)" in that slot, seems like there is an unneeded middle man.
     
  17. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    If I may, I think you're looking at this all wrong. You don't (generally) need one script to have a reference to another script... you just need it to find the other script, on the same game object.

    For example, a RigidBody component needs some sort of Collider component, but it doesn't have a Collider slot; instead, it just finds any old Collider (which could be any of several subclasses) on the same object. (And in fact it supports multiple colliders, but that's a side-issue.)

    In your case, you should make Actor and ActorController both derive from MonoBehaviour. Make PlayerController and AIController both derive from ActorController. Now, just drag on an Actor and any of the controller components onto a GameObject, and boom, you're done.

    Under the hood, the scripts find each other using GetComponent<ActorController> or GetComponent<Actor>. Note that you can GetComponent<ActorController>, and it will return a subclass thereof just as well. For best performance, do this in Start() and cache the result (though in practice, it may not matter, as GetComponent is usually quite quick).

    HTH,
    - Joe
     
    Zaladur likes this.
  18. ThermalFusion

    ThermalFusion

    Joined:
    May 1, 2011
    Posts:
    906
    There are ScriptableObjects that don't need to be attatched to a GameObject but can live as an asset in the project.
    Take a look at the live training video on it:
    http://unity3d.com/learn/tutorials/modules/beginner/live-training-archive/scriptable-objects
     
  19. Afropenguinn

    Afropenguinn

    Joined:
    May 15, 2013
    Posts:
    305
    The way I want to do it is more so for organization purposes that anything. It keeps everything pertaining to the actor within the actor component. I am going to be adding more to actors in the future, such as abilities, which are each their own script as well. This means allows me to give any actor any ability or brain, or even switch them mid-game.
     
  20. BmxGrilled

    BmxGrilled

    Joined:
    Jan 27, 2014
    Posts:
    239
    You could write a custom inspector/editor script to change how your actor script is presented in the inspector, and perhaps handle the drag & drop of your actorController scripts yourself, inside the editor script. And the editor script will only run inside the editor, as long as you place it in a folder named "Editor" inside your scripts folder, so won't impact your final compiled game. Hope this helps! :)
     
  21. Zaladur

    Zaladur

    Joined:
    Oct 20, 2012
    Posts:
    392
    The reason that you cannot do this is that you cannot drag a SCRIPT into a slot that expects an object. There needs to be an instance of the script in order for its methods to be run. If your script is a Monobehavior, this is done by adding it as a component to a Gameobject - in this case, your actor.

    Your actorController class and its derivatives are Monobehaviors, which means they should be attached to gameobjects as components. It would be perfectly acceptable to GetComponent your ActorController, as JoeStrout suggests. The ability to switch abilities or brains is not hindered by this. GetComponent will return anything that derives from ActorController, so it will automatically pick up whatever type of brain you have attached and run the methods accordingly. AddComponent and RemoveComponent can be used at runtime to swap brains.

    This is seriously the exact type of scenario that components were made for.
     
  22. Afropenguinn

    Afropenguinn

    Joined:
    May 15, 2013
    Posts:
    305
    I know, as I said this was more for organizational purposes. I wanted everything relevant related to the actor inside the actor component. If I need to I will put them in GameObjects. Will that have a performance impact?

    EDIT: Also, random question, if I have multiple components that are derived from the same class and I search from that component what order will it return the array?
     
    Last edited: Sep 17, 2014
  23. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    I'm not sure this is documented, but in practice, I bet it will return them in the same order as the components on the object. This is something you can control within the editor — though I would generally recommend instead making the order irrelevant.