Hello i have this code:
public abstract class Test : MonoBehaviour
{
public abstract void OnStart();
public abstract void OnUpdate();
private void Start()
{
OnStart();
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.X))
{
Destroy(this);
}
OnUpdate()
}
}
then I have the this:
public class SecondTest : Test
{
public override void OnStart()
{
}
public override void OnUpdate()
{
Writter.Log("Running");
}
}
However whenever I press X, my writter keeps logging, it seems like the instance of "Test" gets destroyed but not my "SecondTest" is there any way to solve this? thank you!
*OP explained the intention is to cause Children to be destroyed when Parent is.
The solution would probably be better handled at a '(Test)Manager' or 'LevelManager'.
You could assign your Children or prefabs to your (Test)Manager manually, or get (Test)Manager to instantiate the child prefab.
GameObject child = Instantiate(prefab);
child.transform.SetParent(gameObject.transform); //set the child's parent to the Manager
Then in your (Test)Manager
private void Update()
{
if (Input.GetKeyDown(KeyCode.X))
{
for(int i=0;i<gameObject.transform.childCount;i++)
Destroy(gameObject.transform.GetChild(i).gameObject);
}
}
This is just a crude suggestion, there are probably better ways to do this such as holding some references to the children in the manager versus parenting.
Related
I am trying to use virtual and abstract methods to make my game architecture better.
I'm using C# and Unity for this example.
I use a ShipComponent as a base Class because I want all the child classes to do the same thing.
But sometimes I want a certain ShipComponent to do something else.
The code will make it a lot clearer:
ShipComponent.cs:
public abstract class ShipComponent : MonoBehaviour
{
[HideInInspector] public ShipControl shipControl;
public virtual void Init(ShipControl control)
{
this.shipControl = control;
}
public virtual void IsPlayer()
{
SetListeners();
}
public abstract void IsNotPlayer();
public abstract void ReEnable();
public abstract void SetListeners();
}
One of the many child classes that inherits from ShipComponent:
public class Rudder : ShipComponent
{
[Header("Settings")]
public Transform rudder;
[Header("Debug Info")]
[SerializeField] float rudderSpeed;
[SerializeField][Range(-45, 45)] int setRudderAngle = 0;
[SerializeField][Range(-45f, 45f)] float realRudderAngle = 0f;
public override void Init(ShipControl shipControl)
{
base.Init(shipControl);
rudder = transform.GetChild(0).GetChild(4);
StartCoroutine(SmoothRudderChange());
SetListeners();
}
public override void IsPlayer()
{
base.IsPlayer();
}
public override void IsNotPlayer()
{
PlayerShipControl.OnRudderChange -= SetRudder;
}
public override void ReEnable()
{
StartCoroutine(SmoothRudderChange());
SetListeners();
}
public override void SetListeners()
{
PlayerShipControl.OnRudderChange -= SetRudder;
if (!shipControl.shipWrapper.ship.IsPlayer) return;
PlayerShipControl.OnRudderChange += SetRudder;
}
void OnDisable()
{
PlayerShipControl.OnRudderChange -= SetRudder;
StopAllCoroutines();
}
The main draw back I experience with this, is that I have to copy paste all 5 or 6 methods everytime I create a new ShipComponent class.
It seems messy and theres a lot of repeating code, most of the time the only difference in each ShipComponent is the SetListeners part, and StartCoroutines if any.
Is there a way to dynamically set delegate listeners up?
So I could set them in the base class ShipComponent?
Instead of setting each component individually?
Another script that inherits from ShipComponent for completeness:
public class Guns : ShipComponent
{
IEnumerator mouseAimCycle;
public override void Init(ShipControl shipControl)
{
base.Init(shipControl);
InitCannons();
SetListeners();
}
public override void ReEnable()
{
SetListeners();
}
public override void IsPlayer()
{
base.IsPlayer();
mouseAimCycle = AimCycle();
StartCoroutine(mouseAimCycle);
SetListeners();
}
public override void SetListeners()
{
PlayerShipControl.OnFireGuns -= TryFire;
if (!shipControl.shipWrapper.ship.IsPlayer) return;
PlayerShipControl.OnFireGuns += TryFire;
}
public override void IsNotPlayer()
{
StopCoroutine(mouseAimCycle);
PlayerShipControl.OnFireGuns -= TryFire;
}
void OnDisable()
{
PlayerShipControl.OnFireGuns -= TryFire;
StopAllCoroutines();
}
Calling the ShipComponent virtual and abstract methods:
public class ShipControl : MonoBehaviour
{
// Contains Ship + Cargo + Crew and a ref to this ShipControl
public ShipWrapper shipWrapper { get; private set; }
ShipComponent[] shipComponents;
// Gather all ShipComponents and Initialize them.
public void Start()
{
shipComponents = transform.GetComponents<ShipComponent>();
foreach (ShipComponent comp in shipComponents)
{
comp.Init(this);
}
}
// Call this to check if this is players current ship and set the components accordingly.
public void UpdateIsPlayer()
{
if (!shipWrapper.ship.IsPlayer)
foreach (ShipComponent component in shipComponents)
component.IsNotPlayer();
else
foreach (ShipComponent component in shipComponents)
component.IsPlayer();
}
And PlayerShipControl, which I use for input, broadcasting the input through delegates, and the theory is that only the players currently controlled ship will be listening for this input:
public class PlayerShipControl : MonoBehaviour
{
public static event Action<Transform> SetCamToPlayerShip;
public static event Action SetShipPanelUI;
public static event Action<bool> ToggleAnchorIcon, ToggleFlagIcon, ToggleAutofireIcon, ToggleBoatsIcon;
public static event Action OnFireGuns;
public static event Action<int> OnRudderChange;
public static event Action<int> OnSailStateChange;
public static event Action<bool> OnAllAnchorsCommand;
public static event Action<bool> OnAllBoatsCommand;
bool anchor, flag, autofire, boats;
ShipControl shipControl;
void Update()
{
if (Input.GetKeyUp(KeyCode.W)) // Raise Sails SailState++
{
OnSailStateChange?.Invoke(1);
}
if (Input.GetKeyUp(KeyCode.S)) // Furl Sails SailState--
{
OnSailStateChange?.Invoke(-1);
}
if (Input.GetKey(KeyCode.D))
{
OnRudderChange?.Invoke(1);
}
if (Input.GetKey(KeyCode.A))
{
OnRudderChange?.Invoke(-1);
}
if (Input.GetKeyDown(KeyCode.M))
{
OnRudderChange?.Invoke(0);
}
// Drop All Anchors
if (Input.GetKeyDown(KeyCode.V))
{
anchor = true;
ToggleAnchorIcon?.Invoke(anchor);
OnAllAnchorsCommand?.Invoke(anchor);
}
// Haul All Anchors
if (Input.GetKeyDown(KeyCode.H))
{
anchor = false;
ToggleAnchorIcon?.Invoke(anchor);
OnAllAnchorsCommand?.Invoke(anchor);
}
// Drop All Boats
if (Input.GetKeyDown(KeyCode.B))
{
boats = true;
ToggleBoatsIcon?.Invoke(boats);
OnAllBoatsCommand?.Invoke(boats);
}
// Take In All Boats
if (Input.GetKeyDown(KeyCode.U))
{
OnAllBoatsCommand?.Invoke(false);
// TO DO When all boats are back on deck, boatIcon + boatsBoolFlag should be turned off again.
}
if (Input.GetKeyDown(KeyCode.Space))
{
OnFireGuns?.Invoke();
}
}
}
Its a long string of scripts sometimes though so I have left out all the managers and such.
Ship ship inside shipWrapper.ship is a custom data class that stores the info about the ship, not a Monobehaviour, but it holds a bool called IsPlayer aswell. Nothing else of interest I can think of.
The main draw back I experience with this, is that I have to copy paste all 5 or 6 methods every time I create a new ShipComponent class. It seems messy and there's a lot of repeating code, most of the time the only difference in each ShipComponent is the SetListeners part, and StartCoroutines if any.
In the show example you have more differences between implementations then ones described. Without seeing the full code it is hard to suggest something meaningful.
Few notes on the current code:
In Rudder you don't need to specify IsPlayer because the following:
public override void IsPlayer()
{
base.IsPlayer();
}
does not add anything extra, so you can just skip implementation in the derived class.
Based on provided examples it seems that ReEnable can be defined as virtual in base class with default implementation set to calling SetListeners (the same approach as you have with Init and IsPlayer).
PlayerShipControl.Update possibly can be improved by moving handlers to dictionary. Something along this lines:
public class PlayerShipControl : MonoBehaviour
{
// ...
Dictionary<KeyCode, Action> keyActions = new() // not sure about the type
{
{ KeyCode.W, () => OnSailStateChange?.Invoke(1) },
// ...
{ KeyCode.V, () =>
{
anchor = true;
ToggleAnchorIcon?.Invoke(anchor);
OnAllAnchorsCommand?.Invoke(anchor);
}
},
// ...
};
void Update()
{
foreach (var kvp in keyActions)
{
if (Input.GetKeyUp(kvp.Key))
{
kvp.Value();
break;
}
}
}
}
The following is attached to my player and would call upon whatever object is hit to use the objects function. Think the player controls the pointing and clicking, but the object controls whatever the object will do, such as turn on a light.
void Interact()
{
RaycastHit interactablehit;
if (Physics.Raycast(PlayerCamera.transform.position, PlayerCamera.transform.forward, out interactablehit, MaxDistance))
{
// if raycast hits, then it checks if it hit an object with the tag Interactable.
if (interactablehit.transform.tag == "Interactable")
{
object = interactablehit.transform.name;
interactablehit.transform.gameObject.GetComponent<interactablehit.transform.name>().ObjectInteract();
}
}
}
This is a job for either an interface or a common base class:
public interface IInteractable
{
void ObjectInteract();
}
and then have your different implementations like e.g.
public class ExampleInteractable : MonoBevahiour, IInteractable
{
public void ObjectInteract()
{
Debug.Log("Hello!");
}
}
or
public class ToggleLightInteractable : MonoBevahiour, IInteractable
{
[SerializeField] private Light light;
public void ObjectInteract()
{
light.enabled = !light.enabled;
}
}
and then simply do
void Interact()
{
if (Physics.Raycast(PlayerCamera.transform.position, PlayerCamera.transform.forward, out var interactablehit, MaxDistance))
{
var interactable = interactablehit.transform.GetComponent<IInteractable>();
if (interactable != null)
{
interactable.ObjctInteract();
}
}
}
Or the same with a common base class, usefull if you want/need some shared behaviour or default implementations
public class BaseInteractable
{
public virtual void ObjectInteract()
{
Debug.Log("Hello!");
// For demo reasons lets say per default this can be interacted with only once
Destroy(this);
}
}
and then have your different implementations like e.g.
public class ExampleInteractable : BaseInteractable
{
public override void ObjectInteract()
{
// completely override the default behaviour
Debug.Log("Hello World!");
// this can be interacted with forever since the default behavior is not executed
}
}
or
public class ToggleLightInteractable : BaseInteractable
{
[SerializeField] private Light light;
public override void ObjectInteract()
{
// keep the default behavior and only extend it with something additional
base.ObjectInteract();
light.enabled = !light.enabled;
}
}
and then do
void Interact()
{
if (Physics.Raycast(PlayerCamera.transform.position, PlayerCamera.transform.forward, out var interactablehit, MaxDistance))
{
if (interactablehit.transform.TryGetComponent<BaseInteractable>(out var interactable))
{
interactable.ObjctInteract();
}
}
}
If you really really for what reason ever (there shouldn't be any good one) need to stick to string you can (you shouldn't) use SendMessage like
void Interact()
{
if (Physics.Raycast(PlayerCamera.transform.position, PlayerCamera.transform.forward, out var interactablehit, MaxDistance))
{
interactablehit.transform.gameObject.SendMessage(interactablehit.transform.name, options: SendMessageOptions.DontRequireReceiver);
}
}
which would require your object to be called exactly like the method you want to call, regardless of how the component is called.
The main issue is, I'm trying to use a polymorphic list in a ScriptableObject but its contents are lost when restarting Unity; code below.
So I've got a base class:
[Serializable]
public class BaseAction : ScriptableObject
{
public virtual void OnGUI()
{
}
public virtual void ExecuteAction()
{
}
}
And an inheriting class with a simple string attribute:
[Serializable]
public class DebugLogAction : BaseAction
{
[SerializeField]
private string debugText = default;
public override void OnGUI()
{
debugText = EditorGUILayout.TextField(debugText);
}
public override void ExecuteAction()
{
Debug.Log(debugText);
}
}
They all reunite in a ScriptableObject that contains a list of BaseActions:
[Serializable]
[CreateAssetMenu(fileName = "NewActionContainer", menuName = "ScriptableObjects/Action Container")]
public class ActionContainer : ScriptableObject
{
[SerializeField]
private List<BaseAction> actions = default;
public void OnEnable()
{
if (actions == null)
{
actions = new List<BaseAction>();
}
}
public void OnInspectorGUI()
{
foreach (var action in actions)
{
action.OnGUI();
}
if (GUILayout.Button("Add Debug Log Action"))
{
actions.Add(CreateInstance<DebugLogAction>());
}
}
}
I've written up a custom editor for the ActionContainer ScriptableObject which works fine, displaying the button for adding multiple actions which adds editable input fields:
[CustomEditor(typeof(ActionContainer)), CanEditMultipleObjects]
public class CustomActionContainerInspector : Editor
{
private ActionContainer actionContainer;
public void OnEnable()
{
actionContainer = (ActionContainer)target;
}
public override void OnInspectorGUI()
{
actionContainer.OnInspectorGUI();
}
}
However, on restart of Unity, the content of the ScriptableObject is gone (assembly rebuilding is fine, playing/stopping the game keeps the data as well).
I've tried adding a field to BaseAction to check if the values are just sheared off the inheriting class, but no; the entire list of actions from the ActionContainer empties.
I believe there's an issue either with serializing or deserializing the elements from the list itself, since it's polymorphic, but I've got no idea how to debug or solve the issue; internet searches didn't get me to a solution either.
First of all in general you should not call your method OnGUI as this is also a built-in message in MonoBehaviour and might lead to confusion.
Your main pitfall I think is the dirty state.
If you don't mark objects dirty after changing them then your changes don't get saved correctly.
I think a quick fix for this would be to use EditorUtility.SetDirty
public override void OnGUI()
{
EditorGUI.BeginChangeCheck();
debugText = EditorGUILayout.TextField(debugText);
if(EditorGUI.EndChangeCheck())
{
EditorUtility.SetDirty(this);
}
}
or if you wan to properly support undo/redo then rather using Undo.RecordObject
public override void OnGUI()
{
EditorGUI.BeginChangeCheck();
var newDebugText = EditorGUILayout.TextField(debugText);
if(EditorGUI.EndChangeCheck())
{
Undo.RecordObject(this, "changed debug text");
debugText = newDebugText;
}
}
See also
EditorGUI.BeginChangeCheck / EditorGUI.EndChangeCheck
And similar also for the
if (GUILayout.Button("Add Debug Log Action"))
{
//either
Undo.RecordObject(this, "add element");
actions.Add(CreateInstance<DebugLogAction>());
//or
//EditorUtilty.SetDirty(this);
}
I've created a parent class that I expect to have all functions related to testing if the GameObject is grounded, in water, on air, etc... given that this functions will be used by the player as well as other GameObjects. However, the child class seems not to inherit properly the functions.
The parent script it's as follows:
public class CharacterPhysic : MonoBehaviour {
[SerializeField] protected Transform groundPoints;
float grounRadius;
private void Start ()
{
whatIsGround = LayerMask.GetMask("Ground");
groundRadius = 0.01f;
}
protected bool IsGrounded()
{
Collider2D[] colliders = Physics2D.OverlapCircleAll(groundPoints.position, groundRadius, whatIsGround);
if (colliders.Length > 0)
{
return true;
}
else return false;
}
private void FixedUpdate()
{
Debug.Log(IsGrounded());
}
}
And the children script is just:
public class ErrantMove : CharacterPhysic {
private void FixedUpdate()
{
Debug.Log(IsGrounded());
}
}
When added the first script as a component to a Gameobject (after defining the grounPoint) the Debug.Log(IsGrounded()); returns TRUE
However, when added the second script as a component to the same Gameobject (after defining the grounPoint and remove the first script) the Debug.Log(IsGrounded()); returns FALSE even in the same circumstances.
I'm expecting to be able to add movement functions into the second script but this depends on the ability to test if it is grounded.
You can properly inherit Unity's callback functions such as Awake, Start and Update like you would with a normal C# inheritance paradigm. It's very simply.
Make the all the callback functions for the base class to be virtual:
public class YourBaseClass : MonoBehaviour
{
protected virtual void Awake()
{
Debug.Log("Awake Base");
}
protected virtual void Start()
{
Debug.Log("Start Base");
}
protected virtual void Update()
{
Debug.Log("Update Base");
}
protected virtual void FixedUpdate()
{
Debug.Log("FixedUpdate Base");
}
}
For the parent class that will derive from the base class, add the callback functions too but mark them as override. If you need the base function to be called, call it with base.FunctionName before doing anything else in the parent function:
public class Parent : YourBaseClass
{
protected override void Awake()
{
base.Awake();
Debug.Log("Awake Parent");
}
protected override void Start()
{
base.Start();
Debug.Log("Start Parent");
}
protected override void Update()
{
base.Update();
Debug.Log("Update Parent");
}
protected override void FixedUpdate()
{
base.FixedUpdate();
Debug.Log("FixedUpdate Parent");
}
}
When tested in Unity version 2020.1.21f, protected callback functions like Start will automatically be executed in their child classes. There is no need to declare a function as virtual, you should only do that if you intend to override the function in a subclass.
public class ParentClass : MonoBehavior
{
protected void Start()
{
Debug.Log("Hello, my class is ");
Debug.Log(this.GetType());
}
}
public class ChildClass : ParentClass
{
//empty class
}
public class GameManager : MonoBehavior
{
//childPrefab is a prefab containing the ChildClass script as a component
Object.Instantiate(childPrefab);
}
Upon instantiating the childPrefab containing the ChildClass script, the object will print, "Hello, my class is ChildClass".
As for your example, the only thing you would need to do to fix that code is change your Start function to protected. That will ensure that Start is inherited by ErrantMove. Additionally, if you don't intend to use FixedUpdate for anything other than checking IsGrounded(), you could mark FixedUpdate as protected, and then delete it from your subclass.
If you want to add additional functionality to FixedUpdate in your subclass, then you will need follow the advice of the other answerers and use virtual, override, and base.FixedUpdate().
Your Start method will not be executed in child class. Move it into child class if you are not going to use parent class.
public class ErrantMove : CharacterPhysic {
protected void Start ()
{
whatIsGround = LayerMask.GetMask("Ground");
groundRadius = 0.01f;
}
private void FixedUpdate()
{
Debug.Log(IsGrounded());
}
}
You can notice that if you will have more than one child classes, you will be forced to initialize base class's Start method in every child class, so you can create an virtual Start method, then call it inside child classes:
public class CharacterPhysic : MonoBehaviour {
. . .
protected virtual void Start ()
{
whatIsGround = LayerMask.GetMask("Ground");
groundRadius = 0.01f;
}
protected bool IsGrounded()
{
Collider2D[] colliders = Physics2D.OverlapCircleAll(groundPoints.position, groundRadius, whatIsGround);
return colliders.Length > 0;
}
. . .
}
public class ErrantMove : CharacterPhysic {
protected override void Start()
{
base.Start();
}
private void FixedUpdate()
{
Debug.Log(IsGrounded());
}
}
you can go either virtual/override approach, or use new keyword to "unhide" child class's Start method (I'm not sure how new will behave in unity, so better go with first one).
I am trying to do a state machine to controll ai behaviour in Unity 3D.
My question is regarding inheritance. Im trying to set up some base logic that handles how and why states shoul be changed. But further down the inheritance line i need different kind of characters to be able to do character speicfic things. But im not able to do this with inheritnance.
Can someone confirm that my thinking is not how its done? then i know to find another solution.
PSEUDO CODE:
// STATE CONTROLLERS CONTROLL THE CHARACETER BY CHOOSING WITCH STATE THEY SHOULD BE IN
abstract class StateController {
StateBase state;
int HitPoints;
int Hunger:
abstract void Update()
{
CheckIfStateShouldChange();
state.UpdateState(this);
}
}
WolfStateController : StateController {
WolfState state;
override void Update()
{
base.Update();
state.Update(this);
}
}
SheepStateController : StateController {
SheepState state;
override void Update()
{
base.Update();
state.Update(this);
}
}
// STATES CONTAINS LOGIC FOR BEHAVIOUR IN A CERTAIN STATE
StateBase {
virtual void UpdateState( StateController controller)
{
// Does things all inheriting classes should do
}
}
WolfState : StateBase {
override void UpdateState( WolfStateController wolfstate)
{
base.UpdateState(WolfStateController wolfstate)
//Does wolf specific things that needs to be done in all WolfStates
}
}
WolfStalkAndHuntState : WolfState {
override void UpdateState( WolfStateController wolfstate)
{
base.UpdateState(WolfStateController wolfState);
//Hunts sheep and attacks on sight
}
}
SheepState : StateBase {
override void UpdateState( SheepStateController sheepState)
{
//Does sheepy things
}
}
SheepReproduceState : SheepState {
override void UpdateState( SheepStateController sheepState)
{
base.UpdateState(SheepStateController sheepState);
// Looks for mate and gets freaky
}
}
I would suggest something like this:
Controller Class:
public class SoliderController : MonoBehaviour
{
[HideInInspector] public sState currentState;
[HideInInspector] public FireState fireState;
[HideInInspector] public IdleState idleState;
[HideInInspector] public ChaseState chaseState;
private void Awake()
{
fireState = new FireState(this);
idleState = new IdleState(this);
chaseState = new ChaseState(this);
}
private void Start ()
{
currentState = idleState;
}
private void Update()
{
currentState.Update();
}
Interface:
public abstract class sState
{
public abstract void Update();
public abstract void ToChaseState();
public abstract void ToIdleState();
public abstract void ToFireState();
}
Example Class
public class IdleState : sState
{
private readonly SoliderController controller;
public IdleState(SoliderController soliderController)
{
controller = soliderController;
}
public override void Update()
{
Patrol();
//Condition to change state
if (*expresion1*)
ToChaseState();
if (*expresion2*)
ToFireState();
}
private void Patrol()
{
//Your Logic for the behvaiour wanted.
}
public override void ToChaseState()
{
controller.currentState = controller.chaseState;
}
public override void ToFireState()
{
controller.currentState = controller.fireState;
}
public override void ToIdleState()
{
Debug.LogWarning("Can't transition to same state");
}
}
This way checking for changes is way easier and also you can impletement state specific behaviours as well. Also, adding a new state goes really easy, you just implement the new Class and ToNewState method in the interface.
Hope it helped.