So I'm trying to make a game with diferent unit types, each one with diferent movement speeds. I don't really know how to face this problem: At first I did a function on every unitType to define the speed but I don't think this is the most optimal way to do it. Now i was thinking about creating a MovementController class. This is what I did so far: First of all, I did a generic Unit Class -to keep it simple, I will just show the movement speed attribute- :
public class GenericUnit : MonoBehaviour
{
float movementSpeed;
public float mspeed
{
get { return movementSpeed; }
set { movementSpeed = value; }
}
}
after, I defined all atributes in a subClass depending of the unit type:
public class Archer : MonoBehaviour
{
GenericUnit archer;
void Start()
{
archer = new GenericUnit();
archer.mspeed = 3.0f;
}
}
This is the Movement controller I have right now:
public class Movement_Controller : MonoBehaviour
{
GenericUnit msUnit;
public float mspeed;
public bool movimentTrue;
void Start()
{
msunit = new GenericUnit();
mspeed = msUnit.mspeed;
movimentTrue = true;
}
void Update()
{
if (movimentTrue) {
Movement();
}
}
public void Movement(){
Vector3 moviment = new Vector3 (1.0f, 0.0f, 0.0f);
transform.position += Time.deltaTime * mspeed * moviment;
}
}
After all, this isn't working. Do you have any idea of whats the issue? I have been trying many other options that didn't make it. Do you think it's better to have this MovementController script or pasting the movement function on the Archer class? As you can see, im a novice on programming, so any suggestion would help a lot.
Thank you
What you are looking for is simple inheritence.
You describe the features you need in a base class and mark them (and the class) abstract. This enforces you to override it in all non abstract derived classed.
Base class:
public abstract class BaseUnit : MonoBehaviour
{
protected abstract float movementSpeed { get; } // force every deriving class to provide a movement speed
[SerializeField] private bool isMoving;
void Start()
{
isMoving = true;
}
void Update()
{
if (isMoving)
{
Movement();
}
}
public void Movement()
{
Vector3 direction = new Vector3(1.0f, 0.0f, 0.0f);
transform.position += Time.deltaTime * movementSpeed * direction;
}
}
Derived class Archer:
public class Archer : BaseUnit
{
protected override float movementSpeed => 3F;
}
Also note that you cannot create MonoBehaviours using the new keyword. You have to use AddComponent<T> to create or GetComponent<T> to retrieve them (see documentation).
Related
Here is all the elements of my problem: i have a "ShotPattern" class that is supposed to define a bullet pattern that would be shot by a firearm.
A Shot Pattern in code:
[System.Serializable]
public class ShotPattern : MonoBehaviour
{
[Header("Projectile info")]
[SerializeField]
private GameObject projectile;
[SerializeField]
private AMovementPattern projectileMovementPattern;
[Header("Pattern shape info")]
[SerializeField]
private Vector2 startVector = Vector2.zero;
[SerializeField]
private Vector2 endVector = Vector2.zero;
[SerializeField]
private int projectileAmount = 1;
[SerializeField]
private float timeBetweenShots = 0;
[SerializeField]
private float speedIncrementBetweenShots = 0;
private void Start()
{
Shoot(transform.position);
}
public virtual void Shoot(Vector3 origin)
{
float angleDelta = (Vector2.SignedAngle(startVector,endVector)) / projectileAmount;
for (int i = 0 ; i < projectileAmount ; i++)
{
GameObject currentProjectile = Instantiate(projectile,origin,quaternion.identity);
StraightMovePattern currentMovePattern = currentProjectile.AddComponent<StraightMovePattern>();
currentMovePattern.direction = startVector.normalized;
}
}
}
A ShotPattern in inspector
I also have a few classes that define movement patterns for the resulting projectiles (move straight, in a sine wave, etc), these classes all inherit from the "AMovementPattern" Class.
The AMovementPattern abstract class in code:
[RequireComponent(typeof(Rigidbody2D))]
public abstract class AMovementPattern : MonoBehaviour
{
/**
* Direction of the movement pattern. (is down by default since we assume movement patterns are mostly used by enemies)
*/
[Header("General movement info")]
public Vector2 direction = Vector2.down;
/**
* Speed of the movement pattern.
*/
[SerializeField]
protected float speed = 1f;
protected Rigidbody2D rb;
protected virtual void Start()
{
rb = GetComponent<Rigidbody2D>();
}
protected virtual void FixedUpdate()
{
MovementUpdate();
}
/**
* Method to calculate the new position of the object based on the movement pattern and previous position.
*/
protected virtual void MovementUpdate()
{
throw new NotImplementedException();
}
}
A Movement pattern subclass in code:
public class StraightMovePattern : AMovementPattern
{
protected override void MovementUpdate()
{
rb.velocity = direction * speed;
}
}
A Movement pattern subclass in inspector
As you can tell from the ShotPattern inspector tab i want to be able to give a MovementPattern script to the ShotPattern editor for it to add to the projectiles it will produce on instanciation.
Unity won't let me just slide one of my MovePattern subclasses into the "AMovePattern" field, I am guessing it is because this field wants an instance of those classes with its parameters already set , is there a way to make it so that i could slide any of the MovePattern scripts in there and have the the corresponding inspector tab appear?
I know that i could make an empty GameObject that holds a movement pattern script, and initialize the values of that movement script in this GameObjects editor, but that kind of defeats the purpose of having it in inspector where i can edit the overall pattern on the fly to test out different compositions, so i'd like to avoid that.
I would suggest add those changes make MovementUpdate abstract like this so you will need to always implement this method.
protected abstract void MovementUpdate();
The second I would separate data and execution of pattern to two separate classes. Then I would redo the ShootPattern like this.
[Header("Projectile info")]
[SerializeField]
private Projectile projectile;
[SerializeField]
private AMovementPattern projectileMovementPattern;
public virtual void Shoot(Vector3 origin)
{
float angleDelta = (Vector2.SignedAngle(startVector,endVector)) / projectileAmount;
for (int i = 0 ; i < projectileAmount ; i++)
{
var currentProjectile = Instantiate(projectile,origin,quaternion.identity);
currentProjectile.Init(projectileMovementPattern, startVector.normalized);
}
}
And I suggest you to use ScriptableObject to hold such a data.
Remark from a Unity/C# guy
It has nothing to do with a abstract class.
I am making a game when you gather but my MoveTowards method just teleports the object there or to the position I asked. I have made my own MoveTo method so here are my 3 scripts. One is an IUNit script which is an interface and holds the method MoveTo:
using System;
using UnityEngine;
public interface IUnit
{
void MoveTo(Vector2 position, float stopDistance, Action onArrivedAtPosition);
}
Here is my Unit script which is where the AI goes:
using UnityEngine;
public class Unit: MonoBehaviour, IUnit
{
public float speed;
public void MoveTo(Vector2 position, float stopDistance, System.Action arrivedAtPosition)
{
if (Vector2.Distance(transform.position, position) >= stopDistance)
{
transform.position = Vector2.MoveTowards(transform.position, position, speed * Time.deltaTime);
}
}
}
Then my GathererAI which activates my script:
using UnityEngine;
[RequireComponent(typeof(Unit))]
public class GathererAI : MonoBehaviour
{
public Transform treeNode;
public Transform storageNode;
public IUnit unit;
private void Start()
{
unit = gameObject.GetComponent<IUnit>();
unit.MoveTo(treeNode.position, 10f, null);
}
}
But the thing is it just teleports to the position without slowly moving. Please help?
Also, how do you start an action?
Instead of MoveTo, you can try using a Rigidbody (rb), and do:
public Rigidbody2D rb;
public float speed, acceleration;
public Transform goHere;
void FixedUpdate()
{
Vector3 direction = goHere.position - transform.position;
direction = direction.normalized;
Vector3 targetVelocity = new Vector3(direction.x * speed, direction.y * speed);
rb.velocity = Vector3.Lerp(rb.velocity, targetVelocity, acceleration);
}
Unless you don't want to use Rigidbodies, in which case, you should still have a target Vector (direction or location), and Lerp towards that position in a similar fashion ( Lerp = Linear interpolation).
Here are your three scripts which I changed a bit to make it work.
IUnit.cs:
using System;
using UnityEngine;
public interface IUnit
{
void MoveTo(Vector2 position, float stopDistance, Action onArrivedAtPosition);
}
Unit.cs:
using UnityEngine;
public class Unit: MonoBehaviour, IUnit
{
public float speed = 5;
public void MoveTo(Vector2 position, float stopDistance, System.Action arrivedAtPosition)
{
if (Vector2.Distance(transform.position, position) >= stopDistance)
{
transform.position = Vector2.MoveTowards(transform.position, position, speed * Time.deltaTime);
}
}
}
GatheredAI.cs:
using UnityEngine;
[RequireComponent(typeof(Unit))]
public class GathererAI : MonoBehaviour
{
public Transform treeNode;
public Transform storageNode;
public IUnit unit;
private void Start()
{
unit = gameObject.GetComponent<IUnit>();
// unit.MoveTo(treeNode.position, 10f, null);
}
void Update()
{
unit.MoveTo(treeNode.position, 10f, null);
}
}
Usually Vector2.MoveTowards is called in a repeating function, and yours was being called only once. And your speed may have been a bit too much for the distance that your body was supposed to travel, so it just teleported in that single call.
Also it won't be ideal to invoke an Action in this case because you are not finishing the complete movement in single call. To do that you will need to convert this MoveTo function to a coroutine maybe to loop over Vector2.MoveTowards and when the coroutine comes to an end then you simply call the action parameter as a function. It will simply call a callback function when your movement is done.
public void MoveTo(Vector2 position, float stopDistance, System.Action arrivedAtPosition)
{
// your move logic here....
arrivedAtPosition();
//if you need to pass some argument say a bool, then change function parameter as "Action<bool> arrivedAtPosition" and call "arrivedAtPosition(true);"
}
I want to start learning Unity DOTS with a simple 2D movement. I took the currently newest video to dive into it
https://www.youtube.com/watch?v=BNMrevfB6Q0
and know how to setup a custom component with a system. But for the movement I need the Rigidbody2D component now so I attached it to my gameobject.
My conversion script converts that GO to an entity
public class MovementBehaviour : MonoBehaviour, IConvertGameObjectToEntity
{
[SerializeField]
private float movementSpeed;
public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
{
dstManager.AddComponentData(entity, new MovementInputComponent());
dstManager.AddComponentData(entity, new MovementSpeedComponent { Value = movementSpeed });
// rigidbody gets converted automatically ... ?
}
}
And the system should handle a simple physics movement
public class MovementSystem : JobComponentSystem
{
[BurstCompile]
struct MovementJob : IJobForEach<MovementInputComponent, MovementSpeedComponent, Rigidbody2D>
{
public float fixedDeltaTime;
public void Execute(ref MovementInputComponent movementInput, ref MovementSpeedComponent movementSpeed, ref Rigidbody2D rigidbody)
{
// rb.MovePosition(rb.position + movementInput + movementSpeed * fixedDeltaTime);
}
}
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
MovementJob job = new MovementJob { fixedDeltaTime = Time.fixedDeltaTime };
return job.Schedule(this, inputDeps);
}
}
Unfortunately the MovementJob throws an error
The type 'Rigidbody2D' must be a non-nullable value type in order to
use it as parameter 'T2' in the generic type or method
'IJobForEach'
so does someone know how to deal with the Rigidbody component?
I'm trying to make my player have a constant speed forwards and i get this error when compiling.
public class Speed
{
public static int movespeed = 1;
public Vector3 userDirection = Vector3.right;
public Start()
{
}
public void Update()
{
transform.Translate(userDirection * movespeed * Time.deltaTime);
}
}
You forgot the return type of Start method :
public void Start()
By the way you also forgot inheritence to MonoBehavior (if you want to attach your script to a game object) ;-)
public class Speed : MonoBehaviour
public class Speed : MonoBehaviour
{
public static int movespeed = 1;
public Vector3 userDirection = Vector3.right;
public void Start()
{
}
public void Update()
{
transform.Translate(userDirection * movespeed * Time.deltaTime);
}
}
Your start method doesn't have a return type. Assign it one instead.
public void Start()
{
}
By the way this is really basic and shouldn't belong here.
How can i access start() function from another script since start function can be only defined once
This is the script containing start() -
using UnityEngine;
using System.Collections;
public class MoverBolt : MonoBehaviour {
public PlayerControl obj ;
public float speed ;
public Rigidbody rb;
void Start(){
rb = GetComponent<Rigidbody>();
rb.velocity = transform.forward * speed;
}
}
Script which need to access start()
using UnityEngine;
using System.Collections;
[System.Serializable]
public class Boundary{
public float xMax,xMin,zMax,zMin;
}
public class PlayerControl : MonoBehaviour
{
public Boundary boundary ;
public float velocity;
public float tilt;
MoverBolt obj = new MoverBolt();
/* I made an object but it seems you are not supposed to create an object of class which is inheritance of MonoBehaviour */
void FixedUpdate()
{
float moveHorizontal = Input.GetAxis ("Horizontal");
float moveVertical = Input.GetAxis ("Vertical");
Vector3 movement = new Vector3 (moveHorizontal, 0.0f, moveVertical);
obj.rb.velocity = movement*velocity;
Vector3 current_position = obj.rb.position;
obj.rb.position = new Vector3 ( Mathf.Clamp(current_position.x,boundary.xMin,boundary.xMax),
0.0f,
Mathf.Clamp(current_position.z, boundary.zMin, boundary.zMax)
);
obj.rb.rotation= Quaternion.Euler(0.0f,0.0f,obj.rb.velocity.x*-tilt );
}
}
Error You are trying to create a MonoBehaviour using the 'new' keyword. This is not allowed. MonoBehaviours can only be added using AddComponent().
Are there any alternatives ?
it's possible to call a Start() method from outside. just make it public.
public class MoverBolt : MonoBehaviour {
public void Start ()
{
Debug.Log("MoverBolt.Start() was called");
}
}
public class PlayerControl : MonoBehaviour {
[SerializeField]
private MoverBolt _moverBolt;
void Start ()
{
_moverBolt.Start();
}
}
The output in the console of this is
MoverBolt.Start() was called
MoverBolt.Start() was called
UPDATE 1
I would not recommend this, because the Start() method is called by your code and the game engine again.
When I need to make sure a MonoBehaviour is properly setup, before another class uses it. I replace the Awake/Start method with public void Initialize() method and call that from outside.
Very simple answer. You can't Access start () function from any other scripts.
Use "Instantiate". For example, you can create of prefab of the game object that you want to make a copy of and then use the prefab to generate the new objects.
public class ObjectFactory : MonoBehaviour()
{
public GameObject prefab; // Set this through the editor.
public void GenerateObject()
{
// This will create a copy of the "prefab" object and its Start method will get called:
Instantiate(prefab);
}
}