First I will explain whats happening, then what I am expecting to happen, and finally the code behind it
So whats happening is when I press enter the color of the text is green
What I expect to happen is the color turn red
This is based on if i type "Bad" into the field
//Please note I have edited uni9mportant code out
//Event Listener
inputField.onEndEdit.AddListener (delegate {
VerifyWords();
});
//Clss that handles the dictionary
public abstract class WordDictionary: MonoBehaviour{
public static Dictionary<string,bool> _wordDictionary = new Dictionary<string,bool> ();
private void Start(){
_wordDictionary.Add ("Bad",true);
}
}
//Function that handles the word verification
private void VerifyWords(){
if (openChat == false) { //If we done have open chat
bool hasBadWords = false; //Reset boolean
string[] stringSplit = inputField.text.Split (' '); //Split text string
for (int i = 0; i < stringSplit.Length; i++) { // Go through each word in the string array
if (WordDictionary._wordDictionary.ContainsKey (stringSplit[i])) { //If the word is in the dictionary
hasBadWords = true; //Then there is a bad word
}
}
if (hasBadWords == true) { //If a bad word was found
inputField.textComponent.color = Color.red; //Then the text should be red
} else {
inputField.textComponent.color = Color.green; //The text should be green
}
}
}
EDIT
I edited the code with comments to kinda put my thinking into perspective
The problem is that the class is marked as abstract. An abstract class cannot be instantiated, and therefore Unity can't call Start on a class that it cannot instantiate. The easiest fix is to simply remove the abstract from the class definition:
public class WordDictionary: MonoBehaviour{
public static Dictionary<string,bool> _wordDictionary = new Dictionary<string,bool> ();
private void Start(){
_wordDictionary.Add ("Bad",true);
}
}
However you now have a new problem. WordDictionary has a static member that is initialized by a non-static method. This means that each time you create a new WordDictionary, Start() will be called and it will add all the words into the dictionary again (or at least it will attempt to, you will get a duplicate key exception in this case, to avoid that you can also write _wordDictionary["Bad"] = true which replaces an existing key if it exists).
The better option here is to use a static constructor. This will make sure that the dictionary is only initialized once:
public class WordDictionary: MonoBehaviour{
public static Dictionary<string,bool> _wordDictionary = new Dictionary<string,bool> ();
static WordDictionary() {
_wordDictionary.Add ("Bad",true);
}
private void Start(){
}
}
Now you can use WordDictionary without worrying about the dictionary growing each time the class is instantiated. But at this point there really is no use in making WordDictionary a MonoBehavior because really it is just a holder for a bunch of words. So your class now just becomes:
public class WordDictionary: {
private static Dictionary<string,bool> _wordDictionary = new Dictionary<string,bool> ();
public static Dictionary<string, bool> Words {
get { return _wordDictionary; }
}
static WordDictionary() {
_wordDictionary.Add ("Bad",true);
}
}
I added a property here because really you should be using properties, and having the underscore names (in my code world) means that it is a private field. You can extend your dictionary to do other things instead:
public class WordDictionary: {
private static List<string> _wordList = new List<string> ();
static WordDictionary() {
_wordList.Add ("Bad");
}
public static Contains(string word) {
return _wordList.Contains(word);
}
public static ContainsAny(IEnumerable<string> words) {
return words.Any(w => Contains(w));
}
}
I don't see any reason to use a Dictionary here, if it contains the word then it is "bad", if it doesn't contain the word then it would be "good". So changing to a list makes things simpler. If you hide how the "Dictionary" works in the background and just expose the "contains" and "contains any" methods, you get two advantages, use becomes simpler, and you can change the underlying "engine" without changing the interface and downstream code.
And now your colorization function becomes much simpler:
private void VerifyWords() {
if (openChat)
return;
var stringSplit = inputField.text.Split(' ');
if (WordDictionary.ContainsAny(stringSplit))
inputField.textComponent.color = Color.red;
else
inputField.textComponent.color = Color.green;
}
First I would recomend that you dont use MonoBehaviour for your WordDictionary. I see no reason to do so. Insted use ScriptableObject.
Second it doesn't need to be abstract and I dont think you need a dictionary at all a list would do just fine assuming you only store the 'bad' words.
e.g. (and I used dicitonary here, could be list assuming your requirements would work with only the bad words being stored)
[CreateAssetMenu(menuName = "Custom Object/Word Dictionary")]
public class WordDictionary : ScriptableObject
{
public Dictionary<string, bool> Values = new Dictionary<string, bool>();
}
Scriptable objects are created in your assets and can be referenced by GameObjects in your scenes. So in gameObjects that need to use the dicitonary just add a
public WordDictionary gameDictionary;
And set it to the dictionary you created in your assets. In general singletons/static classes and similar approches of making a monoBehaviour act like a static object leads to problems.
Scriptable Objects are a great workaround. They are available at start of game without initalization like a singleton, they can contain data or funcitons like any other class but unlike MonoBehaviour they do not have Start, Update, etc. ... doesn't mean you can call a AtStart() you write in the Scriptable object from some specific initalization behaviour in your game however.
1 important note, in editor the data updated in a scriptable object persists, at runtime it does not. e.g. when testing your game in editor the 'bad' words will be retained between sessions ... in build it will not but I assume you are preparing your dictionary on initalization of the game anyway so this should be a non-issue.
Maybe this will help you
//Please note I have edited uni9mportant code out
//Event Listener
inputField.onEndEdit.AddListener (delegate {
VerifyWords();
});
//Clss that handles the dictionary
public abstract class WordDictionary: MonoBehaviour{
public static Dictionary<string,bool> _wordDictionary = new Dictionary<string,bool> ();
private void Start(){
_wordDictionary.Add ("Bad",true);
}
}
//Function that handles the word verification
private void VerifyWords(){
if (openChat == false) { //If we done have open chat
bool hasBadWords = false; //Reset boolean
string[] stringSplit = inputField.text.Split (' '); //Split text string
int i=0;
for (i = 0; i < stringSplit.Length; i++) { // Go through each word in the string array
if (WordDictionary._wordDictionary.ContainsKey (stringSplit[i])) { //If the word is in the dictionary
inputField.textComponent.color = Color.red; //Then the text should be red
}
}
if (i == stringSplit.Length) { //If a bad word was found
inputField.textComponent.color = Color.green; //The text should be green
}
}
}
Related
I have a List<AbilityEffect> effects and a lot of sub-classes of AbilityEffect, such as DamageEffect, HealEffect e.t.c. that HAVE [System.Serializable] property on it.
If I create class with field such as DamageEffect - Default editor will draw it perfectly! (And other effects too!)
I've added a ContextMenu Attribute to this function in AbilityData.cs
[ContextMenu(Add/DamageEffect)]
public static void AddDamageEffect()
{
effects.Add(new DamageEffect());
}
BUT default Unity Editor draws it if was an AbilityEffect, NOT a DamageEffect!
I've write some Custom Editor for class, that contains List<AbilityEffect> effects = new List<AbilitiEffect>(), write code that draws a custom list! But how do I tell a Editor to draw a DamageEffect specifically, NOT AbilityEffect?
I'll put some code below:
Ability Data Class
using UnityEngine;
using System.Collections.Generic;
[CreateAssetMenu(fileName = "New Ability", menuName = "ScriptableObject/Ability")]
public class AbilityData : ScriptableObject
{
public int cooldown = 0;
public int range = 1;
public List<AbilityEffect> effects = new List<AbilityEffect>();
public bool showEffects = false;
[ContextMenu("Add/DamageEffect")]
public void AddDamageEffect()
{
effects.Add(new DamageEffect());
}
}
Ability Data Editor Class
using UnityEditor;
using UnityEngine;
using System.Collections.Generic;
[CustomEditor(typeof(AbilityData))]
public class AbilityEditor : Editor
{
public override void OnInspectorGUI()
{
var ability = (AbilityData)target;
DrawDetails(ability);
DrawEffects(ability);
}
private static void DrawEffects(AbilityData ability)
{
EditorGUILayout.Space();
ability.showEffects = EditorGUILayout.Foldout(ability.showEffects, "Effects", true);
if (ability.showEffects)
{
EditorGUI.indentLevel++;
List<AbilityEffect> effects = ability.effects;
int size = Mathf.Max(0, EditorGUILayout.IntField("Size", effects.Count));
while (size > effects.Count)
{
effects.Add(null);
}
while (size < effects.Count)
{
effects.RemoveAt(effects.Count - 1);
}
for (int i = 0; i < effects.Count; i++)
{
DrawEffect(effects[i], i);
}
EditorGUI.indentLevel--;
}
}
private static void DrawDetails(AbilityData ability)
{
EditorGUILayout.LabelField("Details");
EditorGUILayout.Space();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Cooldown", GUILayout.MaxWidth(60));
ability.cooldown = EditorGUILayout.IntField(ability.cooldown);
EditorGUILayout.LabelField("Range", GUILayout.MaxWidth(40));
ability.range = EditorGUILayout.IntField(ability.range);
EditorGUILayout.EndHorizontal();
}
private static void DrawEffect(AbilityEffect effect, int index)
{
//if (effect is DamageEffect)
// effect = EditorGUILayout
// HOW??
}
}
Ability Effect class (NOT ABSTRACT)
[System.Serializable]
public class AbilityEffect
{
public virtual void Affect() { }
}
Damage Effect Class
[System.Serializable]
public class DamageEffect : AbilityEffect
{
public int damageAmout = 1;
public override void Affect() { ... }
}
Because of how Serialization works, once you deserialize some data Unity will try to populate an object instance based on the type specified in the class definition. If you have a List<AbilityEffect> Unity won't be able to differentiate which specific AbilityEffect you previously serialized. There is really one solution, change AbilityEffect to be a ScriptableObject, so that Unity doesn't actually serialize them as raw data but as GUID references, so that the referenced assets know by themselves what subtype of AbilityEffect they are. The downside is that this way all your effects will have to be assets in your Assets folder.
First of all: Note that since Unity 2021 the foldout is built-in default for all lists and arrays so actually I see absolutely no need for a custom editor at all really (at least for the list part) ;)
There is a couple of problems with your approach.
BUT default Unity Editor draws it if was an AbilityEffect, NOT a DamageEffect.
Yes, because it is serialized only as a AbilityEffect! For the Serializer all items in that list are of type AbilityEffect and it won't go any further.
So even if you can manage to add subclass items it will only be temporary! After e.g. saving, closing Unity and reopening all the subtypes should be converted to AbilityEffect because that's tue only type the Serializer actually sees for those.
My recommendation would be to rather make your AbilityEffect also of type ScriptableObject. This way you don't even have to bother with a custom drawer for them at all and could have as many instances with different types and configurations as you want, reuse them etc.
This said now a general thing: Don't directly go through the target in editors! (except you know exactly what you are doing)
This doesn't mark this object as "dirty", doesn't work with Undo/Redo and worst of all - it won't save these changes persistently!
Always rather go through the serializedObject and the SerializedPropertys.
[CustomEditor(typeof(AbilityData))]
public class AbilityEditor : Editor
{
SerializedProperty cooldown;
SerializedProperty range;
SerializedProperty effects;
SerializedProperty showEffects;
private void OnEnable ()
{
// Link up the serialized fields you will access
cooldown = serializedObject.FindProperty(nameof(AbilityData.cooldown));
range = serializedObject.FindProperty(nameof(AbilityData.range));
effects = serializedObject.FindProperty(nameof(AbilityData.effects));
showEffects = serializedObject.FindProperty(nameof(AbilityData.showEffects));
}
public override void OnInspectorGUI()
{
// refresh current actual values into the editor
serializedObject.Update();
DrawDetails();
DrawEffects();
// write back any changed values from the editor back to the actual object
// This handles all marking dirty, saving and handles Undo/Redo
serializedObject.ApplyModifiedProperties();
}
private void DrawEffects()
{
// Now always ever only read and set values via the SerializedPropertys
EditorGUILayout.Space();
showEffects.boolValue = EditorGUILayout.Foldout(showEffects.boolValue, effects.displayName, true);
if (showEffects.boolValue)
{
EditorGUI.indentLevel++;
// This already handles all the list drawing by default
EditorGUILayout.PropertyField(effects, GUIContent.none, true);
EditorGUI.indentLevel--;
}
}
private void DrawDetails()
{
EditorGUILayout.LabelField("Details");
EditorGUILayout.Space();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(cooldown.displayName, GUILayout.MaxWidth(60));
cooldown.intValue = EditorGUILayout.IntField(cooldown.intValue);
EditorGUILayout.LabelField(range.displayName, GUILayout.MaxWidth(40));
range.intValue = EditorGUILayout.IntField(range.intValue);
EditorGUILayout.EndHorizontal();
}
}
Now if you really really want to customize the behavior of the list drawing you could use a ReorderableList and can then implement a drawer for each element and there you could indeed perform a type check.
But as said I wouldn't go this way at all since the Serializer doesn't support it anyway.
I am trying to add an object to a UnityEvent listener and set that listeners function to Raise() function as the trigger. Like the below screenshot. That way I won't forget to drag and drop the object, or by chance selected the wrong function;
I have some a custom GameEvent that I use through out my game that contains the Raise() function;
public class GameEvent : ScriptableObject
{
private readonly List<GameEventListener> eventListeners = new List<GameEventListener>();
public void Raise()
{
for(int i = eventListeners.Count -1; i >= 0; i--)
eventListeners[i].OnEventRaised();
}
public void RegisterListener(GameEventListener listener)
{
if (!eventListeners.Contains(listener))
eventListeners.Add(listener);
}
public void UnregisterListener(GameEventListener listener)
{
if (eventListeners.Contains(listener))
eventListeners.Remove(listener);
}
}
To make my life easier, I have created an Editor script that will populate two UnityEvents for me.
I can find the two custom GameEvents I want to add. OnCameraWillTransition and OnCameraFinishedTransition by using:
string[] eventAssets = AssetDatabase.FindAssets("OnCamera t:GameEvent", new[] { "Assets/_Scripts/Library/Camera/Events" });
Then I loop through each of the two events and check their names, and add them to whatever class needs them set. In this example, I am using ProCamera2D and it has two events that I want to set. It's referenced in the screenshot above. This is just an example. I would like to be able to pre-poulate any class that has some UnityEvent.
ProCamera2DRooms pr = m_cam.gameObject.AddComponent<ProCamera2DRooms>();
foreach (string s in eventAssets)
{
string path = AssetDatabase.GUIDToAssetPath(s);
if (path.Contains("OnCameraFinishedTransition"))
{
Debug.Log("OnCameraFinishedTransition");
GameEvent e = (GameEvent)AssetDatabase.LoadAssetAtPath(path, typeof(GameEvent));
// This doesn't work, and I have tried different methods
pr.OnStartedTransition.AddListener( (e)=> {e.Raise()} ) );
}
//if (path.Contains("OnCameraWillTransition"))
//Haven't Started
}
Is there any way to pre-populate UnityEvent's programmatically?
I tried to do the same just a few days ago.
I had an example script made just to activate/deactivate a unityevent:
void SetEventTriggerState(EventTrigger ET, EventTriggerType ETType, string MethodName, UnityEventCallState NewState) {
for (int i = 0; i < ET.triggers.Count; i++) {
EventTrigger.Entry Trigger = ET.triggers[i];
EventTrigger.TriggerEvent CB = Trigger.callback;
for (int j = 0; j < CB.GetPersistentEventCount(); j++) {
if (CB.GetPersistentMethodName(j) == MethodName && Trigger.eventID == ETType) {
CB.SetPersistentListenerState(j, NewState);
}
}
}
}
You call it like that (the button that activate the trigger event, event type and event name that you need to change, new state that you need to set)
SetEventTriggerState(actionButtonEventTrigger, EventTriggerType.PointerDown, "JumpButton", UnityEventCallState.EditorAndRuntime);
I tried to add a line like this:
CB.SetPersistentTarget(j, newGameObject); //after the SetPersistentListenerState line
But I discovered that there is no way to do something like that (there is only the GetPersistentTarget method and I found no alternative way to change the target object at runtime).
Not sure if you can add it at runtime but there is an alternative way to do everything at runtime that I found:
First you need to implement the interface you need:
IDragHandler, IPointerUpHandler, IPointerDownHandler
Those are just examples (I'm not sure what you need for the ProCamera2D since I'm not using it), the first one is for the Drag Event, the second one is for the PointerUp event and the last is for the PointerDown event.
After that I just implement the method I want to use with the object I need to use:
public virtual void OnPointerDown(PointerEventData ped) {
currentObject.GetComponent<ScriptOfThatObject>().MethodIWantToUse();
}
to decide what Object will use that method I created a setter method
public GameObject CurrentObject {
set => CurrentObject= value;
}
And I set that GameObject OnTriggerEnter
public void OnTriggerEnter(Collider other) {
publicScriptOfMyButtonReference.CurrentObject = other.gameObject;
}
This is just an example of how to implement Event without using the UnityEvent.EventTrigger component and just use code.
I will not be able to be more specific since I don't use that Camera script but you should be able to implement a solution with those few things in mind.
I'm currently trying to set up multiple Input Field in Unity and having issue do so. I can only get one input to work (intPressure) but don't know how to add a second one (drillpipe). It seem to me that unity only allow one Input Field at a time. I would think that just changing it to InputField2, ect will do the trick, but that doesn't seem to work. Other have mentioned creating an empty gameobject and inserting the inputfield to each gameobject.
To be honest, I still quite confuse about how to setup the second input field.
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class UserInput : MonoBehaviour {
InputField input;
int intPressure = 0;
int drillpipe = 0;
public DataManager data;
void Start ()
{
var input = gameObject.GetComponent<InputField>();
var se= new InputField.SubmitEvent();
se.AddListener(SubmitName);
input.onEndEdit = se;
}
private void SubmitName(string pressure)
{
var pressureScript =
GameObject.FindObjectOfType(typeof(DataManager)) as DataManager;
if (int.TryParse(pressure, out intPressure))
{
pressureScript.pressure = intPressure;
}
else
{
// Parse fail, show an error or something
}
}
}
I am not sure about the way you want to implement this (using single GameObject), however you most certainly would be able to do this if you had a couple of GameObjects (One object per control) nested within another GameObject (let's call it UserInputRoot) like this:
UserInputRoot (has UserInputController script)
|
+--- InputPressure (has InputField component)
|
+--- InputDrillpipe (has InputField component)
The controlling script would then have either a couple of public InputField's or a couple of private ones, initialized within the Start() or Awake() method:
class UserInputController : MonoBehaviour {
//These can be set from the inspector and controlled from within the script
public InputField PressureInput;
public InputField DrillpipeInput;
// It would be better to pass this reference through
// the inspector instead of searching for it every time
// an input changes
public DataManager dataManager;
private void Start() {
// There is no actual need in creating those SubmitEvent objects.
// You can attach event handlers to existing events without a problem.
PressureInput.onEndEdit.AddListener(SubmitPressureInput);
DrillpipeInput.onEndEdit.AddListener(SubmitDrillpipeInput);
}
private void SubmitDrillpipeInput(string input) {
int result;
if (int.TryParse(input, out result)) {
dataManager.drillpipe = result;
}
}
private void SubmitPressureInput(string input) {
int result;
if (int.TryParse(input, out result)) {
dataManager.pressure = result;
}
}
}
And by the way, the formatting of your code is absolutely atrocious. You MUST fix it.
It looks like the script you are showing is attached to an input field gameobject is that correct? You have a member variable in your object named input but then you create a new variable input in your Start method. Rather than have a script attached to your first InputField, create an empty gameobject in the editor and attach a script to that. In the script add two public members:
public class UserInput : MonoBehaviour {
public InputField PressureInput;
public InputField DrillPipeInput;
Now pop back to the editor, you should see the two input fields showing up when you select your empty gameobject. Drag and drop your two input fields into the slots for each inputfield. Now when the scene starts your UserInput script will be set with the input fields and you can use them both.
I have one class that is going to contain multiple lists that hold a variety of game objects. These lists will get populated by JSON scripts further in development.
Right now, I'm trying to access 1 element in 1 list. I have filled my class out as so:
public class ShopManager : MonoBehaviour
{
public List<GameObject> primaryWeapons = new List<GameObject>();
public List<GameObject> PrimaryWeapons
{
get { return primaryWeapons; }
}
public GameObject gameObj1, gameObj2;
// Use this for initialization
void Start ()
{
FillList();
}
public void FillList()
{
primaryWeapons.Add(gameObj1);
primaryWeapons.Add(gameObj2);
}
}
In my second class I'm trying to access one of the game objects I have placed in the list. This what I have so far:
ShopManager primary;
public GameObject temp;
public List<GameObject> tmpList;
void Awake()
{
primary = new ShopManager();
primary.FillList();
tmpList= new List<GameObject>();
}
// Use this for initialization
void Start()
{
for (int i = 0; i < primary.PrimaryWeapons.Count; i++)
{
tmpList.Add(primary.PrimaryWeapons[i]);
}
Debug.Log(primary.PrimaryWeapons.Count);
}
public void SelectWeapon1()
{
temp = primary.PrimaryWeapons.Where(obj => obj.gameObject.name == "DoorParts_1").SingleOrDefault();
}
}
In my list in the shop manager class I am manually setting the objects myself, so I know the names of them. However, whilst I can get a count returning correctly, I am unable to access this named object.
When I run the code I get a null reference pointing to the following line:
temp = primary.PrimaryWeapons.Where(obj => obj.gameObject.name == "DoorParts_1").SingleOrDefault();
Additonally I even created another list with the idea of passing the contents from my List property in the ShopManager class to this temp one. However, this lists populates with 2 empty positions.
I'm still not 100% on using properties like this. Espcially with lists. Could someone please tell me what it is I'm doing wrong?
You didn't initialize your objects in ShopManager.
Replace this:
public GameObject gameObj1, gameObj2;
With this:
public GameObject gameObj1 = new GameObject(), gameObj2 = new GameObject();
Check GameObject initialization. Especially gameObject field.
primary.PrimaryWeapons.Where(obj => obj.gameObject.name == "DoorParts_1").SingleOrDefault(); can fail with null reference in just few conditions:
primary is null
primary.PrimaryWeapons is null
obj.gameObject is null
The first two look covered in your code. So, check how GameObject.gameObject field is initialized.
I am currently working with C# using the Unity3D engine and have come upon the following problem:
I created a class that has two private references to instances of another class which it has to access. Once I create multiple instances of the class and set the references I found out that all instances were using the same variable. I realized this as I was destroying an instance and just before that set the two variables holding the references to null. Immediately after doing that all other instances were throwing NullReferenceExceptions because they were still trying to access the references. The referenced objects are fine, other scripts can still access them.
Here is some pseudo code illustrating the structure:
public class Character
{
// Character data
}
public class StatusEffect
{
private Character target;
private Character originator;
public void Init(Character _Target, Character _Originator)
{
target = _Target;
originator = _Originator;
}
public void Destroy()
{
target = null;
originator = null;
}
}
In the program it would be called like this:
StatusEffect effect = new StatusEffect();
effect.Init(player1, player2);
// Time goes by
effect.Destroy();
After calling Destroy() every StatusEffect's two references will be null.
This is not only an issue when destroying StatusEffects, but also when creating new ones. As soon as I touch the references from within a new instance all StatusEffects will reference the two Characters specified by the new StatusEffect.
I do not understand why or how I can fix this issue. Can someone enlighten me on this matter?
Cheers,
Valtaroth
EDIT:
Here is the real code as requested:
I have a container class holding several StatusEffects. As soon as it starts, it initializes all of them.
public class CElementTag
{
// ..Other data..
public float f_Duration; // Set in the editor
private CGladiator gl_target;
private CGladiator gl_originator;
private float f_currentDuration;
public CStatusEffect[] ar_statusEffects;
// Starts the effect of the element tag
public void StartEffect(CGladiator _Originator, CGladiator _Target)
{
gl_originator = _Originator;
gl_target = _Target;
f_currentDuration = f_Duration;
for(int i = 0; i < ar_statusEffects.Length; i++)
ar_statusEffects[i].Initialize(gl_originator, gl_target);
}
// Ends the effect of the element tag
public void EndEffect()
{
for(int i = 0; i < ar_statusEffects.Length; i++)
{
if(ar_statusEffects[i] != null)
ar_statusEffects[i].Destroy();
}
}
// Called every update, returns true if the tag can be destroyed
public bool ActivateEffect()
{
f_currentDuration -= Time.deltaTime;
if(f_currentDuration <= 0.0f)
{
EndEffect();
return true;
}
for(int i = 0; i < ar_statusEffects.Length; i++)
{
if(ar_statusEffects[i] != null && ar_statusEffects[i].Update())
RemoveStatusEffect(i);
}
return false;
}
// Removes expired status effects
private void RemoveStatusEffect(int _Index)
{
// Call destroy method
ar_statusEffects[_Index].Destroy();
// Remove effect from array
for(int i = _Index; i < ar_statusEffects.Length - 1; i++)
ar_statusEffects[i] = ar_statusEffects[i+1];
ar_statusEffects[ar_statusEffects.Length - 1] = null;
}
}
The actual StatusEffect class is holding the two references as well as some other data it needs to work. It has virtual methods because there are some classes inheriting from it.
public class CStatusEffect
{
// ..Necessary data..
// References
protected CGladiator gl_target;
protected CGladiator gl_originator;
virtual public void Initialize(CGladiator _Target, CGladiator _Originator)
{
gl_target = _Target;
gl_originator = _Originator;
// ..Initialize other necessary stuff..
}
virtual public void Destroy()
{
gl_target = null;
gl_originator = null;
// ..Tidy up other data..
}
virtual public bool Update()
{
// ..Modifying data of gl_target and gl_originator..
// Returns true as soon as the effect is supposed to end.
}
}
That should be all the relevant code concerning this problem.
EDIT2
#KeithPayne I have a static array of ElementTags defined in the editor and saved to xml. At the beginning of the program the static array is loading the xml and stores all element tags. When creating a new element tag to use I utilize this constructor:
// Receives a static tag as parameter
public CElementTag(CElementTag _Tag)
{
i_ID = _Tag.i_ID;
str_Name = _Tag.str_Name;
enum_Type = _Tag.enum_Type;
f_Duration = _Tag.f_Duration;
ar_statusEffects = new CStatusEffect[_Tag.ar_statusEffects.Length];
Array.Copy(_Tag.ar_statusEffects, ar_statusEffects, _Tag.ar_statusEffects.Length);
}
Do I have to use a different method to copy the array to the new tag? I thought Array.Copy would make a deep copy of the source array and stored it in the destination array. If it is in fact making a shallow copy, I understand where the problem is coming from now.
From Array.Copy Method (Array, Array, Int32):
If sourceArray and destinationArray are both reference-type arrays or
are both arrays of type Object, a shallow copy is performed. A shallow
copy of an Array is a new Array containing references to the same
elements as the original Array. The elements themselves or anything
referenced by the elements are not copied. In contrast, a deep copy of
an Array copies the elements and everything directly or indirectly
referenced by the elements.
Consider this fluent version of the StatusEffect class and its usage below:
public class StatusEffect
{
public Character Target { get; private set; }
public Character Originator { get; private set; }
public StatusEffect Init(Character target, Character originator)
{
Target = target.Clone()
Originator = originator.Clone();
return this;
}
//...
}
public CElementTag(CElementTag _Tag)
{
i_ID = _Tag.i_ID;
str_Name = _Tag.str_Name;
enum_Type = _Tag.enum_Type;
f_Duration = _Tag.f_Duration;
ar_statusEffects = _Tag.ar_statusEffects.Select(eff =>
new StatusEffect().Init(eff.Target, eff.Originator)).ToArray();
// ar_statusEffects = new CStatusEffect[_Tag.ar_statusEffects.Length];
// Array.Copy(_Tag.ar_statusEffects, ar_statusEffects, _Tag.ar_statusEffects.Length);
}
Because you're passing in references to the objects via your Init() method, you're not actually "copying" the objects, just maintaining a reference to the same underlying objects in memory.
If you have multiple players with the same references to the same underlying objects, then changes made by player 1 will effect the objects being used by player 2.
Having said all that, you're not actually disposing the objects in your Destory method. Just setting the local instance references to Null which shouldn't affect any other instances of StatusEffects. Are you sure something else isn't disposing the objects, or that you haven't properly init'd your other instances.
If you do want to take a full copy of the passed in objects, take a look at the ICloneable interface. It looks like you want to pass in a copy of the objects into each Player.
public class Character : ICloneable
{
// Character data
//Implement Clone Method
}
public class StatusEffect
{
private Character target;
private Character originator;
public void Init(Character _Target, Character _Originator)
{
target = _Target.Clone()
originator = _Originator.Clone();
}
The fields aren't shared(static) among other instances. So calling target = null; in Destroy() won't affect other instances.
StatusEffect effect1 = new StatusEffect();
effect1.Init(player1, player2);
StatusEffect effect2 = new StatusEffect();
effect2.Init(player1, player2);
// Time goes by
effect2.Destroy();
// Some more time goes by
// accessing effect1.target won't give a `NullReferenceException` here unless player1 was null before passed to the init.
effect1.Destroy();
I think you did forget the Init(..) on the other instances. Every time you create an instance of StatusEffect, you need to call Init(...).
Update:
This line will clear the reference to the effect, but you never recreate it:
ar_statusEffects[ar_statusEffects.Length - 1] = null;
so the next time you call ar_statusEffects[x].Update() or Initialize() etc it will throw a NullReferenceException
If you want to clear out effects within you array, you could create an Enable bool in the effect, this way you only have to set/reset it.
for(int i = 0; i < ar_statusEffects.Length; i++)
if(ar_statusEffects[i].IsEnabled)
ar_statusEffects[i].Update();
Why don't you use a List instead? Arrays will be faster as long you don't have to shuffle in it. (like circulair buffers etc)
Thanks to Keith Payne I figured out where the problem was. I was creating a deep copy of CElementTag, but not of my ar_statusEffects array. I wrongly assumed Array.Copy was creating a deep copy of an array when it actually was not.
I implemented the IClonable interface for my CStatusEffect and use the Clone() method to create a true deep copy for each member of the static array and add it to the new tags ar_statusEffects array. This way I have seperate instances of the effects instead of references to the same static effect.
Thanks to everyone, especially Keith Payne, for their help and support!