Unity3D - Abstract Class, Monobehaviour functionality - c#

PREAMBLE
So I've been floating this question around on the Unity Answers forum for the past couple of weeks without a single reply. It seems to me that it would be a relatively straight forward question so I was wondering if anyone here might be able to help me with it in lieu of an answer over there.
So I have a bit of a dilemma.
GOAL
I want to use an inheritance structure for guns in my game. I want to make an abstract class (Gun) and then have subclasses of Gun (Laser, Rifle, RocketLauncher) which inherit from it and then create specific instances of these weapons. At the moment I'm doing this as an abstract class because there's a large variance between the way different subclasses of Gun implement the Shoot() function. (i.e. A rocket launcher will instantiate X Rocket GameObjects whereas a laser will probably use raycasting to determine an instant hit).
Furthermore, I would like to be able to have a List of Guns in the Players inventory and don't want to have to add and remove Scripts from my Player dependant on their active weapon. It's just nice for me if I can construct them in the following fashion:
Rifle ActiveWeapon = new Rifle(parameters);
I also don't want to have a finite number of weapons available to the player but want to be able to generate them at runtime (as drops or crafted for example) so I also don't want to write a fixed number weapon scripts.
ISSUE
The issue I'm encountering is that I want to be able to utilise some of the methods which are derivative of MonoBehaviours for example :
public override void Shoot ()
{
if (CanShoot)
{
StartCoroutine(Cooldown());
Rigidbody bullet = Rigidbody.Instantiate(BulletPrefab, transform.position, transform.rotation);
... // Addforce etc.
...
}
}
public IEnumerator Cooldown()
{
CanShoot = false;
yield return new WaitForSeconds (FireRate);
CanShoot = true;
}
Obviously since this isn't a MonoBehaviour class I can't use functions such as Instantiate to create bullets or WaitForSeconds to stagger them.
How should I tackle this issue?

You can have an abstract class that inherits from MonoBehaviour, the following works fine:
public abstract class AbstractClass : MonoBehaviour
{
protected abstract void pouet();
}
public class InheritClass : AbstractClass
{
// Use this for initialization
void Start ()
{
StartCoroutine(pouetCoroutine());
}
protected override void pouet()
{
Debug.Log("pouet");
}
private IEnumerator pouetCoroutine()
{
yield return null;
pouet();
}
}
If you don't want your abstract class to be in a GameObject, you could use an external gameobject to manage the instanciation with an object pooling system.
About the coroutines, you could use a coroutine engine that starts and stop them. You only need to have a dictionary and a public function to start the coroutine and keep an id as a key to access it if you need to stop it later.
private startcoroutine(int id, IEnumerator ienum)
{
Coroutine c = StartCoroutine(ienum);
dictionary.Add(id, c);
}

Related

Unity Inheritance & scope [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 7 months ago.
Improve this question
C++ Developer trying to get started in Unity here.
I've been playing around in Unity and watching some videos / reading the documentation.
I have a couple of questions on inheritance and I hoping someone can point me to something helpful.
MonoBehaviour: this seems to be the standard, most scripts will use this, it is attached to a gameobject and is 'generally' destroyed with the scene.
ScriptableObject: allows you to create 'assets' outside of the scene, which have persistence.
: testing has shown that you can create 'normal' classes and Instantiate them inside of inherited classes with 'new'.
Is it possible to inherit from more than one class? I assume so, but what are the rules here?
Can I do:
Baseclass
SubClass : ScriptableObject, Baseclass
OR
Baseclass
SubClass : MonoBehaviour, Baseclass
etc?
Or do I have to use the same inheritance everytime? ie.
Baseclass : ScriptableObject
SubClass : ScriptableObject, Baseclass
Also can how do I get a 'ScriptableObject' into a scene?
I have managed to do as so:
using UnityEngine;
[CreateAssetMenu(fileName = "Data", menuName = "ScriptableObjects/PlayerData", order = 2)]
public class PlayerData: ScriptableObject
{
public int maxHealth;
public PlayerDictionary.PlayerEnum playerEnum;
}
using System.Collections.Generic;
using UnityEngine;
public class PlayerDictionary : MonoBehaviour
{
[SerializeField] private PlayerData[] playersInGroup;
private readonly Dictionary<PlayerEnum, PlayerData> playerFinder = new Dictionary<PlayerEnum, PlayerData>();
private void Awake()
{
for (int x = 0; x <= playersInGroup.GetUpperBound(0); x++)
{
playerFinder.Add(playersInGroup[x].playerEnum, playersInGroup[x]);
}
}
public PlayerData FindPlayer(PlayerEnum type)
{
PlayerData ret = null;
playerFinder.TryGetValue(type, out ret);
return ret;
}
public enum PlayerEnum
{
TILE_TYPE_NONE = 0,
TILE_TYPE_ENEMY,
TILE_TYPE_ENEMY_ALT1,
TILE_TYPE_ENEMY_ALT2
}
}
One final thing:
How do keep things in scope?
I have two ways;
static vars in a class, the is not in the scene at all.
DontDestroyOnLoad(gameObject);
Are there other / better methods to keep things in scope when switching scenes?
Is it possible to inherit from more than one class?
No, you can't inherit from multiple classes, but you can implement multiple interfaces
I have two ways;
static vars in a class, the is not in the scene at all.
DontDestroyOnLoad(gameObject);
This is the preferred way of keeping game objects across all scenes, this can be achieved by the following code
public class SomeClass : MonoBehaviour
{
public static SomeClass instance;
// other variables
private void Awake()
{
if (instance == null)
{
instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
// other methods
}
A bit of explanation here, the first time the scene loads, instance is null. In the Awake() method, we set instance to this, which refers to this script and we set DontDestroyOnLoad(). So when you switch scenes, the gameobject to which this script is attached stays in the hierarchy. But when you reload the same scene, you don't want a duplicate of this gameobject. If you are reloading into this same scene, all the gameobjects get created in which there will also be a gameobject that was set to DontDestroyOnLoad() previously. Now in the newly created gameobject, the script's instance is not null this time, hence it gets destroyed. Finally, there's only one gameobject with this script, that was previously set to DontDestroyOnLoad().

Getting a script from a gameobject using a string Unity

I am working on a simple weapons controller for my FPS game and have come across an issue while trying to make it dynamic. What I want to happen when the player picks up a weapon is for the weapons stats and effects to be set as default. To do this each weapon has a script which is
weapon.name + "Stats"
But I'm having issues referencing said script. Right now, this is what my code looks like:
string weaponScriptName = weapon.name + "Stats";
weapon.GetComponent<weaponScriptName>().UseWeapon();
where weapon represents the currently equipped weapon's gameobject. Obviously, this doesn't work and various implementations that I've found on the Unity help pages only cause more errors. What can I do to solve this issue?
Thank you.
GetComponent has multiple overloads.
There is the generic version you refer to - the most commonly used one
T GetComponent<T>()
which you can only use with a compile time constant type parameter such as
var renderer = GetComponent<Renderer>();
There is one using a dynamic Type
Component GetComponent (Type type)
like
// Just an example, there are many ways of getting a type
var type = typeof(Renderer);
var renderer = (Renderer) GetComponent(type);
And finally there is one taking a string
Component GetComponent (string typeName);
like
// Again just an example, there are many ways of getting type names
// Especially when dealing with multiple assemblies you might even have to use the AssemblyQualifiedName
var renderer = (Renderer) GetComponent("Renderer");
Note that for any of the dynamic versions you either have to type cast or if it is enough to have only a generic Component reference you can use it of course.
However, as said if anyhow possible don't use string versions at all!
It is always slow and error prone. Rather use e.g. some common base class or interface, or use an enum or Dictionary to decide what to do for which state.
So I would rather have e.g.
public interface IWeapon
{
void UseWeapon();
}
and then every of your different weapons can implement this interface
public class WeaponA : MonoBehaviour, IWeapon
{
public void UseWeapon ()
{
Debug.Log("Used Weapon A");
}
}
public class WeaponB : MonoBehaviour, IWeapon
{
public void UseWeapon ()
{
Debug.Log("Used Weapon B");
}
}
and your code would simply be
var weapon = someObject.GetComponent<IWeapon>(). UseWeapon();
Or if your weapons all share some common implementation such as pickup etc rather have a common base class
public abstract class BaseWeapon : MonoBehaviour
{
// Everything that all weapons share as behavior and properties
// every subclass HAS TO implement and override this method
public abstract void UseWeapon ();
// Alternatively if there even is some common behavior
// subclasses CAN but don't have to override this
//public virtual void UseWeapon ()
//{
// // Implementation that is the default behavior
//}
}
And then
public class WeaponA : BaseWeapon
{
public override void UseWeapon ()
{
Debug.Log("Used Weapon A");
// If using virtual before then this class IGNORES and fully overwrites the default implementation
}
}
public class WeaponB : BaseWeapon
{
public override void UseWeapon ()
{
// If using virtual and you WANT the default behavior then add
//base.UseWeapon();
Debug.Log("Used Weapon B");
}
}
and your code would simply be
var weapon = someObject.GetComponent<BaseWeapon>(). UseWeapon();
If i got it correctly, you want to do something like GetComponent<"Transform">() ?
If so, you should do GetComponent("Transform"); instead

Access variable from another non MonoBehaviour class

Hi I would like to get a variable from another class in Unity
public class CameraMove : MonoBehaviour {
public GameObject lookTarget;
public GameObject MainCamera;
public GameObject nextMovePoint;
private int currentPoint = 0;
[System.Serializable]
public class Paths
{
public float time;
public Transform[] movePoints;
}
public Paths[] Path;
void Update()
{
Paths paths = gameObject.GetComponent<Paths>();
Debug.Log (paths.movePoints [currentPoint]);
if (nextMovePoint.transform.position != paths.movePoints [currentPoint].position)
{
currentPoint += 1;
nextMovePoint.transform.position = paths.movePoints [currentPoint].position;
}
iTween.MoveTo(nextMovePoint,iTween.Hash("time",paths.time));
}
I want to get the movePoints from class Paths, but GetComponent gives me an error? How do I get that variable?
Still struggling with this, anyone got an idea?
You do not need to use GetComponent to get access to your paths.
First, I'm going to clean up your naming a little bit so that what we're talking about a little clearer:
public class CameraMovement : MonoBehaviour {
public GameObject LookTarget;
public GameObject MainCamera;
public GameObject NextMovePoint;
public Path[] Paths;
private int _currentPoint = 0;
}
[System.Serializable]
public class Path {
public float Time;
public Transform[] MovePoints;
}
As I understand it, you want to use your Paths in your Update event. Because Paths is just a regular variable on the component, like LookTarget or MainCamera, you can just refer to it directly. Thus:
void Update() {
Path path = Paths[/*some index*/];
Debug.Log(path.MovePoints[_currentPoint]);
// etc.
}
GetComponent is used to grab a different MonoBehaviour which is attached to the game object. When it's part of the serialized information, it's already on your class and you can just use it directly.
From you code I see that your Path class is inner(local class) of your CameraMove class. So I think you want to move it to separate class(read like file) and attach that as component to what ever game object you need. So then you can use getComponent<Path>() on that object. However if you want Path class to be inner class so you need to initiate (in Start())it like
Path[0]=new Path(); //etc
Then you can use it inside your CameraMove class as
Path[0].movePoints[currentPoint];
But if you explain more what you want to achieve then I can provide more accurate solution.
The primary objective of GetComponent is to get a component attached to the game object. This means that GetComponent can only get objects of classes deriving from UnityEngine.Component. Note that MonoBehaviour is a subclass of Component, so any and all MonoBehaviour subclasses are subclasses of Component. Also note that only components, i.e. classes deriving Component show up in the inspector of a game object.
The class Paths does not inherit the class Component, either directly or indirectly. Thus it can't be added to an object or retrieved from an object using GetComponent.
As you specify in the comments, an object of type CameraMove.Paths is in another component of type CameraMove. If the other CameraMove component is on another game object, things become incredibly easy. You won't even need GetComponent for this. Just create a field public CameraMove otherCameraMove (I prefer a private field with the SerializeField attribute, but it doesn't matter here since you're obviously new to Unity) and select the other CameraMove object in the inspector. You'll be able to access the paths variable as otherCameraMove.Path.
If the other CameraMove component is on the same object however, things get more tricky. I, for one, suggest that game objects should never, ever have more than one instance of the same component. But if you do, you'll have to use GetComponents<CameraMove>() to retrieve all components of type CameraMove on the game object and iterate through the array looking for the CameraMove component which you want. This is just one of the reasons why in my opinion same components shouldn't be added to the same object more than once.

C# - Create class instance inside another class with same meaning

I want to create a class named Enemy, which should be used in a programmed rpg-themed-battlesystem. The problem is that I would want to create multiple monster types in the Enemy class, but then I would have to create a possibility for the battlesystem with every enemy class for example Enemy.Goblin or Enemy.Golem.
Question:
How could I achieve this by using only one parameter in the battlesystem function? I wanted to use
public static void InitiateBattle ( Player player, Enemy enemy )
but now I cannot use the Enemy.Goblin instance, because it cant implicitly convert Enemy.Goblin to Enemy. How could I most easily and with minimal code fix this?
You need to use inheritance.
public class Enemy
{
// put all properties and methods common to all here
}
public class Goblin: Enemy
{
// goblin specific stuff here
}
you will then be able to pass in a goblin as an enemy.
It sounds like you want to use inheritance?
public class Enemy {}
public class Goblin : Enemy {}
public class Golem : Enemy {}
You can then pass in an instance of Goblin or Golem to your method and the statement will be valid because the compiler will 'box' your object into an instance of the parent type.
Then, if you want to use a member from the Goblin or Golem subclasses, you would need to 'cast' the enemy parameter variable back into the appropriate type using as:
public static void InitiateBattle (Player player, Enemy enemy)
{
var golem = enemy as Golem;
var goblin = enemy as Goblin;
}
Make sure you check for null after the cast!
Bear in mind that C# does not allow multiple-inheritance; each class can inherit from only one parent.
I think it would be best to use interface.
public interface IEnemy
{
//e.g.
public void Attack();
}
public class Goblin : IEnemy
{
public void Attack()
{
throw new System.NotImplementedException();
}
}
public class Battle
{
public static void InitiateBattle(Player player, IEnemy enemy);
}

How to Clone a child of an abstract class in WP7 XNA

I'm working on a game for WP7 with XNA. Here is my structure:
public abstract class enemy
{}
Child elements:
public class genericEnemy : enemy{}
...
public class snake : enemy {}
etc...
In WP7, a lot of things have been moved around and/or removed (especially with Serialization) it seems. Despite much searching, I haven't been able to find a solution. I'm trying to duplicate the child elements.
For example: On loading a level, I pass an array of three different enemies into the loading phase. During loading, I need to duplicate each of those enemies so that 20 of each are flying around doing their own thing during gameplay.
All the solutions I've seen refer to things that are not present in the WP7 library.
There's no "library" way of doing this as far as I know. One solution would be:
1) Declare a Clone() method in enemy that returns a copy of that enemy.
abstract class Enemy {
public abstract Enemy Clone();
}
2) Implement it in every concrete type, so a Snake creates a new Snake, etc. Example:
class Snake : Enemy {
int speed;
public override void Enemy Clone() {
var clone = new Snake();
clone.speed = speed;
return clone;
}
}
3) Now any object of a concrete type knows how to clone itself, so if you have an array of Enemies, you can call Clone() on each and it will create the proper concrete type in the proper way.
Create an enemy factory that can create enemies from an id of some sorts. While loading your level, you can then call the factory when you need to create an enemy:
class EnemyFactory
{
Enemy CreateEnemy(int id)
{
if (id == 0)
return new Snake();
return new GenericEnemy();
}
}
void LoadLevel()
{
// bla bla
Level level = new Level();
int enemyId = LoadFromFile();
level.AddEnemy(EnemyFactory.CreateEnemy(enemyId));
}
This way you get rid of the nasty cloning code, and you can control all enemy instantiation in the factory class.
use an abstract method that calls a copy constructor:
public abstract class Enemy
{
private readonly int mEnemyData;
protected Enemy(Enemy pEnemy)
{
mEnemyData = pEnemy.mEnemyData;
}
public abstract Enemy Clone();
}
public sealed class GenericEnemy : Enemy
{
private readonly double mGenericEnemyData;
private GenericEnemy(GenericEnemy pGenericEnemy)
: base(pGenericEnemy)
{
mGenericEnemyData = pGenericEnemy.mGenericEnemyData;
}
public override Enemy Clone()
{
return new GenericEnemy(this);
}
}

Categories

Resources