I wrote this little Unity3D class to fade an array of UI.Image in or out by reducing or increasing the alpha value of their color property.
Then I wanted to use it for an array of materials on meshrenderers but realized that materials and UI.Image, though they both have a color property which can be changed, are totally different classes and you access their color properties differently so I cannot use this class for them both.
I tried making a list of just the colors to be changed, but in unity a Color is a struct and cannot be passed by reference.
Do you have any idea how to make this class generic so it can change the color property on several different classes? I sort of wrote myself into a corner here, I'd rather not just copy the class and change it for meshrenderer.material, that would be ugly
public class FadeThingInOut : MonoBehaviour
{
//public Setting fields
public Image[] ThingsToFade;
public bool Visible = true;
public float FadeTime = 1f;
//private fields
Color[] InColour;
Color[] OutColor;
void Start()
{
if (Visible)
{
SetColours(ref InColour, ref OutColor,0f);
}
else
{
SetColours(ref OutColor,ref InColour,1f);
}
}
void SetColours(ref Color[] one,ref Color[] two,float other)
{
one = new Color[ThingsToFade.Length];
two = new Color[ThingsToFade.Length];
for (int i = 0; i < ThingsToFade.Length; i++)
{
one[i] = ThingsToFade[i].color;
two[i] = Tools.colorfromalpha(one[i], other);
}
}
public void Fade(bool inout)
{
if (inout && !Visible)
{
StartCoroutine(FadeLerp(OutColor,InColour,inout));
}
else if (!inout && Visible)
{
StartCoroutine(FadeLerp(InColour, OutColor, inout));
}
}
IEnumerator FadeLerp(Color[] from, Color[] to,bool endstate)
{
float nowtime = 0f;
while (nowtime < FadeTime)
{
nowtime += Time.deltaTime;
float ratio = nowtime / FadeTime;
for (int i = 0; i < ThingsToFade.Length; i++)
{
ThingsToFade[i].color = Color.Lerp(from[i], to[i], ratio);
yield return null;
}
}
for (int i = 0; i < ThingsToFade.Length; i++)
{
ThingsToFade[i].color = to[i];
}
Visible = endstate;
}
}
UPDATE
Heres what I ended up doing.
I keep an array of the components and then cast them to one of three types that I am dealing with. There is a series of if statements to deal with the different classes differntly.
I'm still interested in a more elegant solution that may use some aspects of .net or C# that I'm not familiar with though
public class FadeThingInOut : MonoBehaviour
{
//public Setting fields
public Component[] ThingsToFade;
public bool Visible = true;
public float FadeTime = 1f;
public float Max = 1f;
//private fields
Color[] InColour;
Color[] OutColor;
void Start()
{
if (Visible)
{
SetColours(ref InColour, ref OutColor,0f);
}
else
{
SetColours(ref OutColor, ref InColour, Max);
}
}
void SetColours(ref Color[] one,ref Color[] two,float other)
{
one = new Color[ThingsToFade.Length];
two = new Color[ThingsToFade.Length];
for (int i = 0; i < ThingsToFade.Length; i++)
{
one[i] = GetColour(i);
two[i] = Tools.colorfromalpha(one[i], other);
}
}
public void Fade(bool inout)
{
if (inout && !Visible)
{
StartCoroutine(FadeLerp(OutColor,InColour,inout));
}
else if (!inout && Visible)
{
StartCoroutine(FadeLerp(InColour, OutColor, inout));
}
}
Color GetColour(int index)
{
if(ThingsToFade[index].GetType() == typeof(MeshRenderer))
{
return (ThingsToFade[index] as MeshRenderer ).material.color;
}
else if (ThingsToFade[index].GetType() == typeof(Image))
{
return (ThingsToFade[index] as Image).color;
}
else if (ThingsToFade[index].GetType() == typeof(RawImage))
{
return (ThingsToFade[index] as RawImage).material.color;
}
return Color.black;
}
void SetColour(int index,Color col)
{
if (ThingsToFade[index].GetType() == typeof(MeshRenderer))
{
(ThingsToFade[index] as MeshRenderer).material.color = col;
return;
}
else if (ThingsToFade[index].GetType() == typeof(Image))
{
(ThingsToFade[index] as Image).color = col;
return;
}
else if (ThingsToFade[index].GetType() == typeof(RawImage))
{
(ThingsToFade[index] as RawImage).material.color = col;
return;
}
}
IEnumerator FadeLerp(Color[] from, Color[] to,bool endstate)
{
float nowtime = 0f;
while (nowtime < FadeTime)
{
nowtime += Time.deltaTime;
float ratio = nowtime / FadeTime;
for (int i = 0; i < ThingsToFade.Length; i++)
{
SetColour(i, Color.Lerp(from[i], to[i], ratio));
yield return null;
}
}
for (int i = 0; i < ThingsToFade.Length; i++)
{
SetColour(i, to[i]);
}
Visible = endstate;
}
}
This isn't super ideal but you can do it this way.
public interface ISetColor
{
public Color color { get; set; }
}
public class ImageSetColor : MonoBehaviour, ISetColor
{
public Image m_Image
public Color color { get {return m_Image.color;} set { m_Image.color = value}
}
public class MaterialSetColor : MonoBehaviour, ISetColor
{
public Material m_Material
public Color color { get {return m_Material.color;} set { m_Material.color = value}
}
Then instead of an Image array you can do an array of public MonoBehaviour[] ThingsToFade then anytime you use ThingsToFade you need to cast it to an ISetColor.
Then any component you want to be able to set colors with will need to create a class that inherits from MonoBehaviour and implements the ISetColor interface
This isn't the easiest solution to use in Unity but it should work
You should not write a class. You should write an extension method. Because structs are immutable though, you cannot change their value, so you should write a static function that has as an argument a reference to color, by using the "ref" keyword. Using structs as mutable objects is not recommended most of the time, but it's the best option here.
I wrote this little Unity3D class to fade an array of UI.Image in or
out by reducing or increasing the alpha value of their color property.
I dont see why to overcomplicate everything.
Edit: This will fade by adjusting the alpha directly, using floats instead of color swapping. Faster, cheaper, shorter.
IEnumerator FadeLerp(float _from, float _to, bool endstate)
{
float nowtime = 0f;
while (nowtime < FadeTime)
{
nowtime += Time.deltaTime;
float ratio = nowtime / FadeTime;
for (int i = 0; i < ThingsToFade.Length; i++)
{
Color _color = ThingsToFade[i].color;
_color.a = Mathf.Lerp(_from, _to, ratio);
ThingsToFade[i].color = _color;
yield return null;
}
}
for (int i = 0; i < ThingsToFade.Length; i++)
{
ThingsToFade[i].renderer.enabled = endstate;
}
}
use color.lerp function.
it will fade the color nicely, and if you know how to use the lerp, with a counter and with an array, there should not be a problem :)
if tomorrow you are still stucked i'll write the lerping function for you, but keep trying :)
It will be a sweet experience if you do it on your own :D
Related
I'm working on Unity3d project where the floor has to unfold gradually. I created a script FloorModule.cs where using coroutine the floor tiles are laying out gradually. Each next module has to unfold right after previous is completed. There for I created Spawner.cs to loop a new FloorModule.cs right after previous one is completed.
I can't seem to get my head around how to use coroutine to synchronize the mainloop (Spawner.cs) with subloop on prefab (FloorModule.cs).
Here is the link to the example
https://1drv.ms/u/s!AkVZpIE6f1GV4M5Ju7G5zPOrQcCe8w?e=QrghRT
P.S.
In given example, as loop goes forward I'm using "Reference.cs" class to change some variable values .
FloorModule.cs
public class FloorModule : MonoBehaviour
{
public float zSpacer = 0f;
public int instPrefabCount;
public Transform spawnPoint;
public int lenght = 15;
public int width = 5;
public GameObject floorTiles;
void Start()
{
spawnPoint = GetComponent<Transform>();
StartCoroutine(FwFloorDelay(spawnPoint));
}
public IEnumerator FwFloorDelay(Transform origin)
{
for (int l = 0; l < lenght; l++)
{
float xAngle = 90;
float yPos = 0;
float zPos = 0 + l;
for (int w = 0; w < width; w++)
{
int xSelection = Random.Range(0, 6);
GameObject xFloor = Instantiate(floorTiles, origin);
TileStatusNames(xFloor, l, w);
// defining positiona and angles
float xPos = w + (zSpacer * w);
xFloor.transform.localEulerAngles = new Vector3(xAngle, 0, 0);
xFloor.transform.localPosition = new Vector3(xPos, yPos, zPos);
yield return new WaitForSeconds(.05f);
}
}
Spawner.cs
public class Spawner : MonoBehaviour
{
public GameObject FloorModPrefab;
public References[] referenceScript;
void Start()
{
StartCoroutine(SpawnModules());
}
IEnumerator SpawnModules()
{
for (int i = 0; i < referenceScript.Length; i++)
{
referenceScript[i].instance =
Instantiate(FloorModPrefab, referenceScript[i].ref_spawnPoint.position, referenceScript[i].ref_spawnPoint.rotation);
referenceScript[i].ref_instFloorModCount = i + 1;
referenceScript[i].Setup();
yield return new WaitForSeconds(5f);
}
}
}
References.cs
[Serializable]
public class References
{
FloorModule prefabObjScript;
public GameObject instance;
public int ref_instFloorModCount;
public Transform ref_spawnPoint;
public int ref_Width = 5;
public int ref_Lenght = 15;
public void Setup()
{
// Get references to the components.
prefabObjScript = instance.GetComponent<FloorModule>();
// Set the player numbers to be consistent across the scripts.
prefabObjScript.instPrefabCount = ref_instFloorModCount;
prefabObjScript.spawnPoint = ref_spawnPoint;
prefabObjScript.width = ref_Width;
prefabObjScript.lenght = ref_Lenght;
}
}
I tried to use coroutines unfortunately in given context I realize it's impossible for me to resolve this task.
You can yield a coroutine from within another coroutine.
Changes to your Code
In References change public GameObject instance; to public FloorModule instance;
In Spawner change public GameObject FloorModPrefab; to public FloorModule FloorModPrefab;
Remove the code from Start of FloorModule.
Modify FwFloorDelay to
public IEnumerator FwFloorDelay(Transform origin = null)
{
if (origin == null)
{
origin = transform;
}
...
}
In SpawnModules, chain the floor delay coroutine
IEnumerator SpawnModules()
{
for (int i = 0; i < referenceScript.Length; i++)
{
...
yield return referenceScript[i].instance.FwFloorDelay();
}
}
Your goal should not be to synchronize separate coroutines, but rather to get the code running sequentially in one coroutine.
For example, you could make References.Setup() asynchronous, and then have it invoke FloorModel.FwFloorDelay directly instead of the FloorModel starting its own separate coroutine.
I am currently programming a game in which an infinite procedural city is generated. so far everything works but because it leads to laggs if there are too many objects in the scene I wanted to make a script in which objects only appear near the player. I watched this video for help:https://www.youtube.com/watch?v=xlSkYjiE-Ck. When I tried to link this to my script (GenerateBuilding script) this error came:ArgumentException:
An item with the same key has already been added. Key: (0.0, 1.0)
System.Collections.Generic.Dictionary...
I need help to make the script work in which the houses are generated as well as the planes do, they should only be showed when the player is nearby
---Relevant Lines---
(Endless City)
calling updateChunk function in update()(updateChunk/building function is in GenerateBuilding script)
public void UpdateBuildings()
{
for (int i = 0; i < buildingObjects.Count; i++)
{
buildingObjects[i].SetVisible(false);
}
buildingObjects.Clear();
}
adding to dictionary line 68-80(UpdateVisibleChunks function)
if (building.cityChunkDictionary.ContainsKey(viewedChunkCoord))
{
building.cityChunkDictionary[viewedChunkCoord].UpdateCityChunk(viewerPosition, viewedChunkCoord, chunkSize, maxViewDst);
if (building.cityChunkDictionary[viewedChunkCoord].IsVisible())
{
building.buildingObjects.Add(building.cityChunkDictionary[viewedChunkCoord]);
}
}
else
{
building.AddTest(viewedChunkCoord, chunkSize);
}
EndlessCity, CityChunk class
CityChunk function, sending position to GenerateBuilding script to instantiate buildings in right position.
building.requestBuildingSquad(positionV3);
GenerateBuilding relevant lines
builderH function, instantiates the buildings
public float builderH(GameObject[] obj, float Height, Vector3 position)
{
Transform objTrans = obj[Random.Range(0, obj.Length)].transform;
//Instantiate house Object
GameObject objekt = Instantiate(objTrans.gameObject, position + new Vector3(xOfsset * spaceBetween, Height, zOfsset * spaceBetween), transform.rotation);
float height = Test.transform.localScale.y;
objectsss.Add(objekt);
return height;
}
AddTest function, adds instantiates objects from builderH to a dictionary
public void AddTest(Vector2 viewedChunkCoord, float chunkSize)
{
for (int i = 0; i < objectsss.Count; i++)
{
cityChunkDictionary.Add(viewedChunkCoord, new Testing(objectsss[i]));
}
}
Testing class, testing function, adds objects to class
public Testing(GameObject obj)
{
MeshObject = obj;
}
that should be all relevant lines
full scripts(really similar)
EndlessCity Script(this scripts generates the planes and gives position for GenerateBuilding script)
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
public class EndlessCity : MonoBehaviour
{
public const float maxViewDst = 10;
public Transform viewer;
private GenerateBuilding building;
public static Vector2 viewerPosition;
int chunkSize;
int chunksVisibleInViewDst;
Dictionary<Vector2, CityChunk> terrainChunkDictionary = new Dictionary<Vector2, CityChunk>();
List<CityChunk> terrainChunksVisibleLastUpdate = new List<CityChunk>();
void Start()
{
chunkSize = 8 - 1;
chunksVisibleInViewDst = Mathf.RoundToInt(maxViewDst / chunkSize);
}
void Update()
{
viewerPosition = new Vector2(viewer.position.x, viewer.position.z);
UpdateVisibleChunks();
}
void UpdateVisibleChunks()
{
building = FindObjectOfType<GenerateBuilding>();
building.UpdateBuildings();
for (int i = 0; i < terrainChunksVisibleLastUpdate.Count; i++)
{
terrainChunksVisibleLastUpdate[i].SetVisible(false);
}
terrainChunksVisibleLastUpdate.Clear();
int currentChunkCoordX = Mathf.RoundToInt(viewerPosition.x / chunkSize);
int currentChunkCoordY = Mathf.RoundToInt(viewerPosition.y / chunkSize);
for (int yOffset = -chunksVisibleInViewDst; yOffset <= chunksVisibleInViewDst; yOffset++)
{
for (int xOffset = -chunksVisibleInViewDst; xOffset <= chunksVisibleInViewDst; xOffset++)
{
Vector2 viewedChunkCoord = new Vector2(currentChunkCoordX + xOffset, currentChunkCoordY + yOffset);
if (terrainChunkDictionary.ContainsKey(viewedChunkCoord))
{
terrainChunkDictionary[viewedChunkCoord].UpdateTerrainChunk();
if (terrainChunkDictionary[viewedChunkCoord].IsVisible())
{
terrainChunksVisibleLastUpdate.Add(terrainChunkDictionary[viewedChunkCoord]);
}
}
else
{
terrainChunkDictionary.Add(viewedChunkCoord, new CityChunk(viewedChunkCoord, chunkSize, transform));
}
if (building.cityChunkDictionary.ContainsKey(viewedChunkCoord))
{
building.cityChunkDictionary[viewedChunkCoord].UpdateCityChunk(viewerPosition, viewedChunkCoord, chunkSize, maxViewDst);
if (building.cityChunkDictionary[viewedChunkCoord].IsVisible())
{
building.buildingObjects.Add(building.cityChunkDictionary[viewedChunkCoord]);
}
}
else
{
building.AddTest(viewedChunkCoord, chunkSize);
}
}
}
}
public class CityChunk
{
private GenerateBuilding building;
public GameObject meshObject;
public Vector3 positionV3;
Vector2 position;
Bounds bounds;
public CityChunk(Vector2 coord, int size, Transform parent)
{
building = FindObjectOfType<GenerateBuilding>();
position = coord * size;
bounds = new Bounds(position, Vector2.one * size);
positionV3 = new Vector3(position.x, 0, position.y);
int xPosition = building.xLength / 2;
int zPosition = building.zLength / 2;
float xOfsset = building.xOfsset;
float zOfsset = building.zOfsset;
float spaceBetween = building.spaceBetween;
//Instantiate plane
meshObject = Instantiate(building.groundObject, positionV3 + new Vector3((xPosition + xOfsset) * spaceBetween, -.5f, (xPosition + 1 + zOfsset) * spaceBetween), Quaternion.identity);
SetVisible(false);
building.requestBuildingSquad(positionV3);
}
public void UpdateTerrainChunk()
{
float viewerDstFromNearestEdge = Mathf.Sqrt(bounds.SqrDistance(viewerPosition));
bool visible = viewerDstFromNearestEdge <= maxViewDst;
SetVisible(visible);
}
public void SetVisible(bool visible)
{
meshObject.SetActive(visible);
}
public bool IsVisible()
{
return meshObject.activeSelf;
}
}
}
GenerateBuilding(this script generates Buildings on the planes)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GenerateBuilding : MonoBehaviour
{
public int minHeight = 2;
public int maxHeight = 8;
public int cubeTileX;
public int cubeTileZ;
public int xLength;
public int zLength;
public float spaceBetween;
public float xOfsset;
public float zOfsset;
public GameObject TesObject;
public GameObject[] Base;
public GameObject[] secondB;
public GameObject[] roof;
public GameObject groundObject;
public List<GameObject> objectsss;
public Dictionary<Vector2, Testing> cityChunkDictionary = new Dictionary<Vector2, Testing>();
public List<Testing> buildingObjects = new List<Testing>();
public GameObject Test;
void Start()
{
//requestBuildingSquad(this.transform.position);
}
void Update()
{
if (Input.GetKeyDown(KeyCode.I))
{
//
}
}
public void requestBuildingSquad(Vector3 position)
{
//*getting the middle of the city squad
int xPosition = xLength / 2;
int zPosition = zLength / 2;
//*
for (int z = 0; z < zLength; z++)
{
zOfsset++;
for (int x = 0; x < xLength; x++)
{
GenerateBuildings(position);
}
xOfsset = 0;
}
zOfsset = 0;
}
public void GenerateBuildings(Vector3 position)
{
int bHeight = Random.Range(minHeight, maxHeight);
float bOfsset = 0;
bOfsset += builderH(Base, bOfsset, position);
for (int i = 0; i < bHeight; i++)
{
bOfsset += builderH(secondB, bOfsset, position);
}
bOfsset += builderH(roof, bOfsset, position);
xOfsset++;
}
public float builderH(GameObject[] obj, float Height, Vector3 position)
{
Transform objTrans = obj[Random.Range(0, obj.Length)].transform;
//Instantiate house Object
GameObject objekt = Instantiate(objTrans.gameObject, position + new Vector3(xOfsset * spaceBetween, Height, zOfsset * spaceBetween), transform.rotation);
float height = Test.transform.localScale.y;
objectsss.Add(objekt);
return height;
}
public void AddTest(Vector2 viewedChunkCoord, float chunkSize)
{
for (int i = 0; i < objectsss.Count; i++)
{
cityChunkDictionary.Add(viewedChunkCoord, new Testing(objectsss[i]));
}
}
public void UpdateBuildings()
{
for (int i = 0; i < buildingObjects.Count; i++)
{
buildingObjects[i].SetVisible(false);
}
buildingObjects.Clear();
}
public class Testing
{
public GameObject MeshObject;
Vector2 position;
Bounds bounds;
public Testing(GameObject obj)
{
MeshObject = obj;
}
public void SetVisible(bool visiblee)
{
MeshObject.SetActive(visiblee);
}
public bool IsVisible()
{
return MeshObject.activeSelf;
}
public void UpdateCityChunk(Vector3 viewerPosition, Vector2 coord, int size, float maxViewDst)
{
position = coord * size;
bounds = new Bounds(position, Vector2.one * size);
float viewerDstFromNearestEdge = Mathf.Sqrt(bounds.SqrDistance(viewerPosition));
bool visible = viewerDstFromNearestEdge <= maxViewDst;
SetVisible(visible);
}
}
}
The problem is that you are trying to add twice elements with the same key.
here is the documentation of the Add method for dictionaries, and as it states, trying to add an existing key throws an error.
You can either use the TryAdd method, which adds an item only if the key doesn't exist already in the dictionary, or update the value with the existing key as you can see here.
Im new to coding in C# and I'm not able to set the particle count in the Nvidia FlexArray script, the particle count public class appears grey and can't be edited. It is somehow permanently set to 0.
Ive attached an image and the script that comes with the package. Id greatly appreciate any help. The image for the inspector view on array actor
using UnityEngine;
namespace NVIDIA.Flex
{
[ExecuteInEditMode]
public class FlexArrayAsset : FlexAsset
{
#region Properties
public Mesh boundaryMesh
{
get { return m_boundaryMesh; }
set { m_boundaryMesh = value; }
}
public Vector3 meshLocalScale
{
get { return m_meshLocalScale; }
set { m_meshLocalScale = value; }
}
public float meshExpansion
{
get { return m_meshExpansion; }
set { m_meshExpansion = value; }
}
public float particleSpacing
{
get { return m_particleSpacing; }
set { m_particleSpacing = Mathf.Max(value, 0.01f); }
}
public float particleCount
{
get { return m_particleCount; }
set { m_particleCount = Mathf.Max(value, 516f); }
}
#endregion
#region Methods
#endregion
#region Messages
#endregion
#region Protected
protected override void ValidateFields()
{
base.ValidateFields();
m_particleSpacing = Mathf.Max(m_particleSpacing, 0.01f);
}
protected override void RebuildAsset()
{
BuildFromMesh();
base.RebuildAsset();
}
#endregion
#region Private
void BuildFromMesh()
{
if (m_boundaryMesh)
{
Vector3[] vertices = m_boundaryMesh.vertices;
if (vertices != null && vertices.Length > 0)
{
for (int i = 0; i < vertices.Length; ++i)
{
Vector3 v = vertices[i];
vertices[i] = new Vector3(v.x * m_meshLocalScale.x, v.y * m_meshLocalScale.y, v.z * m_meshLocalScale.z);
}
int[] indices = m_boundaryMesh.triangles;
if (indices != null && indices.Length > 0)
{
FlexExt.Asset.Handle assetHandle = FlexExt.CreateRigidFromMesh(ref vertices[0], vertices.Length, ref indices[0], indices.Length, m_particleSpacing, m_meshExpansion);
if (assetHandle)
{
FlexExt.Asset asset = assetHandle.asset;
FlexExt.Asset particlesOnly = new FlexExt.Asset();
particlesOnly.numParticles = asset.numParticles;
particlesOnly.maxParticles = asset.numParticles;
particlesOnly.particles = asset.particles;
StoreAsset(particlesOnly);
FlexExt.DestroyAsset(assetHandle);
}
}
}
}
}
[SerializeField]
Mesh m_boundaryMesh = null;
[SerializeField]
Vector3 m_meshLocalScale = Vector3.one;
[SerializeField, Tooltip("Particles will be moved inwards (if negative) or outwards (if positive) from the surface of the mesh according to this factor")]
float m_meshExpansion = 0.0f;
[SerializeField, Tooltip("The spacing used for voxelization, note that the number of voxels grows proportional to the inverse cube of radius, currently this method limits construction to resolutions < 64^3")]
float m_particleSpacing = 0.1f;
#endregion
}
}
Properties can't be serialized directly in Unity.
Recommended flow would be to mark m_particleCount as a [SerializeField], which exposes it to the editor even if it's private.
It looks like it might be buried in the Flex API though, so I'm not sure if you can edit it directly. A more complex editor script could do the job in such a case, I'd recommend looking at trying to hack the value with your own value, then using OnValidate() to write that value to the real one.
That said, is this value really what you think it is? I haven't used the API but 'particleCount' is usually the number of live particles in the system, naturally at edit time it would be 0, but at runtime it could be 10 or 20 or 1000 depending on what the particle system is doing.
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.
I'm learning to use the as operator, and my goal was to create an option window (non windows form) that can:
Have options added to it (for flexibility, in case I want to use if statements to add menu items)
Be able to display text, textures, or a class (using the classes draw function)
Be controlled through the host GameState
I still haven't added the options for indicating an item is selected, my apologies for not posting a complete work. I also have not sorted the code into regions yet. Sorry again!
Is my code (particularly the draw function) properly using the is and as operators properly, from a performance and readability (non spaghetti code) standpoint?
public class OptionWindow : DrawableGameComponent
{
public Dictionary<int, Option> options;
int selectedOption;
bool windowLoops;
Rectangle drawRectangle;
int spacer;
int totalItemHeight;
SpriteFont sf;
SpriteBatch sb;
public Rectangle DrawRectangle
{
get { return drawRectangle; }
set { drawRectangle = value; }
}
public int SelectedOption
{
get { return selectedOption; }
set
{
if (windowLoops)
{
if (selectedOption >= options.Count())
selectedOption = 0;
if (selectedOption < 0)
selectedOption = options.Count() - 1;
}
else
{
if (selectedOption >= options.Count())
selectedOption = options.Count() - 1;
if (selectedOption < 0)
selectedOption = 0;
}
}
}
public OptionWindow(Game game, bool windowLoops, SpriteFont sf, Rectangle drawRectangle)
: base(game)
{
options = new Dictionary<int, Option>();
this.windowLoops = windowLoops;
this.sf = sf;
DrawRectangle = new Rectangle(drawRectangle.X, drawRectangle.Y, drawRectangle.Width, drawRectangle.Height);
}
public void Add(object option, bool selectable, bool defaultSelection, int height)
{
options.Add(options.Count(), new Option(selectable, option, height));
if (defaultSelection)
SelectedOption = options.Count() - 1;
UpdatePositions();
}
public void UpdatePositions()
{
UpdateTotalItemHeight();
if (options.Count() - 1 != 0)
spacer = (drawRectangle.Height - totalItemHeight) / (options.Count() - 1);
for (int i = 0; i < options.Count(); i++)
{
if (i == 0)
options[i].Position = new Vector2(drawRectangle.X, drawRectangle.Y);
else
{
options[i].Position = new Vector2(
drawRectangle.X,
options[i - 1].Position.Y + options[i - 1].Height + spacer);
}
}
}
public void UpdateTotalItemHeight()
{
totalItemHeight = 0;
for (int i = 0; i < options.Count(); i++)
{
totalItemHeight += options[i].Height;
}
}
protected override void LoadContent()
{
sb = new SpriteBatch(GraphicsDevice);
base.LoadContent();
}
public override void Draw(GameTime gameTime)
{
for (int i = 0; i < options.Count(); i++)
{
if (options[i].OptionObject is string)
sb.DrawString(sf, options[i].OptionObject as string, options[i].Position, Color.White);
if (options[i].OptionObject is Texture2D)
sb.Draw(options[i].OptionObject as Texture2D,
new Rectangle(
(int)options[i].Position.X,
(int)options[i].Position.Y,
options[i].Height,
(options[i].Height / (options[i].OptionObject as Texture2D).Height) * (options[i].OptionObject as Texture2D).Width),
Color.White);
if (options[i].OptionObject is DisplayObject)
(options[i].OptionObject as DisplayObject).Draw(gameTime);
}
base.Draw(gameTime);
}
}
public class Option
{
bool selectable;
object optionObject;
int height;
Vector2 position;
public bool Selectable
{
get { return selectable; }
set { selectable = value; }
}
public object OptionObject
{
get { return optionObject; }
set { optionObject = value; }
}
public int Height
{
get { return height; }
set { height = value; }
}
public Vector2 Position
{
get { return position; }
set { position = value; }
}
public Option(bool selectable, object option, int height)
{
Selectable = selectable;
OptionObject = option;
Height = height;
}
}
It is never adviseable to use is and then as. The usual way to go would be to either of the following:
just use is (if you just want to know the type without subsequent casting)
assign the result of as to a variable and check whether that variable is (not) null
The code analysis tool FxCop helps you find any spots in your code that use is and then as and warns you because of performance concerns.
Note however that a better approach altogether might be to declare your OptionObject property as some abstract class with a Draw method. You could then derive a subclass for strings, one for Texture2D instances and another one for DisplayObject instances and just call Draw in your OptionWindow.Draw method. This would leave the decision which actual drawing operations to execute up to built-in polymorphism features of the framework.