I believe I have a design question and I hope to get your input. I made a small program to illustrate my question. Basically, my program consists of a radio system that gets heard on every room in the building. The sound is conditional on the receiving end, depending if the room registers itself to the radio system.
My problem is that the message sent is triggered on every room, even if the room is not registered. I would prefer to do the condition before the message gets sent out, rather then on the receiving end. By doing this, I could save myself unnecessary traffic. Can anyone give me an idea or the correct way to resolve this type of situation?
Just for the record, I would prefer not to have multiple event handlers in the radio, since I don't know how many rooms there will be.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Radio
{
#region Speakers
public interface ISound
{
string sound { get; set; }
}
public abstract class RoomSpeaker : ISound
{
public string sound { get; set; }
}
public class Room1Speaker : RoomSpeaker
{
}
public class Room2Speaker : RoomSpeaker
{
}
public class BuildingSpeaker : RoomSpeaker
{
}
#endregion
#region Rooms
public abstract class Room
{
public Radio radioPlayer;
public string name;
public HashSet<Type> registeredSpeakers = new HashSet<Type>();
public virtual void RoomPlayer(string lyrics)
{
registeredSpeakers.Add(typeof(BuildingSpeaker));
Console.WriteLine(lyrics);
}
}
public class Room1 : Room
{
public Room1(Radio radioPlayer)
{
this.radioPlayer = radioPlayer;
name = "Room1";
registeredSpeakers.Add(typeof(Room1Speaker));
radioPlayer.onRadio += radioPlayer_onRadio;
}
// This is what I don't think I like. It will only do something if it's registered. That's fine.
// But on any radio message out, this room will get called regardless. Should I NOT be doing this? Should I go back to
// making an eventHandler for every room? rather then having one even handler for all the rooms and have a condition on the receiving end.
void radioPlayer_onRadio(object sender, ISound e)
{
if (registeredSpeakers.Contains(e.GetType()))
RoomPlayer(name + e.sound);
}
}
public class Room2 : Room
{
public Room2(Radio radioPlayer)
{
this.radioPlayer = radioPlayer;
name = "Room2";
registeredSpeakers.Add(typeof(Room2Speaker));
radioPlayer.onRadio += radioPlayer_onRadio;
}
void radioPlayer_onRadio(object sender, ISound e)
{
// same problem as in Room1.
if (registeredSpeakers.Contains(e.GetType()))
RoomPlayer(name + e.sound);
}
}
#endregion
public class Radio
{
public event EventHandler<ISound> onRadio;
public void PlayRoom1()
{
onRadio(this, new Room1Speaker() { sound = "Test" });
}
public void PlayRoom2()
{
onRadio(this, new Room2Speaker() { sound = "Test" });
}
public void PlayAllRooms()
{
onRadio(this, new BuildingSpeaker() { sound = "Test All Rooms" });
}
}
class Program
{
static void Main(string[] args)
{
var radio = new Radio();
var room1 = new Room1(radio);
var room2 = new Room2(radio);
radio.PlayRoom1();
radio.PlayRoom2();
radio.PlayAllRooms();
Console.ReadLine();
}
}
}
Okay, what you're looking at is the publish-subscribe pattern (AKA eventbus). In eventbus pattern, you have a class that registers listeners and sends messages. Listeners tell the event bus "I'm listening for an event X". When the eventbus "sends" event X it consults its list of listeners for that event and if they are registered, executes the method that the listener told it to execute.
public class EventBus
{
private Dictionary<Type, List<Action<IEvent>>> actions = new Dictionary<Type, List<Action<IEvent>>>();
public void Listen<T>(Action<IEvent> callback) where T : IEvent
{
if (!actions.ContainsKey(typeof(T)))
{
actions.Add(typeof(T), new List<Action<IEvent>>());
}
actions[typeof(T)].Add(callback);
}
public void ClearCallbacks<T>() where T : IEvent
{
actions[typeof (T)] = null;
}
public void Send<T>(T #event) where T : IEvent
{
if (!actions.ContainsKey(typeof(T)))
{
return;
}
foreach (var action in actions[typeof(T)])
{
action(#event);
}
}
}
public interface IEvent
{
}
Usage:
public static void main () {
var eventBus = new EventBus();
var aRoom = new NoisyRoom(eventBus);
var bRoom = new NoisyRoom(eventBus);
var cRoom = new NoisyRoom(eventBus);
var dRoom = new QuietRoom(eventBus);
eventBus.Send(new NoisyEvent()); //sends to a,b,c room
}
public class EasyListeningEvent : IEvent
{
}
public class QuietRoom
{
public QuietRoom(EventBus eventBus)
{
eventBus.Listen<EasyListeningEvent>(BringTheNaps);
}
private void BringTheNaps(IEvent #event)
{
//its been brought!
}
}
class NoisyEvent : IEvent
{
}
public class NoisyRoom
{
public NoisyRoom(EventBus eventBus)
{
eventBus.Listen<NoisyEvent>(BringTheNoise);
}
private void BringTheNoise(IEvent #event)
{
//its been brought!
}
}
Try something a little more like this:
Edit: Note that this is just a start in the right direction. You can take this a lot further. Basically what you have is a sound source and a sound emitter. Obviously a radio is a sound source, and a speaker is a sound emitter, but something like a room could be both. A radio should not know what a speaker or a room is, it should only know about emitters, and it should only send sounds to them. Based on this, a room should have a collection of emitters (which would probably be speakers), and when a room gets a sound from a radio, it would simply relay that to whatever emitters it has registered. There would also be nothing stopping you from registering speaker directly to a radio. This code should help show how you might implement all of that.
public class Radio
{
private HashTable<string, EventHandler<ISound>> rooms = new ...;
public void RegisterRoom(string room, EventHandler<ISound> onSound)
{
rooms[room] = onSound;
}
public void UnregisterRoom(string room)
{
rooms.Remove(room);
}
public void PlayRoom(string room)
{
EventHandler<ISound> onSound;
if (rooms.TryGetValue(room, out onSound))
{
onSound(this, new BuildingSpeaker() { sound = "Test" });
}
}
public void PlayAllRooms()
{
if (rooms.Count == 0)
{
return;
}
var speaker = new BuildingSpeaker() { sound = "Test All Rooms" };
foreach (var room in rooms)
{
room.Value(this, speaker);
}
}
}
Related
Hello i want to add a feature to my app but i just can't figure out.
i want to pass some arguments or at least 1 argument to EventHandler using subscriber.
That argument will allow me to do some check and then trigger event based on that argument.
public class Client
{
public Client()
{
GameAPI api = new GameAPI();
api.AddedPlayerEvent += Api_ClarkAdded;
api.Do();
}
private void Api_ClarkAdded(object sender, GameAPI.AddedPlayerEvents e)
{
Console.WriteLine("User Clark found");
}
}
public class GameAPI
{
public event EventHandler<AddedPlayerEvents> AddedPlayerEvent;
List<AddedPlayerEvents> AddedPlayers = new List<AddedPlayerEvents>();
public GameAPI()
{
// some code to simulate generating some data
AddedPlayers.Add(new AddedPlayerEvents("Player1","James"));
AddedPlayers.Add(new AddedPlayerEvents("Player2", "Clark"));
AddedPlayers.Add(new AddedPlayerEvents("Player3", "Steve"));
}
public void Do()
{
// simulating code ...
//trigger event
if (AddedPlayers.Any(f => f.Name == "Clark")) /*value Clark should come from client using subsciber or something else*/
{
OnPlayerAdded(AddedPlayers.First(f => f.Name == "Clark"));
}
}
protected virtual void OnPlayerAdded(AddedPlayerEvents e)
{
EventHandler<AddedPlayerEvents> handler = AddedPlayerEvent;
if (handler != null)
{
handler(this, e);
}
}
public class AddedPlayerEvents
{
public string Pseudo { get; set; }
public string Name { get; set; }
public AddedPlayerEvents(string pseudo, string name)
{
Pseudo = pseudo;
Name = name;
}
}
}
This is a simplified version of what i want to do so i try to make it simple so you can deal with it without garbadge stuff.
I already made a search but all i can find is the parameters is visible only in the client in the Methode handler not transmitted to the EventHandler, i think t should be stored somewhere that he can fetch for them.
Thanks in advance.
Update:
Thanks for the clarification. I think you don't fully understand how the event subscription mechanism works. Let me explain this on example. I refactored your code a little bit:
using System;
using System.Collections.Generic;
class Program
{
static void Main(string[] args)
{
Client client = new Client();
}
}
public class Client
{
public Client()
{
GameAPI api = new GameAPI();
api.PlayerAdded += Api_PlayerAdded;
api.AddPlayer(new Player("Player2", "Clark"));
}
private void Api_PlayerAdded(object sender, PlayerAddedEventArgs e)
{
Console.WriteLine($"API PlayerAdded event has triggered. Arguments: e.Player.Name = {e.Player.Name}, e.Player.Pseudo = {e.Player.Pseudo}");
}
}
public class PlayerAddedEventArgs: EventArgs
{
public PlayerAddedEventArgs(Player player)
{
Player = player;
}
public Player Player { get; }
}
public class Player
{
public Player(string pseudo, string name)
{
Pseudo = pseudo;
Name = name;
}
public string Pseudo { get; }
public string Name { get; }
}
public class GameAPI
{
private List<Player> players = new List<Player>();
public event EventHandler<PlayerAddedEventArgs> PlayerAdded;
public void AddPlayer(Player player)
{
players.Add(player);
OnPlayerAdded(new PlayerAddedEventArgs(player));
}
protected virtual void OnPlayerAdded(PlayerAddedEventArgs e)
{
PlayerAdded?.Invoke(this, e);
}
}
The two main classes here are GameAPI and Client.
GameAPI class:
Keeps track of all added players in the private players list.
Provides an AddPlayer method allowing clients to add players.
Provides a PlayerAdded event to notify clients that a player has been added.
Client class:
Subscribes to the PlayerAdded event exposed by the GameAPI.
Calls AddPlayer method to add the new player.
AddPlayer method adds the player to the internal players list and calls OnPlayerAdded which notifies all subscribed clients about the new player. This notification causes the Api_PlayerAdded method in all subscribed instances of the Client class to be called. The added player will be accessible as the Player property in the e argument passed to this method.
Original answer:
I don't see any issues with your code. I modified the Api_JamesAdded method to make sure it works properly, so the full code looks like this:
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main(string[] args)
{
Client client = new Client();
}
}
public class Client
{
public Client()
{
GameAPI api = new GameAPI();
api.AddedPlayerEvent += Api_JamesAdded;
api.Do();
}
private void Api_JamesAdded(object sender, GameAPI.AddedPlayerEvents e)
{
Console.WriteLine($"Name: {e.Name}, Pseudo: {e.Pseudo}");
}
}
public class GameAPI
{
public event EventHandler<AddedPlayerEvents> AddedPlayerEvent;
List<AddedPlayerEvents> AddedPlayers = new List<AddedPlayerEvents>();
public GameAPI()
{
// some code to simulate generating some data
AddedPlayers.Add(new AddedPlayerEvents("Player1", "James"));
OnPlayerAdded(AddedPlayers.First(f => f.Pseudo == "Player1"));
AddedPlayers.Add(new AddedPlayerEvents("Player2", "Clark"));
OnPlayerAdded(AddedPlayers.First(f => f.Pseudo == "Player2"));
AddedPlayers.Add(new AddedPlayerEvents("Player3", "Steve"));
OnPlayerAdded(AddedPlayers.First(f => f.Pseudo == "Player3"));
}
public void Do()
{
// simulating code ...
//trigger event
if (AddedPlayers.Any(f => f.Name == "Clark")) /*value Clark should come from client using subsciber or something else*/
{
OnPlayerAdded(AddedPlayers.First(f => f.Name == "Clark"));
}
}
protected virtual void OnPlayerAdded(AddedPlayerEvents e)
{
EventHandler<AddedPlayerEvents> handler = AddedPlayerEvent;
if (handler != null)
{
handler(this, e);
}
}
public class AddedPlayerEvents
{
public string Pseudo { get; set; }
public string Name { get; set; }
public AddedPlayerEvents(string pseudo, string name)
{
Pseudo = pseudo;
Name = name;
}
}
}
This code prints:
Name: Clark, Pseudo: Player2
If this is not what you expected, please post the expected output.
Is there a way to access all the different types of object inherited from the same class using the same reference in an object without hard-coding it?
I'm developing on unity and I want to add a module in my game that could watch any particular float in a GameObject to then change another float in another GameObject once it reaches a certain value.
As an example: A "Trigger" Object/Module which set the value of Hunger=1 in a brain Object when the value of Fullness<0.5 is reached in a stomach Object.
As I'll have a big number of possible combinations I don't want to hardcode it by creating a daughter of the Trigger Class for each of them.
My initial idea was to use pointers that would be directed to the good floats to watch/change upon initialization. But apparently, we can't use unsafe code inside iterators (IEnumerator) so I'm not sure how to poll the value of Fullness.
To give an example of what I would like :
public Class Trigger{
private float* ToPoll;
private float* ToChange;
public void SetTrigger(float* poll, float* change){
ToPoll = poll;
ToChange = change;
// the loop would be a IEnumerator, not a litteral loop
while(*ToPoll < 0.5f){
sleep(0.1)
}
*ToChange = 1f
}
}
void Main(){
Trigger trigger1, trigger2;
trigger1.SetTrigger(&Stomach.fullness, &Brain.hunger)
trigger2.SetTrigger(&Sun.activityLevel, &Earth.radiationLevel)
// ^ Literally any float from any object
}
Do you have any ideas how to or better ways to implement it?
Expanding on the answer from #kara, the following code implements independent Stomach and Brain objects, and uses Being to wire them up.
What Being knows about Stomach:
it has a NeedsFoodEvent
What Being knows about Brain
there is a OnRaiseIsHungryEvent (i.e. a "hungry" signal -- who cares where it came from)
it has a IsHungryEvent
Keep in mind that in a real implementation there would likely be other objects listening for those events. e.g. maybe you have an emotion system that would switch to "hangry" and a goal-based AI that would switch to food-seeking mode. Neither system would need to be aware of the other, but both could respond to signals from the Brain. In this trivial implementation the Being responds to the Stomach signal and both notifies and responds to the Brain.
The important take-away from this is not the specific method of raising and responding to events (in this case the default .Net mechanism) but the fact that neither object knows anything about the internals of the other (see the different implementations of HumanStomach and ZombieStomach) and instead the connection is wired up at a more appropriate level (Being in this case). Also note the reliance on interfaces, which allows us to do things like create hybrid beings (i.e. pairing a ZombieBrain with a HumanStomach).
Code was written/tested with .Net Core CLI as a console app, but it should be compatible with most any version of .Net > 3.5.
using System;
using System.Linq;
using System.Threading;
namespace so_example
{
public class Program
{
static void Main(string[] args)
{
var person1 = new Being("Human 1", new HumanBrain(), new HumanStomach());
var zombie1 = new Being("Zombie 1", new ZombieBrain(), new ZombieStomach());
var hybrid1 = new Being("Hybrid 1", new ZombieBrain(), new HumanStomach());
var hybrid2 = new Being("Hybrid 2", new HumanBrain(), new ZombieStomach());
Console.WriteLine("Hit any key to exit");
Console.ReadKey();
}
}
public class HungryEventArgs : EventArgs
{
public string Message { get; set; }
}
public interface IStomach
{
event EventHandler<HungryEventArgs> NeedsFoodEvent;
}
public class Stomach : IStomach
{
public event EventHandler<HungryEventArgs> NeedsFoodEvent;
protected virtual void OnRaiseNeedsFoodEvent(HungryEventArgs e)
{
EventHandler<HungryEventArgs> handler = NeedsFoodEvent;
if (handler != null)
{
handler(this, e);
}
}
}
public class HumanStomach : Stomach
{
private Timer _hungerTimer;
public HumanStomach()
{
_hungerTimer = new Timer(o =>
{
// only trigger if breakfast, lunch or dinner (24h notation)
if (new [] { 8, 13, 19 }.Any(t => t == DateTime.Now.Hour))
{
OnRaiseNeedsFoodEvent(new HungryEventArgs { Message = "I'm empty!" });
}
else
{
Console.WriteLine("It's not mealtime");
}
}, null, 1000, 1000);
}
}
public class ZombieStomach : Stomach
{
private Timer _hungerTimer;
public ZombieStomach()
{
_hungerTimer = new Timer(o =>
{
OnRaiseNeedsFoodEvent(new HungryEventArgs { Message = "Need brains in stomach!" });
}, null, 1000, 1000);
}
}
public interface IBrain
{
event EventHandler<HungryEventArgs> IsHungryEvent;
void OnRaiseIsHungryEvent();
}
public class Brain : IBrain
{
public event EventHandler<HungryEventArgs> IsHungryEvent;
protected string _hungryMessage;
public void OnRaiseIsHungryEvent()
{
EventHandler<HungryEventArgs> handler = IsHungryEvent;
if (handler != null)
{
var e = new HungryEventArgs
{
Message = _hungryMessage
};
handler(this, e);
}
}
}
public class HumanBrain : Brain
{
public HumanBrain()
{
_hungryMessage = "Need food!";
}
}
public class ZombieBrain : Brain
{
public ZombieBrain()
{
_hungryMessage = "Braaaaaains!";
}
}
public class Being
{
protected readonly IBrain _brain;
protected readonly IStomach _stomach;
private readonly string _name;
public Being(string name, IBrain brain, IStomach stomach)
{
_stomach = stomach;
_brain = brain;
_name = name;
_stomach.NeedsFoodEvent += (s, e) =>
{
Console.WriteLine($"{_name}: {e.Message}");
_brain.OnRaiseIsHungryEvent();
};
_brain.IsHungryEvent += (s, e) =>
{
Console.WriteLine($"{_name}: {e.Message}");
};
}
}
}
Some Notes
To provide some output, I faked things in the 2 IStomach implementations. The HumanStomach creates a timer callback in the constructor which fires every 1 second and checks if the current hour is a meal hour. If it is, it raises the NeedsFoodEvent. The ZombieStomach also uses a callback every 1 second, but it just fires the NeedsFoodEvent every time. In a real Unity implementation you'd likely trigger the even based on some event from Unity -- an action the player took, after some preset amount of time, etc.
I'm not quite sure, what you want to do, but it sounds like you want to add triggers to you objects. In my understanding a trigger should be a delegate in this case.
Here an example how to define a delegate-type and add a list of triggers to your Brain-class.
Every brain can now have different triggers. I setup two derived brains to show you how to work with it:
public class TestBrain
{
private static int NextId = 1;
public TestBrain(List<MyTrigger> triggers)
{
this.Triggers = triggers;
this.Id = NextId++;
}
public int Id { get; private set; }
public int Hunger { get; set; }
public int StomachFullness { get; set; }
public List<MyTrigger> Triggers { get; private set; }
public void FireTriggers()
{
foreach (MyTrigger t in this.Triggers)
{
t.Invoke(this);
this.StomachFullness = 100;
}
}
public delegate void MyTrigger(TestBrain b);
}
public class HumanBrain : TestBrain
{
static readonly List<MyTrigger> defaultHumanTriggers = new List<MyTrigger>()
{
b => { if (b.StomachFullness < 50) { b.Hunger = 1; Console.WriteLine("{0} is hungry..", b.Id); } }
};
public HumanBrain() : base(defaultHumanTriggers)
{
}
}
public class RobotBrain : TestBrain
{
static readonly List<MyTrigger> defaultRobotTriggers = new List<MyTrigger>()
{
b => { if (b.StomachFullness < 50) { Console.WriteLine("{0} ignores hunger only want's some oil..", b.Id); } }
};
public RobotBrain() : base(defaultRobotTriggers)
{
}
}
static void Main()
{
// Create some test-data
List<TestBrain> brains = new List<TestBrain>()
{
new HumanBrain(),
new HumanBrain(),
new RobotBrain(),
new HumanBrain(),
};
Console.WriteLine(" - - - Output our Testdata - - -");
foreach (TestBrain b in brains)
{
Console.WriteLine("Status Brain {0} - Stomachfulness: {1} Hunger: {2}", b.Id, b.StomachFullness, b.Hunger);
}
Console.WriteLine(" - - - Empty stomachs - - -");
foreach (TestBrain b in brains)
{
b.StomachFullness = 0;
}
Console.WriteLine(" - - - Fire triggers - - -");
foreach (TestBrain b in brains)
{
b.FireTriggers();
}
Console.WriteLine(" - - - Output our Testdata - - -");
foreach (TestBrain b in brains)
{
Console.WriteLine("Status Brain {0} - Stomachfulness: {1} Hunger: {2}", b.Id, b.StomachFullness, b.Hunger);
}
}
I am working on a cardgame conform the MVVM pattern. My model contains the players, hands and cards as well as the game with its rules.
There are 2 classes not playing nice here: the "card" class that has a "submitted" event: when a player clicks on an image of the card, among other things the submitted event fires. This triggers the UI to move the card from a hand to the center of the window.
Next I have a class "trick", that all players add a card to. When the trick is full, it fires the TrickFull event: this triggers the UI to show the cards in the trick and then clear the table.
During gameplay the TrickFull event fires nanoseconds after the last card was submitted. This means the table is cleared before the 4th card can be shown. I would like to be able to force the UI to process the cardsubmitted event before the Trickfull event.
I have tried to accomplish this by Thread.Sleep (which does not work), I have also tried to move the TrickFull event to the gameclass (meaning it gets triggered much later). This works, but it does seem very out of place. I have looked into locking the events (but that does not seem to be the way to go), directly taking to control of the Dispatcher, changing the priority, or maybe calling the events asynchonously and blocking the stuff somehow in the EndInvoke.
I would like to know what the best solution for this would be. My research suggests that maybe Events would not be the best pattern for this behaviour, but I am stumped. Can you bright people please advise me on how to fix this (probably architectural) flaw?
Code below, beware: Dutch classnames and stuff in there
Card (=Kaart)
public class Kaart : IComparable<Kaart>
{
public readonly Kleur Kleur;
public readonly Waarde Waarde;
public Kaart(Kleur kleur, Waarde waarde)
{
Kleur = kleur;
Waarde = waarde;
}
public event KaartGespeeld Opgegooid;
public delegate void KaartGespeeld(Kaart kaart);
public void Opgooien()
{
Opgegooid?.Invoke(this);
}
public int CompareTo(Kaart other)
{
var comparer = new KlaverjasComparer(null, null);
return comparer.Compare(this, other);
}
public Speler Speler { get; set; }
}
Trick (=Slag)
public class Slag
{
private readonly List<Kaart> _kaarten;
[Browsable(false)]
public IReadOnlyList<Kaart> Kaarten => _kaarten;
public Slag(Kleur troef)
{
_kaarten = new List<Kaart>(4);
Troef = troef;
}
public Speler Winnaar { get; private set; }
public int Punten => PuntenTeller.Punten(this);
public int Roem => PuntenTeller.Roem(this);
[Browsable(false)]
public Kleur Troef { get; }
public Kleur GevraagdeKleur { get; set; }
[Browsable(false)]
public bool Vol =>_kaarten.Count == 4;
public void Add(Kaart kaart)
{
if (!Vol)
{
if (_kaarten.Count == 0)
{
GevraagdeKleur = kaart.Kleur;
}
_kaarten.Add(kaart);
}
else
{
throw new Exception("Te veel kaarten in een slag");
}
if (!Vol) return;
Winnaar = bepaalHoogsteKaart(this).Speler;
VolleSlag?.Invoke(this);
}
public event SlagIsVol VolleSlag;
public delegate void SlagIsVol(Slag slag);
}
ViewModel:
public TafelViewModel(Boompje boompje)
{
Speler1 = boompje.Deelnemers[0];
Speler2 = boompje.Deelnemers[1];
Speler3 = boompje.Deelnemers[2];
Speler4 = boompje.Deelnemers[3];
Troef = boompje.Potje.Troef;
//boompje.SlagIsVol += Boompje_SlagIsVol;
// ToDo: als ik naar dit event kijk gaat het mis
boompje.Potje.Slag.VolleSlag += Boompje_SlagIsVol;
boompje.Potje.TroefGedraaid += delegate { Troef = boompje.Potje.Troef; };
foreach (Speler _deelnemer in boompje.Deelnemers)
{
foreach (Kaart _kaart in _deelnemer.Hand)
{
_kaart.Opgegooid += moveKaart;
}
_deelnemer.DoeIkHet += DeelnemerOnDoeIkHet;
}
_spelerKaart = new Dictionary<Speler, string>
{
{Speler1, "Kaart1"},
{Speler2, "Kaart2"},
{Speler3, "Kaart3"},
{Speler4, "Kaart4"}
};
_spelerRichting = Dictionary.SpelersRichting(boompje.Deelnemers);
WinnaarVisible = Visibility.Hidden;
}
private void Boompje_SlagIsVol(Slag slag)
{
WinnaarVisible = Visibility.Visible;
Richting = _spelerRichting[slag.Winnaar];
Application.DoEvents();
Thread.Sleep(2000);
Kaart1 = null;
Kaart2 = null;
Kaart3 = null;
Kaart4 = null;
WinnaarVisible = Visibility.Hidden;
}
private void moveKaart(Kaart kaart)
{
PropertyInfo prop = GetType().GetProperty(_spelerKaart[kaart.Speler]);
prop?.SetValue(this, kaart);
}
public void OpKaartGeklikt(Kaart kaart)
{
if (kaart.Speler != Speler3)
{
return;
}
Speler3.SpeelKaart(kaart);
}
}
}
Set ManualResetEvent in your ViewModel
ManualResetEvent manualResetEvent = new ManualResetEvent(false);
Pass this object into Kaart of yours
ManualResetEvent _manualResetEvent;
public Kaart(Kleur kleur, Waarde waarde, ManualResetEvent manualResetEvent)
{
Kleur = kleur;
Waarde = waarde;
_manualResetEvent = manualResetEvent;
}
This is the method that's being invoked when card is added I assume
public void Opgooien()
{
Opgegooid?.Invoke(this);
_manualResetEvent.Set();
}
And the main part (you also need to pass ManualResetEvent to the Slag object.
public void Add(Kaart kaart)
{
if (!Vol)
{
if (_kaarten.Count == 0)
{
GevraagdeKleur = kaart.Kleur;
}
_kaarten.Add(kaart);
}
else
{
throw new Exception("Te veel kaarten in een slag");
}
if (!Vol) return;
Winnaar = bepaalHoogsteKaart(this).Speler;
var result = _manualResetEvent.WaitOne(TimeSpan.FromSeconds(5));
if(!result)
{
/* Did not receive signal in 5 seconds */
}
VolleSlag?.Invoke(this);
_manualResetEvent.Reset();
}
Just the basic concept, it might not work right away due to your code language and lack of some part of it in the example, but you should catch the idea
I'm making QuestSystem in unity.
what i want to do is assigning to my questData an event so it can know when the quest objective has been completed.
Lets say there is a class named A and action called a.
and i want Class B, Action b want to have reference to A.a
So if i do
b = A.a;,
b+= someAction;, it actually does a+=someAction;
but when if i do that. It will just simply b+=someAction and A.a will remain null
what should i do to perform what i want?
here are some tags. (i don't know what the answer would be. so..)
# event
# subscribing event
# assigning event
# referencing event
# action
# delegate
====== Edited =======
here is my code.
QuestData.cs
public class QuestData
{
public string questName;
public string qusetID;
public string questDescription;
public SceneType questSceneType;
private string isActivePrefsKey { get { return $"QuestKey{qusetID}"; } }
public bool isActive {
get {
return Convert.ToBoolean (PlayerPrefs.GetInt (isActivePrefsKey));
}
set { PlayerPrefs.SetInt (isActivePrefsKey, Convert.ToInt16 (value)); }
}
public QuestObjective questObjective;
public QuestReward questReward;
public void Activate ()
{
if (AppController.CurrentScene == questSceneType) {
questObjective.ActivateObjective ();
}
}
}
QuestObjective.cs
public class QuestObjective
{
// TODO rename all
public int goalObjectiveCount;
public int currentObjectiveCount;
public Action questAction;
public void OnConditionMatch ()
{
Debug.Log ("OnConditionMatch");
currentObjectiveCount += 1;
}
public void ActivateObjective ()
{
questAction += OnConditionMatch;
}
}
QuestManager.cs
public class QuestManager : MonoBehaviour
{
List<QuestData> questDatas;
void Awake ()
{
PrepareQuestDatas ();
ActivateActiveQuests ();
}
void ActivateActiveQuests ()
{
var activeQuests = GetActiveQuests ();
foreach (var activeQuest in activeQuests) {
activeQuest.Activate ();
}
}
List<QuestData> GetActiveQuests ()
{
// for debuging
return questDatas;
// real code
return questDatas.Where (q => q.isActive == true).ToList ();
}
public void PrepareQuestDatas ()
{
questDatas = new List<QuestData> {
new QuestData {
questName = "Foot Print",
questDescription = "win the game for first time",
questSceneType = SceneType.Main,
questObjective = new QuestObjective {
goalObjectiveCount = 1,
questAction = GamePlayController.instance.endGameCon.onWinGame
},
questReward = new QuestCoinReward{
rewardAmount = 100,
},
}
};
}
}
One potential solution is to create a new set of EventArgs, like this:
public class QuestCompletedEventArgs : System.EventArgs
{
public QuestObjective FinishedObjective { get; }
public QuestCompletedEventArgs(QuestObjective objectiveIn) {
this.FinishedObjective = objectiveIn;
}
}
(probably in a different file)
... and use it like this:
First, create an event delegate:
public delegate void QuestObjectiveCompleteHandler(object sender, QuestCompletedEventArgs e);
Instantiate the event delegate:
public event QuestObjectiveCompletedHandler CompletedObjective;
Define the method that will do something when the objective is completed:
public void ObjectiveCompleted(object sender, QuestCompletedEventArgs e)
{
// do something
}
Assign that method to the event:
this.CompletedObjective += this.ObjectiveCompleted;
From here, you can make the FinishedObjective object within the QuestCompletedEventArgs a List<QuestObjective>, and FinishedObjective.add(objectiveIn) whenever appropriate.
You should also be able to make the event handling method act differently when a certain amount of objectives have been completed, or whatever you want to do with that information.
Of course, you can also add multiple different methods to respond to this event by adding more this.CompletedObjective += this.methodName; lines, as long as the signature of the new method(s) carry that same signature.
Reading into your example, I have written up some code where "A" is QuestObjective and "B" is Quest. The Quest object needs to know when objective has been marked as completed.
Using event handlers, we can set it up so that B is notified when an action occurs on A.
Like this:
// B
public class Quest
{
public Quest()
{
Objectives = new List<QuestObjective>();
// load objectives... Fake
Objectives.Add(new QuestObjective("obj 1"));
Objectives.Add(new QuestObjective("obj 2"));
Objectives.Add(new QuestObjective("obj 3"));
foreach(var o in Objectives) // subscribe to QuestObjective events
{
o.ObjectiveCompleted += (sender, args) => ReportObjectiveCompleted();
}
}
public void ReportObjectiveCompleted()
{
// let 'em know
}
public List<QuestObjective> Objectives { get; set; }
}
// A
public class QuestObjective
{
public string Name { get; set; }
public QuestObjective(string name = "unknown")
{
Name = name;
}
public event EventHandler ObjectiveCompleted;
public void MarkCompleted()
{
// when a task is marked as complete and IF there are
// subscribers to this event then call the event handler
var a = ObjectiveCompleted;
if (a != null)
{
a(this, new EventArgs()); // use different event args to pass data
}
}
}
I'm studying for an exam and I came across a question I couldn't figure out. It asks to Create a TurnOnRadio method for the Radio class. This method should remove any TV subscribers to the remote control object. I thought I could do this with just = without the += or -=. When I go to do this is says This event " RemoteControl.channelChange " can only be on the left hand side of += or -= (except when used from within the type 'Remote Control') Any help on accomplishing this task would be appreciated. Code posted below.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace RemoteControlApp2
{
class RemoteControl
{
public delegate void ChannelChanged(object remote, RemoteEventsArgs re);
public event ChannelChanged channelChange;
private int currentChannel;
public void ChangeTheCrrentChannel(int newChannel)
{
RemoteEventsArgs newRe = new RemoteEventsArgs(newChannel);
if (channelChange!=null)
{
channelChange(this, newRe);
}
}
}
class RemoteEventsArgs : EventArgs
{
public int newChannel;
public RemoteEventsArgs(int nc)
{
this.newChannel = nc;
}
}
class Television
{
private int tvChannel;
//Your code here
public void TurnOnTV(RemoteControl Remote)
{
Remote.channelChange += new RemoteControl.ChannelChanged(TVChannelChanged);
Console.WriteLine(Remote.ToString() + " is detected");
}
public void TurnOffTV(RemoteControl Remote)
{
Remote.channelChange -= new RemoteControl.ChannelChanged(TVChannelChanged);
Console.WriteLine(Remote.ToString() + " is no longer detected");
}
public void TVChannelChanged(Object Remote, RemoteEventsArgs nc)
{
Console.WriteLine("The TV channel is changed. New channel is: {0}", nc.newChannel);
}
}
class Radio
{
private int radioChannel;
//Your code here
public void TurnOnRadio(RemoteControl Remote)
{
Remote.channelChange = new RemoteControl.ChannelChanged(TVChannelChanged);
Console.WriteLine(Remote.ToString() + " is deteceted")
}
//May need to write RadioChannelChanged method
}
class Program
{
static void Main(string[] args)
{
RemoteControl rc = new RemoteControl();
Television tv = new Television();
tv.TurnOnTV(rc);
rc.ChangeTheCrrentChannel(29);
rc.ChangeTheCrrentChannel(32);
tv.TurnOffTV(rc);
Console.ReadKey();
}
}
}
I took out event from public event ChannelChanged channelchange;
So now it is public ChannelChanged channelchange;
Next I finished the radio class and TurnOnRadio method and now that event has been removed I can use = to remove all other subscriptions and now subscribes whatever channel the remote is changed to in main. Radio class code posted below.
class Radio
{
private int radioChannel;
//Your code here
public void TurnOnRadio(RemoteControl Remote)
{
Remote.channelChange = new RemoteControl.ChannelChanged(RadioChannelChanged);
//Console.WriteLine(Remote.ToString() + " is deteceted");
}
public void RadioChannelChanged(object Remote,RemoteEventsArgs re)
{
radioChannel = re.newChannel;
Console.WriteLine("Radio channel is changed. New channel is :{0}", re.newChannel);
}
//May need to write RadioChannelChanged method
}