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);
Related
I am using the Transform function in Unity inorder to rotate my gun in my 2d top down shooter when the mouse is aimed in a certain direction. However, the problem is that Unity returns one of my functions as Null and therefore says that the game object can't be found. I have done some debugging and found that my Debug.Log(aimTransform); come back as null.
The code goes like this;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerAimWeapon : MonoBehaviour
{
public static Vector3 GetMouseWorldPosition()
{
Vector3 vec = GetMouseWorldPositionWithZ(Input.mousePosition, Camera.main);
vec.z = 0f;
return vec;
}
public static Vector3 GetMouseWorldPositionWithZ()
{
return GetMouseWorldPositionWithZ(Input.mousePosition, Camera.main);
}
public static Vector3 GetMouseWorldPositionWithZ(Camera worldCamera)
{
return GetMouseWorldPositionWithZ(Input.mousePosition, worldCamera);
}
public static Vector3 GetMouseWorldPositionWithZ(Vector3 screenPosition, Camera worldCamera)
{
Vector3 worldPosition = worldCamera.ScreenToWorldPoint(screenPosition);
return worldPosition;
}
private Transform aimTransform;
private void Start()
{
Debug.Log(aimTransform);
aimTransform = transform.Find("Aim");
}
private void Update()
{
Vector3 mousePosition = GetMouseWorldPosition();
Vector3 aimDirection = (mousePosition - transform.position).normalized;
float angle = Mathf.Atan2(aimDirection.y, aimDirection.x) * Mathf.Rad2Deg;
aimTransform.eulerAngles = new Vector3(0, 0, angle);
Debug.Log(angle);
}
}
The main problem I think is below.
private Transform aimTransform;
private void Start()
{
Debug.Log(aimTransform); <--------------- This comes back as Null which is the problem.
aimTransform = transform.Find("Aim"); <-------- Aim is just the object in my game (Gun)
}
I have spent some time figuring out how to Initialize the object and then call it using the Find function so it doesn't come back as Null but I haven't been successfull.
Once unity can find the Object, it should work fine with the mouse pointer code I have further below.
As said before, I debugged the Null from the aimTransform and it returns as null. I'm not sure how to fix it and allow Unity to actually find my game object and let it be transformed. I also know that the main issue is that the object hasn't been initialized properly and I don't really know how to do it. Thanks.
Problem in aimTransform field, it's not initialized and you try to get transform with name "Aim" from null field. Transform.Find() search transform by name in it's childs. If you want to find gameobject with name "Aim" you have several ways:
If you have possibility you can serialize field aimTransform and drag and drop reference from Inspector.
You can call GameObject.Find("Aim") to find gameobject by name, but it will work only in case if gameobject with apropriate name exists on your scene.
The correct code would be this:
private void Start()
{
aimTransform = transform.Find("Aim"); // <-- assuming you don't mean
// GameObject.Find() at which point
// you'd need to change the type of
// aimTransform as well :)
Debug.Log(aimTransform);
}
aimTransform is not yet "set", therefore, it's null. First assign it, then you can log it.
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 use the following script to copy the transform of a gameObject.
public Transform alignment;
void Update()
{
DontDestroyOnLoad(transform.gameObject);
transform.position = new Vector3(alignment.position.x, alignment.position.y, alignment.position.z);
float y = alignment.transform.localRotation.eulerAngles.y;
float x = alignment.transform.localRotation.eulerAngles.x;
float z = alignment.transform.localRotation.eulerAngles.z;
transform.localEulerAngles = new Vector3(x, y, z);
}
I want to acces this transform in other scenes (apply it to gameObjects).
How can I reach the transforms, that are in DontDestroyOnLoad?
First of all, don't use DontDestroyOnLoad in your update function. Use it in your Start or Awake funciton.
Secondly you can access your Non-Destroyed object like you would any other object.
Only differences between the two is that the Non-Destroyed stays in the scene when changing scenes.
Here are some ways to access it:
var obj = GameObject.Find("My Non Destroyed Object Name");
Or my personal favorite, if the object is used for scoring or settings I'll create a new gameobject and add the following script to it:
public class GameManager : MonoDevelop {
/* These are all script on the same gameobject that is not being destroyed */
public static Settings settings;
public static PlayerMananger playerMananger;
public static UIManager uiManager;
void Start() {
DontDestroyOnLoad(this.gameobject);
// Get the components
settings = GetComponent<Settings>();
playerMananger = GetComponent<PlayerMananger>();
uiManager = GetComponent<UIManager>();
}
}
Now I can create a script from anywhere and I can access all these Manager and setting files simply using:
var settings = GameManager.settings;
You need to give alignment as parameter to the DontDestroyOnLoad method, not transform.gameObject.
DontDestroyOnLoad(alignment.gameObject);
Also, you don't need to call this method inside Update(). Just calling it once it enough. You can call it inside Start(), like this:
void Start()
{
DontDestroyOnLoad(alignment.gameObject);
}
Now your alignment gameobject will still be accessible when you change scenes. If you want to access it from other gameobjects in the new scene, you might want to give it a tag or make it a singleton.
New to unity.
So I created a simple a simple muzzle flash particle animation that is supposed to be displayed on enemies gun when the player gets close to him, simulating a shot without the actual bullet. However I get a null reference exception in this part muzzleFlash.Play(); I believe it's because I am not actually getting the muzzle flash component in the start function with the code I have, actually I know that is it after going to in to debug mode I found out. I am having a really hard time figuring out how to access that component. Below is my code and I'm also posting a picture of my hierarchy. Thanks in advance.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class StaticShootingEnemy : MonoBehaviour
{
[SerializeField] private float _range = 12f;
private Transform _player;
private bool _alive;
private float _distance;
private ParticleSystem muzzleFlash;
// Use this for initialization
void Start()
{
_player = GameObject.Find("Player").transform;
_alive = true;
muzzleFlash = (ParticleSystem)this.gameObject.GetComponent("muzzleFLash");
}
// Update is called once per frame
void Update()
{
_distance = Vector3.Distance(this.transform.position, _player.transform.position);
if (_alive && _distance < _range)
AttackPlayer();
}
private void AttackPlayer()
{
//Turning enemy to look at player
transform.LookAt(_player);
Ray ray = new Ray(transform.position, transform.forward);
RaycastHit hit;
if (Physics.SphereCast(ray, 0.75f, out hit))
{
//TODO: Fix enemy shooting fast when gettting close to him.
GameObject hitObject = hit.transform.gameObject;
if (hitObject.GetComponent<PlayerController>())
{
muzzleFlash.Play();
Debug.Log("Player Hit!");
}
else
muzzleFlash.Stop();
}
}
public void SetAlive(bool alive)
{
_alive = alive;
}
}
You probably have an object "muzzleFlash" as child to object your script attached to. So, in this case you'd better have a reference to your ParticleSystem object that is called muzzleFlash.
[SerializeField] private ParticleSystem muzzleFlash; // drag and drop your ParticleSystem muzzleFlash in inspector
or at least you could find that muzzleFlash like this
GameObject muzzleFlashObj = GameObject.Find("muzzleFlash");
ParticleSystem muzzleFlash = muzzleFlashObj.GetComponent<ParticleSystem>();
In your case it's null because there is probably no component that is called MuzzleFlash on that object. The component you want to get is ParticleSystem.
What component is the staticshootingenemy script on? if it is not on the same component as the particle system then its not finding it because this.gameObject.GetComponent("muzzleFLash") does not exist on that component. You can use GameObject.Find("muzzleFLash") to search for the particle system.
So back to your comment, you could implement something like a pool for your muzzle flashes.
public class MuzzleFlashEffect : MonoBehaviour
{
[SerializeField] private ParticleSystem particleEffect;
private Queue<MuzzleFlashEffect> poolQueue;
public void SetPoolQueue(Queue<MuzzleFlashEffect> queue)
{
poolQueue = queue;
}
public void Play()
{
StartCoroutine(Playing());
}
private IEnumerator Playing()
{
particleEffect.Play();
while (particleEffect.isPlaying)
{
yield return null; // wait until particle animation is done, then recycle effect
}
particleEffect.Stop();
poolQueue.Enqueue(this); // recycle this effect
}
// you can do the same thing for Animation as well, or even write some abstract PoolableVFX class that would be usefull for Animation , ParticleSystems etc..
}
//assume you have some game controller that manage what is going on in the scene
public class GameController : MonoBehaviour
{
[SerializeField] private MuzzleFlashEffect muzzleFlashPrefab;
private Queue<MuzzleFlashEffect> poolQueue = new Queue<MuzzleFlashEffect>(10); // 10 is enough i guess and it's good to set it at instantiation to avoid memory fragmentation
private MuzzleFlashEffect GetMuzzleFlash(Vector3 pos, Quaternion rot)
{
MuzzleFlashEffect muzzleFlash;
// if we already have some effects, then play them, otherwise make a new one and recycle it then
if (poolQueue.Count > 0)
{
muzzleFlash = poolQueue.Dequeue();
}
else
{
muzzleFlash = Instantiate(muzzleFlashPrefab);
muzzleFlash.SetPoolQueue(poolQueue);
}
muzzleFlash.transform.position = pos;
muzzleFlash.transform.rotation = rot;
return muzzleFlash;
}
void Update()
{
// your fancy logic ...
GameObject mutantGunEnd = new GameObject("mutant");
//assume that here you want your muzzle flash effect, so you do:
var muzzleFlash = GetMuzzleFlash(mutantGunEnd.transform.position, mutantGunEnd.transform.rotation); // or you might want to pass mutantGunEnd.transform.forward instead, it depends...
muzzleFlash.Play();
// your fancy logic ...
}
}
So, in this case you have only as many instance of ParticleEffect as you need and saving some resources. You could also create a universal generic pool for any type of object you want to recycle. (you want to recycle instead of instantiation, cuz Instantiation is cpu expensive).
M.b this is a bit overkill here, but i just wanted to share how would i think about this here
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.