Ca ne fait pas si longtemps que Jim Bauwens nous a agréablement surpris avec une façon élégante de personnaliser l’objet « gc » avec vos propres fonctions. Mais il frappe encore !
En effet, il a trouvé/créé un nouveau moyen, plus intelligent qu’avant, de créer un Screen Manager. Si vous n’êtes pas familier avec ce superbe concept, allez-donc vous renseigner 😉
Pour ceux qui le sont, par contre, vous devez savoir qu’une partie du code nécessaire est le listing/lien complet des fonctions à utiliser, des événements vers les screens.
Par exemple :
function on.arrowKey(key) activeScreen:arrowKey(key) end function on.timer() activeScreen:timer() end -- long list of all handled events... Then, somewhere else : function myScreen:arrowKey(key) -- Your awesome code goes here end function myScreen:timer() print("I'm the myScreen timer, ticking....") end -- etc. |
Ca marche, certes, mais … c’est un peu barbant.
Allons voir ce que Jim a créé.
Je définis cette nouvelle façon comme « plus intelligente » parce qu’avec ce code, vous n’aurez même plus besoin explicitement de vous tracasser sur la gestion des événements comme vous en avez l’habitude (avec des on.arrowKey, on.enterKey… ou le bon vieux on.paint ). Oui, vous avez bien lu : plus besoin de « function on.paint(gc) … » etc. !
« Qu’est-ce que c’est que cette magie noire », me direz-vous ?
Hé bien, encore une fois, tout repose sur l’utilisation futée des métatables Lua.
En gros, les métatables sont un ensemble de propriétés définissant le comportement d’un objet (généralement une table).
Il existe une propriété « __index » que vous pouvez définir, qui va décrire comment la table réagira quand le script appelle un élément non-défini de celle-ci. Assez utile, croyez-moi 😉
Bref, le truc c’est que quand vous écrivez « function on.paint(gc) », tout ce que vous faites, c’est implémenter la méthode « paint » de la table « on » (d’où le point).
Ce que l’on souhaite pouvoir faire, c’est de se débarasser de cette décalration explicite, et de rediriger l’événement « paint » vers tel ou tel Screen (et les autres événements aussi…)
Bref, nous allons donc utiliser une méthode « eventDistributer » qui prend comme arguments tout ce qui lui est passé, grâce à l’utilisation de « … » (l’événement suivi de ses paramètres, si c’est le cas), et qui les « passe » au Screen que l’on veut (tout en vérifiant que cela est bien possible pour le screen en question):
local triggeredEvent = "paint" -- first declaration, it will be overwritten anyway local eventDistributer = function (...) local currentScreen = GetScreen() if currentScreen[triggeredEvent] then currentScreen[triggeredEvent](currentScreen, ...) end end |
Ce code devrait vous paraître assez clair.
Ce qu’il nous reste désormais à faire, c’est de lier cette fonction à l’__index de la métatable de « on » (vous remarquerez l’intelligente utilisation des closures) :
local eventCatcher = {} eventCatcher.__index = function (tbl, event) triggeredEvent = event return eventDistributer end setmetatable(on, eventCatcher) |
Ce code permet au script de comprendre que quand un événement, sans handler explicite, est déclenché, il devra exécuter cette fonction (qui retourne (closure) une fonction qui prendra les arguments de l’événement et qui redirigera le tout via l’eventDistributer, donc vers l’event handler approprié du bon Screen). Je conçois que ceci peut vous paraître un peu confus/complexe, mais vous finirez par comprendre 🙂
Bref, voila le code complet du Screen Manager et de l’eventDistributer.
(Comme d’habitude, vous aurez à créer (et push) vos Screens, par la suite)
local screens = {} local screenLocation = {} local currentScreen = 0 function RemoveScreen(screen) screen:removed() table.remove(screens, screenLocation[screen]) screenLocation[screen] = nil currentScreen = #screens -- sets the current screen as the top one on the stack. if #screens<=0 then print("Uh oh. This shouldn't have happened ! You must have removed too many screens.") end end function PushScreen(screen) -- if already activated, remove it first (so that it will be on front later) if screenLocation[screen] then RemoveScreen(screen) end table.insert(screens, screen) screenLocation[screen] = #screens currentScreen = #screens screen:pushed() end function GetScreen() return screens[currentScreen] or RootScreen end Screen = class() function Screen:init() end function Screen:pushed() end function Screen:removed() end RootScreen = Screen() -- "fake", empty placeholder screen. local eventCatcher = {} local triggeredEvent = "paint" local eventDistributer = function (...) local currentScreen = GetScreen() if currentScreen[triggeredEvent] then currentScreen[triggeredEvent](currentScreen, ...) end end eventCatcher.__index = function (tbl, event) triggeredEvent = event return eventDistributer end -- finally linking everything setmetatable(on, eventCatcher) |
Je suis sur que parmi vous lecteurs, nombreux sont ceux intéressés par un .tns d’exemple directement, « tout prêt » 😉 Je vous ai préparé ceci : cliquez ici pour télécharger le fichier. C’est un exemple de base avec quelques événements, mais sans une seule fonction on.xxx définie explicitement, et dans 2 screens. J’ai plutôt assez bien commenté le code pour que vous puissiez comprendre rapidement 🙂
Pour un exemple plus terre-à-terre, montrant en même temps cette technique et celle de la personnalisation du gc, je vous suggère de télécharger le jeu Memory de Jim, ici.