So I have these three classes for a game I'm making. Infection Controller, then 2 child classes, InfectedClass and BloaterClass. This is in the InfectionController:
void UpdateTarget()
{
GameObject[] Humans = GameObject.FindGameObjectsWithTag(humanTag);
GameObject[] Vaccines = GameObject.FindGameObjectsWithTag(vaccineTag);
GameObject[] HV = Humans.Concat(Vaccines).ToArray();
float shortestDistance = Mathf.Infinity;
GameObject nearestHuman = null;
Vector3 currentPosition = this.transform.position;
foreach (GameObject huvacs in HV)
{
Vector3 directionToTarget = huvacs.transform.position - currentPosition;
float distanceToTarget = directionToTarget.sqrMagnitude;
if (distanceToTarget < shortestDistance)
{
shortestDistance = distanceToTarget;
nearestHuman = huvacs;
}
else
{
targetHuman = null;
}
}
if (nearestHuman != null && shortestDistance <= range)
{
targetHuman = nearestHuman.transform;
infectionAgent.SetDestination(targetHuman.transform.position);
}
}
So both the Infected and Bloater share this script but I want them to have different speeds. I ran into the issue where if I change the speed of the NavMeshAgent in my Start() function, I'm changing the speed of all the infection units to the same thing.
So... should I just put this movement method in the subclasses? Or is there a way to change the speed based on the subclass inside of the parent class?
So... should I just put this movement method in the subclasses?
Is it going to be the exact same code? Then no. Never copy and paste the same code to multiple places.
If I change the speed of the NavMeshAgent in my Start() function, I'm changing the speed of all the infection units to the same thing [...] Is there a way to change the speed based on the subclass inside of the parent class?
You haven't shown your Start() method, but you should set the speed value from your sub-classes, not the parent class. Note that the Unity magic names (like Start) don't inherit well. You can create your own method (that's called from Start()) and is marked virtual allowing your subclasses to override it and get the behavior you want.
Related
i was working on a simple car controller script and a humvee model i downloaded. the script works as its supposed to but the car wheels are out of place and are spinning crazy. i tried searching everywhere but didn't found any solution.
here's the code:
using UnityEngine;
public class GroundVehicleController : MonoBehaviour {
public void GetInput()
{
m_horizontalInput = Input.GetAxis("Horizontal");
m_verticalInput = Input.GetAxis("Vertical");
}
private void Steer()
{
m_steeringAngle = maxSteerAngle * m_horizontalInput;
frontDriverW.steerAngle = m_steeringAngle;
frontPassengerW.steerAngle = m_steeringAngle;
}
private void Accelerate()
{
frontDriverW.motorTorque = m_verticalInput * motorForce;
frontPassengerW.motorTorque = m_verticalInput * motorForce;
}
private void UpdateWheelPoses()
{
UpdateWheelPose(frontDriverW, frontDriverT);
UpdateWheelPose(frontPassengerW, frontPassengerT);
UpdateWheelPose(rearDriverW, rearDriverT);
UpdateWheelPose(rearPassengerW, rearPassengerT);
}
private void UpdateWheelPose(WheelCollider _collider, Transform _transform)
{
Vector3 _pos = _transform.position;
Quaternion _quat = _transform.rotation;
_collider.GetWorldPose(out _pos, out _quat);
_transform.position = _pos;
_transform.rotation = _quat;
}
private void FixedUpdate()
{
GetInput();
Steer();
Accelerate();
UpdateWheelPoses();
}
private float m_horizontalInput;
private float m_verticalInput;
private float m_steeringAngle;
public WheelCollider frontDriverW, frontPassengerW;
public WheelCollider rearDriverW, rearPassengerW;
public Transform frontDriverT, frontPassengerT;
public Transform rearDriverT, rearPassengerT;
public float maxSteerAngle = 30;
public float motorForce = 50;
}
The error on Unity console is telling you the problem:
UnassignedReferenceException: The variable frontDriverW of
groundVehicleController has not been assigned. You problably need to
assign the frontDriverW variable of the GroundVehicleController script
in the inspector.
Well, when your script runs, it tries to access the field frontDriverW, but since it was never assigned a value, it throws the mentioned exception.
You need to go in the inspector for the object that contains the GroundVehicleController script and add a value for the frontDriverW field. While you're there, make sure all the other fields have values too, because if they don't, those will also cause the same error.
From the pictures you've show us, it is not possible to know in which object you added the GroundVehicleController script, but just check all of the Humvee objects until you find it in the inspector. My guess is that it will probably be in the object Wheels, or the object Humvee.
a) Check the prefab you've created to see if these offsets are there even on the prefab.
b) You're updating the wheel pose with global positions and rotations. this is probably what is causing the offsets. try setting the pose local, or just make the wheel meshes follow whatever the wheel colliders are doing, make sure they're all under the same co-ordinate space. Why not just child each individual wheel to it's corresponding wheel collider? theyre just meshes after all.
c) You haven't assigned your wheel meshes at all in your script.
c) play around with different mass values on the RigidBody (keep it heavy), and also consider attaching a physic material to your wheel colliders to reduce slip and spinning (If the car controller already doesnt have a slip setting)
I'm working on a small game: I want all GameObjects to be pulled into the middle of the screen where they should collide with another GameObject.
I tried this attempt:
using UnityEngine;
using System.Collections;
public class Planet : MonoBehaviour
{
public Transform bird;
private float gravitationalForce = 5;
private Vector3 directionOfBirdFromPlanet;
void Start ()
{
directionOfGameObjectFromMiddle = Vector3.zero;
}
void FixedUpdate ()
{
directionOfGameObjectFromMiddle = (transform.position-bird.position).normalized;
bird.rigidbody2D.AddForce (directionOfGameObjectFromMiddle * gravitationalForce);
}
}
sadly I can't get it to work. I've been told that I have to give the object that is being pulled another script but is it possible to do this just with one script that is used on the object that pulls?
So first you have a lot of typos / code that doesn't even compile.
You use e.g. once directionOfBirdFromPlanet but later call it directionOfGameObjectFromMiddle ;) Your Start is quite redundant.
As said bird.rigidbody2D is deprecaded and you should rather use GetComponent<Rigidbody2D>() or even better directly make your field of type
public Rigidbody2D bird;
For having multiple objects you could simply assign them to a List and do
public class Planet : MonoBehaviour
{
// Directly use the correct field type
public List<Rigidbody2D> birds;
// Make this field adjustable via the Inspector for fine tuning
[SerializeField] private float gravitationalForce = 5;
// Your start was redundant
private void FixedUpdate()
{
// iterate through all birds
foreach (var bird in birds)
{
// Since transform.position is a Vector3 but bird.position is a Vector2 you now have to cast
var directionOfBirdFromPlanet = ((Vector2) transform.position - bird.position).normalized;
// Adds the force towards the center
bird.AddForce(directionOfBirdFromPlanet * gravitationalForce);
}
}
}
Then on the planet you reference all the bird objects
On the birds' Rigidbody2D component make sure to set
Gravity Scale -> 0
you also can play with the Linear Drag so in simple words how much should the object slow down itself while moving
E.g. this is how it looks like with Linear Drag = 0 so your objects will continue to move away from the center with the same "energy"
this is what happens with Linear Drag = 0.3 so your objects lose "energy" over time
In my game I have a game object called ExclamationMark which I want to spawn above enemies heads when the player gets into range and they become "Alerted".
I've made this simple script to do that, but for some reason it will only work on one game object.
My enemy script:
void CheckForPlayer()
{
// Define player and get position
var player = GameObject.FindWithTag("Player");
var playerPos = (int)player.transform.position.x;
if (transform.Find("Graphics"))
{
// Define gameobject position
var enemyPos = transform.Find("Graphics").gameObject.transform.position.x;
// Define range to spawn tiles in
var range = 5;
var rangeInfront = enemyPos + range;
var rangeBehind = enemyPos - range;
if (playerPos >= rangeBehind && playerPos <= rangeInfront)
{
enemyIsActive = true;
if (transform.Find("ExclamationMark"))
{
var exMark = transform.Find("ExclamationMark").gameObject.GetComponent<ExclamationMarkSpawn>();
exMark.SpawnExclamationMark();
}
}
else
{
enemyIsActive = false;
}
}
}
My ! script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ExclamationMarkSpawn : MonoBehaviour {
public GameObject spawnPos;
public GameObject exclamationMark;
public GameObject exclamationMarkAudio;
public void SpawnExclamationMark()
{
StartCoroutine(GameObject.FindGameObjectWithTag("MainCamera").GetComponent<CameraShake>().Shake(0.2f, 0.2f, 0.2f));
Instantiate(exclamationMark, spawnPos.transform.position, Quaternion.identity);
if (exclamationMarkAudio)
Instantiate(exclamationMarkAudio, spawnPos.transform.position, Quaternion.identity);
StartCoroutine(DestroyExclamationMark());
}
IEnumerator DestroyExclamationMark()
{
yield return new WaitForSeconds(1);
var children = new List<GameObject>();
foreach (Transform child in transform) children.Add(child.gameObject);
children.ForEach(child => Destroy(child));
}
}
Just to be sure: I assume every player has its own instance of both of your scripts attached (some maybe nested further in their own hierarchy).
I assume that since you are using transform.Find which looks for the object by name within it's own children.
In general using Find and GetComponent over and over again is very inefficient! You should in both classes rather store them to fields and re-use them. Best would be if you can actually already reference them via the Inspector and not use Find and GetComponent at all.
In general finding something by name is always error prone. Are you sure they are all called correctly? Or are others maybe further nested?
Note: Find does not perform a recursive descend down a Transform hierarchy.
I would prefer to go by the attached components. You say it has e.g. a RigidBody. If this is the only Rigidbody component in the hierarchy below your objects (usually this should be the case) then you could instead rather simply use
// pass in true to also get disabled or inactive children
Rigidbody graphics = GetComponentInChildren<Rigidbody>(true);
the same for the ExclamationMarkSpawn
// Would be even beter if you already reference these in the Inspector
[SerializeField] private Rigidbody graphics;
[SerializeField] private ExclamationMarkSpawn exclamationMark;
[SerializeField] private Transform player;
private void Awake()
{
if(!player) player = GameObject.FindWithTag("Player");
if(!graphics) graphics = GetComponentInChildren<Rigidbody>(true);
if(!exclamationMark) exclamationMark = GetComponentInChildren<ExclamationMarkSpawn>(true);
}
private void CheckForPlayer()
{
// If really needed you can also after Awake still use a lazy initialization
// this adds a few later maybe unnecessary if checks but is still
// cheaper then using Find over and over again
if(!player) player = FindWithTag("Player");
if(!graphics) graphics = GetComponentInChildren<Rigidbody>(true);
if(!exclamationMark) exclamationMark = GetComponentInChildren<ExclamationMarkSpawn>(true);
var playerPos = (int)player.position.x;
// always if making such a check also give a hint that something might be missing
if (!graphics)
{
// by adding "this" you can now simply click on the message
// in the console and it highlights the object where this is happening in the hierarchy
Debug.LogWarning("graphics is missing here :'( ", this);
return;
}
// Define gameobject position
var enemyPos = graphics.transform.position.x;
// Define range to spawn tiles in
// this entire block can be shrinked down to
if (Mathf.Abs(playerPos - enemyPos) <= 5)
{
enemyIsActive = true;
if (exclamationMark) exclamationMark.SpawnExclamationMark();
}
else
{
enemyIsActive = false;
}
}
The same also in ExclamationMarkSpawn.cs.
I would additionally only allow 1 exclamation mark being visible at the same time. For example when a player jitters in the distance especially assuming both, the player and the enemy, I would move the entire instantiation to the routine and use a flag. Especially since this is called every frame in Update while the player stays in the range!
Also re-check and make sure your enemies are not maybe referencing the same spawnPos and thus all instantiating their exclamation marks on top of each other.
public class ExclamationMarkSpawn : MonoBehaviour
{
public Transform spawnPos;
public GameObject exclamationMark;
public GameObject exclamationMarkAudio;
[SerializeField] private CameraShake cameraShake;
// only serialized for debug
[SerializeField] private bool isShowingExclamation;
private void Awake()
{
if(!cameraShake) cameraShake = Camera.main.GetComponent<CameraShake>();
// or assuming this component exists only once in the entire scene anyway
if(!cameraShake) cameraShake = FindObjectOfType<CameraShake>();
}
public void SpawnExclamationMark()
{
StartCoroutine(ShowExclamationMark());
}
private IEnumerator ShowExclamationMark()
{
// block concurrent routine call
if(isShowingExclamation) yield brake;
// set flag blocking concurrent routines
isShowingExclamation = true;
// NOTE: Also for this one you might want to rather have a flag
// multiple enemy instances might call this so you get concurrent coroutines also here
StartCoroutine(cameraShake.Shake(0.2f, 0.2f, 0.2f));
Instantiate(exclamationMark, spawnPos.position, Quaternion.identity);
if (exclamationMarkAudio) Instantiate(exclamationMarkAudio, spawnPos.position, Quaternion.identity);
yield return new WaitForSeconds(1);
var children = new List<GameObject>();
foreach (var child in transform.ToList()) children.Add(child.gameObject);
children.ForEach(child => Destroy(child));
// give the flag free
isShowingExclamation = false;
}
}
Try this;
if (transform.Find("ExclamationMark"))
{
var exMark = transform.Find("ExclamationMark").gameObject.GetComponent<ExclamationMarkSpawn>();
exMark.SpawnExclamationMark(transform.position); //Add transform.position here
}
public void SpawnExclamationMark(Vector3 EnemyPos)
{
StartCoroutine(GameObject.FindGameObjectWithTag("MainCamera").GetComponent<CameraShake>().Shake(0.2f, 0.2f, 0.2f));
Instantiate(exclamationMark, EnemyPos, Quaternion.identity);
if (exclamationMarkAudio)
Instantiate(exclamationMarkAudio, EnemyPos, Quaternion.identity);
StartCoroutine(DestroyExclamationMark());
}
I made a simple message box that should show a message to the user. It's a prefab and does a couple of things, mostly animations upon instantiation. To run code upon instantiation I used the Start() function.
It worked when I already knew what to message but I need something like a constructor, that runs before Start(), but upon instantiation and can take parameters.
Now, I'm fully aware that I could instantiate, set the message and run everything - so using 3 lines of code where I instantiate it, but I'm curious if there is another, more proper solution? All I found on the net was that instantiate, then do something.
EDIT:
My calling of a messagebox to show up:
var timeBox =
Instantiate(messageBox, penaltySpawnLoc.position, penaltyPrefab.transform.rotation, transform);
var scr = timeBox.GetComponent<MessageBox>();
scr.OnCreated(message);
OnCreated does the initialization, show up animations, so basically everything.
But it needs a string input to know what to show up and I don't want to set the text value "on the fly" - it would make appear some weird flickering when the messagebox is visible but the text isn't set.
EDIT2:
the last parameter transform in the Instantiation is the Canvas this script is on as this is a UI script. That parameter means that the freshly instantiated GameObject is the child of that.
EDIT3:
timeBox is just an instance of messageBox, they are GameObjects.
One message box is used only once. Its purpose is to appear with the message and after like 0.5 seconds fade out and move away. After moving away it destroys itself.
You can do this with a function or extension method.
In this case, extension method is more appropriate.
We will make an extension object with Object instead of GameObject. Since GameObject inherits from Object, this extension method should work for both Object, GameObject and Transform.
Create a class called ExtensionMethod then paste everything below inside it.
using UnityEngine;
public static class ExtensionMethod
{
public static Object Instantiate(this Object thisObj, Object original, Vector3 position, Quaternion rotation, Transform parent, string message)
{
GameObject timeBox = Object.Instantiate(original, position, rotation, parent) as GameObject;
MessageBox scr = timeBox.GetComponent<MessageBox>();
scr.OnCreated(message);
return timeBox;
}
}
Usage:
Only one line call in the Start function should handle all other tasks.
public class Test: MonoBehaviour
{
GameObject messageBox = null;
Transform penaltySpawnLoc = null;
GameObject penaltyPrefab = null;
void Start()
{
gameObject.Instantiate(messageBox, penaltySpawnLoc.position, penaltyPrefab.transform.rotation, transform, "Hello");
}
}
I'd use a Factory for the message boxes, so something like
var timebox = MessageBoxFactory.Create(message);
timebox.Show();
and in the factory class do your setup and return the message box
public static MessageBox Create(string message) {
var newTimeBox = Instantiate(messageBox, penaltySpawnLoc.position, penaltyPrefab.transform.rotation, transform);
var scr = newTimeBox.GetComponent<MessageBox>();
scr.SetMessage(message);
return scr;
where Show() is the new OnCreated(). I find this pattern helps out with reducing the calling code and if I need tweaks to how the thing I want gets created it is all in one place with the shared factory code.
Had this problem today and came up with this. The idea is to replace the Start() method with a custom one and implement a wrapper that takes your prefab and its script's constructor as arguments for its (the wrapper) constructor, then have the wrapper instantiate and call your constructor. You can modify this to support more of the overloads provided by unity, but for now it works just fine for me.
EditableGO.cs:
public class EditableGO<T>
{
GameObject toInstantiate;
Action<T> constructor;
public EditableGO(Action<T> constructor, GameObject toInstantiate = null)
{
this.constructor = constructor;
this.toInstantiate = toInstantiate == null? new GameObject() : toInstantiate;
}
public GameObject Instantiate(Vector3 position, Quaternion rotation, Transform parent = null)
{
GameObject instance;
if(parent != null)
instance = GameObject.Instantiate(toInstantiate, position, rotation, parent);
else
instance = GameObject.Instantiate(toInstantiate, position, rotation);
constructor(instance.GetComponent<T>());
return instance;
}
}
To pass the Action<T> constructor to the constructor of EditableGO, what I do is implement a static constructor generator. Here's an example of a simple customizable particle, notice the constructor generator:
public class SimpleParticle : MonoBehaviour
{
public float Duration, Distance, Speed, traveledDistance, birth;
public Vector3 Direction, Size;
public static Action<SimpleParticle> ConstructorGenerator(float duration, float distance, Vector3 size, float speed, Vector3 direction)
{
return (self) => {
self.Duration = duration;
self.Distance = distance;
self.Size = size;
self.Direction = direction;
self.Speed = speed;
self.Init();
};
}
void Init()
{
birth = Time.time;
transform.localScale = Size;
}
void Update()
{
float deltaDist = Speed * Time.deltaTime;
traveledDistance += deltaDist;
transform.position += Direction * deltaDist;
if(Time.time - birth > Duration)
Destroy(this.gameObject);
}
}
And then, instantiating new custom particles is as easy as
EditableGO<SimpleParticle> particle = new EditableGO<SimpleParticle>(SimpleParticle.ConstructorGenerator(duration, distance, size, speed, direction), Game.Resources.SimpleParticle);
particle.Instantiate(position, rotation);
I'm working on Unity 5, and I need to create a list of transforms in order to modify them in the inspector (position and rotation) to emulate a different camera. This emulation is for a demo, like a camera moving on its own during the demo.
I currently have a list of transforms, but I don't know how to make them modifiable in the inspector and change their position at runtime?
Edit: 18/11/15
Here is the solution that if find with a help of a friend of mine more aware of what really does unity, hope it will help you and thanks again for all your reply it helped me a lot :D :
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class MoveCamera : MonoBehaviourSingleton<MoveCamera>
{
public List<Transform> cameraPositions = new List<Transform>();
private Transform m_Target;
private float m_Speed;
private bool m_Translate;
private bool m_Rotate;
public void SwitchToNext(int index, float speed)
{
m_Target = cameraPositions[index];
m_Translate = true;
m_Rotate = true;
}
public void Update()
{
if (m_Target != null && (m_Translate || m_Rotate))
{
float ratio = Time.deltaTime * m_Speed;
transform.position = Vector3.Lerp(transform.position, m_Target.position, ratio);
transform.rotation = Quaternion.Slerp(transform.rotation, m_Target.rotation, ratio);
if (Vector3.Distance(transform.position, m_Target.position) < 0.001f)
{
transform.position = m_Target.position;
m_Translate = false;
}
if (Quaternion.Angle(transform.rotation, m_Target.rotation) < 0.001f)
{
transform.rotation = m_Target.rotation;
m_Rotate = false;
}
}
}
}
Ok lets see if this helps you.(i will write my code assuming you want this)
You have x gameObjects in your scene.
You will need a GameObject List. Lets say you get them by doing
public List<GameObject> myObjectList = new List<GameObject>();
public List<Transform> myTransformList = new List<Transform>();
myObjectList = GameObject.FindGameObjectsWithTag("YourCustomTagHere");
foreach(Gameobject g in myObjectList)
{
myTransformList.Add(g.transform);
}
I dont know if this code works, i cant test it right now, but i think the logic is there. Go ahead and try a few things, give some feedback whether you made it or not.
I would suggest just a simple public list in your MoveCamera script and then dragging the targets into that list. Doing it this way you have full control over the order of the objects and thus you can do animated transitions from camera position n to n + 1.
public List<Transform> cameraPositions = new List<Transform> ();
Maybe a simple sequence does not fit your requirements and you need a more sophisticated way to decide which position is allowed to switch to which other position. In this case I'd suggest a helper script e.g. CameraTransition.cs. Here you can place your check logic and definition parameters.
Attach this to every allowed target position object and replace the list in MoveCamera by List<CameraTransition>.