Damage over time unity - c#

I want to make spell damage over time. So here is my code:
public class Spell : MonoBehaviour
{
public float damage = 1.0f;
public bool ignoreCaster = true;
public float delayBeforeCasting = 0.4f;
public float applyEveryNSeconds = 1.0f;
public int applyDamageNTimes = 5;
private bool delied = false;
private int appliedTimes = 0;
void OnTriggerStay(Collider other)
{
IDamageable takeDamage = other.gameObject.GetComponent<IDamageable>();
if(takeDamage != null)
{
StartCoroutine(CastDamage(takeDamage));
}
}
IEnumerator CastDamage(IDamageable damageable)
{
if(!delied)
{
yield return new WaitForSeconds(delayBeforeCasting);
delied = true;
}
while(appliedTimes < applyDamageNTimes)
{
damageable.TakeDamage(damage);
yield return new WaitForSeconds(applyEveryNSeconds);
appliedTimes++;
}
}
}
Problem is where while starts. I want to check if appliedTimes < applyDamageNTimes, then if it is true to do damage, wait for delay (applyEveryNSeconds) and then check again but i am not handy with coroutine and for some reason it is not doing that.
Here is working code. Also look for other answers if someone need!
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Spell : MonoBehaviour
{
public float damage = 1.0f;
public bool ignoreCaster = true;
public float delayBeforeCasting = 0.0f;
public float applyEveryNSeconds = 1.0f;
public int applyDamageNTimes = 1;
private bool delied = false;
private int appliedTimes = 0;
private bool test = false;
void OnTriggerStay(Collider other)
{
IDamageable takeDamage = other.gameObject.GetComponent<IDamageable>();
if(takeDamage != null)
{
StartCoroutine(CastDamage(takeDamage));
}
}
IEnumerator CastDamage(IDamageable damageable)
{
if(!test && appliedTimes <= applyDamageNTimes || !test && applyEveryNSeconds == 0)
{
test = true;
if(!delied)
{
yield return new WaitForSeconds(delayBeforeCasting);
delied = true;
}
else
{
yield return new WaitForSeconds(applyEveryNSeconds);
}
damageable.TakeDamage(damage);
appliedTimes++;
test = false;
}
}
}

OnTriggerStay is called every frame 2 objects are colliding. This means that an asynchronous instance of CastDamage coroutine is called every frame. So what happens is that you're getting a whole lot of damage per seconds, which simulates not any damage per second, hehe.
So change OnTriggerStay to OnTriggerEnter.
Depending on the kind of spell it is, I'd rather create a DPS script and apply that to the game object so...
Spell hits Object
AddComponent< MyDamageSpell>();
And then the MyDpsSpell does damage every N seconds to the Object it's on, and removes itself when it is done:
// Spell.cs
void OnTriggerEnter(Collider col) {
// if you don't want more than 1 dps instance on an object, otherwise remove if
if (col.GetComponent<MyDpsAbility>() == null) {
var dps = col.AddComponent<MyDpsAbility>();
dps.Damage = 10f;
dps.ApplyEveryNSeconds(1);
// and set the rest of the public variables
}
}
// MyDpsAbility.cs
public float Damage { get; set; }
public float Seconds { get; set; }
public float Delay { get; set; }
public float ApplyDamageNTimes { get; set; }
public float ApplyEveryNSeconds { get; set; }
private int appliedTimes = 0;
void Start() {
StartCoroutine(Dps());
}
IEnumerator Dps() {
yield return new WaitForSeconds(Delay);
while(appliedTimes < ApplyDamageNTimes)
{
damageable.TakeDamage(damage);
yield return new WaitForSeconds(ApplyEveryNSeconds);
appliedTimes++;
}
Destroy(this);
}
If it's an aura spell where units take damage every second they're in range you could do something like:
float radius = 10f;
float damage = 10f;
void Start() {
InvokeRepeating("Dps", 1);
}
void Dps() {
// QueryTriggerInteraction.Collide might be needed
Collider[] hitColliders = Physics.OverlapSphere(gameObject.position, radius);
foreach(Collider col in hitColliders) {
col.getComponent<IDamagable>().TakeDamage(10);
}
}
https://docs.unity3d.com/ScriptReference/Physics.OverlapSphere.html

Why not create, instead a system that applies auras to in-game characters? For example, if a spell like this adds a damage over time debuff it could instead add the debuff-aura script to the game object and then remove itself once its conditions are met.

Related

How do i add multiple enemies to my unity wave system?

I have a wave script for my game and it works fine, however after a while I have realised it gets quite dull fighting against the same enemies over and over again, so I was wondering if I could make a list of some sort to store all enemies that will be in each wave.
Here is my script;
SpawnManager.cs
using System.Collections;
using UnityEngine;
[System.Serializable]
public class Wave
{
public int EnemiesPerWave;
public GameObject Enemy;
}
public class SpawnManager : MonoBehaviour
{
public Wave[] Waves; // class to hold information per wave
public Transform[] SpawnPoints;
public float TimeBetweenEnemies = 2f;
public GameObject HelpText;
public GameObject Shop;
public GameObject shopfx;
public Transform ShopL;
bool shopactive = false;
private int _totalEnemiesInCurrentWave;
private int _enemiesInWaveLeft;
private int _spawnedEnemies;
private int _currentWave;
private int _totalWaves;
void Start ()
{
_currentWave = -1; // avoid off by 1
_totalWaves = Waves.Length - 1; // adjust, because we're using 0 index
StartNextWave();
}
void Update()
{
if (_enemiesInWaveLeft <= 0 && Input.GetKeyDown(KeyCode.R))
{
StartNextWave();
}
}
void StartNextWave()
{
_currentWave++;
// win
if (_currentWave > _totalWaves)
{
return;
}
_totalEnemiesInCurrentWave = Waves[_currentWave].EnemiesPerWave;
_enemiesInWaveLeft = 0;
_spawnedEnemies = 0;
StartCoroutine(SpawnEnemies());
}
// Coroutine to spawn all of our enemies
IEnumerator SpawnEnemies()
{
GameObject enemy = Waves[_currentWave].Enemy;
while (_spawnedEnemies < _totalEnemiesInCurrentWave)
{
_spawnedEnemies++;
_enemiesInWaveLeft++;
int spawnPointIndex = Random.Range(0, SpawnPoints.Length);
// Create an instance of the enemy prefab at the randomly selected spawn point's
// position and rotation.
Instantiate(enemy, SpawnPoints[spawnPointIndex].position,
SpawnPoints[spawnPointIndex].rotation);
yield return new WaitForSeconds(TimeBetweenEnemies);
}
yield return null;
}
// called by an enemy when they're defeated
public void EnemyDefeated()
{
_enemiesInWaveLeft--;
// We start the next wave once we have spawned and defeated them all
if (_enemiesInWaveLeft == 0 && _spawnedEnemies == _totalEnemiesInCurrentWave)
{
HelpText.SetActive(true);
Invoke("SetFalse",5.0f);
Shop.SetActive(true);
Invoke("LateUpdate",1f);
Instantiate(shopfx, new Vector3(ShopL.transform.position.x, ShopL.transform.position.y, ShopL.transform.position.z), Quaternion.identity);
shopactive = true;
}
}
void SetFalse()
{
HelpText.SetActive(false);
}
void LateUpdate()
{
if(Input.GetKeyDown(KeyCode.R) && shopactive == true)
{
Shop.SetActive(false);
Instantiate(shopfx, new Vector3(Shop.transform.position.x, Shop.transform.position.y, Shop.transform.position.z), Quaternion.identity);
shopactive = false;
}
}
}

How can I change the moving objects speed at runtime?

The manager script
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class DronesManager : MonoBehaviour
{
public GameObject target;
public float movementSpeed;
public float launchTime;
public Transform dronesUnchild;
private List<GameObject> drones = new List<GameObject>();
private float currentDroneSpeed;
private void Awake()
{
currentDroneSpeed = movementSpeed;
}
// Start is called before the first frame update
void Start()
{
target = GameObject.Find("Base");
drones = GameObject.FindGameObjectsWithTag("Drone").ToList();
StartCoroutine(MoveDrone());
}
// Update is called once per frame
void Update()
{
if(currentDroneSpeed != movementSpeed)
{
for(int i = 0; i < drones.Count; i++)
{
var droneControl = drones[i].GetComponent<DroneControl>();
droneControl.movingSpeed = movementSpeed;
}
currentDroneSpeed = movementSpeed;
}
}
private IEnumerator MoveDrone()
{
// same as you did:
drones = GameObject.FindGameObjectsWithTag("Drone").ToList();
foreach(var drone in drones)
{
drone.GetComponent<DroneControl>().target = target.transform;
}
while (drones.Count > 0)
{
// pick one at random, get it
int index = Random.Range(0, drones.Count);
var drone = drones[index];
// remove it from list
drones.RemoveAt(index);
// TODO: might want to check/guard if drone == null ... this guards against it
// being Destroy()ed and yet still lying around in our list marked as "dead"
// simplified your get-component-and-go-if-not-already-going code here
var droneControl = drone.GetComponent<DroneControl>();
if (droneControl.go == false)
{
droneControl.movingSpeed = movementSpeed;
droneControl.go = true;
drone.transform.parent = dronesUnchild;
}
// wait
yield return new WaitForSeconds(launchTime);
}
}
}
I tried to add this part in the Update
void Update()
{
if(currentDroneSpeed != movementSpeed)
{
for(int i = 0; i < drones.Count; i++)
{
var droneControl = drones[i].GetComponent<DroneControl>();
droneControl.movingSpeed = movementSpeed;
}
currentDroneSpeed = movementSpeed;
}
}
and this script is attached to each moving object
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DroneControl : MonoBehaviour
{
public Transform target;
public float turnSpeed = .01f;
Quaternion rotGoal;
Vector3 direction;
public float movingSpeed;
public bool go = false;
private bool waitBeforeRotate = false;
private bool startRotating = false;
#region AddedCode
public float targetRange = 1.0f;
private bool IsTargetReached(Vector3 dronePos, Vector3 targetPos)
{
var distance = Vector3.Distance(dronePos, targetPos);
return distance < targetRange;
}
#endregion AddedCode
// Update is called once per frame
void Update()
{
// next line is modified to incorporate the range check
if (go && !IsTargetReached(transform.position, target.position))
{
transform.position += transform.forward * movingSpeed * Time.deltaTime;
if (waitBeforeRotate == false)
{
StartCoroutine(StartRotating());
waitBeforeRotate = true;
}
if (startRotating)
{
direction = (target.position - transform.position).normalized;
rotGoal = Quaternion.LookRotation(direction);
transform.rotation = Quaternion.Slerp(transform.rotation, rotGoal, turnSpeed);
}
}
}
IEnumerator StartRotating()
{
yield return new WaitForSeconds(3f);
startRotating = true;
}
}
but it's never change the speed of moving objects.
if the speed of each moving object in the editor start is 5 for example and in the manager script I change the speed to 100 the speed of each object is still 5.
Maybe this is happening because after picking a random drone in IEnumerator you instantly remove it from the list?
So in Update() you set a speed for all drones, except the ones that are already moving.

How can I loop using foreach each time once in the Update and also using a bool flag each time once?

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Cinemachine;
public class Waypoints : MonoBehaviour
{
[Header("Objects To Move")]
public Transform objectToMovePrefab;
public int numberOfObjectsToMove = 1;
[Header("Speed")]
public float speed;
public bool randomSpeed = false;
public float minRandomSpeed = 1;
public float maxRandomSpeed = 100;
private bool changeSpeedOnce = false;
private bool currentSpeedState;
[Header("Waypoints")]
[SerializeField] private List<Transform> waypoints;
[Header("Delay")]
public bool useDelay = false;
public float delay = 3;
public bool randomDelay = false;
public float minRandomDelay = 0.3f;
public float maxRandomDelay = 5;
[Header("LineRenderer")]
public LineRenderer lineRenderer;
public bool moveOnLineRenderer = false;
public List<Vector3> lineRendererPositions = new List<Vector3>();
[Header("Cinemachine Cameras")]
public CinemachineVirtualCamera virtualCamera;
private List<WaypointsFollower> waypointsFollowers = new List<WaypointsFollower>();
private void Start()
{
currentSpeedState = changeSpeedOnce;
for (int i = 0; i < numberOfObjectsToMove; i++)
{
var parent = GameObject.Find("Moving Object Parent");
var objectToMove = Instantiate(objectToMovePrefab, parent.transform);
objectToMove.name = "Platfrom";
waypointsFollowers.Add(objectToMove.GetComponent<WaypointsFollower>());
}
virtualCamera.Follow = waypointsFollowers[0].gameObject.transform;
virtualCamera.LookAt = waypointsFollowers[0].gameObject.transform;
foreach (Transform wp in waypoints)
{
lineRendererPositions.Add(wp.position);
}
if (moveOnLineRenderer)
{
foreach (WaypointsFollower wpf in waypointsFollowers)
{
wpf.go = true;
}
}
SpeedUpdater();
if (useDelay)
StartCoroutine(SendObjectstomoveWithDelay());
}
private void Update()
{
lineRendererPositions.Clear();
lineRendererPositions.AddRange(GetLinePointsInWorldSpace());
SpeedUpdater();
}
public int Count => waypoints.Count;
public Vector3 GetWaypoint(int index)
{
return waypoints[index].position;
}
public int CountLR => lineRendererPositions.Count;
public Vector3 GetLRWaypoint(int index)
{
return lineRendererPositions[index];
}
IEnumerator SendObjectstomoveWithDelay()
{
{
foreach (WaypointsFollower follower in waypointsFollowers)
{
if (randomDelay)
{
delay = Random.Range(minRandomDelay, maxRandomDelay);
}
yield return new WaitForSeconds(delay);
follower.go = true;
}
}
}
private void SpeedUpdater()
{
foreach (WaypointsFollower follower in waypointsFollowers)
{
if (randomSpeed)
{
follower.speed = Random.Range(minRandomSpeed, maxRandomSpeed);
}
else
{
follower.speed = speed;
}
}
}
Vector3[] GetLinePointsInWorldSpace()
{
var positions = new Vector3[lineRenderer.positionCount];
//Get the positions which are shown in the inspector
lineRenderer.GetPositions(positions);
//the points returned are in world space
return positions;
}
}
Inside the SpeedUpdtaer I'm using a flag and I want to be able to change the flag in run time too. The problem is calling the SpeedUpdater in the Update will make the foreach loop every frame and it might be expensive because in the List waypointsFollowers there might be a lot of items.
and about the delay part, I want to do that if I change the useDelay flag in run time then from that moment each moving object will get to the next waypoint and then from there the delay will start working. same if changing the useDelay flag to false at run time then don't wait at the next waypoint/s.
The delay is working fine but only in the Start for now and also the SpeedUpdater is working fine but I'm not sure if making foreach each frame is a good way. and not sure how to make and wherein the code that I will be able to change the speed and the speed random flag at run time without making the loop each frame.
I would use properties. With properties you can keep track in the variable value set, to conditionally or on value change execute some logic, like this (I did not compile check);
private bool useDelay = false;
public bool UseDelay { get => useDelay; set {
useDelay = value;
if (useDelay)
StartCoroutine(SendObjectstomoveWithDelay());
}};
private bool randomDelay = false;
public bool RandomDelay { get => randomDelay; set {
SpeedUpdater(value);
randomDelay = value;
}};
For that I would make also that SpeedUpdater() accepts the bool argument:
private void SpeedUpdater(bool randomSpeedArgument)
{
foreach (WaypointsFollower follower in waypointsFollowers)
{
if (randomSpeedArgument)
{
follower.speed = Random.Range(minRandomSpeed, maxRandomSpeed);
}
else
{
follower.speed = speed;
}
}
}
I was not very careful in adjusting to the logic of your question. The sample code is just to show you how you can run logic on value get/set. Other way are events, a bit more complicated option, and not much needed for this case I believe.
You can give a read to the docs

Can't make cooldown work, bugs out and isn't working properly

So I am making a VR game and what I want to do is, as soon as I press the trigger on the joystick the "sword" gets activated and can stay activated for 1 second, after that it has a cooldown of 1 second also in which it cannot get activated, and then it resets. I thought it would be simple but I can't for the life of me make it work.
Here's the code:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Valve.VR;
public class Sword : MonoBehaviour
{
public SteamVR_Action_Boolean ActivateSword;
public SteamVR_Input_Sources handType;
private bool IsSwordActivated = false;
private bool canSwordGetActivated = true;
private bool cooldownStart = false;
public Material activatedSword;
public Material defaultSword;
public float timeStamp;
public float timer = 0;
public float cooldown = 2;
void Start()
{
ActivateSword.AddOnStateDownListener(TriggerDown, handType);
ActivateSword.AddOnStateUpListener(TriggerUp, handType);
timeStamp = Time.time;
}
public void TriggerUp(SteamVR_Action_Boolean fromAction, SteamVR_Input_Sources fromSource)
{
Debug.Log("Trigger is up");
IsSwordActivated = false;
this.GetComponent<MeshRenderer>().material = defaultSword;
}
public void TriggerDown(SteamVR_Action_Boolean fromAction, SteamVR_Input_Sources fromSource)
{
if (canSwordGetActivated == true)
{
Debug.Log("Trigger is down");
IsSwordActivated = true;
cooldownStart = true;
this.GetComponent<MeshRenderer>().material = activatedSword;
}
}
private void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.CompareTag("Enemy"))
{
if (IsSwordActivated == true)
{
Destroy(collision.gameObject);
}
}
}
private void Update()
{
//if (timeStamp <= Time.time)
//{
// if (IsSwordActivated == true)
// {
// timeStamp += 2;
// canSwordGetActivated = false;
// Debug.Log("test");
// }
//}
if (cooldownStart == true)
{
timer += Time.deltaTime;
cooldown -= Time.deltaTime;
if (timer >= 1f)
{
this.GetComponent<MeshRenderer>().material = defaultSword;
IsSwordActivated = false;
timer = 0;
}
if (timer == 0)
{
canSwordGetActivated = false;
}
if (cooldown <= 1f)
{
canSwordGetActivated = false;
}
if (cooldown <= 0)
{
cooldown = 2f;
canSwordGetActivated = true;
cooldownStart = false;
}
}
}
}
You should not use Update but a Coroutine for that. Especially the "cooldown" should run independently from the "timer".
// Not needed
//private bool cooldownStart = false;
//public float timer = 0;
// reference this already via the Inspector if possible
[SerializeField] private MeshRenderer _meshRenderer;
[SerializeField] private float cooldownTime = 1;
[SerializeField] private float maxEnabledTime = 1;
// Otherwise get it only ONCE on runtime
private void Awake()
{
if(!_meshRenderer) _meshRenderer = GetComponent<MeshRenderer>();
}
public void TriggerUp(SteamVR_Action_Boolean fromAction, SteamVR_Input_Sources fromSource)
{
Debug.Log("Trigger is up");
// stop the timeout
StopAllCoroutines();
// disable sword and start cooldown
StartCoroutine(SwordCooldown());
}
public void TriggerDown(SteamVR_Action_Boolean fromAction, SteamVR_Input_Sources fromSource)
{
if(IsSwordActivated) return;
if (!canSwordGetActivated) return;
Debug.Log("Trigger is down");
// start timeout
StartCoroutine(SwordEnabledTimer());
}
///<summary>
/// Disables sword and Runs cooldown before sword can be enabled again
///<summary>
private IEnumerator SwordCooldown()
{
canSwordGetActivated = false;
IsSwordActivated = false;
_meshRenderer.material = defaultSword;
yield return new WaitForSeconds(cooldownTime);
canSwordGetActivated = true;
}
///<summary>
/// Disables the sword and jumps to cooldown after it was enabled for too long
///<summary>
private IEnumerator SwordEnabledTimer()
{
canSwordGetActivated = false;
yield return new WaitForSeconds(maxEnabledTime);
StartCoroutine(SwordCooldown());
}
See WaitForSeconds
Sideeffect/Advantage:
Coroutines are often better read- and maintainable and more flexible then polling states in Update!
For example You can now very simple extend both routines and add a progress display for both like e.g.
private IEnumerator SwordCooldown()
{
canSwordGetActivated = false;
IsSwordActivated = false;
_meshRenderer.material = defaultSword;
var timePassed = 0f;
while(timePassed < cooldownTime)
{
var cooldownProgress = timePassed / cooldownTime;
// do something with the cooldownProgress value 0-1
timePassed += Mathf.Min(Time.deltaTime, cooldownTime - timePassed);
yield return null;
}
canSwordGetActivated = true;
}
One simple way of doing this would be a coroutine:
class Sword {
private bool isCoolingDown;
public void Swing() {
if (isCoolingDown) {
Debug.Log("Sorry, cooling down right now");
return;
}
StartCoroutine(DoSwingSword());
}
private IEnumerator DoSwingSword() {
isCoolingDown = true;
yield return new WaitForSeconds(1);
isCoolingDown = false;
}
}
This will use a bool to track if you are in cooled down state and automatically reset this bool after one second, without you having to track Time.deltaTime. You can then wrap the rest of your logic around this bool.

How I call the ShootingSettings method from the Update only when there are changes?

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class ShootingManager : MonoBehaviour
{
[Header("Main")]
public float launchForce = 700f;
public bool automaticFire = false;
public float bulletDestructionTime;
[Space(5)]
[Header("Slow Down")]
public float maxDrag;
public float bulletSpeed;
public bool bulletsSlowDown = false;
public bool overAllSlowdown = false;
[Range(0, 1f)]
public float slowdownAll = 1f;
private List<GameObject> shooters = new List<GameObject>();
private List<Shooting> shootingScripts = new List<Shooting>();
// Start is called before the first frame update
void Start()
{
shooters.AddRange(GameObject.FindGameObjectsWithTag("Shooter").ToList());
ShootingSettings();
}
// Update is called once per frame
void Update()
{
}
private void ShootingSettings()
{
for (int i = 0; i < shooters.Count; i++)
{
shootingScripts.Add(shooters[i].GetComponent<Shooting>());
shooters[i].GetComponent<Shooting>().launchForce = launchForce;
shooters[i].GetComponent<Shooting>().automaticFire = automaticFire;
shooters[i].GetComponent<Shooting>().bulletDestructionTime = bulletDestructionTime;
shooters[i].GetComponent<Shooting>().maxDrag = maxDrag;
shooters[i].GetComponent<Shooting>().bulletSpeed = bulletSpeed;
shooters[i].GetComponent<Shooting>().bulletsSlowDown = bulletsSlowDown;
shooters[i].GetComponent<Shooting>().overAllSlowdown = overAllSlowdown;
shooters[i].GetComponent<Shooting>().slowdownAll = slowdownAll;
}
}
}
If I will call the ShootingSettings from the Update it will keep making loop all the time. Is that right depending on performance ? Or should I make some IF's and call the method only when one of the settings has changed somehow ?
I have some objects in the hierarchy with the same script attached to it :
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Shooting : MonoBehaviour
{
[Header("Main")]
public Rigidbody bulletPrefab;
public float launchForce = 700f;
public bool automaticFire = false;
public float bulletDestructionTime;
[Space(5)]
[Header("Slow Down")]
public float maxDrag;
public float bulletSpeed;
public bool bulletsSlowDown = false;
public bool overAllSlowdown = false;
[Range(0, 1f)]
public float slowdownAll = 1f;
private List<Transform> firePoints = new List<Transform>();
private Animator anim;
private void Start()
{
GatherAllChilds(transform);
if (anim != null)
{
anim.SetBool("Shooting", true);
}
}
public void Update()
{
if (overAllSlowdown == true)
{
Time.timeScale = slowdownAll;
}
if (firePoints.Count > 0 && anim != null)
{
for (int i = 0; i < firePoints.Count; i++)
{
if (isAnimationStatePlaying(anim, 0, "AIMING") == true)
{
if (Input.GetButtonDown("Fire1") && automaticFire == false)
{
if (anim.GetBool("Shooting") == true)
{
anim.Play("SHOOTING");
LaunchProjectile(firePoints[i]);
}
}
else if (Input.GetButtonDown("Fire1") && automaticFire == true)
{
automaticFire = false;
}
else
{
if (Input.GetButtonDown("Fire2"))
{
automaticFire = true;
}
if (automaticFire == true)
{
anim.Play("SHOOTING");
LaunchProjectile(firePoints[i]);
}
}
}
}
}
}
private void LaunchProjectile(Transform firePoint)
{
Rigidbody projectileInstance = Instantiate(
bulletPrefab,
firePoint.position,
firePoint.rotation);
projectileInstance.AddForce(new Vector3(0, 0, 1) * launchForce);
if (bulletsSlowDown == true)
{
if (projectileInstance != null)
{
StartCoroutine(AddDrag(maxDrag, bulletSpeed, projectileInstance));
}
}
if ((automaticFire == true || automaticFire == false) && bulletsSlowDown == false)
{
projectileInstance.gameObject.AddComponent<BulletDestruction>().destructionTime = bulletDestructionTime;
projectileInstance.gameObject.GetComponent<BulletDestruction>().Init();
}
}
IEnumerator AddDrag(float maxDrag, float bulletSpeed, Rigidbody rb)
{
if (rb != null)
{
float current_drag = 0;
while (current_drag < maxDrag)
{
current_drag += Time.deltaTime * bulletSpeed;
rb.drag = current_drag;
yield return null;
}
rb.velocity = Vector3.zero;
rb.angularVelocity = Vector3.zero;
rb.drag = 0;
rb.gameObject.AddComponent<BulletDestruction>().destructionTime = bulletDestructionTime;
rb.gameObject.GetComponent<BulletDestruction>().Init();
}
}
bool isAnimationStatePlaying(Animator anim, int animLayer, string stateName)
{
if (anim.GetCurrentAnimatorStateInfo(animLayer).IsName(stateName))
return true;
else
return false;
}
private void GatherAllChilds(Transform parent)
{
for (int i = 0; i < parent.childCount; i++)
{
if (parent.GetChild(i).name == "Sci-Fi_Soldier")
{
anim = parent.GetChild(i).GetComponent<Animator>();
}
if (parent.GetChild(i).tag == "Fire Point")
{
firePoints.Add(parent.GetChild(i));
}
GatherAllChilds(parent.GetChild(i));
}
}
}
Now this Shooting script effect each individual object when changing the setting also when the game is running.
I want to use now the ShootingManager script to control and effect and change settings on all over the Shooting scripts at once at the same time in real time also when the game is running.
What the other answer does not cover is syncing those changes when you make them live in the UnityEditor (Inspector) e.g. for fine tuning them.
It sounds like the perfect use case for ScriptableObject
[CreateAssetMenu (fileName = "new ShootingSettings", menuName = "ShootingSettings")]
public class ShootingSettings : ScriptableObject
{
[Header("Main")]
public float launchForce = 700f;
public bool automaticFire = false;
public float bulletDestructionTime;
[Space(5)]
[Header("Slow Down")]
public float maxDrag;
public float bulletSpeed;
public bool bulletsSlowDown = false;
public bool overAllSlowdown = false;
[Range(0, 1f)]
public float slowdownAll = 1f;
}
Create an instance by right click in the Assets -> Create -> ShootingSettings and give it a name.
Now change your Shooting class and the manager class instead have a
public ShootingSettings settings;
So from the manager use FindObjectsOfType which btw is way more efficient than using FindObjectsWithTag and multiple times GetComponent!
private void Awake()
{
// This is way more efficient than using find and GetComponent over and over again
foreach(var shooting in FindObjectsOfType<Shooting>())
{
shooting.settings = settings;
}
}
Now reference the asset you created before to the settings field of the manager script.
From now on any change you make to that created asset will be applied to all settings of all Shootig instances. So all that's left to do is change your Shooting script to use those settings instead ;)
Alternatively you could do the same thing actually also without ScriptableObject by simply having the class
[Serializable]
public class ShootingSettings
{
[Header("Main")]
public float launchForce = 700f;
public bool automaticFire = false;
public float bulletDestructionTime;
[Space(5)]
[Header("Slow Down")]
public float maxDrag;
public float bulletSpeed;
public bool bulletsSlowDown = false;
public bool overAllSlowdown = false;
[Range(0, 1f)]
public float slowdownAll = 1f;
}
instead. In this case you can make all settings directly in the manager class. Since all Shooting instances will then use the same instance reference every later change to the settings in the manager are done on the same settings Instance all your components share.
=> You wouldn't need any method nor event for getting the settings updated everywhere :)
Typed on smartphone so no warranty but I hope the idea gets clear
Its always better to call something as an event when it should work like one, in this case it doesn`t look like it needs to be in the update.
The thing is, where is your change coming from?
You could make ShootingSettings(); public and call it whenever you are changing it. Does it solve your problem?
Also, here are some suggestions
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class ShootingManager : MonoBehaviour
{
[Header("Main")]
public float launchForce = 700f;
public bool automaticFire = false;
public float bulletDestructionTime;
[Space(5)]
[Header("Slow Down")]
public float maxDrag;
public float bulletSpeed;
public bool bulletsSlowDown = false;
public bool overAllSlowdown = false;
[Range(0, 1f)]
public float slowdownAll = 1f;
//Making it public so you can drag and drop the
//references in the inspector
public List<Shooting> shooters;
// Start is called before the first frame update
void Start()
{
ShootingSettings();
}
// Update is called once per frame
void Update()
{
}
public void ShootingSettings()
{
for (int i = 0; i < shooters.Count; i++)
{
shooters[i].launchForce = launchForce;
shooters[i].automaticFire = automaticFire;
shooters[i].bulletDestructionTime = bulletDestructionTime;
shooters[i].maxDrag = maxDrag;
shooters[i].bulletSpeed = bulletSpeed;
shooters[i].bulletsSlowDown = bulletsSlowDown;
shooters[i].overAllSlowdown = overAllSlowdown;
shooters[i].slowdownAll = slowdownAll;
}
}
}
In the current code shootingScripts doesn't have a use.
shooters can be a list of Shooting, this way you don't need to do a GetComponent every time you want to acess it's script. and you still have the gameObject reference in case you need.
you could turn the Shooting list public and reference it
If you still want to use FindGameObjectsWithTag you can use this, but it can throw an error if any gameObject with Shooter tag doesnt have a Shooting script attached to it.
Edit: As #derHugo suggested is his answer, FindObjectsOfType is a better choice than finding it by tag.
Without using System.Linq
private List<Shooting> shooters;
void Start() {
Shooting[] shooterObjects = FindObjectsOfType<Shooting>();
shooters = new List<Shooting>(shooterObjects.Length);
for (int i = 0; i < shooterObjects.Length; i++)
{
shooters[i] = shooterObjects[i];
}
ShootingSettings();
}
Using System.Linq
private List<Shooting> shooters;
void Start() {
shooters = FindObjectsOfType<Shooting>().ToList();
ShootingSettings();
}
If you want it to work in the editor (for tests purpose) you can do this. This will only work in the editor. if you want to update in runtime you need to call ShootingSettings(); when you are making changes.
void Update()
{
#if UNITY_EDITOR
ShootingSettings();
#endif
}

Categories

Resources