I am recently new to c# and I need some help.
Essentially I have two scripts, one for spawning objects and one for moving an object along a path. I need a way to combine these two mechanics so that when a new object is instantiated it automatically joins the path and follows it.
The path is made using iTween.
![The objects the scripts are attached to] (https://i.stack.imgur.com/QPQn2.png)
I've tried changing the variable m_PlayerObj to the cube prefab and I've tried adding the Path script to the instantiation script but nothing seems to work.
The scripts attached do nkt include these attempts I made as I wanted to make the code very clear.
Spawner script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SpawnerScript : MonoBehaviour
{
public GameObject cubeprefab;
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
Instantiate(cubeprefab, transform.position, Quaternion.identity);
}
}
}
Path script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Path : MonoBehaviour
{
public GameObject m_PlayerObj;
public Transform[] positionPoint;
[Range(0, 1)]
public float value;
// Start is called before the first frame update
void Start()
{
Debug.Log(iTween.PathLength(positionPoint));
}
float tempTime;
// Update is called once per frame
void Update()
{
if (value < 1)
{
value += Time.deltaTime / 10;
}
iTween.PutOnPath(m_PlayerObj, positionPoint, value);
}
private void OnDrawGizmos()
{
iTween.DrawPath(positionPoint,Color.green);
}
}
As stated above, any help would be greatly appreciated as I am really stuck on this conceot and since I am new to Unity I really can’t see a way around it // how to fix it.
Instead of only storing the player object in the Path script, store a collection of objects. That way, the path can keep track of more than one object.
//public GameObject m_PlayerObj; // Get rid of this
public List<GameObject> followers; // Add this
Then, in your Update loop, you can loop through all of them.
void Update()
{
for (var i = 0; i < followers.Length; ++i)
{
if (value < 1)
{
value += Time.deltaTime / 10;
}
iTween.PutOnPath(m_PlayerObj, positionPoint, value);
}
}
Of course, now, you need to make sure you pass your cube instance to the Path GameObject when you spawn it, so the path knows about the cube follower. That means your spawner also needs to know about the path.
public class SpawnerScript : MonoBehaviour
{
public GameObject cubeprefab;
public Path path; // Need to populate this in the Editor, or fetch it during Awake()
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
var cubeInst = Instantiate(cubeprefab, transform.position, Quaternion.identity);
path.followers.Add(cubeInst);
}
}
}
Now a new problem is going to be that each object is going to be at the same position on the path, because the path only stores one value - a better term might be progress. So if they're all the same, like the cube, you won't be able to tell because they'd overlap.
So you have to decide what you want to do instead. Evenly space them? You could do that with some math. Or have them all start from the beginning and keep track of their progress separately? Then you'd need to store progress for each of them. A better place to do that is probably on the cube object, which means you need to add a new script to your cube prefab:
public class PathFollower : MonoBehaviour
{
[Range(0, 1)]
public float pathProgress;
}
And, you need to start referring to the prefab by this script, instead of just GameObject:
public class SpawnerScript : MonoBehaviour
{
public PathFollower pathFollower;
public Path path; // Need to populate this in the Editor, or fetch it during Awake()
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
var followerInst = Instantiate(pathFollower, transform.position, Quaternion.identity);
path.followers.Add(followerInst);
}
}
}
public class Path : MonoBehaviour
{
//public GameObject m_PlayerObj; // Get rid of this
public List<PathFollower> followers; // Add this
//...
Finally, you need to make sure to use the individual progress for each path follower, rather than a single progress value like your old Path script did:
for (var i = 0; i < followers.Count; ++i)
{
if (followers[i].pathProgress < 1)
{
followers[i].pathProgress += Time.deltaTime / 10;
}
iTween.PutOnPath(followers[i].gameObject, positionPoint, followers[i].pathProgress);
}
Putting it all together (separate files of course, with their own includes!):
public class SpawnerScript : MonoBehaviour
{
public PathFollower pathFollower;
public Path path; // Need to populate this in the Editor, or fetch it during Awake()
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
var followerInst = Instantiate(pathFollower, transform.position, Quaternion.identity);
path.followers.Add(followerInst);
}
}
}
public class Path : MonoBehaviour
{
//public GameObject m_PlayerObj; // Get rid of this
public List<PathFollower> followers; // Add this
public Transform[] positionPoint;
//[Range(0, 1)]
//public float value; // Don't need this anymore either
// Start is called before the first frame update
void Start()
{
Debug.Log(iTween.PathLength(positionPoint));
}
// Update is called once per frame
void Update()
{
for (var i = 0; i < followers.Count; ++i)
{
if (followers[i].pathProgress < 1)
{
followers[i].pathProgress += Time.deltaTime / 10;
}
iTween.PutOnPath(followers[i].gameObject, positionPoint, followers[i].pathProgress);
}
}
private void OnDrawGizmos()
{
iTween.DrawPath(positionPoint,Color.green);
}
}
public class PathFollower : MonoBehaviour
{
[Range(0, 1)]
public float pathProgress;
}
Related
I picked up unity and C# a month ago so I'm still a noobie.
So far I managed to build a simple space-based arcade shooter (i have my ship, i have a way to shoot bullets). What I'm trying to achieve is a way to keep the script that takes my keyboard input to shoot separate from the possible bullet types.
The way my bullet types are currently implemented is by having a gameobject for each with its own scripts for a) taking keyboard input and b) instancing a prefab with different properties to shoot. Currently i have 2 shooting modes, and a separate script lets me swap between them with the spacebar by enabling disabling the gameobjects. An example of the scripts I'm using for one bullet type:
Script for instantiating bullet. One method simply shoots every time a button is pressed, the other "charges" an array of bullets, accompanied in the second script by a "growing aura" signifing the power increase. These two methods have the same name across different bullet classes, but are implemented differently.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BulletA_Basic : MonoBehaviour
{
public GameObject bulletPrefab;
public GameObject aura;
public Transform firingPoint;
public Transform chargingPoint;
public float bulletForce = 20f;
public float altCooldown = 1f;
public float fireRate = 1f;
public float altFirePowerMultiplier = 1f;
private void Update()
{
}
public void Shoot()
{
GameObject bullet = Instantiate(bulletPrefab, firingPoint);
Rigidbody2D rb = bullet.GetComponent<Rigidbody2D>();
rb.AddForce(firingPoint.up * bulletForce, ForceMode2D.Impulse);
}
public void SpecialShoot(int n)
{
StartCoroutine(Special(n));
}
public IEnumerator Special(int n)
{
for (int i = 0; i < n; i++)
{
Shoot();
yield return new WaitForSeconds(0.1f);
}
}
}
Script for taking keyboard input
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Shooting_A : MonoBehaviour //This needs to be copied across all firing types
{
private BulletA_Basic bulletScript; //Change the class to make new projectile types with different firing modes
public Vector3 scaleChange;
private GameObject auraInstance;
private float timePassed = 0f;
private float timePassedMain = 0f;
public float timeToDetonation = 3f;
private void Start()
{
bulletScript = GetComponent<BulletA_Basic>();
}
// Update is called once per frame
void Update()
{
bool isFiring = Input.GetButtonDown("Main Cannon");
bool alternateFire = Input.GetButton("Main Cannon");
timePassedMain += Time.deltaTime;
if (isFiring && timePassedMain > bulletScript.fireRate)
{
bulletScript.Shoot();
timePassedMain = 0;
}
if (alternateFire)
{
timePassed += Time.deltaTime;
if (!auraInstance && timePassed >= bulletScript.altCooldown)
{
auraInstance = Instantiate(bulletScript.aura, bulletScript.chargingPoint);
}
if (alternateFire && auraInstance && timePassed < timeToDetonation)
{
Charge();
//Will need to add shader here
}
else if (timePassed >= timeToDetonation)
{
Destroy(auraInstance);
timePassed = 0;
}
}
else
{
if (auraInstance)
{
Destroy(auraInstance);
int powerAltFire = (int)(bulletScript.altFirePowerMultiplier * (Mathf.Pow(2 , timePassed))); //Equation returns a number of projectiles based on how long charge was held
bulletScript.SpecialShoot(powerAltFire);
}
timePassed = 0;
}
}
void Charge()
{
auraInstance.transform.localScale += scaleChange;
}
}
The key here is the bulletScript field.
Basically i'd like to make the second script general so that i don't have to implement it in a different way and copy-pasting it again and again for each type of bullet I'm going to make, and changing the bulletScript field type each time.
I tried doing it with interfaces but I'm not sure how to implement it in the general script since I need to access each field of the subclasses, which have each their own references (like bulletPrefab, or aura). In general i feel interfaces are not well integrated into unity but that might just be me.
I also tried with delegates, but i had similar problems. I simply changed the type of bulletScript to my delegate type (ShootingDelegate bulletScript), and wrote this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public delegate void ShootDelegate();
public delegate void SpecialShootDelegate(int n);
public class ShootingDelegate : MonoBehaviour
{
public ShootDelegate delShoot;
public SpecialShootDelegate delSpecialShoot;
private int weaponIndex;
public GameObject bulletPrefab;
public GameObject aura;
public Transform firingPoint;
public Transform chargingPoint;
public float bulletForce;
public float altCooldown;
public float fireRate;
public float altFirePowerMultiplier;
// Start is called before the first frame update
void Start()
{
WeaponSwap weapon = GetComponent<WeaponSwap>();
weaponIndex = weapon.weaponIndex;
switch (weaponIndex)
{
case 1:
BulletB_Fan bulletB = GetComponent<BulletB_Fan>();
delShoot = bulletB.Shoot;
delSpecialShoot = bulletB.SpecialShoot;
bulletPrefab = bulletB.bulletPrefab;
aura = bulletB.aura;
firingPoint = bulletB.firingPoint;
chargingPoint = bulletB.chargingPoint;
bulletForce = bulletB.bulletForce;
altCooldown = bulletB.altCooldown;
fireRate = bulletB.fireRate;
altFirePowerMultiplier = bulletB.altFirePowerMultiplier;
break;
case 0:
BulletA_Basic bullet = GetComponent<BulletA_Basic>();
delShoot = bullet.Shoot;
delSpecialShoot = bullet.SpecialShoot;
bulletPrefab = bullet.bulletPrefab;
aura = bullet.aura;
firingPoint = bullet.firingPoint;
chargingPoint = bullet.chargingPoint;
bulletForce = bullet.bulletForce;
altCooldown = bullet.altCooldown;
fireRate = bullet.fireRate;
altFirePowerMultiplier = bullet.altFirePowerMultiplier;
break;
}
}
// Update is called once per frame
void Update()
{
}
}
This is the error it throws:
ArgumentException: Value does not fall within the expected range.
ShootingDelegate.Start () (at Assets/Scripts/ShootingDelegate.cs:54)
which corresponds to this line
delShoot = bullet.Shoot;
I don't really care if a solution employs either interfaces or delegates, those were just things I tried. Any thoughts?
I created a sample just for you.
Here I am creating BulletType parent class for all bullet types. And atting some variable and method for all bullets.
public class BulletType : MonoBehaviour
{
// Protected is less stric than private, more stric than public
// Protected variables only accesible to this class and its child.
protected string name;
protected int bulletDamage;
protected int bulletSpeed;
protected virtual void Start()
{
}
// Virtual means you can override this method in child classes
protected virtual void Damage() { }
public virtual void PlaySound() { }
protected virtual void ShowEffect() { }
}
Now I will add child class and inherit from parent BulletType class. Everything write in comment lines.
// This class inherits from BulletType class.
public class FireBullet : BulletType
{
// If you want to completely ignore parent class variable to by adding new keyword.
new int bulletSpeed = 3;
protected override void Start()
{
// when overriding a method automatically adds this method.
// base means parent class. with base you can access parent methods.
base.Start();
// If you remove base method, parent method won't be called in this class.
// Here I accessed parent class variable and set its value.
// This doesn't effect parent or other child classes. That's the beauty.
name = "Fire Bullet";
}
void StopSound()
{
}
protected override void PlaySound()
{
// Sound played from parent.
base.PlaySound();
// You can add your own variable and methods in parent method.
StopSound();
}
}
// FireBullet inherited from BulletType, and LavaBullet inherited from FireBullet.
// You can do this as much as you want.
public class LavaBullet : FireBullet
{
protected override void PlaySound()
{
// Here base will be FireBullet
base.PlaySound();
}
}
public class IceBullet : BulletType
{
// Add as much as you thing.
}
And for using BulletType script in your player just add this line
public class Player
{
public BulletType currentBulletType;
// You can get child from main class.
FireBullet fireBullet = currentBulletType.GetComponent<FireBullet>();
// Now you can access and use child class methods.
fireBullet.PlaySound();
}
and when you want to change bullet type, just assign new bullettype. Because all bullet type inherit from this class. You can assign every bullet type.
I don't know if it's the answer but,
delShoot = bulletB.Shoot;
delShoot is a reference to a script.
bulletB.Shoot is a function.
I don't think you can make one equal to the other.
If I understand your question, you want to make one script for two type of bullets shoot. You can create a scipts Shoot.cs who is instantiate with a value (1 for the default fire 2 for the second fire) and other scrits defaultFire.cs / secondFire.cs with they own properties.
Like this you'll juste instantiate once your Bullet like this :
public void Start(){
case 1 : Shoot shoot = new Shoot(1)
case 2 : Shoot shoot = new Shoot(2)
}
Hope this help a little..
Im making a terror game, and i want to spawn little collectibles in my scene, my code works fine, but they repeat the world location in every instantiate:
using UnityEngine;
public class objectivesGeneration : MonoBehaviour
{
GameObject[] objSpawn;
GameObject objActual;
public GameObject obj;
int index;
void Start()
{
objSpawn = GameObject.FindGameObjectsWithTag("spawnObj");
}
void Update()
{
if (Input.GetKeyDown(KeyCode.I))
{
generateNewObj();
}
}
public void generateNewObj()
{
index = Random.Range(0, objSpawn.Length);
objActual = objSpawn[index];
createObj();
}
public void createObj()
{
Instantiate(obj, objActual.transform.position, objActual.transform.rotation);
}
}
Can somebody help me?
Your question can be understood in two ways. Leoverload gave the answer to the repeating position.
In case you do not want to repeat the same type of object (so all objects are unique), then do the following:
Turn objSpawn into a List<GameObject> variable and in the start() function, add all instances from the array that's returned from FindGameObjectsWithTag to it.
Turn objSpawn.Length into objSpawn.Count (which does the same but for lists)
In that same function add: objSpawn.Remove(objActual) at the end.
If those objects are destroyed at some point and you want to create new instances of destroyed objects, ensure that in their Destroy event, you Add(gameObject) to the list again.
Am giving you instructions instead of just code so you can learn to do this yourself in the future.
Also I have the feeling you need to learn how arguments\parameters work. Then you can ommit that objActual variable and instead pass the object to createObj(GameObject theObj) directly.
You are spawning the object in the same position objActual.transform.position
You should set the limits of the spawn with 2 variables, for example:
public float startx;
public float endx;
public float starty;
public float endy;
Then you can easily Instantiate randomly in this positions:
float posX = Random.Range(startx, endx);
float posy = Random.Range(starty, endy);
Instantiate(obj, new Vector3(posx, posy, transform.position.z, Quaternion.Identity);
And so you have a random spawn!
Thanks to AlexGeorg for the answer,
this was how my final code was
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
public class objectivesGeneration : MonoBehaviour
{
public GameObject recipient;
public List<GameObject> objSpawns = new List<GameObject>();
int spawnSelected;
void Start()
{
//Set all the objs with the tag spawnLocation inside of the list
foreach (GameObject OBJ in GameObject.FindGameObjectsWithTag("spawnLocation"))
{
objSpawns.Add(OBJ);
}
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.I))
{
//Check if there are more possible spawns
if (objSpawns.Count > 0)
generateNewObj(recipient);
}
}
public void generateNewObj(GameObject frasco)
{
//Get a random number of the list which contains one obj
spawnSelected = Random.Range(0, objSpawns.Count);
//Instantiate the GameObject in the location of the random selected object
Instantiate(frasco, objSpawns[spawnSelected].transform.position, objSpawns[spawnSelected].transform.rotation);
//Delete that possible location from the list
objSpawns.RemoveAt(spawnSelected);
}
}
I am new to coding and am not quite sure how to add context from 1 script to another. For my game so far, I have been Frankensteining code from multiple sources and have head into a dead end. I want my player to take the same amount of damage as stated by enemyDamage in the 'Enemy' script. I'm not too sure what other context to give you, but if you know how you could help me out, that would be awesome!
Enemy Script
using System.Collections;
using System.Collections.Generic;
using System.Security.Cryptography;
using UnityEngine;
public class Enemy : MonoBehaviour
{
public int health = 100;
public int enemyDamage = 10;
public GameObject deathEffect;
public void TakeDamage (int damage)
{
health -= damage;
if (health <= 0)
{
Die();
}
}
void Die ()
{
Instantiate(deathEffect, transform.position, Quaternion.identity);
Destroy(gameObject);
}
}
PlayerHealth Script
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using UnityEngine;
public class PlayerHealth : MonoBehaviour
{
public int maxHealth = 10;
public int currentHealth;
public int damage = 1;
public HealthBar healthBar;
// Start is called before the first frame update
void Start()
{
currentHealth = maxHealth;
healthBar.SetMaxHealth(maxHealth);
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.R))
{
TakenDamage(1);
}
if (currentHealth <= 0)
{
PlayerDeath();
}
}
public void TakenDamage(int damage)
{
currentHealth -= damage;
healthBar.SetHealth(currentHealth);
}
void PlayerDeath()
{
UnityEngine.Debug.Log("bean guy");
}
public void OnTriggerEnter2D(Collider2D hitInfo)
{
UnityEngine.Debug.Log("We did it boys");
PlayerHealth player = hitInfo.GetComponent<PlayerHealth>();
{
UnityEngine.Debug.Log("beans");
TakenDamage(enemyDamage); // I need this to update with enemyDamage 's value
}
}
}
There are multiple ways:
Make EnemyDamage a Static Variable
public static int enemyDamage = 10;
Then you can call it in other scripts with Enemy.enemyDamage
Be aware that you can't set static variables in the Inspector.
Use GetComponent
Enemy enemy = gameObject.GetComponent(typeof(Enemy )) as Enemy;
enemy.enemyDamage
Create a GameManager
GameManager.CS:
#region Singelton
public static GameManager instance;
void Awake()
{
if (instance != null)
{
Debug.LogWarning("More than one Instance of Inventory found");
return;
}
instance = this;
}
#endregion
public int enemyDamage = 10;
Referencing the GameManager Script:
GameManager gm;
void Start()
{
gm = GameManager.instance;
}
//Calling Function in GameManager
gm.EndGame();
// Getting Value from GameManager
gm.enemyDamage();
When to use what?
If you want a more short term solution I would use a static variable, not advised with multiple enemies (different enemyDamage Values are now immosible)
If you have more variables or even functions that need to be accessible from multiple scripts I would advise you to use a Game Manager instead
You need to get a reference of the Enemy to use GetComponent, but makes it possible to add multiple different enemyDamage Values
Have script which runs in [ExecuteAlways] mode. It is mainly a function called at Start() and OnValidate() to update the position of objects based on changes in an Editor. This all works fine.
When an object is added as a child to the object with the script in the Hierarchy window I want UpdateRing() to be called and integrate that into the ring. Putting OnHierarchyChange() with UpdateRing() doesn't seem to do anything. In other questions OnHierarchyChange() is put in the Editor file but I don't know how I can put OnHierarchyChange() in the Editor file and call UpdateRing()...or if that is even something I should do...
GameObject Code:
using UnityEngine;
using System;
using System.ComponentModel;
[Serializable]
[ExecuteAlways]
public class ObjectsRing : MonoBehaviour
{
//public float radius = { get { return m_Radius; } set { m_Radius = value; } }
[Range(0f, 100f)]
public float radius = 10;
[Range(0f,360f)]
public float beginAngle = 0f;
[Range(0f,360f)]
public float endAngle = 360f;
public bool flip = false;
public enum orientationList {[Description("X-up")] Xup, [Description("Y-up")] Yup, [Description("Z-up")] Zup};
public orientationList orientation;
// Start is called before the first frame update
void Start()
{
UpdateRing();
}
// OnValidate is called when fields are changed in an Editor
void OnValidate()
{
UpdateRing();
}
// OnHierarchyChange is called when changes are made in the Hierarchy pane.
void OnHierarchyChange()
{
UpdateRing();
}
private void UpdateRing()
{
//Input error handling
if (endAngle < beginAngle)
{
float tempAngle = beginAngle;
beginAngle = endAngle;
endAngle = tempAngle;
}
// Attach mesh, rotate object and add material
float objectAngle = (endAngle - beginAngle) / (transform.childCount);
float rotation = beginAngle;
for (int cnt = 0; cnt < transform.childCount; cnt++)
{
// Translate and rotate each object
transform.GetChild(cnt).GetComponent<Transform>().localPosition = new Vector3(radius, 0, 0);
// transform.GetChild(cnt).GetComponent<Transform>().rotation = Quaternion.Euler(0, rotation, 0);
rotation = beginAngle + cnt * objectAngle;
transform.GetChild(cnt).RotateAround(transform.position, new Vector3(0,1,0), rotation);
transform.GetChild(cnt).LookAt(transform.position);
if (flip)
{
transform.GetChild(cnt).Rotate(new Vector3(0,180,0));
}
switch (orientation)
{
case orientationList.Xup:
{
transform.GetChild(cnt).Rotate(new Vector3(0,0,0));
break;
}
case orientationList.Yup:
{
transform.GetChild(cnt).Rotate(new Vector3(90,0,0));
break;
}
case orientationList.Zup:
{
transform.GetChild(cnt).Rotate(new Vector3(0,0,90));
break;
}
}
}
}
}
Editor Code:
using UnityEditor;
[CustomEditor(typeof(ObjectsRing)), CanEditMultipleObjects]
public class ObjectsRingEditor : Editor
{
private SerializedProperty radiusProperty;
private SerializedProperty beginAngleProperty;
private SerializedProperty endAngleProperty;
private SerializedProperty flipProperty;
private SerializedProperty orientationProperty;
public void OnEnable()
{
radiusProperty = serializedObject.FindProperty("radius");
beginAngleProperty = serializedObject.FindProperty("beginAngle");
endAngleProperty = serializedObject.FindProperty("endAngle");
flipProperty = serializedObject.FindProperty("flip");
orientationProperty = serializedObject.FindProperty("orientation");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
radiusProperty.floatValue = EditorGUILayout.Slider ("Radius", radiusProperty.floatValue, 0, 100);
beginAngleProperty.floatValue = EditorGUILayout.Slider ("Begin Angle", beginAngleProperty.floatValue, 0, 360);
endAngleProperty.floatValue = EditorGUILayout.Slider ("End Angle", endAngleProperty.floatValue, 0, 360);
flipProperty.boolValue = EditorGUILayout.Toggle ("Flip", flipProperty.boolValue);
orientationProperty.enumValueIndex = EditorGUILayout.Popup ("Orientation", orientationProperty.enumValueIndex, orientationProperty.enumDisplayNames);
serializedObject.ApplyModifiedProperties();
EditorApplication.update.Invoke();
}
}
As already mentioned in other answers OnHierarchyChange is a message of EditorWindow and will only be called by Unity in this type of class, not in a MonoBehaviour.
However, the solution is actually quite simple!
If tagging your class [ExecuteAllways] or [ExecuteInEditMode] the method in MonoBehaviour classes getting called if anything in the Scene changes is simply Update!
Update is only called when something in the Scene changed.
Changing something in the hierarchy implies that also something in the scene is changed.
So you can simply put it I Update and only have to prevent it from running later in the build application
#if UNITY_EDITOR
private void Update()
{
UpdateRing();
}
#endif
the #if UNITY_EDIROR preprocessor will make sure this part is removed in a build avoiding overhead of Update getting called entirely.
In case you need the Update for something else you could alternatively also either do
private void Update()
{
#if UNITY_EDITOR
UpdateRing();
#endif
...
}
or also
private void Update()
{
if(Application.isEditor)
{
UpdateRing();
}
...
}
Sidenotes
Actually what is the custom Editor script for? I don't see it adding anything that wouldn't be drawn in the Inspector by default anyway ...
[Serializable] is redundant on a class of type MonoBehaviour.
OnHierarchyChange belongs to the EditorWindow class and as such will only work within scripts derived from EditorWindow.
By using in a Monobehaviour script you are simply creating a new method called OnHierarchyChange within your script which is not associated with the Unity message EditorWindow.OnHierarchyChange.
Take a look at: https://docs.unity3d.com/ScriptReference/EditorApplication-hierarchyChanged.html
I was able to modify the example code a bit to execute within a Monobehaviour with [ExecuteAlways] attribute.
using UnityEditor;
using UnityEngine;
[ExecuteAlways]
public class HierarchyMonitor : MonoBehaviour
{
static HierarchyMonitor()
{
EditorApplication.hierarchyChanged += OnHierarchyChanged;
}
static void OnHierarchyChanged()
{
Debug.Log("Heirarchy Has changed");
//do you ring update here
}
}
Alternatively you could modify your editor code to be an EditorWindow and use EditorWindow.OnHeirarchyChange but then you would need the window open to allow it to execute.
Put...
void OnHierarchyChanged()
{
UpdateRing();
}
void Update()
{
EditorApplication.hierarchyChanged += OnHierarchyChanged;
}
...in the ObjectRing class and it does update immediately on changes to the hierarchy without regards the selection.
Not sure its the best way...but it works.
I'm having a problem with a class which derives from MonoBehavior and is attached to a GameObject. When this GameObject("the projectile") collides with something, the component "Effect" should be duped and stored in a List() on the hitted target. After the assignment the projectile should be destroyed.
When i run this, i see in the List only "Missing (Effect)". I guess i assign just a reference into the list, and when the projectile gets deleted the reference is getting lost.
Projectile GameObject has Effect and Projectile classes as components attached:
public class Effect : MonoBehaviour {
public float dps = 2f;
public float duration = 2f;
public operatingDelay = 0.1f;
public bool freezing = false;
void Start(){
StartCoroutine("DamageHealRoutine");
}
IEnumerator DamageHealRoutine() {
int goalCount = (int)(duration / operatingDelay);
float partialDamage = dps * operatingDelay;
for (int i = 0; i < goalCount; i++) {
if (dps > 0)
stats.TakeDamage(partialDamage);
else if (dps < 0)
stats.GetHeal(partialDamage);
yield return new WaitForSeconds(operatingDelay);
}
}
}
public class Projectile : MonoBehaviour {
public Effect effect;
private void Awake() {
effect = GetComponent<Effect>(); // not null
}
public void hittedSomething(GameObject go) {
go.GetComponent<Stats>().effects.Add(effect);
// Without destroying, the List entry is assinged accordingly.
Destroy(this.gameObject);
//Became Missing when destroying the Projectile
}
}
Target GameObject has the stats class as component attached:
public class Stats : MonoBehaviour {
public List<Effect> effects;
}
Effect has to a inheritor from MonoBehavior, because it should be able to start Coroutines and i want to alter it's values.
Is there a possibility to achieve this without adding the Effect as a component on the target?
Edit1:
Missing
Effect has to a inheritor from MonoBehavior, because it should be able
to start Coroutines and i want to alter it's values.
Is there a possibility to achieve this without adding the Effect as a
component on the target?
Yes. You simply need a reference of any MonoBehaviour from any script you are 100% sure that won't be destroyed. I said this because if they are destroyed, the coroutine may stop running.
In this example I will get the reference from Stats script but you can get it from any script you wish.
New Stats script script:
public class Stats : MonoBehaviour
{
public List<Effect> effects;
private MonoBehaviour mono;
public MonoBehaviour monoRef
{
get
{
return mono;
}
}
// Use this for initialization
void Awake()
{
mono = this;
}
}
New Effect script. No MonoBehaviour required:
public class Effect
{
public float dps = 2f;
public float duration = 2f;
public operatingDelay = 0.1f;
public bool freezing = false;
MonoBehaviour coroutineMono;
public Effect()
{
coroutineMono = GameObject.Find("StatsObj").GetComponent<Stats>().monoRef;
coroutineMono.StartCoroutine("DamageHealRoutine");
}
IEnumerator DamageHealRoutine()
{
int goalCount = (int)(duration / operatingDelay);
float partialDamage = dps * operatingDelay;
for (int i = 0; i < goalCount; i++)
{
if (dps > 0)
stats.TakeDamage(partialDamage);
else if (dps < 0)
stats.GetHeal(partialDamage);
yield return new WaitForSeconds(operatingDelay);
}
}
}
You should now use Effect effect = new Effect () and it will start the coroutine without MonoBehaviour.