I am working in Unity 3d and I made a player script. Now I want to have different kinds of players but they all have the same movement behaviour. Since my object in inheriting from Monobehaviour I can't inherit from something else. How would I solve this, because I don't want to have to redo every player later on when I change something about the movement. I can solve it with namespaces but is that a good solution?
You have basically two options:
Create an abstract Player class, that inherits from MonoBehaviour, implementing only the movement. Then let the concrete player classes (e.g. Wizard or Warrior) inherit from it and handle the specific locig in those
Create a Player class and use the "Strategy-" (and maybe "Factory-") "Pattern", to create different types of players (you may also read up on "composition over inheritance")
An implementation of the first approach might look like this:
public abstract class Player : MonoBehaviour
{
public void Move() { /* Movement logic here */ }
public abstract void Attack(MonoBehaviour target);
}
public class Wizard : Player
{
public void Attack(MonoBehaviour target) { /* Attack logic for a wizard here */ }
}
public class Warrior : Player
{
public void Attack(MonoBehaviour target) { /* Attack logic for a warrior here */ }
}
// ---
// How to create players
var wizard = new Wizard();
var warrior = new Warrior();
And for the second approach:
public interface IAttack
{
public void Attack(MonoBehaviour target);
}
public class WizardAttack : IAttack
{
public void Attack(MonoBehaviour target) { /* Wizard attack logic here*/ }
}
public class WarriorAttack : IAttack
{
public void Attack(MonoBehaviour target) { /* Warrior attack logic here*/ }
}
public class Player : MonoBehaviour
{
private IAttack attack;
public Player(IAttack attack)
{
this.attack = attack;
}
public void Move() { /* Movement logic here */ }
public void Attack(MonoBehaviour target)
{
this.attack.Attack(target);
}
}
// ---
// How to create players
var warrior = new Player(new WarriorAttack());
var wizard = new Player(new WizardAttack());
As you can see, the second approach gives you a lot more flexibility, because you're of course not limited to having only an "attack strategy", but can create as many different ones as you like and - more importantly - combine them in any way imaginable. You could even "outsource" the movement logic into its own strategy and change it on the fly. For example, when the player picked up a power up. With inheritance, you would really struggle to have a Priest, which might combine traits of a Warrior and Wizard.
This also fits in nicely with how Unity was designed; that is, combining different components (e.g. Rigidbodies and Renderers) for increased functionality.
As for solving it with namespaces: How? Namespaces are nothing more as a way to organize code and avoid naming collisions. They don't solve the problem of having duplicate code.
solution for you is to use aggregation instead of inheritance. Unity very good support Entity Component System paradigm. To not duplicate check this thread
http://answers.unity3d.com/questions/669643/entity-component-system.html.
There is a ready framework to work with ECS for unity, you can check out to get a better idea of approach:
https://github.com/andoowhy/EgoCS
https://github.com/sschmid/Entitas-CSharp
I haven't use any of those, but I hope it helps to get into it. I would really recommend to go with it, regardless of the way you will implement it.
Just start thinking in a way of components, which mean if you have a several types of players, only difference is which components they construct of. Then you can have a movement component and use it on every player ;)
You can achieve this by simple and easy way by making a separate MoveScript, which will only have the move behaviour that is same for all players. Other variable behaviours you can write separate script (WizardScript and WarriorScript). So there will be two scripts on all players one MoveScript and one script (WizardScript and WarriorScript) related to their specific behaviour.
Sounds like a good time to use the strategy design pattern.
But first, let's generalize the players.
public abstract class Player : Monobehaviour
{
protected PlayerMovementStrategy moveStrategy;
protected void Start()
{
moveStrategy = new SimplePlayerMovementStrategy(this);
}
protected void Update()
{
moveStrategy.UpdateMethod();
}
protected void MoveTo(Vector3 location)
{
moveStrategy.MoveTo(location);
}
}
Obviously, from this we can create different types of players:
public class Archer : Player
{
//...
}
And how will the PlayerMovementStrategy class look like?
public abstract class PlayerMovementStrategy
{
protected Player _player;
public PlayerMovementStrategy(Player player_)
{
_player = player_;
}
public abstract void UpdateMethod();
public abstract void MoveTo(Vector3 location);
}
For an example:
public class SimplePlayerMovementStrategy : PlayerMovementStrategy
{
protected bool _move;
protected Vector3 _destination;
public SimplePlayerMovementStrategy(Player player_) : base(player_)
{
_move = false;
_destination = Vector3.Zero;
}
public override void UpdateMethod()
{
if (_move)
{
//_player.transform.position..
}
if (destinationReached) _move = false;
}
public override void MoveTo(Vector3 location)
{
_destination = location;
_move = true;
}
}
The advantage here is that you can write new movement strategy classes (that should confirm to PlayerMovementStrategy) and just assign them to your player class, whilst your MoveTo() method in your player class is completely controlled by whichever derived movement strategy you assigned it.
Related
Hiya - so i think i'm at a bit of a misunderstanding here with using abstract classes so if it isn't too much trouble i need someone to explain to me where i've gone wrong here;
So i have an abstract class structured like this:
[RequireComponent(typeof(Rigidbody2D))]
[RequireComponent(typeof(Collider2D))]
public abstract class Enemy : MonoBehaviour {
// Physics
protected Rigidbody2D rb;
protected Collider2D col;
protected virtual void Awake(){
// Physics
rb = this.GetComponent<Rigidbody2D>();
col = this.GetComponent<Collider2D>();
col.isTrigger = true;
}
}
Then a class inheriting it:
public class Whizzer : Enemy {
protected override void Awake(){
// Physics
rb = GetComponent<Rigidbody2D>();
rb.bodyType = RigidbodyType2D.Dynamic;
rb.gravityScale = 0f;
col = GetComponent<Collider2D>();
col.isTrigger = true;
}
}
However the problem i'm having is that when i reference rb or col inside the Whizzer class it's (i think) accessing the variables defined in the abstract Enemy class - this results in only one instance of a gameobject with this script working at a time since the last script to run assigns their rigidbody and collider as the variables in the abstract Enemy class
My question is how can i create seperate instances of the variables rb and col for every Whizzer class instance created whilst keeping the variables desired inside the abstract enemy class?
Or if this is even the right way to go about doing this?
Thankyou in advance!
They should be saparate. If you want a variable to be shared among all instances you should use "static" modifier. But you do not use that so each instance of Enemy (Whizzer is also an Enemy) should have it's own value for these fields. How do you know that they point to the same instances of collider and rigidbody?
About your implementation you are slightly missing the point:
public abstract class Enemy : MonoBehaviour {
protected Rigidbody2D rb;
protected Collider2D col;
protected virtual void Awake(){
rb = this.GetComponent<Rigidbody2D>();
col = this.GetComponent<Collider2D>();
col.isTrigger = true;
}
}
Then a class inheriting it:
public class Whizzer : Enemy {
protected override void Awake(){
base.Awake();
rb.bodyType = RigidbodyType2D.Dynamic;
rb.gravityScale = 0f;
}
}
That makes more sense imho, you should use base method in most cases instead of pasting the same chunk of code to override
public class GameControl : NetworkBehaviour {
public GameObject localPlayer;
[TargetRpc]
public void TargetGetLocalPlayer(NetworkConnection conn)
{
localPlayer = GameObject.Find("Local");
}
public override void OnStartServer()
{
base.OnStartServer();
TargetGetLocalPlayer(connectionToClient);
}
}
i have a script attached to a server object which should be able to grab the local player GameObject (which i denoted by changing it's name to 'Local' once it's spawned in another script) from the client but when i try to call TargetGetLocalPlayer , i get the following error :
Exception in OnStartServer:Object reference not set to an instance of an object at
UnityEngine.Networking.NetworkBehaviour.SendTargetRPCInternal
(UnityEngine.Networking.NetworkConnection conn, UnityEngine.Networking.NetworkWriter writer,
System.Int32 channelId, System.String rpcName) [0x0002e]
i am totally new to networking in unity and i feel like i should have gone with photon instead of unet , it seems like no one is interested in unet anymore and the docs suck at explaining anything and i will be very grateful if anyone could answer me , thanks in advance
I think a better solution would be to attach a script to each player. and make it so that when a "new" player joins it runs a method in your GameControl to add the player to a player list. like this:
//this is on your gameControl.
public List<Player> players;
public void AddPlayer(Player player)
{
players.Add(player);
}
this is working if you have your GameControl as a singleton. if you do not know how to do that check the last piece of code.
//this is on your Player script called player(if you have another name change all
//Player scripts in here to that name
public void Start()
{
GameControl.AddPlayer(this);
}
Or
Instead of making the players list a List you can make it a dictionary and make a key for each player who joins to make it more accesible.
How to make a script a singleton and why.
why:
if you have a manager class/script you always want there to only be ONE instance of it so you can call its methods easily and without problems.
How:
Basically you make it so that Only THIS script can change values and variables in the manager, and other scripts can get/call methods and functions. this makes it easily accesible and you will have less problems.
private static GameControl _GameControl;
private Player player;
public static GameControl gameControl
{
get
{
if(_GameControl == null)
{
Debug.LogError("Game Control is null");
}
return _GameControl;
}
}
void Awake()
{
_GameControl = this;
player = GameObject.Find("Player").GetComponent<Player>();
}
Well i see what you mean, well create a method that can be run by any script like a singleton. then you pass in the gameobject that you want to add like this:
public class GameManager
{
public GameObject _player;
//this is a singleton.
private static GameManager _gm;
public static GameManager gameManager
{
get
{
if(_gm == null)
{
Debug.LogError("Game manager is null");
}
return _gm;
}
}
void awake()
{
_gm = this;
}
void GetPlayer(GameObject player)
{
_player = player;
}
void AddPlayer(GameObject player)
{
//add it to whatever you want to.
}
}
call the method this way:
public class Player : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
GameManager.gameManager.GetPlayer(this.gameObject);
}
}
I have a Unity Project which should create a HoloLens Application. Now i want to test some of my Code in Unity with NUnit. I created some PlayMode tests
public class Test
{
private GameObject empty;
[SetUp]
public void BeforEveryTest()
{
empty = new GameObject();
empty.AddComponent<MainController>();
}
// A Test behaves as an ordinary method
[Test]
public void MainControllerGetInstance()
{
Assert.IsNotNull(MainController.Instance);
}
}
This is the Singleton I want to test
public class MainController : Singleton<MainController>
{
[SerializeField]
private GameObject SomePref;
private void Start()
{
SomePrefab.SetActive(true);
}
}
I get an error that SomePref is not set to an instance of an object, but I can't set it up because its a private seralizedfield.
Now what is good practice to erase that error. I know, I could use Resource.Load in the Start Methode from MainController, but is this the good way?
I have an array of prefabs and I want to be able to Instantiate randomly picked prefabs thru Zenject Factory and perform their bindings in their sub-containers.
What I want to do is the same as in this code sample from Zenject documentation, but for randomly selected prefabs.
https://github.com/modesttree/Zenject/blob/master/Documentation/SubContainers.md#using-game-object-contexts-no-monobehaviours
using UnityEngine;
using Zenject;
public class GameInstaller : MonoInstaller
{
[SerializeField]
GameObject ShipPrefab;
public override void InstallBindings()
{
Container.BindInterfacesTo<GameRunner>().AsSingle();
Container.BindFactory<float, ShipFacade, ShipFacade.Factory>()
.FromSubContainerResolve().ByNewPrefabInstaller<ShipInstaller>(ShipPrefab);
}
}
I was able to partially make it work with
[SerializeField] private GameObject[] ships;
...
Container.BindFactory<float, ShipFacade, ShipFacade.Factory>()
.FromSubContainerResolve().ByNewGameObjectMethod(SpawnShip);
...
private void SpawnShip(DiContainer container, float speed)
{
container.Bind<ShipFacade>().AsSingle();
container.Bind<Transform>().FromComponentOnRoot();
var shipPrefab = ships[Random.Range(0, ships.Length)];
var ship = container.InstantiatePrefab(shipPrefab);
container.Bind<ShipHealthHandler>().FromNewComponentOn(ship).WhenInjectedInto<ShipFacade>();
container.BindInstance(speed).WhenInjectedInto<ShipInputHandler>();
}
But it's awkward and in this case I guess I'm not using an advantage of sub-container. And also prefabs spawns in an empty GameObject.
What I want to achieve is to be able to use ShipInstaller for sub-container binding.
You're right, there wasn't really a very elegant way to choose the sub-container prefab dynamically.
I took some time to make this better today with this commit. If you use the latest version of Extenject then you can now do things like this:
public class QuxInstaller : Installer {
float _speed;
public QuxInstaller(float speed) {
_speed = speed;
}
public override void InstallBindings() {
Container.BindInstance(_speed);
Container.Bind<QuxFacade>().AsSingle();
}
}
public class CubeInstaller : MonoInstaller
{
public List<GameObject> QuxPrefabs;
public override void InstallBindings()
{
Container.BindFactory<float, QuxFacade, QuxFacade.Factory>()
.FromSubContainerResolve().ByNewPrefabInstaller<QuxInstaller>(ChooseQuxPrefab);
}
UnityEngine.Object ChooseQuxPrefab(InjectContext _) {
return QuxPrefabs[Random.Range(0, QuxPrefabs.Count)];
}
}
If an object has a component with a baseclass BaseClass<T>, calling GetComponent<BaseClass<T>>() will not return that component. The generic argument seems to throw it off, as a BaseClass without using generics will correctly return the derived class as a component when GetComponent<BaseClass>() is called.
Does anyone know a solid workaround for this? The use of a generic arg in this class is somewhat important, so I'd obviously rather not re-write the program's class structure just to accommodate this.
Here's a rough sketch of the classes in question
//the base class that I'd like to be able to fetch the subclasses of using GetComponent
public abstract class BaseUIClass<T> : MonoBehaviour where T :BaseEntity {}
//EntityType1&2 are derived from BaseEntity
public class DerivedUIClass1 : BaseUIClass<EntityType1> {}
public class DerivedUIClass2 : BaseUIClass<EntityType2> {}
BaseUIClass has this method:
public virtual void Setup(T entity) {}
Which needs to be called shortly after the component is added to a GO.
EDIT:
Effectively what I'm looking to do is the following, without having to hard-code in (I want to actually make use of the generic args I've defined)
if(uiClassObj is typeof(DerivedUIClass1) go.GetComponent<BaseUIClass<EntityType1>>();
else if(uiClassObj is typeof(DerivedUIClass2) go.GetComponent<BaseUIClass<EntityType2>>();
//etc
But considering that there is a component of type BaseUIClass<BaseEntity> on the go, and the two+ derived classes I'm interested in are defined by DerivedUIClass1<EntityType1> and DerivedUIClass2<EntityType2>, the conversion should surely just be implicit?
You can't do what you want the way you want, because Unity doesn't accept components which are generic classes.
I.E., if you have:
public class MyGenericClass<T> : MonoBehaviour {}
you will not be able to add it as a component unless you specify T via a subclass inheriting from it:
public class MySpecifiedClass : MyGenericClass<[specificType]> {}
So, to solve your problem, you should simply implement a specific interface for everything that should be done when the base class is added as a component. I'll show you an example with float and int derived types, you can extend easily to any type you need.
BaseClass
using UnityEngine;
public interface ISetup {
void CallSetup();
}
public class BaseClass<T> : MonoBehaviour, ISetup {
public T myEntity;
public void CallSetup() {
Setup(myEntity);
}
private void Setup(T entity) {
Debug.Log(entity);
//Your setup code
}
}
Your components classes
public class BaseClassInt : BaseClass<int> {
private void Awake() {
myEntity = 25;
}
}
public class BaseClassFloat : BaseClass<float> {
private void Awake() {
myEntity = 10.6f;
}
}
Code that gets the interface and calls Setup()
var componentsWithSetup = GetComponents<ISetup>();
foreach (var component in componentsWithSetup) {
component.CallSetup();
}
One workaround would be to use a specific type instead of a generic type at the top level for each type that you need the base class for.
For example:
public class SpecificToFirstTypeClass : BaseClass<FirstType>
{
}
and then use GetComponent<SpecificToFirstTypeClass>
Based on a suggestion from this answers.unity.com question
I couldn't believe this didn't work so I hacked it using tags, where UI is a base, abstract class:
public UI currentUI;
public void GetUI()
{
foreach (Transform child in transform)
{
if (child.tag == "UI Canvas")
{
currentUI = child.GetComponent<UI>();
}
}
}
Where I may have a different active "UI" depending on the scene. It might be the main game scene, or the menu scene. Now, I can just interact with the UI manager via generic/abstract methods like LoadUI() or HideUI().
Tbf, this really should be handled by events but it's overkill for my current project.