【Unity游戏开发】用C#和Lua实现Unity中的事件分发机制EventDispatcher

一、简介

  最近马三换了一家大公司工作,公司制度规范了一些,因此平时的业余时间多了不少。但是人却懒了下来,最近这一个月都没怎么研究新技术,博客写得也是拖拖拉拉,周六周天就躺尸在家看帖子、看小说,要么就是吃鸡,唉!真是罪过罪过。希望能从这篇博客开始有些改善吧,尽量少玩耍,还是多学习吧~

  好了扯得有点远了,来说说我们今天博客的主题——“用C#和Lua实现Unity中的事件分发机制”,事件分发机制或者叫事件监听派发系统,在每个游戏框架中都是不可或缺的一个模块。我们可以用它来解耦,监听网络消息,或者做一些异步的操作,好处多多(其实是别人的框架都有这个,所以我们的框架也必须有这玩意~)。今天马三就和大家一起,分别使用C#和Lua实现两种可以用在Unity游戏开发中的事件分发处理机制,希望能对大家有些帮助吧~

二、C#版的事件分发机制

  首先我们来实现C#版本的事件分发机制,目前这套流程已经集成到了马三自己的 ColaFrameWork框架 中了。这套框架还在架构阶段,里面很多东西都不完善,马三也是会随时把自己的一些想法放到里面,大家感兴趣的话也可以帮忙维护一下哈!

  一般来说事件订阅、派发这种机制都是使用观察者模式来实现的,本篇博客也不例外,正是利用了这种思想。为了解耦和面向接口编程,我们制定了一个接口IEventHandler,凡是观察者都需要实现这个接口,而GameEventMgr事件中心维护了一个IEventHandler列表,保存着一系列的观察者,并在需要的时候进行一系列的动作。这样操作正是遵循了依赖倒置的设计原则:“高层模块不应该依赖于低层模块,两者都应该依赖于抽象概念;”、“抽象接口不应该依赖于实现,而实现应该依赖于抽象接口”。下面的代码定义了IEventHandler接口和一些委托还有事件传递时需要携带的参数。

 1 using System.Collections;
 2 using System.Collections.Generic;
 3 using EventType = ColaFrame.EventType;
 4
 5 /// <summary>
 6 /// 接收消息后触发的回调
 7 /// </summary>
 8 /// <param name="data"></param>
 9 public delegate void MsgHandler(EventData data);
10
11 /// <summary>
12 /// 事件处理器的接口
13 /// </summary>
14 public interface IEventHandler
15 {
16     bool HandleMessage(GameEvent evt);
17
18     bool IsHasHandler(GameEvent evt);
19 }
20
21 /// <summary>
22 /// 事件消息传递的数据
23 /// </summary>
24 public class EventData
25 {
26     public string Cmd;
27     public List<object> ParaList;
28 }
29
30 /// <summary>
31 /// 游戏中的事件
32 /// </summary>
33 public class GameEvent
34 {
35     /// <summary>
36     /// 事件类型
37     /// </summary>
38     public EventType EventType { get; set; }
39     /// <summary>
40     /// 携带参数
41     /// </summary>
42     public object Para { get; set; }
43 }
44
45
46 namespace ColaFrame
47 {
48     /// <summary>
49     /// 事件的类型
50     /// </summary>
51     public enum EventType : byte
52     {
53         /// <summary>
54         /// 系统的消息
55         /// </summary>
56         SystemMsg = 0,
57         /// <summary>
58         /// 来自服务器推送的消息
59         /// </summary>
60         ServerMsg = 1,
61         /// <summary>
62         /// UI界面消息
63         /// </summary>
64         UIMsg = 2,
65     }
66 }

  然后我们再来看一下最核心的事件中心处理器GameEventMgr是如何实现的,还是先上一下全部的代码 GameEventMgr.cs:

  1 using System;
  2 using System.Collections;
  3 using System.Collections.Generic;
  4 using UnityEngine;
  5 using EventType = ColaFrame.EventType;
  6
  7 /// <summary>
  8 /// 事件消息管理中心
  9 /// </summary>
 10 public class GameEventMgr
 11 {
 12     /// <summary>
 13     /// 存储Hander的字典
 14     /// </summary>
 15     private Dictionary<int, List<IEventHandler>> handlerDic;
 16
 17     private static GameEventMgr instance = null;
 18
 19     private GameEventMgr()
 20     {
 21         handlerDic = new Dictionary<int, List<IEventHandler>>();
 22     }
 23
 24     public static GameEventMgr GetInstance()
 25     {
 26         if (null == instance)
 27         {
 28             instance = new GameEventMgr();
 29         }
 30         return instance;
 31     }
 32
 33     /// <summary>
 34     /// 对外提供的注册监听的接口
 35     /// </summary>
 36     /// <param name="handler"></param>监听者(处理回调)
 37     /// <param name="eventTypes"></param>想要监听的事件类型
 38     public void RegisterHandler(IEventHandler handler, params EventType[] eventTypes)
 39     {
 40         for (int i = 0; i < eventTypes.Length; i++)
 41         {
 42             RegisterHandler(eventTypes[i], handler);
 43         }
 44     }
 45
 46     /// <summary>
 47     /// 内部实际调用的注册监听的方法
 48     /// </summary>
 49     /// <param name="type"></param>要监听的事件类型
 50     /// <param name="handler"></param>监听者(处理回调)
 51     private void RegisterHandler(EventType type, IEventHandler handler)
 52     {
 53         if (null != handler)
 54         {
 55             if (!handlerDic.ContainsKey((int)type))
 56             {
 57                 handlerDic.Add((int)type, new List<IEventHandler>());
 58             }
 59             if (!handlerDic[(int)type].Contains(handler))
 60             {
 61                 handlerDic[(int)type].Add(handler);
 62             }
 63         }
 64     }
 65
 66     /// <summary>
 67     /// 反注册事件监听的接口,对所有类型的事件移除指定的监听
 68     /// </summary>
 69     /// <param name="handler"></param>
 70     public void UnRegisterHandler(IEventHandler handler)
 71     {
 72         using (var enumerator = handlerDic.GetEnumerator())
 73         {
 74             List<IEventHandler> list;
 75             while (enumerator.MoveNext())
 76             {
 77                 list = enumerator.Current.Value;
 78                 list.Remove(handler);
 79             }
 80         }
 81     }
 82
 83     /// <summary>
 84     /// 反注册事件监听的接口,移除指定类型事件的监听
 85     /// </summary>
 86     /// <param name="handler"></param>
 87     /// <param name="types"></param>
 88     public void UnRegisterHandler(IEventHandler handler, params EventType[] types)
 89     {
 90         EventType type;
 91         for (int i = 0; i < types.Length; i++)
 92         {
 93             type = types[i];
 94             if (handlerDic.ContainsKey((int)type) && handlerDic[(int)type].Contains(handler))
 95             {
 96                 handlerDic[(int)type].Remove(handler);
 97             }
 98         }
 99     }
100
101     /// <summary>
102     /// 分发事件
103     /// </summary>
104     /// <param name="gameEvent"></param>想要分发的事件
105     public void DispatchEvent(GameEvent gameEvent)
106     {
107         bool eventHandle = false;
108
109         List<IEventHandler> handlers;
110         if (null != gameEvent && handlerDic.TryGetValue((int)gameEvent.EventType, out handlers))
111         {
112             for (int i = 0; i < handlers.Count; i++)
113             {
114                 try
115                 {
116                     eventHandle = handlers[i].HandleMessage(gameEvent) || eventHandle;
117                 }
118                 catch (Exception e)
119                 {
120                     Debug.LogError(e);
121                 }
122             }
123
124             if (!eventHandle)
125             {
126                 if (null != gameEvent)
127                 {
128                     switch (gameEvent.EventType)
129                     {
130                         case EventType.ServerMsg:
131                             break;
132                         default:
133                             Debug.LogError("消息未处理,类型:" + gameEvent.EventType);
134                             break;
135                     }
136                 }
137             }
138         }
139     }
140
141     /// <summary>
142     /// 分发事件
143     /// </summary>
144     /// <param name="evt"></param>分发的消息名称
145     /// <param name="eventType"></param>消息事件类型
146     /// <param name="para"></param>参数
147     public void DispatchEvent(string evt, EventType eventType = EventType.UIMsg, params object[] para)
148     {
149         GameEvent gameEvent = new GameEvent();
150         gameEvent.EventType = eventType;
151         EventData eventData = new EventData();
152         eventData.Cmd = evt;
153         if (null != para)
154         {
155             eventData.ParaList = new List<object>();
156             for (int i = 0; i < para.Length; i++)
157             {
158                 eventData.ParaList.Add(para[i]);
159             }
160         }
161         gameEvent.Para = eventData;
162
163         this.DispatchEvent(gameEvent);
164     }
165
166     /// <summary>
167     /// 分发事件
168     /// </summary>
169     /// <param name="evt"></param>分发的消息名称
170     /// <param name="eventType"></param>消息事件类型
171     public void DispatchEvent(string evt, EventType eventType = EventType.UIMsg)
172     {
173         GameEvent gameEvent = new GameEvent();
174         gameEvent.EventType = eventType;
175         EventData eventData = new EventData();
176         eventData.Cmd = evt;
177         eventData.ParaList = null;
178         gameEvent.Para = eventData;
179
180         this.DispatchEvent(gameEvent);
181     }
182 }

我们在其内部维护了一个handlerDic字典,它的key是int类型的,对应的其实就是我们在上面定义的EventType 这个枚举,它的value是一个元素为IEventHandler类型的列表,也就是说我们按照不同的事件类型,将监听者分为了几类进行处理。监听者是可以监听多个消息类型的,也就是说一个监听者实例可以存在于多个列表中,这样并不会产生冲突。我们就从RegisterHandler(IEventHandler handler, params EventType[] eventType)这个对外提供的注册监听的接口入手,逐步的分析一下它的工作流程:

  1. 调用RegisterHandler方法,传入监听者和需要监听的事件类型(可以是数组,支持多个事件类型),然后遍历事件类型,依次调用RegisterHandler(EventType type, IEventHandler handler)接口,将监听者逐个的注册到每个事件类型对应的监听者列表中;
  2. 当需要分发事件的时候,调用DispatchEvent方法,传入一个GameEvent类型的参数gameEvent,它包含了需要派发的事件属于什么类型,和对应的事件消息需要传递的参数,其中这个参数又包含了字符串具体的事件名称和一个参数列表;
  3. 在DispatchEvent中,会根据事件类型来判断内部字段中是否有注册了该事件的监听者,如果有就取到存有这个监听者的列表;
  4. 然后依次遍历每个监听者,调用其HandleMessage方法,进行具体消息的处理,该函数还会返回一个bool值,表示是否处理了该消息。如果遍历了所有的监听者以后,发现没有处理该消息的监听者,则会打印一个错误消息进行提示;
  5. DispatchEvent(string evt, EventType eventType = EventType.UIMsg, params object[] para)和DispatchEvent(string evt, EventType eventType = EventType.UIMsg)这两个接口是对DispatchEvent接口的进一步封装,方便用户进行无参消息派发和含参数消息派发;

最后我们再来看一下具体的监听者应该如何实现IEventHandler接口,以 ColaFrameWork框架 中的UI基类——UIBase举例,在UIBase内部维护了一个Dictionary<string, MsgHandler> msgHanderDic 结构,用它来保存具体的事件名称对应的回调函数,然后再去具体地实现HandleMessage和IsHasHandler接口中的抽象方法,代码如下:

 1 /// <summary>
 2     /// 处理消息的函数的实现
 3     /// </summary>
 4     /// <param name="gameEvent"></param>事件
 5     /// <returns></returns>是否处理成功
 6     public bool HandleMessage(GameEvent evt)
 7     {
 8         bool handled = false;
 9         if (EventType.UIMsg == evt.EventType)
10         {
11             if (null != msgHanderDic)
12             {
13                 EventData eventData = evt.Para as EventData;
14                 if (null != eventData && msgHanderDic.ContainsKey(eventData.Cmd))
15                 {
16                     msgHanderDic[eventData.Cmd](eventData);
17                     handled = true;
18                 }
19             }
20         }
21         return handled;
22     }
23
24     /// <summary>
25     /// 是否处理了该消息的函数的实现
26     /// </summary>
27     /// <returns></returns>是否处理
28     public bool IsHasHandler(GameEvent evt)
29     {
30         bool handled = false;
31         if (EventType.UIMsg == evt.EventType)
32         {
33             if (null != msgHanderDic)
34             {
35                 EventData eventData = evt.Para as EventData;
36                 if (null != eventData && msgHanderDic.ContainsKey(eventData.Cmd))
37                 {
38                     handled = true;
39                 }
40             }
41         }
42         return handled;
43     }

为了使用更加简洁方便,我们还可以再封装一些函数出来,以便随时注册一个消息和取消注册一个消息,主要是RegisterEvent和UnRegisterEvent接口,代码如下:

 1 /// <summary>
 2     /// 初始化注册消息监听
 3     /// </summary>
 4     protected void InitRegisterHandler()
 5     {
 6         msgHanderDic = null;
 7         GameEventMgr.GetInstance().RegisterHandler(this, EventType.UIMsg);
 8         msgHanderDic = new Dictionary<string, MsgHandler>()
 9         {
10         };
11     }
12
13     /// <summary>
14     /// 取消注册该UI监听的所有消息
15     /// </summary>
16     protected void UnRegisterHandler()
17     {
18         GameEventMgr.GetInstance().UnRegisterHandler(this);
19
20         if (null != msgHanderDic)
21         {
22             msgHanderDic.Clear();
23             msgHanderDic = null;
24         }
25     }
26
27     /// <summary>
28     /// 注册一个UI界面上的消息
29     /// </summary>
30     /// <param name="evt"></param>
31     /// <param name="msgHandler"></param>
32     public void RegisterEvent(string evt, MsgHandler msgHandler)
33     {
34         if (null != msgHandler && null != msgHanderDic)
35         {
36             if (!msgHanderDic.ContainsKey(evt))
37             {
38                 msgHanderDic.Add(Name + evt, msgHandler);
39             }
40             else
41             {
42                 Debug.LogWarning(string.Format("消息{0}重复注册!", evt));
43             }
44         }
45     }
46
47     /// <summary>
48     /// 取消注册一个UI界面上的消息
49     /// </summary>
50     /// <param name="evt"></param>
51     public void UnRegisterEvent(string evt)
52     {
53         if (null != msgHanderDic)
54         {
55             msgHanderDic.Remove(Name + evt);
56         }
57     }

关于C#版的事件分发机制大概就介绍到这里了,马三在这里只是大概地讲了下思路,更细致的原理和使用方法大家可以去马三的 ColaFrameWork框架 中找一下相关代码。

三、Lua版的事件分发机制

  Lua版本的事件分发机制相对C#版的来说就简单了很多,Lua中没有接口的概念,因此实现方式和C#版的也大有不同,不过总的来说还是对外暴露出以下几个接口:

  • Instance():获取单例
  • RegisterEvent():注册一个事件
  • UnRegisterEvent():反注册一个事件
  • DispatchEvent():派发事件
  • AddEventListener():增加监听者
  • RemoveEventListener():移除监听者

  照例还是先上一下核心代码EventMgr.lua,然后再逐步解释:

 1 require("Class")
 2 local bit = require "bit"
 3
 4 EventMgr = {
 5     --实例对象
 6     _instance = nil,
 7     --观察者列表
 8     _listeners = nil
 9 }
10 EventMgr.__index = EventMgr
11 setmetatable(EventMgr, Class)
12
13 -- 构造器
14 function EventMgr:new()
15     local t = {}
16     t = Class:new()
17     setmetatable(t, EventMgr)
18     return t
19 end
20
21 -- 获取单例接口
22 function EventMgr:Instance()
23     if EventMgr._instance == nil then
24         EventMgr._instance = EventMgr:new()
25         EventMgr._listeners = {}
26     end
27     return EventMgr._instance
28 end
29
30 function EventMgr:RegisterEvent(moduleId, eventId, func)
31     local key = bit.lshift(moduleId, 16) + eventId
32     self:AddEventListener(key, func, nil)
33 end
34
35 function EventMgr:UnRegisterEvent(moduleId, eventId, func)
36     local key = bit.lshift(moduleId, 16) + eventId
37     self:RemoveEventListener(key, func)
38 end
39
40 function EventMgr:DispatchEvent(moduleId, eventId, param)
41     local key = bit.lshift(moduleId, 16) + eventId
42     local listeners = self._listeners[key]
43     if nil == listeners then
44         return
45     end
46     for _, v in ipairs(listeners) do
47         if v.p then
48             v.f(v.p, param)
49         else
50             v.f(param)
51         end
52     end
53 end
54
55 function EventMgr:AddEventListener(eventId, func, param)
56     local listeners = self._listeners[eventId]
57     -- 获取key对应的监听者列表,结构为{func,para},如果没有就新建
58     if listeners == nil then
59         listeners = {}
60         self._listeners[eventId] = listeners -- 保存监听者
61     end
62     --过滤掉已经注册过的消息,防止重复注册
63     for _, v in pairs(listeners) do
64         if (v and v.f == func) then
65             return
66         end
67     end
68     --if func == nil then
69     --    print("func is nil!")
70     --end
71     --加入监听者的回调和参数
72     table.insert(listeners, { f = func, p = param })
73 end
74
75 function EventMgr:RemoveEventListener(eventId, func)
76     local listeners = self._listeners[eventId]
77     if nil == listeners then
78         return
79     end
80     for k, v in pairs(listeners) do
81         if (v and v.f == func) then
82             table.remove(listeners, k)
83             return
84         end
85     end
86 end

  在实际使用的时候主要是调用 RegisterEvent、UnRegisterEvent 和 DispatchEvent这三个接口。RegisterEvent用来注册一个事件,UnRegisterEvent 用来反注册一个事件,DispatchEvent用来派发事件。先从RegisterEvent接口说起,它需要传入3个参数,分别是ModuleId,EventId和回调函数func。ModuleId就是我们不同模块的id,他是一个模块的唯一标识,在实际应用中我们可以定义一个全局的枚举来标识这些模块ID。EventId是不同的消息的标识,它也是数字类型的枚举值,并且因为有了模块ID的存在,不同模块可以使用相同的EventId,这并不会导致消息的冲突。在RegisterEvent内部操作中,我们首先对ModuleId进行了左移16位的操作,然后再加上EventID组成我们的消息key,左移16位可以避免ModuleID直接与EventId组合后会产生Key冲突的问题,一般来说左移16位已经可以满足定义很多模块和事件id的需求了。然后调用 self:AddEventListener(key, func, nil) 方法,将计算出来的key和回调函数进行注册。在EventMgr的内部其实还是维护了一个监听者列表,注册消息的时候,就是把回调和参数添加到监听者列表中。反注册消息就是把对应key的回调从监听者列表中移除。派发事件的时候就是遍历key所对应的监听者列表,然后依次执行里面的回调函数。好了,接着说AddEventListener这个函数的操作,它首先会去获取key对应的监听者列表,结构为{func,para},如果没有就新建一个table,并把它保存为key所对应的监听者列表。得到这个监听者列表以后,我们首先会对其进行遍历,如果里面已经包含func回调函数的话,就直接return掉,过滤掉已经注册过的消息,防止重复注册。如果通过了上一步检查的话,就执行 table.insert(listeners, { f = func, p = param })操作,加入监听者的回调和参数。对于UnRegisterEvent方法,我们依然会计算出key,然后调用 RemoveEventListener 操作,把监听者从监听者列表中移除。在使用DispatchEvent接口进行事件派发的时候,我们依然会先计算出Key,然后取出key对应的监听者列表。接着依次遍历这些监听者,然后执行其中保存着的回调函数,并且把需要传递的事件参数传递进去。具体的使用方法,可以参考下面的Main.lua:

 1 require("EventMgr")
 2
 3 local function TestCallback_1()
 4     print("Callback_1")
 5 end
 6
 7 local function TestCallback_2(param)
 8     print("Callback_2")
 9     print(param.id)
10     print(param.pwd)
11 end
12
13 local EventMgr = EventMgr:Instance()
14 EventMgr:RegisterEvent(1, 1, TestCallback_1)
15 EventMgr:RegisterEvent(2, 1, TestCallback_2)
16 EventMgr:DispatchEvent(1, 1)
17 EventMgr:DispatchEvent(2, 1, { id = "abc", pwd = "123" })

支持含参数事件分发和无参数事件分发,上面代码的执行结果如下,可以发现成功地监听了注册的消息,并且也获取到了传递过来的参数:

 图1:代码执行结果

四、总结

通过本篇博客,马三和大家一起学习了如何在Unity中使用C#和Lua分别实现事件分发机制,希望本篇博客能为大家的工作过程中带来一些帮助与启发。

本篇博客中的样例工程已经同步至Github:https://github.com/XINCGer/Unity3DTraining/tree/master/lua/LuaEventMgr,欢迎大家Fork!

马三的开源Unity客户端框架 ColaFramework框架:https://github.com/XINCGer/ColaFrameWork

如果觉得本篇博客对您有帮助,可以扫码小小地鼓励下马三,马三会写出更多的好文章,支持微信和支付宝哟!

       

作者:马三小伙儿
出处:https://www.cnblogs.com/msxh/p/9539231.html 
请尊重别人的劳动成果,让分享成为一种美德,欢迎转载。另外,文章在表述和代码方面如有不妥之处,欢迎批评指正。留下你的脚印,欢迎评论!

原文地址:https://www.cnblogs.com/msxh/p/9539231.html

时间: 2024-10-13 11:12:45

【Unity游戏开发】用C#和Lua实现Unity中的事件分发机制EventDispatcher的相关文章

Android 开发艺术探究V第三章之view的事件分发机制

在介绍点击事件的传递机制,首先我们要分析的对象就是MOtionEvent,即点击事件,(当点击屏幕时由硬件传递过来,关于MotionEvent在View的基础知识中做了介绍),所谓的点击事件的分发就是MotionEvent的分发过程.即当一个MoTionEvent产生以后,系统需要把这个事件具体传递给一个具体的View,而这个传递过程就是分发过程,点击事件传递过程有三个很重要的方法,下面先来介绍这几个方法.  public boolean dispatchTouchEvent(MOtionEve

C# Unity游戏开发——Excel中的数据是如何到游戏中的 (二)

本帖是延续的:C# Unity游戏开发——Excel中的数据是如何到游戏中的 (一) 上个帖子主要是讲了如何读取Excel,本帖主要是讲述读取的Excel数据是如何序列化成二进制的,考虑到现在在手游中应用很广泛的序列化技术Google的ProtoBuf,所以本文也是按照ProtoBuf的方式来操作的.ProtoBuf是一个开源库,简单来说ProtoBuf就是一个能使序列化的数据变得更小的类库,当然这里指的更小是相对的.好了ProtBuf的东西就不在多说,以后会专门写一篇帖子的.本帖其实就相当于上

C# Unity游戏开发——Excel中的数据是如何到游戏中的 (三)

本帖是延续的:C# Unity游戏开发——Excel中的数据是如何到游戏中的 (二) 前几天有点事情所以没有继续更新,今天我们接着说.上个帖子中我们看到已经把Excel数据生成了.bin的文件,不过其实这样到游戏中还是不能用的.主要有两个方面,1.bin文件的后缀使我们随便取的名字,但是这种文件Unity不买账.因为Unity中的二进制文件必须是以.bytes命名的.2.在写文件之前其实还可以对二进制进行压缩,这样可以最大化节省设备空间.也就是说我们在生成数据实例后还需要做以下几件事:序列化 -

Unity 游戏开发技巧集锦之使用cookie类型的纹理模拟云层的移动

Unity 游戏开发技巧集锦之使用cookie类型的纹理模拟云层的移动 使用cookie类型的纹理模拟云层的移动 现实生活中,当阳光直射大地,而天空中又有很多云时,云层的影子总是会投射在大地上,风吹着云层移动,影子也跟着运动,如图3-28所示. 图3-28  天空中的云朵与大地上的影子 要在游戏中,模拟与之类似的大气现象时,就需要使用cookie类型的纹理. 制作云层效果的纹理 本小节将使用PhotoShop绘制有云层效果的纹理图,然后为其添加透明度信息.具体操作过程如下: (1)使用Photo

Unity 游戏开发技巧集锦之创建透明的材质

Unity 游戏开发技巧集锦之创建透明的材质 Unity创建透明的材质 生活中不乏透明或者半透明的事物.例如,擦的十分干净的玻璃,看起来就是透明的:一些塑料卡片,看起来就是半透明的,如图3-23所示.在Unity中,可以创建模拟了透明效果的材质,这也是本节主要讲解的内容. 图3-23  半透明的卡片 Unity创建并配置材质 在Project视图里,创建一个材质,并命名为TransMaterial,选中它然后在Inspector视图里修改Shader属性为Transparent/Diffuse,

?Unity 游戏开发技巧集锦之使用忍者飞镖创建粒子效果

Unity 游戏开发技巧集锦之使用忍者飞镖创建粒子效果 使用忍者飞镖创建粒子效果 游戏中,诸如烟.火.水滴.落叶等粒子效果,都可以使用粒子系统(particle system)来实现.例如,<明朝传奇>中的篝火,如图2-32所示.粒子系统的最新版本也被称做忍者飞镖(Shuriken),因为场景中添加的粒子系统酷似忍者飞镖,如图2-33所示. 图2-32  游戏中的篝火               图2-33  粒子系统,也被称为忍者飞镖 unity中粒子基本属性 在使用粒子系统前,先了解一下它

C#开发Unity游戏教程循环遍历做出判断及Unity游戏示例

C#开发Unity游戏教程循环遍历做出判断及Unity游戏示例 Unity中循环遍历每个数据,并做出判断 很多时候,游戏在玩家做出判断以后,游戏程序会遍历玩家身上大量的所需数据,然后做出判断,即首先判定玩家是否有权限做出这种选择,然后才决定是否为玩家执行此选择.例如,<仙剑奇侠传>这款游戏,进入剧情"荷叶迷宫时",会要求玩家击碎迷宫里的5尊雕塑,如图5-12所示.但是击碎的前提是,玩家身上必须携带有"锤子".也就是说系统会遍历玩家身上所有的资源,如果有锤

unity游戏开发之自定义事件测试demo

上文中写了unity游戏开发自定义消息事件点击打开链接 下面是测试demo 第一,打卡unity,新建一个场景,然后新建一个空的游戏对象,如图中的EventObj 第二步,测试代码EventTest.as,直接拖拽给上面的空游戏对象EentObj 测试代码如下: using UnityEngine; using System.Collections; public class EventTest : MonoBehaviour { // Use this for initialization v

Unity 游戏开发技巧集锦之制作一个望远镜与查看器摄像机

Unity 游戏开发技巧集锦之制作一个望远镜与查看器摄像机 Unity中制作一个望远镜 本节制作的望远镜,在鼠标左键按下时,看到的视图会变大:当不再按下的时候,会慢慢缩小成原来的视图.游戏中时常出现的狙击手就是使用望远镜的一个例子,如图2-22所示.   图2-22  游戏中狙击手所看到的视图 制作望远镜的过程如下: (1)在Project视图里,创建一个C#脚本文件,命名为TelescopicView.打开这个脚本文件,并在里面添加下面的代码: 01     using UnityEngine