I have a Skill base class and 2 separate Skill classes that inherit from the Class Skill. Each skill has its own SkillCoolDown value. I set the CoolDownManager Class that is visualising the cooldowns for the skills, but I don't understand how to set the SkillCoolDown value to the manager which is different for each skill. Would appreciate the help.
public class Skill
{
private string skillName;
private int skillDamage;
private string skillDescription;
private int skillCoolDown;
public string SkillName {get;set;}
public int SkillDamage {get;set;}
public string SkillDescription {get;set;}
public int SkillCoolDown {get;set;}
public Skill () {}
}
public class CoolDownManager : MonoBehaviour
{
public Skill skill;
private Stopwatch cooldown;
private Button button;
private Image fillImage;
private int skillCoolDown;
public void OnSkillUse(Button btn) {
skill = new Skill(); // HERE THE VALUE SkillCoolDown SHOULD BE IMPLEMENTED AND I WANT SOMEHOW TO SHOW WHICH SKILL HAS BEEN USED.
button = btn.GetComponent<Button>();
fillImage = btn.transform.GetChild(0).gameObject.GetComponent<Image>();
btn.interactable = false;
fillImage.fillAmount = 1;
cooldown = new Stopwatch();
cooldown.Start();
StartCoroutine(CoolDownAnimation());
}
private IEnumerator CoolDownAnimation() {
while(cooldown.IsRunning && cooldown.Elapsed.TotalSeconds < skill.SkillCoolDown) {
fillImage.fillAmount = ((float)cooldown.Elapsed.TotalSeconds / skill.SkillCoolDown);
yield return null;
}
fillImage.fillAmount = 0;
button.interactable = true;
cooldown.Stop();
cooldown.Reset();
}
}
And here is example of the Skill class from where the value SkillCoolDown should be passed.
public class BasicAttack : Skill
{
public BasicAttack()
{
SkillCoolDown = 2;
}
}
A Skill could be passed to the CooldownManager via constructor injection:
public class CoolDownManager : MonoBehaviour
{
public Skill skill;
// other properties
public CoolDownManager(Skill skillParam)
{
skill = skillParam;
}
// do whatever you want with skill
}
This is the perfect problem to solve using scriptable objects.
The following class inherits from ScriptableObject and defines the same data you have in your Skill class. Using the CreateAssetMenu attribute for this class, you can make a new asset of this type. Right-click in your project window and navigate to the menu item Combat/Skills/New Skill, and then you will have an asset of this type.
Note: Because this is an asset, it implicitly has the property name, so you can name the Skill asset accordingly.
[CreateAssetMenu(fileName = "NewSkill.asset", menuName = "Combat/Skills/New Skill")]
public class Skill : ScriptableObject
{
[SerializeField] private int _damage = default;
[SerializeField] private string _description = default;
[SerializeField] private int _cooldown = default;
public int damage => _damage;
public string description => _description;
public int cooldown => _cooldown;
}
Now that you can create Skill assets, go ahead and create your two skills and fill in the details in the inspector.
Next, you'll need a way to use the Skill. I've gotten rid of the CoolDownManager in favor of a button. Check out the following.
[RequireComponent(typeof(Button))]
public class SkillButton : MonoBehaviour
{
[SerializeField] private Skill _skill = default;
[SerializeField] private Button _button = default;
[SerializeField] private Image _fillImage = default;
private Stopwatch _stopwatch = new Stopwatch();
private void Awake()
{
// Hook up a method to listen for when the button is clicked.
_button.onClick.AddListener(OnClick);
}
// This method is invoked when the button is clicked.
private void OnClick()
{
StartCoroutine(CoolDownAnimation());
}
private IEnumerator CoolDownAnimation()
{
// Immediately keep the button from being clicked again.
_button.interactable = false;
_stopwatch.Start();
while (_stopwatch.IsRunning && _stopwatch.Elapsed.TotalSeconds < _skill.cooldown)
{
_fillImage.fillAmount = (float)_stopwatch.Elapsed.TotalSeconds / _skill.cooldown;
yield return null;
}
_fillImage.fillAmount = 0;
_button.interactable = true;
_stopwatch.Stop();
_stopwatch.Reset();
}
// Use this to hook up references automatically.
private void OnValidate()
{
if (_button == null)
{
_button = GetComponent<Button>();
}
if (_button != null && _fillImage == null)
{
_fillImage = _button.transform.GetChild(0).gameObject.GetComponent<Image>();
}
}
}
Attach the SkillButton component to the Button you wish to have controlling the skill, then hook up your various Skill objects to have them associated with that button.
With all of that out of the way, you should have functionality close to what you desire.
Related
I have an Item class in C# and I want to have two methods, OnRightClick and OnLeftClick, but I want every item to do different things upon using them (I am on Unity and this class is not a monoBehavior).
I heard about virtual methods, but from what I realized they can be overridden only from other classes that inherit them. However, I want to do without making a separate class for every item I make. How can I make those 2 methods vary?
I thought of using delegate, but it doesn't work the way I expected it to either. Is it even possible?
EDIT
(Ik the following line are not a thing but this is the best way I can somehow explain what I want to do)
Imagine having the following simple class Item
public class Item
{
private string name;
private float weight;
private int price;
private bool dropable;
public Item(string name, float weight, int price, bool dropable)
{
this.name = name;
this.weight = weight;
this.price = price;
this.dropable = dropable;
}
public Item(string name, float weight, int price)
{
this.name = name;
this.weight = weight;
this.price = price;
this.dropable = true;
}
public string GetName()
{
return this.name;
}
public float GetWeight()
{
return this.weight;
}
public int GetPrice()
{
return this.price;
}
public bool GetDropable()
{
return this.dropable;
}
public void SetDropable(bool dropable)
{
this.dropable = dropable;
}
}
I want to be able to make an OnRightClick and OnLeftClick that would vary from every item I create if I could do something like(As I said I know it's not valid but this is the best way I can explain it, also didn't mention it in the class above, this is the original class I currently have)
Item item1 = new Item(*all the stuff here*);
Item item2 = new Item(*other stuff*);
item1.OnRightClick = delegate {*what I want on right click*};
item1.OnRightClick(); //executes the function for item1
item2.OnRightClick = delegate {*other stuff on right click*};
item2.OnRightClick(); //executes a different function for item2
Again, this is not a valid code but I just used this to try and explain what I want to try and do, and to ask if there is any solutions to this that exist. In the worst case, if there aren't, I could just use virtual but I'd like that to be my last case of no choice.
You can have a parent Item class then different children item classes that inherit from it the syntax it
public class MyItem : MonoBehaviour {
public enum ItemType {HEALING, OFFENSIVE, CONSUMABLE, EQUIPMENT}
public ItemType itemType;
public float potency;
public MyItem(ItemType _it, float _potency) {
itemType = _it;
potency = _potency;
}
public void OnRightClick() {
switch (itemType) {
case ItemType.HEALING:
HealCharacter(character, potency);
break;
case ItemType.OFFENSIVE:
break;
// more cases
}
}
public void OnLeftClick() {
//fill in like onrightclick
}
}
You can use Action and Func to do what you are asking.
https://learn.microsoft.com/en-us/dotnet/api/system.action-1?view=netcore-3.1
https://learn.microsoft.com/en-us/dotnet/api/system.func-2?view=netcore-3.1
public enum ItemType
{
Sword,
Shield,
Potion
}
public class Item
{
private readonly Action leftClickHandler;
private readonly Action<int> leftClickHandlerWithParam;
private readonly Action rightClickHandler;
private readonly Action<int> rightClickHandlerWithParam;
public Item(ItemType itemType)
{
switch (itemType)
{
case ItemType.Potion:
this.rightClickHandler = this.HandleClickWithFlash;
this.leftClickHandler = this.HandleClickWithSound;
this.leftClickHandlerWithParam = this.HandleClickWithFlash;
this.rightClickHandlerWithParam = this.HandleClickWithSound;
break;
}
}
public void HandleLeftClick()
{
this.leftClickHandler();
}
public void HandleRightClick()
{
this.rightClickHandler();
}
private void HandleClickWithFlash()
{
// Logic here.
}
private void HandleClickWithFlash(int parameter)
{
// Logic here.
}
private void HandleClickWithSound()
{
// Logic here.
}
private void HandleClickWithSound(int parameter)
{
// Logic here.
}
}
Here it is with the items exposed, if say you wanted a item factory concept.
public class ItemSettableHandlers
{
public ItemSettableHandlers()
{
}
public Action LeftClickHandler { get; set; }
public Action RightClickHandler { get; set; }
public void HandleLeftClick()
{
this.LeftClickHandler?.Invoke();
}
public void HandleRightClick()
{
this.RightClickHandler?.Invoke();
}
}
public class ItemCreator
{
public void CreateItems()
{
var itemSword = new ItemSettableHandlers();
itemSword.LeftClickHandler = () =>
{
// Do sword left click here.
};
itemSword.RightClickHandler = () =>
{
// Do sword right click here.
};
var itemShield = new ItemSettableHandlers();
itemShield.LeftClickHandler = () =>
{
// Do shield left click here.
};
itemShield.RightClickHandler = () =>
{
// Do shield right click here.
};
}
}
You could use EventHandler:
in you class, you define:
public event EventHandler RightClick;
public void OnRightClick()
{
EventHandler handler = RightClick;
if (null != handler) handler(this, EventArgs.Empty);
}
You use like that:
// Enable Event
RightClick += new EventHandler(OnRightClick);
OnRightClick();
public void OnRightClick(object s, EventArgs e)
{
}
I have this Item class:
public class Item
{
public string Name;
public Entity ItemOwner;
public Action<Entity> effect;
public Item(string name, Action<Entity> effect)
{
Name = name;
this.effect = effect;
}
public void Use()
{
effect(ItemOwner);
}
}
and this Entity Class:
public class Entity
{
public string Name;
public float Health;
public List<Item> Items = new List<Item>();
public bool CanDie;
public bool Dead;
public Entity(string name, float health, bool canDie)
{
Name = name;
Health = health;
CanDie = canDie;
}
public void UseItem(string name)
{
foreach (var item in Items)
{
if (item.Name == name)
{
item.Use();
Items.Remove(item);
break;
}
}
}
}
I want to know is it possible to set 'EntityOwner' to a given entity when that item is placed within the entity's inventory.
what I currently can do is this 'Main Program':
Entity Player = new Entity("Dave", 50, true);
Item Potion = new Item("Healing Potion", ItemOwner => ItemOwner.Health += 25);
Potion.ItemOwner = Player;
Player.Items.Add(Potion);
Player.UseItem("Healing Potion");
I want this specific piece of code:
Potion.ItemOwner = Player;
To trigger when the item is placed inside the entity's inventory.
Is this possible somehow?
I don't think this could be achieved from within the item class super cleanly.
What you could do is add a function to the Entity class something like this :
public void AddItem(Item item) {
Items.Add(item);
item.EntityOwner = this;
}
Similarly you'd want a RemoveItem function that unsets the EntityOwner.
If you do this method you would likely want Items to be a protected variable so that no other class could add an item to the list without the AddItem function.
The other option is to add a OnAddedToList function to the Item class and in place of item.EntityOwner above you simply call the added to list function on that item. Might be what you want if each item has more unique things to do than just setting the owner.
Now it's working fine in editor and in runtime for each door.
But I want to add a global public flag that will control all the doors at once in editor and in runtime. If I change the global flag to true all the doors will be locked and same if set to false.
The DoorsLockManager script:
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
[ExecuteInEditMode]
public class DoorsLockManager : MonoBehaviour
{
[HideInInspector]
public List<HoriDoorManager> Doors = new List<HoriDoorManager>();
private void Awake()
{
var doors = GameObject.FindGameObjectsWithTag("Door");
Doors = new HoriDoorManager[doors.Length].ToList();
for (int i = 0; i < doors.Length; i++)
{
Doors[i] = doors[i].GetComponent<HoriDoorManager>();
}
}
}
And the editor script:
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(DoorsLockManager))]
public class DoorsLockManagerEditor : Editor
{
private SerializedProperty _doors;
private void OnEnable()
{
_doors = serializedObject.FindProperty("Doors");
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
for (int i = 0; i < _doors.arraySize; i++)
{
var door = _doors.GetArrayElementAtIndex(i);
// if door == null the script itself has an error since it can't even find the SerializedProperty
if (door == null)
{
EditorGUILayout.HelpBox("There was an error in the editor script!\nPlease check the log", MessageType.Error);
Debug.LogError("Couldn't get door property", target);
return;
}
if (door.objectReferenceValue == null) continue;
// FindPropertyRelative seems not to only work for MonoBehaviour classes
// so we have to use this hack around
var serializedDoor = new SerializedObject(door.objectReferenceValue);
// If it's public no worry anyway
// If it's private still works since we made it a SerializeField
var lockState = serializedDoor.FindProperty("doorLockState");
// Fetch current values into the serialized "copy"
serializedDoor.Update();
if (lockState == null)
{
EditorGUILayout.HelpBox("There was an error in the editor script!\nPlease check the log", MessageType.Error);
Debug.LogError("Couldn't get lockState property", target);
return;
}
// for the PropertyField there is
// no return value since it automatically uses
// the correct drawer for the according property
// and directly changes it's value
EditorGUILayout.PropertyField(lockState, new GUIContent("Door " + i + " Lockstate"));
// or alternatively
//lockState.boolValue = EditorGUILayout.Toggle("Door " + i + " Lockstate", lockState.boolValue);
// Write back changes, mark as dirty if changed
// and add a Undo history entry
serializedDoor.ApplyModifiedProperties();
}
}
}
Well, you could simply add one to the DoorsLockManager
[ExecuteInEditMode]
public class DoorsLockManager : MonoBehaviour
{
[HideInInspector]
public List<HoriDoorManager> Doors = new List<HoriDoorManager>();
// The global state
[SerializeField] private bool _globalLockState;
// During runtime use a property instead
public bool GlobalLockState
{
get { return _globalLockState; }
set
{
_globalLocakState = value;
// apply it to all doors
foreach(var door in Doors)
{
// now you would need it public again
// or use the public property you had there
Door.doorLockState = _globalLocakState;
}
}
}
private void Awake()
{
var doors = GameObject.FindGameObjectsWithTag("Door");
Doors = new HoriDoorManager[doors.Length].ToList();
for (int i = 0; i < doors.Length; i++)
{
Doors[i] = doors[i].GetComponent<HoriDoorManager>();
}
}
}
and in the editor make it "overwrite" all the other flags if changed:
[CustomEditor(typeof(DoorsLockManager))]
public class DoorsLockManagerEditor : Editor
{
private SerializedProperty _doors;
private SerializedProperty _globalLockState;
private bool shouldOverwrite;
private void OnEnable()
{
_doors = serializedObject.FindProperty("Doors");
_globalLockState = serializedObject.FindProperty("_globalLockState");
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
serializedObject.Update();
shouldOverwrite = false;
// Begin a change check here
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(_globalLockState);
if(EditorGUI.EndChangeCheck())
{
// overwrite only once if changed
shouldOverwrite = true;
}
for (int i = 0; i < _doors.arraySize; i++)
{
var door = _doors.GetArrayElementAtIndex(i);
// if door == null the script itself has an error since it can't even find the SerializedProperty
if (door == null)
{
EditorGUILayout.HelpBox("There was an error in the editor script!\nPlease check the log", MessageType.Error);
Debug.LogError("Couldn't get door property", target);
return;
}
if (door.objectReferenceValue == null) continue;
var serializedDoor = new SerializedObject(door.objectReferenceValue);
var lockState = serializedDoor.FindProperty("doorLockState");
serializedDoor.Update();
if (lockState == null)
{
EditorGUILayout.HelpBox("There was an error in the editor script!\nPlease check the log", MessageType.Error);
Debug.LogError("Couldn't get lockState property", target);
return;
}
// HERE OVERWRITE
if(shouldOverwrite)
{
lockState.boolValue = _globalLockState.boolValue;
}
else
{
EditorGUILayout.PropertyField(lockState, new GUIContent("Door " + i + " Lockstate"));
}
serializedDoor.ApplyModifiedProperties();
}
serializedObject.ApplyModifiedProperties();
}
}
You could create a static class with the flag in it, and then have the door's "activated" state tied to it. Example:
public static class GlobalVariables()
{
public static bool DoorsLocked = true; //Doors would be locked dependent on logic
}
Then you just set the doors' state to the global variable in code. When you change the global bool, the doors change.
GlobalVariables.DoorsLocked = false; //unlocks all doors reading the global bool
Hope this helps!
I have the following, and it works:
My player class:
public Player(string Str, string SP)
{
Strength = Str;
StatPoints = SP;
}
public string StatPoints
{
get;
set;
}
public string Strength
{
get;
set;
}
Now on my form1 I have a textbox, and a button. The button increments the value in the textbox by one so long as there is a value above 0 in the SP textbox. Problem is, I am declaring six variables in order to manage two values because I have to convert strings to ints and all that crap. Is there a way to replace text boxes with something that is inherently int? Here is my character sheet code so far.
private void AddButton_Click(object sender, EventArgs e)
{
Player PCStats = new Player(StrBox.Text, SPBox.Text);
int IntPCSP = Convert.ToInt16(PCStats.StatPoints);
int IntPCStr = Convert.ToInt16(PCStats.Strength);
if (IntPCSP >= 1 && IntPCStr <= 7)
{
IntPCStr++;
IntPCSP--;
PCStats.Strength = IntPCStr.ToString();
PCStats.StatPoints = IntPCSP.ToString();
StrBox.Text = PCStats.Strength;
SPBox.Text = PCStats.StatPoints;
}
else
{
MessageBox.Show("Earn more experience!");
}
/*
MessageBox.Show("PCStats.StatPoints equals" + PCStats.StatPoints);
MessageBox.Show("PCStats,Strength equals" + PCStats.Strength);
MessageBox.Show("IntPCSP Equals" + IntPCSP.ToString());
MessageBox.Show("IntPCStr Equals" + IntPCStr.ToString());
*/
}
Or is there an even easier way to do this I completely overlooked. I was super excited to finally get this bit to work after a lot of trial and error, but I am open to redoing it. I would rather however just replace the text boxes so I am not converting variables all over the place.
This is quick go, not at a computer with Visual Studio on it but should give you a start. Also, try naming your variables etc to have a bit more meaning. Also, this is to fix what you has as-is but see my update / suggestion further down about moving logic into the Player class...
My player class:
public Player(int strength, int statPoints)
{
this.Strength = strength;
this.StatPoints = statPoints;
}
public int StatPoints { get; set; }
public int Strength { get; set; }
My Form:
private void AddButton_Click(object sender, EventArgs e)
{
Player thePlayer = new Player(int.Parse(StrBox.Text), int.Parse(SPBox.Text));
if (thePlayer.StatPoints > 0 && thePlayer.Strength < 8)
{
thePlayer.Strength++;
thePlayer.StatPoints--;
StrBox.Text = thePlayer.Strength.ToString();
SPBox.Text = thePlayer.StatPoints.ToString();
}
else
{
MessageBox.Show("Earn more experience!");
}
}
Obviously you would need to check that the values in the text box were integers. You could use another control, mask the text box etc or on the code replace int.Parse with int.TryParse which checks it is possible before conversion. Just a few ideas to get you going!
- UPDATE -
Another thing you could do is more the logic into the Player class. This is better as it keep the logic contained in one place so you can see what a Player can DO rather than having to search the whole program:
New Player class:
// The Player class
public class Player
{
// Constructor
public Player(int strength, int statPoints)
{
this.Strength = strength;
this.StatPoints = statPoints;
}
// Method to gain strength if enough StatPoints
public bool GainStrength()
{
bool playerHasEnoughStatPoints = true;
if (this.StatPoints < 1)
{
playerHasEnoughStatPoints = false;
}
else if (this.Strength < 8)
{
this.Strength++;
this.StatPoints--;
}
return playerHasEnoughStatPoints;
}
// Property for StatPoints
public int StatPoints { get; set; }
// Property for Strength
public int Strength { get; set; }
}
New Form:
// The Form or similar
public class MyFormOrSimilar
{
// When button pressed try and add strength to the player
protected void AddButton_Click(object sender, EventArgs e)
{
// Create new INSTANCE of the player and try to give them strength
Player thePlayer = new Player(int.Parse(StrBox.Text), int.Parse(SPBox.Text));
if (thePlayer.GainStrength())
{
StrBox.Text = thePlayer.Strength.ToString();
SPBox.Text = thePlayer.StatPoints.ToString();
}
else
{
MessageBox.Show("Earn more experience!");
}
}
}
I have two classes that inherit from the same abstract class. I want both of them or at least one to be aware of changes in a specific property of the other. Is there any simple method for doing this? I've been trying to move the variable to the parent class, but that just creates 2 of the same variable, and when I create a reference to the other class inside the first one the same thing happens. thanks.
This is what my code looks like:
public abstract class Animal
{
public int MovementSpeed;
public bool Death;
public string Feedback;
public bool DeerCaught;
public int tiredRate;
public virtual int Movement()
{
MovementSpeed = MovementSpeed - tiredRate;
return MovementSpeed;
}
public virtual string Print()
{
return Feedback;
}
}
public class Deer : Animal
{
public string hidden;
public string Foraging;
public int DeerCount;
public Deer()
{
this.DeerCount = 10;
this.DeerCaught = false;
this.MovementSpeed = 10;
this.tiredRate = 2;
}
public void Hide()
{
if (Hunting)
{
Feedback = "The deer is hiding.";
if (DeerCount > 0)
{
Print();
}
}
else
{
//Forage();
}
}
public void Forage()
{
if (!Hunting)
{
Feedback = "The deer is searching for food.";
if (DeerCount > 0)
{
Print();
}
}
else
{
//Hide();
}
}
}
public class Wolf : Animal
{
public int Hunger;
public bool Hunting;
public Wolf()
{
this.Hunting = false;
this.Hunger = 10;
this.MovementSpeed = 10;
this.tiredRate = 1;
}
public bool Hunt()
{
if (Hunger < 5)
{
Hunting = true;
Feedback = "The wolf is searching for his next meal.";
if (DeerCaught == true)
{
Hunger++;
}
else
{
Hunger--;
}
return Hunting;
}
else
{
Hunting = false;
Feedback = "The wolf decides to rest.";
Hunger--;
return Hunting;
}
}
public void Die()
{
if (Hunger < 0)
{
Death = true;
Feedback = "The wolf has lost the hunt.";
}
}
}
I've tried setting Hunting as static in the base class, but I just end up getting two different versions of 'Hunting' when I run the methods of each class.
If this is intended as a simulation, then Deer isn't told when a wolf is hunting, it has to find out. The analogue here is to have some way that the Deer can query about the presence of wolves (something like Deer.LookForWolves(), then to check the value of the Hunting property on each wolf. This will require some sort of controller class, representing the world.
class World
{
public static List<Animal> Animals = new List<Animal>();
//...
}
class Deer : Animal
{
//...
bool IsSafe()
{
return LookForWolves().All(wolf => !wolf.Hunting);
}
List<Wolf> LookForWolves()
{
return World.Animals.OfType<Wolf>();
}
//...
Alternatively, you could reference World as a member of each Animal, passed in via the constructor. It's up to you, and will depend on whether you need to have multiple World objects, each with a different list of Animals.
Something like implementing INotifyPropertyChanged could help:
First, declare some classes that implement INotifyPropertyChanged:
abstract class Base {
}
class ClassA : Base, INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
private string _property;
public string ClassAProperty {
get {
return _property;
}
set {
_property = value;
PropertyChanged(this, new PropertyChangedEventArgs("ClassAProperty"));
}
}
}
class ClassB : Base, INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
private string _property;
public string ClassBProperty {
get {
return _property;
}
set {
_property = value;
PropertyChanged(this, new PropertyChangedEventArgs("ClassBProperty"));
}
}
}
Then, wire up new instances to subscribe to the PropertyChanged event:
using System.ComponentModel;
static void Main(string[] args) {
ClassA a = new ClassA();
a.PropertyChanged += PropertyChanged;
a.ClassAProperty = "Default value";
ClassB b = new ClassB();
b.PropertyChanged += PropertyChanged;
b.ClassBProperty = "Default value";
b.ClassBProperty = "new value in B";
a.ClassAProperty = "new value in A";
Console.Read();
}
static void PropertyChanged(object sender, PropertyChangedEventArgs e) {
Console.WriteLine("Property {0} on object {1} was changed, the value is \"{2}\"", e.PropertyName, sender.GetType().Name, sender.GetType().GetProperty(e.PropertyName).GetValue(sender));
}
Output of this is:
Property ClassAProperty on object ClassA was changed, the value is "Default value"
Property ClassBProperty on object ClassB was changed, the value is "Default value"
Property ClassBProperty on object ClassB was changed, the value is "new value in B"
Property ClassAProperty on object ClassA was changed, the value is "new value in A"
Each time either property is set, PropertyChanged is called, which in the above example writes the details to the console.
In your use case, you would have the event call a method in the other class (if I understand you correctly).
A very basic way to notify property changed with your own delegate definition. Since you do not provide any code I made up some classes myself. Use this as an example to modify your own code:
public delegate void PropertyChangedEventHandler();
public abstract class Base
{
}
public class A : Base
{
public event PropertyChangedEventHandler PropertyChanged;
private int _value;
public int Value
{
get { return _value; }
set
{
_value = value;
if (PropertyChanged != null)
{
PropertyChanged();
}
}
}
public class B : Base
{
private A _a;
public B(A a)
{
_a = a;
a.PropertyChanged += new PropertyChangedEventHandler(a_PropertyChanged);
}
private void a_PropertyChanged()
{
Console.WriteLine(_a.Value);
}
}
public class Application()
{
public void DoStuff()
{
var a = new A();
var b = new B(a);
}
}
The basic idea is to pass a reference of one object to the other. For example tell the deer it is being hunted by the wolf:
public class Wolf : Animal
{
public void Hunt(Deer deer)
{
deer.SetHunter(this);
}
}
Now the deer can check whether a wolf is hunting it:
public class Deer : Animal
{
Wolf _hunter;
public void SetHunter(Wolf wolf)
{
_hunter = wolf;
}
public void Hide()
{
if (_hunter != null)
{
Feedback = "The deer is hiding.";
}
else
{
//Forage();
}
}
}
This can be improved to be more generic, but it's the basic idea of passing a reference of one object to the other.
Don't use public fields for the properties of your classes. This way you will never be aware of changes and therefore can not notify others. Put the public fields into properties and always use these properties to change the value even from inside the Animal class. The property setter can then be used to notify others of changes.
public abstract class Animal
{
private int _movementSpeed;
public int MovementSpeed
{
get
{
return _movementSpeed;
}
set
{
if (_movementSpeed != value)
{
_movementSpeed = value;
OnMovementSpeedChanged();
}
}
}
protected virtual void OnMovementSpeedChanged()
{
// Derived classes can override this method.
// It will be called each time MovementSpeed changes.
}
public virtual int Movement()
{
// always use the property to change the value
// otherwise OnMovementSpeedChanged would never be called
MovementSpeed -= tiredRate;
return MovementSpeed;
}
}
Like others already mentioned you can also implement INotifyPropertyChanged in your base class. Since this uses events for notification not only derived classes can use that but also any other object that has a reference to an animal. The approach is basically the same. Each time the property value changes you call a method that fires the event. Any other object can then handle that event.
public abstract class Animal : INotifyPropertyChanged
{
private int _movementSpeed;
public int MovementSpeed
{
get
{
return _movementSpeed;
}
set
{
if (_movementSpeed != value)
{
_movementSpeed = value;
// call this method each time a property changes
OnPropertyChanged(new PropertyChangedEventArgs("MovementSpeed"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs args)
{
// always implement events like this
// -> check if the event handler is not null, then fire it
if (PropertyChanged != null)
{
PropertyChanged(this, args);
}
}
}
A class that wants to handle the event can do it like so:
public class AnyClass
{
public AnyClass(Animal anAnimal)
{
TheAnimal = anAnimal;
anAnimal += Animal_PropertyChanged;
}
public Animal TheAnimal { get; private set; }
private void Animal_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "MovementSpeed")
{
Console.WriteLine("MovementSpeed changed");
}
}
}
Derived classes however don't need to handle the event. Since the OnPropertyChanged method is declared as protected virtual they can just override it.
public class Deer : Animal
{
protected override void OnPropertyChanged(PropertyChangedEventArgs args)
{
if (args.PropertyName == "MovementSpeed")
{
Console.WriteLine("MovementSpeed changed");
}
// don't forget to call the base class otherwise the event will never get fired
base.OnPropertyChanged(args);
}
}