Jump to content
Unity Insider Forum

Nax's Blog

  • Einträge
    13
  • Kommentare
    19
  • Aufrufe
    21.792

#3 Snippets - Globales Event System


Nax

1.626 Aufrufe

Heyho Leute,

mal wieder habe ich ein neues Snippet oder Skript für euch am Start. Diesmal geht es um ein Globales Event System. Sprich ein Ort, wo ich Events ablegen kann, um später mit Listener darauf zu horchen, falls irgendwas ansteht. Z.B. der Charakter springt und es soll eine Audio Datei für das Springen abgespielt werden. Oder der Charakter hebt ein Item auf und der Sound des Items soll abgespielt werden.

Also mal direkt ins kalte Wasser. Hier ist das Skript für die Datei "GlobalEventSystem.js":

[CODE]
#pragma strict
import System;
public class GlobalEventSystem {
// Singleton / Class
private static var _instance : GlobalEventSystem = new GlobalEventSystem();
public static function GetInstance() : GlobalEventSystem {
return _instance;
}
private function GlobalEventSystem() {
//if the constructor must be public, you can do this:
if (_instance != null) {
throw new Exception("This is a singleton class! Use GlobalEventSystem.GetInstance() instead!");
}
}
// Instance / Object
private var events : Hashtable = new Hashtable();
public function On(eventName : String, callback : Function) : void {
if(!events.ContainsKey(eventName)) {
events.Add(eventName, new Array());
}
var callbacks : Array = events[eventName] as Array;
callbacks.Push(callback);
}
public function Broadcast(eventName : String, args : Hashtable) : void {
if(events.ContainsKey(eventName)) {
var callbacks : Array = events[eventName] as Array;
for(var index : int = 0; index < callbacks.length; index++) {
var callback : Function = callbacks[index] as Function;
callback(args);
}
}
}
}
[/CODE]

So hier kommt die Erklärung!

Der Anfang ist denk ich mal selbsterklärend. Wir definieren eine neue Klasse namens GlobalEventSystem und importieren System, da wir etwas daraus benötigen.

[CODE]
#pragma strict
import System;
public class GlobalEventSystem {
[/CODE]

Da wir ein Globales Event System wollen, darf das Objekt dieser Klasse im gesamten Spielablauf auch nur einmal vorkommen. Die einfachste Möglichkeit sowas zu realisieren, sind Singletons! Mehr zu Singleton erfahrt ihr von Dr. Google :rolleyes:

Hier seht ihr auch, das wenn jemand versucht ein Objekt der Klasse durch den Kontruktor zu erzeugen, das eine Exception geworfen wird (dafür der Import von System).

[CODE]
// Singleton / Class
private static var _instance : GlobalEventSystem = new GlobalEventSystem();
public static function GetInstance() : GlobalEventSystem {
return _instance;
}
private function GlobalEventSystem() {
//if the constructor must be public, you can do this:
if (_instance != null) {
throw new Exception("This is a singleton class! Use GlobalEventSystem.GetInstance() instead!");
}
}
[/CODE]

Nun kommt der Interessante Part! Die Instance ;)
[CODE]
// Instance / Object
private var events : Hashtable = new Hashtable();
public function On(eventName : String, callback : Function) : void {
if(!events.ContainsKey(eventName)) {
events.Add(eventName, new Array());
}
var callbacks : Array = events[eventName] as Array;
callbacks.Push(callback);
}
public function Broadcast(eventName : String, args : Hashtable) : void {
if(events.ContainsKey(eventName)) {
var callbacks : Array = events[eventName] as Array;
for(var index : int = 0; index < callbacks.length; index++) {
var callback : Function = callbacks[index] as Function;
callback(args);
}
}
}
[/CODE]

Wir definieren:[list]
[*]ein Attribut, das alle unsere Events und deren Listener festhält.
[*]eine Instanz Methode zum hinzufügen eines Listeners
[*]eine Instanz Methode zum senden eines Events
[/list]
Beim Triggern (absenden/auslösen) von Events können wir eine Hashtable mit Argumenten mit geben, welche an die Listener Funktionen übergeben wird. (das Löschen von Listenern, ist aktuell nicht vorgesehen :wacko:)

Aber wir benutzen wir das ganze nun? Gute Frage!

Man stelle sich vor wir haben eine Datei "Example.js", welche darauf wartet eine Audio Datei abzuspielen. Dabei ruft sie vom Event System die Methode "On" mit einem Schlüssel (hier "PlaySoundEvent") auf und übergibt zusätzlich eine Callback Funktion. Eine Funktion die ausgeführt werden soll, wenn das Event ausgelöst wird.

[CODE]
#pragma strict
var anySound : AudioClip;
private var eventSystem : GlobalEventSystem = GlobalEventSystem.GetInstance();
private var audioSource : AudioSource;
function Start () {
audioSource = GetComponent(AudioSource);
eventSystem.On("PlaySoundEvent", function(args) {
PlaySound();
});
}
function PlaySound() : void {
audioSource.clip = anySound;
audioSource.Play();
}
[/CODE]

Irgendwo anders in unserem Spiel sagen wir, das wenn irgendwas passiert (z.B. die Leertaste gedrückt wird), dann soll dieses Event ausgelöst werden.

[CODE]
#pragma strict
private var eventSystem : GlobalEventSystem = GlobalEventSystem.GetInstance();
function Update() {
if(Input.GetKeyDown(KeyCode.Space)) {
eventSystem.Broadcast('PlaySoundEvent", new Hashtable());
}
}
[/CODE]

Und "BÄÄMMM" passiert es :D

Ich hoffe es gefällt euch.

Wieso ich das geschrieben habe?

Als ich über Unity und Events via Google auf dieser Seite gelandet bin: [url="http://unity3d.com/learn/tutorials/modules/intermediate/scripting/events"]http://unity3d.com/l...cripting/events[/url]

Las ich folgendes unter JS Source:
[quote]
[color=#708090]//Javascript doesn't have a built-in "event". Instead,[/color][color=#708090]//you will need to use C# to gain this functionality.[/color][color=#708090]//Other scripts written in Javascript can still subscribe[/color][color=#708090]//to events created in C#.[/color]
[/quote]

Schrecklich oder?

EDIT: 20.03.2015 - 21:12 Uhr

Heyho - ich hab wirklich nen miesen Bug in der ganzen Sache gefunden. Beim wieder laden von Szenen kann/wird es passieren das Instanzen der Singleton mehrfach existieren, da sie beim wechseln einer Szene nicht die Static Member eine Klasse entfernen xD ist das nicht schrecklich. Man glaub nen leeren/neuen Kontext zu bekommen, aber das ist nur augenscheinlich der Fall. Jedenfalls soll mich das nicht stören. Ich hab die Klasse wesentlich angepasst, damit es in die Kategorie MonoBehaviour Objekt fällt und somit vom GC beachtet wird.

Im groben sieht es nun so aus:

[CODE]
#pragma strict
import System;
import UnityEngine;
public class GlobalEventSystem extends MonoBehaviour {
// Singleton / Class
private static var _instance : GlobalEventSystem; // = new GlobalEventSystem()
public static function GetInstance() : GlobalEventSystem {
return _instance;
}
function Awake() : void {
_instance = this;
}
// private function GlobalEventSystem() {
// //if the constructor must be public, you can do this:
// if (_instance != null) {
// throw new Exception("This is a singleton class! Use GlobalEventSystem.GetInstance() instead!");
// }
// }
// Instance / Object
private var events : Hashtable = new Hashtable();
public function On(eventName : String, callback : Function) : void {
if(!events.ContainsKey(eventName)) {
events.Add(eventName, new Array());
}
var callbacks : Array = events[eventName] as Array;
callbacks.Push(callback);
}
public function Broadcast(eventName : String) {
Broadcast(eventName, null);
}
public function Broadcast(eventName : String, args : Hashtable) : void {
if(args == null) args = new Hashtable();
if(events.ContainsKey(eventName)) {
var callbacks : Array = events[eventName] as Array;
for(var index : int = 0; index < callbacks.length; index++) {
var callback : Function = callbacks[index] as Function;
callback(args);
}
}
}
}
[/CODE]

Damit das ganze läuft hab ich mir einfach ne System Prefab gebastelt, die alle meine Szenenweiten System Singleton Skripte hält ;-). Diese einfach in die Szene einzubinden sollte ja wohl easy sein xD

So Far - Nax

2 Kommentare


Recommended Comments

Hier ein kleiner Bonus!

Da es doch vorkommen kann das man sich bei den Identifiern vertippt und Ewig nach einer Lösung suchen kann, habe ich nach einer eleganten Lösung gesucht Events zusammen zu fassen. Neben Enums in String Listen bin ich auf die einfach Variante mit Klassen und Statischen Klassen zurück gesprungen. Ungefähr so:

[CODE]
#pragma strict
public class Fruit {
public static class Events {
// Some Event
public var FruitDropEvent : String = "SpecialUniqueIdentifierForFruitDropEvent";
}
}
[/CODE]

Nun kann man einfach folgendes machen:
[CODE]
eventSystem.Broadcast(Fruit.Events.FruitDropEvent);
[/CODE]
Link zu diesem Kommentar
Noch mehr Anmerkungen...:[list]
[*]Es hat sich gezeigt, das ich die Prefab nur in einer Szene brauchte...ich lade sie trotzdem in jeder
[*]Des Weiteren sollte das EventSystem nicht mehr Deklaration - initialisiert werden, sondern in der Start Funktion eines Skriptes
[/list]
Nicht so gut:
[CODE]
private var eventSystem : GlobalEventSystem = GlobalEventSystem.GetInstance();
[/CODE]

Besser:

[CODE]
private var eventSystem : GlobalEventSystem;
function Start() {
eventSystem = GlobalEventSystem.GetInstance();
}
[/CODE]

Ersteres sollte Funktionieren wenn man die Prefab einmalig in der ersten Szene initialisiert und danach nicht wieder in szenen einbindent. Da aktuell aber kein "[url="http://docs.unity3d.com/ScriptReference/30_search.html?q=DontDestroyOnLoad"]DoNotDetroyOnLoad[/url]" aufgerufen wird kann es sein, das die Instanz trotzdem mal verschwindet. Zweitere Methode sollte stabiler sein. Ich werd es weiter beobachten ;D
Link zu diesem Kommentar
Gast
Kommentar schreiben...

×   Du hast formatierten Text eingefügt.   Formatierung jetzt entfernen

  Only 75 emoji are allowed.

×   Dein Link wurde automatisch eingebettet.   Einbetten rückgängig machen und als Link darstellen

×   Dein vorheriger Inhalt wurde wiederhergestellt.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Lädt...
×
×
  • Neu erstellen...