unity-Script optimization-Message processing system

Source: Internet
Author: User
Tags class definition instance method message queue

We often run into a running state to find an existing object. In this example, we need to add new enemies to the enemymanagercomponent so that we can control the enemy objects in any way we want in our scene. Because of the overhead involved, we need a reliable and fast way to act on objects to find existing objects without using the Find () method and the SendMessage () method. This section mainly explains if you do not use Find (), but also the completion of the call between objects.

We can solve this problem in a variety of ways, each with its own advantages and disadvantages:
Static class;
Single-instance components;
Assigning references to pre-existing objects;
A global information management system.

The

Singleton mode is a common way to make sure that we have a globally accessible object that retains only one instance in memory. However, the use of singleton patterns in unity projects can be easily overridden in C # with static classes, and does not require the implementation of private constructors and unnecessary property access for an instance variable. Basically, implementing a typical singleton design pattern C #需要更多的代码, time to implement the same effects as static classes. Note that each member and method has a static keyword connection, which means that there is only one instance, and that object will always reside in memory. Static classes, by definition, do not allow any non-static instance members to be defined, because this means that we can replicate objects to some extent. This type of global class is generally considered to be cleaner and easier to use than the typical singleton design pattern in C #上的发展版本. The disadvantage of static class methods is that they must inherit from the lowest class----Object. This means that static classes cannot inherit Monobehaviour, so we cannot use any of its unity features, including callback functions for all important events, including the co-process. And, because there are no objects to choose from, we lose the ability to check the object's data at run time through the Inspector panel. These are features that we can use in a singleton mode. A common solution is "singleton as a component", allowing a game object to contain itself. and provides a static method to grant global access. It is important to note that in this case, we must basically implement a typical singleton design pattern with a private static instance variable and a global instance method of global access. The following is a class that defines a singleton pattern:

public class singletonascomponent<t>: Monobehaviour where T:
singletonascomponent<t> {
private static T __instance;
Protected static singletonascomponent<t> _instance {
get {
if (!__instance)

{
T [] managers = Gameobject.findobjectsoftype (typeof (T)) as t[];
if (managers! = null) {
if (managers. Length = = 1)

{
__instance = Managers[0];
return __instance;
}

else if (managers. Length > 1)

{

Debug.logerror ("You had more than one" +typeof (T). Name + "in the scene. You are only need 1, it's a singleton! ");
for (int i = 0; i < managers. Length; ++i)

                     {
          &NB Sp                t manager = Managers[i];
                          Destroy (Manager.gameobject) ;
                       }
                     }
                 }
                     gameobject go = new Gameobject (typeof (T). N Ame,typeof (T));
                   __instance = go. Getcomponent<t> ();
                  dontdestroyonload (__instance.gameobject);
             }

                   return __instance;
             }
             set {
                &NBSP ;       __instance = value as T;
                   }
         }
}

This is a common single-instance component. Because we want this to be a global and persistent object, we need to call Dontdestroyonload () shortly after the game object is created. This is a special function to tell unity that as long as the application exists, it is always there in the scene switch. Based on this, when a new scene is loaded, the object will not be destroyed, and all the data will be preserved. This class definition assumes two points. First, because it uses generics to define its behavior, it must be a specific class from the creation. Second, the method defines the specified _instance variable to the correct type. As an example, the following is the need to successfully generate a new Singletonascomponent class, derived from Mysingletoncomponent:
public class Mysingletoncomponent: Singletonascomponent<mysingletoncomponent>
{
              Public Static mysingletoncomponent Instance {
                 get {return (MyS ingletoncomponent) _instance); }
                 set {_instance = value;}
               }
}

This class can use any other object at run time to access the instance property at any time. If the component does not already exist in our scene, then the Singletonascomponent base class instantiates its own game object and takes an instance of the derived class as a component. Based on this, access to singleton properties will involve the creation of the component. If possible, we should not put singletonascomponent derived classes into our scene panel. This is because the Dontdestroyonload () method has never been called. This will cause the singleton component object to not exist when the next scene is loaded. Due to the split of unity itself scene switching, it is a bit complicated to clean up the singleton components appropriately. The OnDestroy () method is called when the object is destroyed in the running state. The same method is called during application shutdown, and each component in unity has its own OnDestroy () method. Closing the application also occurs when we end the playback mode in the editor and enter edit mode. However, the destruction of objects is a random order, and we cannot guarantee that the order example component will be the last object to be destroyed. Therefore, if any object tries to do anything in OnDestroy () that is related to a singleton, it invokes the singleton attribute. If the singleton has been destroyed at this moment, the object's destruction process creates a new singleton during application shutdown. This can damage our scene files because our singleton components are left behind the scenes. If this is the case, unity will make an error: "Some objects were not clean up when closing scence" This simple English I will not translate. Why some objects invoke singleton mode during the destroy phase is because the singleton uses the Observer pattern frequently. This design pattern allows other objects to register/unregister their specific tasks, similar to how unity locks callbacks, but in a less automated way. We will see an example of a global information system in the coming section. The object will be registered while the system is being built, and when it is closed, the most convenient place to do this is within the OnDestroy () method. As a result, such an object might encounter the problem, which is where the singleton pattern occurs during application shutdown. To solve this problem, we need to make a three-point change. First, we need to add an additional flag to the singleton component to keep track of its active state and disable it when appropriate. This includes the Singleton's own destruction, and the application shutdown (Onapplicationquit () is another unity-useful callback during this time):
private bool _alive = true;
void OnDestroy () {_alive = false;}
void Onapplicationquit () {_alive = false;}

Then, we should implement an external object to validate the current state of the Singleton:
public static bool IsAlive {
get {
if (__instance = = null)
return false;
return __instance._alive;
}
}

Finally, any attempt to invoke a singleton in its own OnDestroy () method must first use the IsAlive property to verify the state. As an example:
public class Somecomponent:monobehaviour {
void OnDestroy () {
if (mysingletoncomponent.isalive) {
MySingletonComponent.Instance.SomeMethod ();
}
}
}

This will ensure that no one attempts to access the instance during the destruction process. If we do not abide by this rule, we will return to the editing mode, because the singleton still exists in the scene and run an error. Ironically, after the advent of the singleton component, we used the find () method to determine whether the singleton component existed in the scene before we visited the singleton instance. Fortunately, this will only happen when the first time a singleton component is accessed. But the initialization of the singleton does not necessarily occur at the time of the initialization of the scene, so it may cause a bad performance in the course of the game when the object is instantiated and called the Find () method. The solution is one of the highest classes by simply invoking each instance to determine the initialization of a singleton when the scene is initialized. The disadvantage of this approach is that if we later decide that there are multiple management classes, we want to isolate its behavior more modular, there will be a lot of code needs to change. There are further options that we can explore, such as leveraging Unity's built-in scripting code and checking interfaces between interfaces. Another way to communicate problems between objects is to use Unity's serialization system. The purists of software design are controversial because it breaks the encapsulation; it exposes the private behavior of some variables to the public. Even if this value is only exposed to Unity's inspector, this is where special attention is needed. When we create a common variable, unity automatically serializes the value on the Inspector panel when the component is selected. However, from the point of view of software design, public variables are dangerous, and these variables can be modified at any time by code, which makes it difficult to trace the variable and there are many unexpected errors. Instead, we can define a private variable and a protection variable, and then use [Serializefield] to make it appear in the Unity Editor inspector. This method is superior to public variables because it can be better controlled in context. This way, at least we know that at runtime outside of the class (or derived classes) the variable cannot be changed by code, which guarantees the encapsulation of the script code.

So we want the variable to be seen on the panel, but at the same time we don't want other classes to access the variable, we usually use [Serializefield] to modify it.


One suggestion for inter-object communication is to implement a global messaging system where any object can send messages to a specific type of message by any object that might be of interest. object or send a message or listen for a message, it is the responsibility of the listener to find something of interest to it. The sender of the message can broadcast the message. This is a good way to keep our code modular and decoupled.

We want to send various kinds of information can take many forms, such as including data values, references, listeners ' instructions, etc., but they should have a common premise, our message system can be used to determine what the message is, what the purpose is. Here is a simple class definition for the message object:
public class Basemessage {
public string name;
Public Basemessage () {name = this. GetType (). Name; }
}

The type of the constructor for the Basemessage cached class is used in the local attribute to be written and allocated for later use. It is necessary to cache this value every time you call GetType (). The name will cause the new string to be allocated on the heap, and we want to reduce this possibility as much as possible. Our custom messages must derive from this class, which allows them to add any data they want, while still maintaining the ability to send through our information systems. Note that although the type name is obtained in the base class constructor, the Name property will still contain the name of the derived class, not the base class.

To move to our Messagingsystem class, we should use what needs to define its characteristics:
1 It should be a global access;
2 any object (whether monobehaviour or not) should be able to register/unregister the listener to receive a specific message type (i.e. observer mode);
3 The registered object should provide a method to be called when a given message is broadcast;
4 The system should send the information to all listeners within a reasonable time, but don't stop too many requests at once.

The first requirement makes the messaging system a candidate for a good singleton object, because we only need an instance of the system. Although it is advisable to think again and again before implementing a singleton. If we later decide that we want multiple instances of this object to exist, then we will be hard to refactor because of all the dependencies, and we will introduce our system incrementally as our code is used. The first requirement makes the messaging system a candidate for a good singleton object, because we only need an instance of the system. Although it is advisable to think again and again before implementing a singleton. If we later decide that we want multiple instances of this object to exist, then we will be hard to refactor because of all the dependencies, and we will introduce our system incrementally as our code is used. The principle of this message mechanism is the delegation of C #. Delegation is the root of this messaging mechanism. So it is necessary to learn the Commission well.

In some cases, we may want to broadcast a general notification message to some listener, such as the enemy hatching information. Other times, we may send a message specifically for one of the listeners in a group. For example, when an enemy is injured, it is necessary to send an "enemy's health Value change" message, so that the enemy's blood bar changes. If we implement a method that allows listeners to stop processing early, we can save a lot of processor cycles if there are many listeners waiting for the same message type.

The delegate that we define provides a way to retrieve a message through a parameter and returns a response that determines whether the processing of the message should stop when the listener finishes. About
Whether to stop processing or not to return it by a simple Boolean value, to indicate that the listener has processed the message, the processing of the message will stop.
Here is the definition of a delegate:
Public delegate bool Messagehandlerdelegate (Basemessage message);

When registering with Messagingsystem, the listener must define a method table to pass its reference. This provides an entry point when the broadcast message is broadcast.
The final requirement of our information system is that the object has some sort of timing-based mechanism to prevent it from blocking too many messages at a time. This also means that, somewhere in the process, we need to count the time with the Monobehaviour event callback during Unity's update (). This can be achieved through the static classes and the singleton that we said earlier, which will require some monobehaviour management classes to call it, notifying it that the scene has been updated. In addition, we can use singletonascomponent to do the same thing, but this is independent of any management class. The difference between the two is whether the system relies on object control. The Singletonascomponent method is probably the most because there are not too many occasions when we want to make this system independent, even though most of our game logic depends on it. For example, even if the game is paused, we do not want the game logic to pause our messaging system. We still want this message system to continue to receive and process information so that we can communicate with each other in the game in a paused state, with components related to the user interface.
By extracting the Singletonascomponent class to define our communication system and providing an object method to register it, the code is as follows:

Using System.Collections.Generic;
public class Messagingsystem:singletonascomponent<messagingsystem> {
public static Messagingsystem Instance

{
get {return ((Messagingsystem) _instance);}
set {_instance = value;}
}
Private dictionary<string,list<messagehandlerdelegate>> _listenerdict =new Dictionary<string,List <MessageHandlerDelegate>> ();
public bool Attachlistener (System.Type Type, Messagehandlerdelegatehandler)

{

if (type = = NULL)

{
Debug.Log ("Messagingsystem:attachlistener failed due to no MessageType specified");
return false;
}
String msgname = type. Name;
if (!_listenerdict.containskey (msgname))

                          {
      &N Bsp                           _listenerdict.add (Msgname, new List<messagehandlerdelegate> ());
                         }
                         list<messagehandlerdelega Te> listenerlist = _listenerdict[msgname];
                       if (Listenerlist.contains (handler))  

{
return false; Listener already in list
}
Listenerlist.add (handler);
return true;
}
}

_listenerdict is a variable that maps to a string list dictionary messagehandlerdelegates, and uses a dictionary to arrange the listener's delegate by the type of message the listener wants to hear. So, if we know what message type is being sent, then we can quickly retrieve a list of all the representatives that have been registered for that message type. Then we can iterate through the list and query each listener to see if one of them wants to handle it.

The Attachlistener () method requires two parameters, a message type in a system, and a messagehandlerdelegate when a message is sent through the system.
In order to process the message, our Messagingsystem let the incoming object keep a queue, so that we can let them play in order.

                    private queue<basemessage> _messagequeue = New Queue<basemessage> ();
                    public bool Queuemessage (Basemessage msg) {
& nbsp                        if (!_listenerdict.containskey (msg.name ) {
                              return FA Lse
                       }
                    _messagequeue.enqueue (msg);
                     return true;
                     }

This method simply checks to see if the given message type exists in our dictionary and adds it to the queue. This effectively tests whether an object really cares about the information we've heard before we queue up for it to be processed, and we've introduced a new private member variable, _messagequeue, to do this. Next, we'll add the update () definition. This method is called periodically by the Unity engine. The purpose is to traverse the current contents of the message queue, one message at a time, to verify that too much time has passed before we start processing, and if not, to pass them to the next stage.

private float maxqueueprocessingtime = 0.16667f;
void Update ()

{
float timer = 0.0f;
while (_messagequeue.count > 0)

{
if (Maxqueueprocessingtime > 0.0f)

{
if (Timer > Maxqueueprocessingtime)
Return
}
Basemessage msg = _messagequeue.dequeue ();
if (! Triggermessage (msg))
Debug.Log ("Error When processing message:" + msg.name);
if (Maxqueueprocessingtime > 0.0f)
Timer + = Time.deltatime;
}
}

Time-based maintenance is to ensure that the processing time threshold is not exceeded. If too many messages are pushed to the system too quickly, this prevents the message system from freezing our game. If the total time limit is exceeded, then all message processing stops, and all remaining messages are processed to the next frame. Finally, we need to define the Triggermessage () method to distribute messages to listeners:
public bool Triggermessage (Basemessage msg)

{
string msgname = Msg.name;
if (!_listenerdict.containskey (msgname))

{
Debug.Log ("Messagingsystem:message \" "+ Msgname +" \ "has nolisteners!");
return false; No listeners for message so ignore it
}
List<messagehandlerdelegate> listenerlist = _listenerdict[msgname];
for (int i = 0; i < Listenerlist.count; ++i)

{
if (Listenerlist[i] (msg))
return true; Message consumed by the delegate
}
return true;
}

This approach is the main load of information systems work, and the purpose of triggerevent () is also to get a list of listeners for a given message type, giving them each object an opportunity to deal with it. If one of the delegates returns true, the processing of the current message stops, and the method exits, leaving the update () method to process the next message. Typically, we are going to use the Queueevent () broadcast message, but can call Triggerevent () instead. This approach allows message senders to force the messages they want to process to be processed immediately without waiting for the next update () event. This avoids the stifling mechanism, but this can be an important message when the game is running, and if waiting for the next frame can lead to strange performance.

We have created an information system, but an example of how to use it will help us sort out the concepts in our minds. Let's first define a simple message class that we can use to transfer some data:

public class Mycustommessage:basemessage

{
public ReadOnly int _intvalue;
Public ReadOnly float _floatvalue;
Public mycustommessage (int intval, float floatval

{
_intvalue = intval;
_floatvalue = Floatval;
}
}

A good practice for message objects is to make their member variables read-only. This ensures that the data cannot be changed after the object is built. This prevents our message content from being modified during the delivery process.

Here is a simple class used to register information systems, when M Ycustomm essage objects broadcast from elsewhere into our code, requiring the Handlemycustommessage () method to tune
Use.
public class Testmessagelistener:monobehaviour

{
void Start ()

{
MessagingSystem.Instance.AttachListener (typeof (Mycustommessage),
This. Handlemycustommessage);
}
BOOL Handlemycustommessage (Basemessage msg)

{
Mycustommessage castmsg = msg as mycustommessage;
Debug.Log (String. Format ("Got the message! {0}, {1} ", Castmsg._intvalue, Castmsg._floatvalue));
return true;
}
}

Whenever the Mycustommessage object is broadcast (regardless of where it is from), the listener retrieves the message through the Handlemycustommessage () method. It can be converted to the corresponding derivative information type and processed in its own unique way. Other classes can register the same message through its own custom delegate method (assuming that an older object does not return its own delegate) to handle it. We know what kind of news will be provided through the Handlemycustommessage () method, as we define the time passed to a

unity-Script optimization-Message processing system

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.