事件处理
When writing your application logic, one of the biggest challenges you're likely to face is connecting the various changing elements in a way that works. For example, making a character move, jump, or adding up the score can be relatively easy to do on its own. But connecting all things that happen in your game or application without making it confusing to work with can be very challenging.在编写应用程序逻辑时,您可能面临的最大挑战之一是以一种有效的方式连接各种变化的元素。例如,让角色移动、跳跃或增加分数可以相对容易地自行完成。但是,将游戏或应用中发生的所有内容连接起来而又不让人感到困惑是非常具有挑战性的。
The Event System enables you to create application logic that is executed when an event is triggered during the application execution. It allows objects to subscribe one or more of their own functions to a subject's event. Then, when the subject triggers the event, the objects' functions are called in response. Such functions are also known as event handlers. 事件系统使您能够创建在应用程序执行期间触发事件时执行的应用程序逻辑。它允许对象将自己的一个或多个函数订阅到主题的事件。然后,当主题触发事件时,将调用对象的函数作为响应。这样的函数也称为事件处理程序。
The Event System features the following:事件系统的特点如下:
- Strict type checking for signatures: you can see how many and which exactly arguments an event handler function requires.签名的严格类型检查:您可以看到事件处理程序函数需要多少参数以及确切需要哪些参数。
- Compile-time checking: it ensures that argument types match event types, preventing runtime errors.编译时检查:确保参数类型匹配事件类型,防止运行时错误。
- Simple subscription/unsubscription to events with lambda functions with no need to perform internal type conversions.使用lambda函数简单订阅/取消订阅事件,无需执行内部类型转换。
- Automatic event unsubscription.事件自动退订。
- Temporary event deactivation: particular events can be temporarily disabled to perform specific actions without triggering them. 临时事件停用:可以暂时禁用特定事件以执行特定操作而不触发它们。
- Batch management: you can unsubscribe from several subscriptions in a single function call.批处理管理:您可以在单个函数调用中取消订阅多个订阅。
Events事件#
An event is represented by the abstract Event class. It serves as an interface for interaction with the event. Typically, you get this interface via a reference as Event<args...>, where args represents a list of arguments the event will pass to a handler function.事件由抽象Event类表示。它充当与事件交互的接口。通常,通过引用Event<args...>获得该接口,其中args表示事件将传递给处理程序函数的参数列表。
For example, Body.EventPosition returns the event with the following signature:例如, Body.EventPosition返回带有以下签名的事件:
Event<Body>
It means the handler function must receive an argument of the same type when connected with the event.这意味着处理程序函数在与事件连接时必须接收相同类型的参数。
Emulating Events模拟事件#
Sometimes, it is necessary to emulate events. For custom events, you can use the EventInvoker.Run() function that receives the same arguments as the event and invokes its handler functions.有时,模拟事件是必要的。对于自定义事件,可以使用EventInvoker.Run()函数,该函数接收与事件相同的参数并调用其处理程序函数。
The following example shows how to create your event and then run it when necessary:下面的例子展示了如何创建事件并在必要时运行它:
class MyEventClass
{
public Event<int> MyEvent { get { return my_event; } }
public void RunEvent()
{
num_runs++;
my_event.Run(num_runs);
}
private int num_runs = 0;
private EventInvoker<int> my_event = new EventInvoker<int>();
};
static void Main(string[] args)
{
MyEventClass my_class = new MyEventClass();
my_class.MyEvent.Connect(
(int n) =>
{
System.Console.WriteLine("n = {0}", n);
}
);
my_class.RunEvent();
my_class.RunEvent();
}
The existing events that are implemented for built-in objects and available through API can be emulated using the corresponding RunEvent*() methods (without having to use EventInvoker.Run()). For example, to emulate the Show event for a widget, call Widget.RunEventShow().为内置对象实现并通过API可用的现有事件可以使用相应的RunEvent*()方法进行模拟(而不必使用EventInvoker.Run())。例如,要模拟小部件的Show事件,调用Widget.RunEventShow()。
widget.RunEventShow();
Event Handlers事件处理程序#
The event handler functions can receive no more than 5 arguments.事件处理函数接收的参数不能超过5个。
In addition, the Event System performs strict type checking for handler function signatures: you can subscribe to the event only if the types of the function arguments match the event types. For example, in the case of the event with a single int argument, you are only able to link it with a handler that also accepts a single integer argument. Even if the types can be implicitly converted (as in the example), subscribing is not permitted.此外,事件系统对处理程序函数签名执行严格的类型检查:只有当函数参数的类型与事件类型匹配时,您才能订阅事件。例如,对于具有单个int参数的事件,您只能将其与也接受单个整数参数的处理程序链接。即使类型可以隐式转换(如示例中所示),也不允许订阅。
Event<int> event; // event signature
void on_event(int a); // types match, subscription is allowed
void on_event(long a); // type mismatch, no subscription
This restriction also applies to the in, out, and ref modifiers. For instance, when the event type is a user class with no modifiers:这个限制也适用于in, out和ref修饰符。例如,当事件类型是没有修饰符的用户类时:
Event<MyClass> event;
void on_event(MyClass a); // types match, subscription is allowed
void on_event(out MyClass a); // type mismatch
void on_event(in MyClass a); // type mismatch
Discarding Arguments丢弃的论点#
In most cases, not all arguments passed to the handler function by the event are necessary. So, events allow for discarding unnecessary arguments when functions subscribe to them. You can only discard one argument at a time, starting with the last one. For example, the following handler functions can subscribe to the event:在大多数情况下,并非事件传递给处理程序函数的所有参数都是必需的。因此,当函数订阅事件时,事件允许丢弃不必要的参数。一次只能放弃一个参数,从最后一个开始。例如,以下处理函数可以订阅事件:
// the event
Event<int, float, string, vec3, MyClass> event;
// the event handlers with discarded arguments
on_event(int a, float b, string s, vec3 v, MyClass c);
on_event(int a, float b, string s, vec3 v);
on_event(int a, float b, string s);
on_event(int a, float b);
on_event(int a);
on_event();
Receiving Additional Arguments接收附加参数#
To receive an additional user argument in the handler function, you need to add the required argument to the end of the handler arguments list and pass its value to the Connect() function.要在处理程序函数中接收额外的用户参数,需要将所需的参数添加到处理程序参数列表的末尾,并将其值传递给Connect()函数。
class UserClass
{
{ /* ... */ }
};
static EventInvoker<int, float> my_event = new EventInvoker<int, float>();
void on_event_0(int a, float b, int my_var) { /* ... */ }
void on_event_1(int a, float b, UserClass c) { /* ... */ }
void on_event(float f, string str) { /* ... */ }
static MyClass my_class = new MyClass();
static void Main(string[] args)
{
// pass the value of the additional "my_var" argument to the handler function
my_event.Connect(on_event_0, 33);
// pass the value of the additional "c" argument to the handler function
my_event.Connect(on_event_1, my_class);
// discard the int and float handler arguments, add the custom float and const char* and pass them to connect()
my_event.Connect(on_event, 33.3f, "test");
return 0;
}
Subscribing to Events订阅事件#
For convenience, the Event System provides the EventConnection and EventConnections classes that allow simple event subscription/unsubscription. Let's go through them in detail.为方便起见,事件系统提供了EventConnection和EventConnections类,它们允许简单的事件订阅/取消订阅。让我们详细介绍一下。
Single Subscription with EventConnection带有EventConnection的单订阅#
The EventConnection class keeps a connection between an event and its handler. You can subscribe to events via the Event.Connect() function and unsubscribe via Event.Disconnect():EventConnection类保持事件与其处理程序之间的连接。您可以通过Event.Connect()函数订阅事件,并通过Event.Disconnect()取消订阅:
- The Connect() function receives the handler function as an argument. It returns EventConnection that can be used to unsubscribe from the event. The number of the Connect() function arguments may vary.函数Connect()接收处理程序函数作为参数。它返回可用于取消订阅事件的EventConnection。Connect()函数参数的数量不是固定的。
- The Disconnect() function receives the handler function as an argument.函数Disconnect()接收处理程序函数作为参数。
For example, to set the connection between the event and the static handler function, you can implement the following:例如,要设置事件与静态处理函数之间的连接,您可以实现以下操作:
static EventInvoker<int, float> my_event = new EventInvoker<int, float>();
// a static handler function
static void on_event(int a, float b) { /*...*/ }
static void Main(string[] args)
{
// connect the handler function with the event
EventConnection connection = my_event.Connect(on_event);
}
You can temporarily turn the event off to perform specific actions without triggering it.您可以暂时关闭事件以执行特定的操作而不触发它。
// disable the event
my_event.Enabled = false;
/* perform some actions */
// and enable it again
my_event.Enaled = true;
Moreover, you can toggle individual connections on and off, providing flexibility when working with events.此外,您可以切换单个连接的打开和关闭,从而在处理事件时提供灵活性。
EventConnection connection = my_event.Connect(on_event);
/* ... */
// disable the connection
connection.Enabled = false;
/* perform some actions */
// and enable it back when necessary
connection.Enabled = true;
Later, you can unsubscribe from the event in one of the following ways:稍后,您可以通过以下其中一种方式退订该事件:
-
By using the handler function:通过使用handler函数:
// break the connection by using the handler function my_event.Disconnect(on_event);
-
By using EventConnection:通过使用EventConnection:
// break the connection by using EventConnection connection.Disconnect()
If the handler function is a class method, you should create a class instance, subscribe to the event, and unsubscribe later as follows:如果处理函数是一个类方法,您应该创建一个类实例,订阅事件,然后取消订阅,如下所示:
class MyClass
{
public void on_event(int a, float b) { /*...*/ }
}
static EventInvoker<int, float> my_event = new EventInvoker<int, float>();
static void Main(string[] args)
{
MyClass obj = new MyClass();
// connect the handler function with the event
EventConnection connection = my_event.Connect(obj.on_event);
/* ... */
// break the connection by using the handler function later
my_event.Disconnect(obj.on_event);
}
Multiple Subscriptions with EventConnections具有EventConnections的多个订阅#
The EventConnections class is a container for the EventConnection instances. Multiple subscriptions to a single event or different events can be linked to a single EventConnections instance. EventConnections 类是 EventConnection 实例的容器。 对单个事件或不同事件的多个订阅可以链接到单个EventConnections实例。
For example, you can create multiple subscriptions to a single event as follows:例如,您可以为单个事件创建多个订阅,如下所示:
EventConnections connections = new EventConnections();
static EventInvoker my_event = new EventInvoker();
// event handlers
void on_some_event_0() { Log.Message("\Handling the 1st event\n"); }
void on_some_event_1() { Log.Message("\Handling the 2nd event\n"); }
void init()
{
// add two handlers for the event
// and link it to an EventConnections instance to remove a pack of subscriptions later
my_event.Connect(connections, on_some_event_0);
my_event.Connect(connections, on_some_event_1);
}
Also, you can create multiple subscriptions to different events:此外,您可以为不同的事件创建多个订阅:
EventConnections connections = new EventConnections();
static EventInvoker my_event_0 = new EventInvoker();
static EventInvoker my_event_1 = new EventInvoker();
// event handlers
void on_some_event_0() { Log.Message("\Handling the 1st event\n"); }
void on_some_event_1() { Log.Message("\Handling the 2nd event\n"); }
void init()
{
// subscribe for events with handlers to be executed when the events are triggered;
// here multiple subscriptions are linked to a single EventConnections class instance
my_event_0.Connect(connections, on_some_event_0);
my_event_1.Connect(connections, on_some_event_1);
}
Later all of these linked subscriptions can be removed with a single line:之后,所有这些链接订阅都可以通过一行删除:
// break the connection by using EventConnections
// all instances of EventConnection will be removed from the EventConnections container
connections.DisconnectAll();
Using Lambda Functions使用Lambda函数#
You can pass a lambda function as an argument to the Connect() function to handle the event: there is no need to perform internal type conversions. All features available for the handler functions are also applicable to lambda functions, except additional arguments.您可以将lambda函数作为参数传递给Connect()函数来处理事件:不需要执行内部类型转换。处理程序函数可用的所有特性也适用于lambda函数,除了附加参数。
class MyClass
{
};
static EventInvoker<int, float> my_event = new EventInvokerlt;int, float>();
static void Main(string[] args)
{
EventConnection connection = my_event.Connect(
(int a, float b) =>
{
System.Console.WriteLine("a = {0}, b = {1}", a, b);
},
);
connection = my_event.Connect(
(int a) =>
{
System.Console.WriteLine("a = {0}", a);
},
);
connection = my_event.Connect(
(int a, string s) =>
{
System.Console.WriteLine("a = {0}, s = {1}", a, s);
},
"my string"
);
connection = my_event.Connect(
(int a, float b, string s) =>
{
System.Console.WriteLine("a = {0}, b = {1}, s = {2}", a, b, s);
},
"test"
);
my_event.Run(3, 33.0f);
}
See more examples of practical use of lambda functions below.请参阅下面 有关lambda函数实际使用的更多示例。
Using Predefined Events使用预定义事件#
Some Unigine API members have several predefined events that can be handled in specific cases. The following chapters showcase the practical use of the concepts described above.一些Unigine API成分有几个预定义的事件,可以在特定情况下处理。下面的章节展示了上述概念的实际应用。
Triggers触发器#
Triggers are used to detect changes in nodes position or state. Unigine offers three types of built-in triggers:触发器用于检测节点位置或状态的变化。Unigine提供三种类型的内置触发器:
- NodeTrigger triggers events when the trigger node is enabled or its position has changed. 当触发节点启用或位置发生变化时,NodeTrigger触发事件。
- WorldTrigger triggers events when any node (collider or not) gets inside or outside it. 当任何节点(碰撞器或非碰撞器)进入或离开它时,WorldTrigger会触发事件。
- PhysicalTrigger triggers events when physical objects get inside or outside it. PhysicalTrigger 在物理对象进入或离开它时触发事件。
Here is a simple NodeTrigger usage example. The event handlers are set via pointers specified when subscribing to the following events: EventEnabled and EventPosition.下面是一个简单的NodeTrigger使用示例。事件处理程序是通过订阅以下事件时指定的指针来设置的: EventEnabled和EventPosition。
private NodeTrigger trigger;
private ObjectMeshStatic obj;
// the position event handler
void position_event_handler(NodeTrigger trigger)
{
Log.Message("Object position has been changed. New position is: {0}\n", trigger.WorldPosition.ToString());
}
// the enabled event handler
void enabled_event_handler(NodeTrigger trigger)
{
Log.Message("The enabled event handler is {0}\n", trigger.Enabled);
}
private void Init()
{
// create a mesh
Mesh mesh = new Mesh();
mesh.AddBoxSurface("box_0", new vec3(1.0f));
// create a node (e.g. an instance of the ObjectMeshStatic class)
obj = new ObjectMeshStatic("core/meshes/box.mesh");
// change material albedo color
obj.SetMaterialParameterFloat4("albedo_color", new vec4(1.0f, 0.0f, 0.0f, 1.0f), 0);
// create a trigger node
trigger = new NodeTrigger();
// add it as a child to the static mesh
obj.AddWorldChild(trigger);
// add the enabled event handler to be executed when the node is enabled/disabled
trigger.EventEnabled.Connect(enabled_event_handler);
// add the position event handler to be executed when the node position is changed
trigger.EventPosition.Connect(position_event_handler);
}
private void Update()
{
float time = Game.Time;
Vec3 pos = new Vec3(MathLib.Sin(time) * 2.0f, MathLib.Cos(time) * 2.0f, 0.0f);
// change the enabled flag of the node
obj.Enabled = pos.x > 0.0f || pos.y > 0.0f;
// change the node position
obj.WorldPosition = pos;
}
And here is an example of WorldTrigger that demonstrates how to subscribe to the Enter event with a corresponding handler and keep this connection to unsubscribe later.下面是一个WorldTrigger的示例,演示了如何使用相应的处理程序订阅Enter事件,并保持其连接,以便稍后退订。
using System;
using System.Collections;
using System.Collections.Generic;
using Unigine;
#if UNIGINE_DOUBLE
using Vec3 = Unigine.dvec3;
using Vec4 = Unigine.dvec4;
using Mat4 = Unigine.dmat4;
#else
using Vec3 = Unigine.vec3;
using Vec4 = Unigine.vec4;
using Mat4 = Unigine.mat4;
#endif
[Component(PropertyGuid = "AUTOGENERATED_GUID")] // <-- this line is generated automatically for a new component
public class EventHandlers : Component
{
// implement the Enter event handler
void enter_event_handler(Node node)
{
Log.Message("\nA node named {0} has entered the trigger\n", node.Name);
}
// implement the Leave event handler
void leave_event_handler(Node node)
{
Log.Message("\nA node named {0} has left the trigger\n", node.Name);
}
WorldTrigger trigger;
EventConnection enter_event_connection;
private void Init()
{
// create a world trigger
trigger = new WorldTrigger(new vec3(3.0f));
// subscribe for the enter event with a handler to be executed when a node enters the world trigger
// and keep its connection to be used to unsubscribe when necessary
enter_event_connection = trigger.EventEnter.Connect(enter_event_handler);
// add the leave event handler to be executed when a node leaves the world trigger
trigger.EventLeave.Connect(leave_event_handler);
}
private void Update()
{
}
private void Shutdown()
{
// removing the subscription for the Enter event by using the connection (enter_event_connection)
enter_event_connection.Disconnect();
}
}
Widgets小部件#
The widgets base class Widget allows subscribing to events.小部件基类Widget允许订阅事件。
The example below demonstrates how to set a lambda function to handle the widget's Clicked event.下面的示例演示了如何设置lambda函数来处理小部件的Clicked事件。
using System;
using System.Collections;
using System.Collections.Generic;
using Unigine;
#if UNIGINE_DOUBLE
using Vec3 = Unigine.dvec3;
using Vec4 = Unigine.dvec4;
using Mat4 = Unigine.dmat4;
#else
using Vec3 = Unigine.vec3;
using Vec4 = Unigine.vec4;
using Mat4 = Unigine.mat4;
#endif
[Component(PropertyGuid = "AUTOGENERATED_GUID")] // <-- this line is generated automatically for a new component
public class EventHandlers : Component
{
private void Init()
{
// get the system GUI
Gui gui = Gui.GetCurrent();
// create a button widget and set its caption
WidgetButton widget_button = new WidgetButton(gui, "Press me");
// rearrange a button size
widget_button.Arrange();
// set a button position
widget_button.SetPosition(10,10);
// set a lambda function to handle the CLICKED event
widget_button.EventClicked.Connect(() => Log.Message("Button pressed\n"));
// add the created button widget to the system GUI
gui.AddChild(widget_button, Gui.ALIGN_OVERLAP | Gui.ALIGN_FIXED);
}
}
Physics物理#
You can track certain events of the physics-related Bodies and Joints:您可以跟踪物理相关的体和关节的某些事件:
- Body.EventFrozen to track an event when a body freezes. Body.EventFrozen跟踪物体静止不动时的事件。
- Body.EventPosition to track an event when a body changes its position.Body.EventPosition用于在物体改变位置时跟踪事件。
- Body.EventContactEnter to track an event when a contact emerges (body starts touching another body or collidable surface).Body.EventContactEnter用于跟踪接触出现时的事件(物体开始接触另一个物体或可碰撞表面)。
- Body.EventContactLeave to track an event when a contact ends (body stops touching another body or collidable surface).当接触结束(物体停止接触另一个物体或可碰撞表面)时跟踪事件。
- Body.EventContacts to get all contacts of the body including new ones (enter) and the ending ones (leave). Leave contacts are removed after the callback execution stage, so this is the only point where you can still get them.Body.EventContacts 获取物体的所有接触,包括新接触(进入)和结束接触(离开)。离开接触在回调执行阶段后将被删除,因此这是您可以获得它们最后的一点。
- Joint.EventBroken to track an event when a joint breaks.Joint.EventBroken用于跟踪关节断裂时的事件。
The following example demostrates how to subscribe to the Body events by using lambda functions and then remove all the event subscriptions for the Body.下面的示例演示如何使用lambda函数订阅Body事件,然后删除Body的所有事件订阅。
using System;
using System.Collections;
using System.Collections.Generic;
using Unigine;
#if UNIGINE_DOUBLE
using Vec3 = Unigine.dvec3;
using Vec4 = Unigine.dvec4;
using Mat4 = Unigine.dmat4;
#else
using Vec3 = Unigine.vec3;
using Vec4 = Unigine.vec4;
using Mat4 = Unigine.mat4;
#endif
[Component(PropertyGuid = "AUTOGENERATED_GUID")] // <-- this line is generated automatically for a new component
public class EventHandlers : Component
{
EventConnections body_event_connections = new EventConnections();
private void Init()
{
// create a box
ObjectMeshStatic meshStatic = new ObjectMeshStatic("core/meshes/box.mesh");
meshStatic.Position = new Vec3(0, 0, 5.0f);
// add a rigid body to the box
BodyRigid body = new BodyRigid(meshStatic);
// subscribe for body events by using lambda functions and storing connections to remove them later
body.EventFrozen.Connect(body_event_connections, b => b.Object.SetMaterialParameterFloat4("albedo_color", new vec4(1.0f, 0.0f, 0.0f, 1.0f), 0));
body.EventPosition.Connect(body_event_connections, b => b.Object.SetMaterialParameterFloat4("albedo_color", new vec4(0.0f, 0.0f, 1.0f, 1.0f), 0));
body.EventContactEnter.Connect(body_event_connections, (b, num) => b.Object.SetMaterialParameterFloat4("albedo_color", new vec4(1.0f, 1.0f, 0.0f, 1.0f), 0));
// add a shape to the body
ShapeBox shape = new ShapeBox(body, new vec3(1.0f));
}
private void Shutdown()
{
// removing all previously stored event subscriptions for the body
body_event_connections.DisconnectAll();
}
}
Properties属性#
Events can be used to determine actions to be performed when adding or removing node and surface properties as well as when swapping node properties. Here is an example demonstrating how to track adding a node property via events.事件可用于确定在添加或删除节点和表面属性以及交换节点属性时要执行的操作。下面是一个演示如何通过事件跟踪添加节点属性的示例。
using System;
using System.Collections;
using System.Collections.Generic;
using Unigine;
#if UNIGINE_DOUBLE
using Vec3 = Unigine.dvec3;
using Vec4 = Unigine.dvec4;
using Mat4 = Unigine.dmat4;
#else
using Vec3 = Unigine.vec3;
using Vec4 = Unigine.vec4;
using Mat4 = Unigine.mat4;
#endif
[Component(PropertyGuid = "AUTOGENERATED_GUID")] // <-- this line is generated automatically for a new component
public class EventHandlers : Component
{
void node_property_added(Node n, Property property)
{
Log.Message("Property \"{0}\" was added to the node named \"{1}\".\n", property.Name, n.Name);
}
void parameter_changed(Property property, int num)
{
Log.Message("Parameter \"{0}\" of the property \"{1}\" has changed its value.\n", property.GetParameterPtr(num).Name, property.Name);
}
public void property_removed(Property property)
{
Log.Message("Property \"{0}\" was removed.\n", property.Name);
}
private void Init()
{
NodeDummy node = new NodeDummy();
// search for a property named "new_property_0"
Property property = Properties.FindProperty("new_property_0");
// subscribing for the PropertyNodeAdd event to handle adding a property to a node
Node.EventPropertyNodeAdd.Connect(node_property_added);
// add the property named "new_property_0" to the node
node.AddProperty("new_property_0");
// subscribing for the ParameterChange event to handle changing property parameter
property.EventParameterChanged.Connect(parameter_changed);
// change the value of the "my_int_param" parameter
property.GetParameterPtr("my_int_param").SetValue(3);
// inherit a new property named "new_property_1" from the base property "surface_base"
Properties.FindManualProperty("surface_base").Inherit("new_property_1");
// subscribing for property removal event
Properties.EventRemoved.Connect(property_removed);
// remove the property named "new_property_1"
Properties.RemoveProperty(Properties.FindProperty("new_property_1").GUID);
}
}