溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Python實現(xiàn)Event回調機制的方法

發(fā)布時間:2020-08-27 05:43:59 來源:腳本之家 閱讀:178 作者:tab_space 欄目:開發(fā)技術

0.背景

在游戲的UI中,往往會出現(xiàn)這樣的情況:

在某個戰(zhàn)斗副本中獲得了某個道具A,那么當進入主界面的時候,你會看到你的背包UI上有個小紅點(意思是有新道具),點擊進入背包后,發(fā)現(xiàn)新增了道具A,顯示個數(shù)為1,并且在下個界面中有個使用的按鈕由灰色不可使用變成橙色的可使用狀態(tài)

Python實現(xiàn)Event回調機制的方法

圖1. 事件觸發(fā)說明圖

其中這里是由道具獲得這個事件,觸發(fā)了上述的三個行為。如果使用顯示調用行為,會使得代碼難擴展,易出錯,邏輯混亂等問題,如果使用Event回調機制,就會變得十分方便。

其實Event回調機制就是觀察者模式,如下圖:

Python實現(xiàn)Event回調機制的方法

圖2. 觀察者模式

在C#中存在(delegate & event)的語義來實現(xiàn)Event回調機制:具體使用如下:

public delegate void NewToolGotEvent();

public class ToolBag
{
 event NewToolGotEvent newToolGotHandler;

 void Start()
 {
  newToolGotHandler += renderRedPoint;
  newToolGotHandler += renderNewTool;
  newToolGotHandler += renderAvaliableUseBtn;
 }

 void renderRedPoint()
 {
  //TODO
 }

 void renderNewTool()
 {
  //TODO
 }

 void renderAvaliableUseBtn()
 {
  //TODO
 }

 void EventHappened()
 {
  newToolGotHandler(); // usage, fill args if necessary
 }
}

如果在Python,可以在注冊事件的回調時,帶入一個參數(shù)callback,在注冊函數(shù)實體內,存在一個list將callback添加進去,形如:

def register_callback(self, cb):
 self.callbacks.append(cb)

但是這樣是一個最為普遍的做法,既然是Python,這里我們有更Pythonic的做法,而且相比于上述的觀察者模式,它的做法更加簡潔,使用更加方便,接下來我們來解析一下Python實現(xiàn)Event callback的步驟。

1. UML類圖

上述案例中,是針對游戲客戶端UI的案例。所以我們呈現(xiàn)出的UML圖也是與UI相關。如圖3所示,它顯示了Python中實現(xiàn)Event回調的機制。

Python實現(xiàn)Event回調機制的方法

圖3. UML關系圖

如上圖所示,此機制主要由三個類及他們的實例(instance)組成:UIBase, UIScene, UIDataEvent。

1 . UIBase: 所有UIScene的基類,其實例有scene_id變量,包含兩個必要的方法, __init__ 是初始化方法,init_data_listeners方法是將實例中的某些方法, 例如ui_updata_func中包含的UIDataEvent實例(所有的UIDataEvent實例都是單例)遍歷,并把ui_update_func注冊在每一個UIDataEvent實例中。

2 . UIScene: 場景類,管理某個場景的UI渲染。在其實例中,存在某些方法,例如ui_update_func需要在某些UIDataEvent實例觸發(fā)時候,也被同時觸發(fā)調用。ui_update_func在Python中一個bound method object, 它會擁有一個特殊的屬性events,即所有需要觸發(fā)此方法的UIDataEvent實例集合。這個通過裝飾器(decorator)來實現(xiàn),即圖中的:

“ui_update_func” is a Python object which add a amount of UIDataEvent instances by Python decorator named “data_listener”

3 . UIDataEvent: 事件類,該類有個類變量_events, 記錄了所有的UIDataEvent實例,每一個UIDataEvent實例都是單例,而且都有一個名字,和一個回調方法集合_callbacks, 里面的每一個方法都是在本事件觸發(fā)后需要回調的方法。實例還有個__iadd__方法,將需要回調的函數(shù)cb注冊進去。__call__事件觸發(fā)是實際觸發(fā)的函數(shù)。

2. 代碼

上一步講述了三個類之間的聯(lián)系與各自的作用,此步展示代碼實現(xiàn)相關功能。

a) UIBase.py

首先列出來的是UIBase的類,除了上述的__init__與init_data_listeners方法,還多了destroy方法

# -*- coding: utf-8 -*-
from UIDataNotifier import UIDataEvent
import inspect

class UIBase(object):

 def __init__(self, in_scene_id):
  self.id = in_scene_id
  self.init_data_listeners()

 def init_data_listeners(self):
  """為所有標有@data_listener的成員函數(shù)注冊事件監(jiān)聽器"""
  for listener_name, listener in inspect.getmembers(self, lambda f: hasattr(f, 'events')):
   for event in listener.events:
    event += listener

 def destroy(self):
  print '%s.destroy' % self.__class__.__name__
  UIDataEvent.clear()

init_data_listener比較難理解,我們看一下built-in的inspect.getmembers的源碼:

def getmembers(object, predicate=None):
 """Return all members of an object as (name, value) pairs sorted by name.
 Optionally, only return members that satisfy a given predicate."""
 results = []
 for key in dir(object):
  try:
   value = getattr(object, key)
  except AttributeError:
   continue
  if not predicate or predicate(value):
   results.append((key, value))
 results.sort()
 return results

其實源碼的意思就是,在dir(object)的value中找,找到能夠滿足predicate(value) == True的value,然后將(key, value)收集,進行排序后返回。

放在代碼的意思是:

  for listener_name, listener in inspect.getmembers(self, lambda f: hasattr(f, 'events')):
   for event in listener.events:
    event += listener

在dir(scene)中找,找到value中存在名叫events的屬性, 返回得到是一個list,每個list的元素是一個二元tuple: (key, value),其中key,即listener_name是dir(scene)的屬性名,而value, 即listener就是屬性對象,這里其實就是包含事件的函數(shù)對象,然后遍歷listener中的每一個UIDataEvent實例,并將listener注冊到event中(+= ==> __iadd__ )

b) UIScene.py

UIScene的代碼如下:

# -*- coding: utf-8 -*-
from UIDataNotifier import *
from UIBase import UIBase

class UIScene(UIBase):

 def __init__(self, in_scene_id):
  super(UIScene, self).__init__(in_scene_id)

 @data_listener(OnItemAdded)
 def ui_render_red_point(self, item):
  print 'ui_render_red_point'

 @data_listener(OnItemAdded)
 def ui_render_new_tool(self, item):
  print 'ui_render_new_tool: ' + item

 @data_listener(OnItemAdded)
 def ui_render_avaliable_use_btn(self, item):
  print 'ui_render_avaliable_use_btn'

bag_ui_scene = UIScene(123)

在UIScene中只是要填寫對于OnItemAdded這個事件觸發(fā)之后,需要回調的函數(shù),上述代碼中寫了三個函數(shù)。注意需要在函數(shù)上加上裝飾器@data_listener(OnItemAdded),這樣此函數(shù)就會添加一個特殊的屬性events,具體裝飾器的代碼見UIDataNotifier.py。

最后新建一個bag_ui_scene的scene。

c) UIDataNotifier.py

UIDataNotifier.py代碼如下:

# -*- coding: utf-8 -*-
import sys

def data_listener(*events):
 def wrapped_f(f):
  f.events = events
  return f
 return wrapped_f

class UIDataEvent(object):
 _events = []
 def __init__(self, name):
  self._name = name
  self._callbacks = []
  UIDataEvent._events.append(self)

 def __iadd__(self, cb):
  self._callbacks.append(cb)
  return self

 def __call__(self, *args, **kwargs):
  for cb in self._callbacks:
   try:
    cb(*args, **kwargs)
   except:
    ex = sys.exc_info()
    print "UIDataNotifier cb error, function:", cb.__name__, ex

 def __repr__(self):
  return 'UIDataEvent %s' % self._name

 @classmethod
 def clear(cls):
  """清空所有事件上的所有監(jiān)聽器,在銷毀一個界面的時候調用"""
  for event in cls._events:
   event._cb = []

OnItemAdded = UIDataEvent('OnItemAdded')

data_listener裝飾器其實就是聲明一個特殊的events屬性,并將所有在UIScene中填寫的UIDataEvent實例元組集合賦值給它。

__iadd__是將參數(shù)cb添加到實例的變量中_callbacks中,此方法在UIBase的init_data_listeners中使用。

__call__是當UIDataEvent實例自調用時,例如OnItemAdded(item),實際調用的函數(shù),在函數(shù)體里,會回調_callbacks中的每個方法,這也就是Event回調機制的核心部分,相當于觀察者模式的notify方法

最后新建一個OnItemAdded事件。

c) client.py

創(chuàng)建上述幾個類之后,使用Event回調就非常簡單了,代碼如下:

# -*- coding: utf-8 -*-
from UIScene import UIScene
from UIDataNotifier import *

OnItemAdded('liu_xin_biao') #新道具流星鏢獲得事件發(fā)生了

輸出:

ui_render_avaliable_use_btn
ui_render_new_tool: liu_xin_biao
ui_render_red_point

3.使用方法

1. 在本模塊內增加一個事件定義,并在注釋中寫明事件的參數(shù)及意義。

如果要監(jiān)聽一個事件,請仔細閱讀相關注釋。

2. 在ui類最頂端import需要的事件及data_listener。

3. 在需要響應該事件的方法(監(jiān)聽器方法)前增加裝飾器@data_listener,參數(shù)內列出要監(jiān)聽的所有事件。

如:

@data_listener(OnEventA, OnEventB)
def my_listener_method(arg1):
 ...

注意保持監(jiān)聽器方法的參數(shù)個數(shù)及意義與事件觸發(fā)的地方一致。

4. 在邏輯代碼中適當?shù)奈恢脤κ录M行觸發(fā)。如OnEventA(arg1, ...)

注意:并不是所有與UI的交互都必須使用事件,事件機制是為了方便多對多的交互。比如背包物品改變事件,有多個UI都會監(jiān)聽背包物品的變化,而有多種邏輯都會導致背包物品變化,這時使用事件就比較方便。

4. 總結

本文主要講述了如何使用Python實現(xiàn)Event回調機制,上述的示例代碼參考我的[github-EventCallBack] (https://github.com/csdz/SnapToSnap/tree/master/EventCallBack)。

以上這篇Python實現(xiàn)Event回調機制的方法就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支持億速云。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內容。

AI