Aufbauend auf dem vorherigen Beispiel Tutorial #03: Card Game wird hier demonstriert, wie das Card-Game ausschließlich mit Lua implementiert werden kann.
An dieser Stelle wird davon ausgegangen, dass entsprechende Vorkenntnisse im Bereich der Lua Programmierung vorhanden sind.
- Zu beachten
- Um Lua Skripte verwenden zu können, muss das optionale Lua Addon in das Projekt eingebunden werden. Am einfachsten geht das mit dem Murl Dashboard und dem Kommando Project / Configure Project.
Ressourcen Paket
Dieses Beispiel benutzt eine Kopie der Ressourcen game.murlres
aus Tutorial #03.
Zusätzlich werden dem Paket Skript-Ressourcen in einem neuen Ordner lua
hinzugefügt.
data/packages/game.murlres/lua/card_game_logic.lua
data/packages/game.murlres/lua/card_game_card_instance.lua
Die Skript Ressourcen müssen wie alle anderen Ressourcen in das Paket eingebunden werden.
<?xml version="1.0" ?> <!-- Copyright 2014 Spraylight GmbH --> <Package id="game"> <!-- Animation resources --> <Resource id="anim_game_screen" fileName="anim_game_screen.xml"/> <!-- Bitmap resources --> <Resource id="gfx_clubs" fileName="gfx_clubs.png"/> <Resource id="planes_clubs" fileName="planes_clubs.xml"/> <Resource id="gfx_diamonds" fileName="gfx_diamonds.png"/> <Resource id="planes_diamonds" fileName="planes_diamonds.xml"/> <Resource id="gfx_hearts" fileName="gfx_hearts.png"/> <Resource id="planes_hearts" fileName="planes_hearts.xml"/> <Resource id="gfx_misc" fileName="gfx_misc.png"/> <Resource id="planes_misc" fileName="planes_misc.xml"/> <Resource id="gfx_spades" fileName="gfx_spades.png"/> <Resource id="planes_spades" fileName="planes_spades.xml"/> <!-- Font resources --> <Resource id="arial_color_24_glyphs" fileName="fonts/arial_color_24_glyphs.murl"/> <Resource id="arial_color_24_map" fileName="fonts/arial_color_24_map.png"/> <Resource id="arial_color_48_glyphs" fileName="fonts/arial_color_48_glyphs.murl"/> <Resource id="arial_color_48_map" fileName="fonts/arial_color_48_map.png"/> <!-- Graph resources --> <Resource id="graph_camera" fileName="graph_camera.xml"/> <Resource id="graph_game_card" fileName="graph_game_card.xml"/> <Resource id="graph_game_card_suit" fileName="graph_game_card_suit.xml"/> <Resource id="graph_game_screen" fileName="graph_game_screen.xml"/> <Resource id="graph_materials" fileName="graph_materials.xml"/> <Resource id="graph_textures" fileName="graph_textures.xml"/> <!-- Lua resources --> <Resource id="lua_card_game_logic" fileName="lua/card_game_logic.lua" enableCompression="yes"/> <Resource id="lua_card_game_card_instance" fileName="lua/card_game_card_instance.lua" enableCompression="yes"/> <!-- Graph instances --> <Instance graphResourceId="graph_materials"/> <Instance graphResourceId="graph_textures"/> <Instance graphResourceId="graph_camera"/> <!-- Script instances --> <Instance scriptResourceId="lua_card_game_logic"/> </Package>
Die Ressource lua_card_game_logic
wird mit <Instance
scriptResourceId="lua_card_game_logic"/>
im Paket instanziert. Dies hat zur Folge, dass das angegebene Skript nach dem Laden des Pakets während der Paket-Initialisierung automatisch als Logik-Prozessor eingebunden und wie jeder andere Logik-Prozessor ausgeführt wird.
Szenengraph
Das zweite Skript lua_card_game_card_instance
wird als Skript Kontroller eines Graph-Knotens eingesetzt.
<?xml version="1.0" ?> <!-- Copyright 2014 Spraylight GmbH --> <Graph> <Namespace id="{cardName}" activeAndVisible="no" controller="ScriptLogicController" controller.scriptResourceId="game:lua_card_game_card_instance"> <Transform id="position"> <Reference targetId="/textures/{cardName}"/> <SubState> <TextureState textureId="/textures/tex_misc"/> <Transform axisX="0" axisY="1" axisZ="0" angle="180 deg"> <Switch id="back_switch"> <Reference targetId="/textures/black_back"/> <Reference targetId="/textures/red_back"/> </Switch> </Transform> </SubState> <Button id="button" sizeX="90" sizeY="130"/> </Transform> </Namespace> </Graph>
Dies hat zur Folge, dass jede Karten Graph-Instanz eine eigene Instanz des lua_card_game_card_instance
Skripts erhält, welche nach dem Initialisieren des Graph-Knotens automatisch bei jedem Logik-Tick ausgeführt wird.
Applikation
Eine reine Lua Applikation benutzt einen kleinen generischen C++ Programmteil, welcher zum Laden des ersten Lua Skripts dient. Dieses Skript muss eine Lua Implementierung der IApp
-Klasse beinhalten und direkt im Paket Ordner liegen.
data/packages/lua_card_game_app.lua
- Zu beachten
- Tipp: Lua Projekte mit den entsprechenden Programmteilen lassen sich einfach mit dem Murl Dashboard erstellen wenn bei Create Project das Template Lua ausgewählt ist.
Der Lua Programmablauf ist nahezu identisch mit dem C++ Programm aus Tutorial #03.
-- Copyright 2014 Spraylight GmbH -- Open Lua standard libraries local luaAddon = Murl.Addons.Lua.Factory.GetAddon() luaAddon:OpenLibrary("math") luaAddon:OpenLibrary("string") luaAddon:OpenLibrary("table") luaAddon:OpenLibrary("utf8") -- Parameter for Murl.IApp local name = ... -- print("\nLUA IApp new '" .. name .. "'") -- Murl IApp callbacks local mainIApp = { Configure = function (self, engineConfig, fileInterface) -- print("LUA IApp Configure") local appConfig = engineConfig:GetAppConfiguration() local platformConfig = engineConfig:GetPlatformConfiguration() engineConfig:SetProductName("LuaCardGame") appConfig:SetWindowTitle("LuaCardGame powered by Murl Engine") appConfig:SetSystemDebugInfoItems(Murl.IEnums.STATISTIC_ITEM_FRAMES_PER_SECOND) if (platformConfig:IsTargetClassMatching(Murl.IEnums.TARGET_CLASS_COMPUTER)) then if (platformConfig:IsOperatingSystemMatching(Murl.IEnums.OPERATING_SYSTEM_WINDOWS)) then engineConfig:SetVideoApi(Murl.IEnums.VIDEO_API_DX90) end appConfig:SetDisplaySurfaceSize(1024, 768) appConfig:SetLockWindowAspectEnabled(true) appConfig:SetFullScreenEnabled(false) elseif (platformConfig:IsTargetClassMatching(Murl.IEnums.TARGET_CLASS_HANDHELD)) then -- set landscape orientation appConfig:SetScreenOrientation(Murl.IEnums.SCREEN_ORIENTATION_LANDSCAPE_1) -- enable landscape orientations appConfig:SetAllowedScreenOrientations(Murl.IEnums.SCREEN_ORIENTATIONS_LANDSCAPE) -- enable auto rotation appConfig:SetAutoRotationActive(true) appConfig:SetOrientationActive(true) -- enable multi touch appConfig:SetMultiTouchActive(true) end engineConfig:SetDeactivatedAppRunState(Murl.IEnums.APP_RUN_STATE_PAUSED) return true end, IsUserConfigurationMatching = function (self, userConfigId) -- print("LUA IApp IsUserConfigurationMatching", userConfigId) return false end, RegisterCustomAddonClasses = function (self, addonRegistry) -- print("LUA IApp RegisterCustomAddonClasses") return true end, UnregisterCustomAddonClasses = function (self, addonRegistry) -- print("LUA IApp UnregisterCustomAddonClasses") return true end, Init = function (self, appState) -- print("LUA IApp Init") local loader = appState:GetLoader() loader:AddPackage("startup", Murl.ILoader.LOAD_MODE_STARTUP) loader:AddPackage("game", Murl.ILoader.LOAD_MODE_BACKGROUND) return true end, DeInit = function (self, appState) -- print("\nLUA IApp DeInit") return true end } return Murl.IApp.new(mainIApp)
Dieses Skript wird unmittelbar nach dem Programmstart geladen und ausgeführt. Als Rückgabewert wird ein IApp
Objekt erwartet.
- Im ersten Block (Zeile 4) des Programmcodes werden diverse Lua Standard-Bibliotheken geladen.
- Im zweiten Block (Zeile 11) werden die Parameter ausgewertet. Das Lua Applikations-Skript bekommt genau einen Parameter mit dem Namen der Skript-Ressource.
- Im dritten Block (Zeile 15) wird eine Lua Tabelle mit Funktionen erzeugt. Diese Funktionen fungieren als Callbacks und haben dieselbe Funktion wie die gleichnamigen C++
IApp
-Methoden. - An letzter Stelle (Zeile 92) wird eine Instanz der
IApp
-Klasse, welche die Lua Callback Tabelle beinhaltet, erzeugt und zurückgegeben.
Im Anschluss werden die Lua Callback Funktionen in mainIApp
genauso ausgeführt, wie normalerweise die gleichnamigen C++ Methoden.
Die Funktion Configure()
(Zeile 17) beinhaltet den gleichen Programmablauf wie die C++ Methode in card_game_app.cpp
aus Tutorial #03. Selbiges gilt für die Funktion Init()
(Zeile 75) welche die Ressourcen-Pakete lädt.
Die Programmierung in Lua unterscheidet sich syntaktisch geringfügig von C++, jedoch sind weitgehend alle Murl Klassen auch in gleichnamigen Lua Tabellen abgebildet. Eine vollständige Dokumentation aller Lua Tabellen findet sich in der Download Sektion im MurlEngineLuaAPI Archiv.
Skript einer Karteninstanz
Unser Spiel hat 54 Karteninstanzen, welche alle separat gesteuert werden. Um dies möglichst einfach zu erreichen, wird ein Skript implementiert, welches genau eine Karte steuert. Dieses Skript wird wie weiter oben gezeigt im Sub-Graphen graph_game_card.xml
als Controller eingebunden.
-- Copyright 2014 Spraylight GmbH -- Parameters for Logic IAppGraph local resourceId, nodeId = ... -- print("\nLUA IAppGraph new", resourceId, nodeId) -- Logic IAppGraph callbacks local logicIAppGraph = { mNodeObserver = Murl.Logic.StaticFactory.CreateNodeObserver(), mNamespaceNode = Murl.Logic.NamespaceNode.new(), mTransformNode = Murl.Logic.TransformNode.new(), mButton = Murl.Logic.ButtonNode.new(), mBackSwitch = Murl.Logic.SwitchNode.new(), mStepableObserver = Murl.Logic.StaticFactory.CreateStepableObserver(), mPositionAnim = Murl.Logic.AnimationVector.new(), mRotationAnim = Murl.Logic.AnimationVector.new(), mIsShowingFront = false, OnPostInit = function (self, state) -- print("LUA IAppGraph OnInit", nodeId) self.mNodeObserver:Add(self.mNamespaceNode:GetReference(state:GetCurrentGraphNode())) if (not self.mNamespaceNode:IsValid()) then return false end self.mNodeObserver:Add(self.mTransformNode:GetReference(self.mNamespaceNode:FindNode("position"))) self.mNodeObserver:Add(self.mButton:GetReference(self.mNamespaceNode:FindNode("button"))) self.mNodeObserver:Add(self.mBackSwitch:GetReference(self.mNamespaceNode:FindNode("back_switch"))) if (not self.mNodeObserver:AreValid()) then return false end self.mPositionAnim:AddKey(0.0, Murl.Math.Vector.new(Murl.Math.Vector.ZERO_POSITION), Murl.IEnums.INTERPOLATION_EASE_OUT) self.mPositionAnim:AddKey(1.0, Murl.Math.Vector.new(Murl.Math.Vector.ZERO_POSITION)) self.mRotationAnim:AddKey(0.0, Murl.Math.Vector.new(), Murl.IEnums.INTERPOLATION_LINEAR) self.mRotationAnim:AddKey(0.5, Murl.Math.Vector.new(), Murl.IEnums.INTERPOLATION_EASE_OUT) self.mRotationAnim:AddKey(1.0, Murl.Math.Vector.new()); self.mStepableObserver:Add(self.mPositionAnim:GetStepable()) self.mStepableObserver:Add(self.mRotationAnim:GetStepable()) self:SetBack(CARDBACKSIDE_BLACK) self:EnableButton(false) self:SetCardPosition(self.mTransformNode:GetPosition(), false, 0) self.mIsShowingFront = true return true end, OnPreDeInit = function (self, state) -- print("LUA IAppGraph OnDeInit", nodeId) local ret = true if (not self.mNodeObserver:RemoveAll()) then ret = false end return ret end, OnPreProcessTick = function (self, state) self.mStepableObserver:ProcessTick(state) if (self.mPositionAnim:IsOrWasRunning()) then self.mTransformNode:SetPosition(self.mPositionAnim:GetCurrentValue()) end if (self.mRotationAnim:IsOrWasRunning()) then local currentRotation = self.mRotationAnim:GetCurrentValue() self.mTransformNode:SetRotation(currentRotation.x, currentRotation.y, currentRotation.z) end end, SetObtained = function (self, isObtained) self.mNamespaceNode:GetNodeInterface():SetActiveAndVisible(isObtained) end, SetSortDepth = function (self, sortDepth) self.mTransformNode:SetDepthOrder(sortDepth) end, SetBack = function (self, backSide) if (backSide == CARDBACKSIDE_RED) then self.mBackSwitch:SetIndex(1) else self.mBackSwitch:SetIndex(0) end end, SetCardPosition = function (self, position, showFront, duration) self.mTransformNode:SetPosition(position) if (showFront) then if (not self.mIsShowingFront) then if (duration > 0) then self.mRotationAnim:ModifyKeyValue(0, Murl.Math.Vector.new(0, -Murl.Math.PI, 0, 0)) self.mRotationAnim:ModifyKeyValue(1, Murl.Math.Vector.new(0, -Murl.Math.HALF_PI, 0, 0)) self.mRotationAnim:ModifyKeyValue(2, Murl.Math.Vector.new(0, 0, 0, 0)) self.mRotationAnim:ModifyKeyTime(1, duration / 2) self.mRotationAnim:ModifyKeyTime(2, duration) self.mRotationAnim:StartForward() else self.mTransformNode:SetRotation(0, 0, 0) end end else if (self.mIsShowingFront) then if (duration > 0) then self.mRotationAnim:ModifyKeyValue(0, Murl.Math.Vector.new(0, 0, 0, 0)) self.mRotationAnim:ModifyKeyValue(1, Murl.Math.Vector.new(0, Murl.Math.HALF_PI, 0, 0)) self.mRotationAnim:ModifyKeyValue(2, Murl.Math.Vector.new(0, Murl.Math.PI, 0, 0)) self.mRotationAnim:ModifyKeyTime(1, duration / 2) self.mRotationAnim:ModifyKeyTime(2, duration) self.mRotationAnim:StartForward() else self.mTransformNode:SetRotation(0, Murl.Math.PI, 0) end end end self.mIsShowingFront = showFront end, MoveCardToPosition = function (self, position, showFront, duration) self.mPositionAnim:SetKey(0, 0.0, self.mTransformNode:GetPosition(), Murl.IEnums.INTERPOLATION_EASE_OUT) self.mPositionAnim:SetKey(1, duration, position) self.mPositionAnim:StartForward() if (showFront) then if (mIsShowingFront) then self.mRotationAnim:ModifyKeyValue(0, Murl.Math.Vector.new(0, 0, 0, 0)) self.mRotationAnim:ModifyKeyValue(1, Murl.Math.Vector.new(0, -1.0, 0.25, 0)) self.mRotationAnim:ModifyKeyValue(2, Murl.Math.Vector.new(0, 0, 0, 0)) else self.mRotationAnim:ModifyKeyValue(0, Murl.Math.Vector.new(0, Murl.Math.PI, 0, 0)) self.mRotationAnim:ModifyKeyValue(1, Murl.Math.Vector.new(0, Murl.Math.HALF_PI, 0.5, 0)) self.mRotationAnim:ModifyKeyValue(2, Murl.Math.Vector.new(0, 0, 0, 0)) end else if (mIsShowingFront) then self.mRotationAnim:ModifyKeyValue(0, Murl.Math.Vector.new(0, 0, 0, 0)) self.mRotationAnim:ModifyKeyValue(1, Murl.Math.Vector.new(0, Murl.Math.HALF_PI, 0.5, 0)) self.mRotationAnim:ModifyKeyValue(2, Murl.Math.Vector.new(0, Murl.Math.PI, 0, 0)) else self.mRotationAnim:ModifyKeyValue(0, Murl.Math.Vector.new(0, Murl.Math.PI, 0, 0)) self.mRotationAnim:ModifyKeyValue(1, Murl.Math.Vector.new(0.5, Murl.Math.PI - 0.5, 0.5, 0)) self.mRotationAnim:ModifyKeyValue(2, Murl.Math.Vector.new(0, Murl.Math.PI, 0, 0)) end end self.mRotationAnim:ModifyKeyTime(1, duration / 2) self.mRotationAnim:ModifyKeyTime(2, duration) self.mRotationAnim:StartForward() self.mIsShowingFront = showFront end, IsMoving = function (self) return self.mPositionAnim:IsOrWasRunning() end, EnableButton = function (self, enable) self.mButton:SetEnabled(enable) end, WasPressed = function (self) return self.mButton:WasPressed() end, } -- add this instance to the global card instances table table.insert(mCards, logicIAppGraph) return Murl.Logic.IAppGraph.new(logicIAppGraph)
Dieses Skript wird beim Erzeugen des zugehörigen Graph-Knotens geladen und ausgeführt. Als Rückgabewert wird ein Logic.IAppGraph
Objekt erwartet.
- Im ersten Block (Zeile 4) werden die Parameter ausgewertet, das Lua Kontroller Skript bekommt genau zwei Parameter. Der erste Parameter ist der Name der Skript-Ressource und der zweite Parameter ist der Name des Kontroller-Knotens.
- Im zweiten Block (Zeile 8) wird eine Lua Tabelle mit Funktionen erzeugt. Diese Funktionen fungieren als Callbacks und haben dieselbe Funktion wie die gleichnamigen C++
Logic::IAppGraph
Methoden. - Im dritten Block (Zeile 175) wird die Callback-Tabelle einer globalen Tabelle
mCards
hinzugefügt, von wo aus später der Logik Programmteil auf die einzelnen Karten Instanzen zugreifen kann. - An letzter Stelle (Zeile 177) wird eine Instanz der
Logic.IAppGraph
-Klasse, welche die Lua Callback Tabelle beinhaltet, erzeugt und retourniert.
Die Funktionen und Variablen der logic.IAppGraph
Tabelle entsprechen den Methoden und Membervariablen von card_game_card_instance.cpp
aus Tutorial #03.
Skript Prozessor
Die Logik-Prozessor Instanz wird wie weiter oben gezeigt direkt in package.xml
instanziert.
-- Copyright 2014 Spraylight GmbH -- Parameters for Logic IAppProcessor local resourceId, replication = ... -- print("\nLUA IAppProcessor new", resourceId, replication) -- the global table holding all card instances mCards = {} CARDBACKSIDE_BLACK = 0 CARDBACKSIDE_RED = 1 -- Logic IAppProcessor callbacks local logicIAppProcessor = { STATE_IDLE = 0, STATE_DEAL = 1, STATE_PLAY = 2, mStateMachine = 0, mCardsPerSuit = 13, mCardSizeX = 90, mCardSizeY = 130, mScreenTimeline = Murl.Logic.TimelineNode.new(), mGameInfoText = Murl.Logic.TextGeometryNode.new(), mStackButton = Murl.Logic.ButtonNode.new(), mRng = Murl.Util.TT800.new(), mCardDistribution = Murl.UInt32Array.new(), mCardsToDeal = Murl.SInt32Array.new(), mCardStack = Murl.SInt32Array.new(), mCardTray = Murl.SInt32Array.new(), mPlayfield = Murl.SInt32Array.new(), mGameStartTimeout = Murl.Logic.Timeframe.new(), mGameEndTimeout = Murl.Logic.Timeframe.new(), mDealCount = 0, mNumberOfGamesPlayed = 0, OnInit = function (self, state) -- print("\nLUA IAppProcessor OnInit", resourceId) state:GetLoader():UnloadPackage("startup") local root = state:GetGraphRoot() local processor = state:GetCurrentProcessor() processor:AddGraphNode(self.mScreenTimeline:GetReference(root, "/game_screen/screen_timeline")) processor:AddGraphNode(self.mGameInfoText:GetReference(root, "/game_screen/info_text")) processor:AddGraphNode(self.mStackButton:GetReference(root, "/game_screen/stack_button")) if (not processor:AreGraphNodesValid()) then return false end self.mScreenTimeline:Start(0.0, 0.5) processor:AddStepable(self.mGameStartTimeout:GetStepable()) processor:AddStepable(self.mGameEndTimeout:GetStepable()) return true end, OnDeInit = function (self, state) -- print("\nLUA IAppProcessor OnDeInit", resourceId) local ret = true return ret end, OnProcessTick = function (self, state) if (self.mScreenTimeline:WasRunning()) then self:EnterDeal(state) end local deviceHandler = state:GetDeviceHandler() if (deviceHandler:WasRawButtonPressed(Murl.RAWBUTTON_BACK)) then deviceHandler:TerminateApp() end --print("State", self.mStateMachine) if (self.mStateMachine == self.STATE_DEAL) then self:ProcessTickDeal(state) elseif (self.mStateMachine == self.STATE_PLAY) then self:ProcessTickPlay(state) end end, OnRunStateChanged = function (self, state, currentState, previousState) -- print("\nLUA IAppProcessor OnRunStateChanged", resourceId) if (self.mGameStartTimeout:IsOrWasRunning()) then return end if (currentState == Murl.IEnums.APP_RUN_STATE_PAUSED) then self.mGameInfoText:SetText("- Paused -") if (self.mStateMachine == self.STATE_PLAY) then self:UpdatePlayfield(0.0, true) end elseif (currentState == Murl.IEnums.APP_RUN_STATE_RUNNING) then self.mGameInfoText:SetText("- Play -") if (self.mStateMachine == self.STATE_PLAY) then self:UpdatePlayfield(0.3, false) end end end, EnterDeal = function (self, state) -- print("\nLUA IAppProcessor EnterDeal", resourceId) self.mStateMachine = self.STATE_DEAL self.mNumberOfGamesPlayed = self.mNumberOfGamesPlayed + 1 self:ShuffleCards(self.mNumberOfGamesPlayed % 3) self.mDealCount = 0 self.mGameStartTimeout:Start(1.0) self.mGameInfoText:SetText("") end, ProcessTickDeal = function (self, state) -- print("LUA IAppProcessor ProcessTickDeal", resourceId) if (self.mGameStartTimeout:IsRunning()) then return end if (self.mCardsToDeal:GetCount() > 0) then local index = self.mCardsToDeal:Pop() local n = self.mCardStack:GetCount() self.mCardStack:Add(index) mCards[index + 1]:SetObtained(true) mCards[index + 1]:SetSortDepth(n + 1) mCards[index + 1]:SetCardPosition(self:GetStackPosition(n), false, 0) mCards[index + 1]:EnableButton(false) if ((self.mNumberOfGamesPlayed % 2) == 0) then mCards[index + 1]:SetBack(CARDBACKSIDE_RED) else mCards[index + 1]:SetBack(CARDBACKSIDE_BLACK) end elseif (self.mDealCount < self.mPlayfield:GetCount()) then local index = self.mCardStack:Pop() local showFront = (self.mDealCount >= 18) mCards[index + 1]:SetSortDepth(self.mDealCount + 1000) mCards[index + 1]:MoveCardToPosition(self:GetPlayfieldPosition(self.mDealCount), showFront, 0.5) mCards[index + 1]:EnableButton(showFront) self.mPlayfield[self.mDealCount] = index self.mGameStartTimeout:Start(0.1) self.mDealCount = self.mDealCount + 1 else self:MoveToTray(self.mCardStack:Pop()) self:UpdateStackButton() self:EnterPlay(state) end end, EnterPlay = function (self, state) -- print("\nLUA IAppProcessor EnterPlay", resourceId) self.mStateMachine = self.STATE_PLAY self.mGameInfoText:SetText("- Play -") end, ProcessTickPlay = function (self, state) -- print("LUA IAppProcessor ProcessTickPlay", resourceId) if (self.mGameEndTimeout:IsRunning()) then return end if (self.mGameEndTimeout:WasRunning()) then self:EnterDeal(state) return end local canAnyMoveToTray = false local isPlayfieldEmpty = true for i, index in Murl.ipairs(self.mPlayfield) do if (index >= 0) then isPlayfieldEmpty = false if (mCards[index + 1].mIsShowingFront) then local canMoveToTray = self:CanMoveToTray(index) if (canMoveToTray) then canAnyMoveToTray = true end if (mCards[index + 1]:WasPressed()) then if (canMoveToTray) then self:MoveToTray(index) self.mPlayfield[i] = -1 self:UpdatePlayfield(0.3, false) end end end end end if (isPlayfieldEmpty) then -- Pyramid complete self.mGameInfoText:SetText("Congratulations!") self.mGameEndTimeout:Start(3.0) elseif (self.mCardStack:GetCount() > 0) then if (self.mStackButton:WasPressed()) then self:MoveToTray(self.mCardStack:Pop()) self:UpdateStackButton() end else if (not canAnyMoveToTray) then -- No more moves self.mGameInfoText:SetText("Game Over") self.mGameEndTimeout:Start(3.0) end end end, UpdateStackButton = function (self) local additionalSizeX = self.mCardStack:GetCount() * 4 if (additionalSizeX > 0) then self.mStackButton:SetScaleFactorX(self.mCardSizeX + additionalSizeX) self.mStackButton:SetScaleFactorY(self.mCardSizeY + 4) local position = self:GetStackPosition(0) position.x = position.x + (additionalSizeX / 2) self.mStackButton:GetTransformInterface():SetPosition(position) end end, GetStackPosition = function (self, index) local position = Murl.Math.Vector.new(Murl.Math.Vector.ZERO_POSITION) position.x = -400 + (index * 4) position.y = -260 return position end, GetTrayPosition = function (self, index) local position = Murl.Math.Vector.new(Murl.Math.Vector.ZERO_POSITION) position.x = 200 + (index * 2) position.y = -260 return position end, GetPlayfieldPosition = function (self, index) local position = Murl.Math.Vector.new(Murl.Math.Vector.ZERO_POSITION) local DISTANCE_X = self.mCardSizeX + 10 local DISTANCE_Y = self.mCardSizeY - 50 if (index < 3) then position.x = (index * 3) * DISTANCE_X - (6 * DISTANCE_X / 2) position.y = DISTANCE_Y + DISTANCE_Y / 2 elseif (index < 9) then position.x = (index - 3) * DISTANCE_X - (7 * DISTANCE_X / 2) position.x = position.x + (Murl.Math.Floor((index - 3) / 2) * DISTANCE_X) position.y = DISTANCE_Y / 2 elseif (index < 18) then position.x = (index - 9) * DISTANCE_X - (8 * DISTANCE_X / 2) position.y = -DISTANCE_Y / 2 else position.x = (index - 18) * DISTANCE_X - (9 * DISTANCE_X / 2) position.y = -DISTANCE_Y - DISTANCE_Y / 2 end position.y = position.y + 100 return position end, IsCardFree = function (self, index) -- Playfield index -- 0 1 2 -- 3 4 5 6 7 8 -- 9 10 11 12 13 14 15 16 17 -- 18 19 20 21 22 23 24 25 26 27 local left = -1 if (index < 3) then left = (index * 2) + 3 elseif (index < 9) then left = (Murl.Math.Floor((index - 3) / 2)) + index + 6 elseif (index < 18) then left = index + 9 end local right = -1 if (left >= 0) then right = left + 1 end local free = true if (left >= 0 and left < self.mPlayfield:GetCount()) then if (self.mPlayfield[left] >= 0) then free = false end end if (right >= 0 and right < self.mPlayfield:GetCount()) then if (self.mPlayfield[right] >= 0) then free = false end end return free end, CanMoveToTray = function (self, index) local isValid = false local trayCard = self.mCardTray:Top() if (trayCard >= 4 * self.mCardsPerSuit or index >= 4 * self.mCardsPerSuit) then -- Joker isValid = true else local playCard = index % self.mCardsPerSuit local trayCardPlus = (trayCard + 1) % self.mCardsPerSuit local trayCardMinus = (trayCard + self.mCardsPerSuit - 1) % self.mCardsPerSuit if (playCard == trayCardPlus or playCard == trayCardMinus) then isValid = true end end return isValid end, MoveToTray = function (self, index) local n = self.mCardTray:GetCount() self.mCardTray:Add(index) mCards[index + 1]:SetSortDepth(n + 2000) mCards[index + 1]:MoveCardToPosition(self:GetTrayPosition(n), true, 0.5) mCards[index + 1]:EnableButton(false) end, UpdatePlayfield = function (self, duration, isPause) for i, index in Murl.ipairs(self.mPlayfield) do if (index >= 0) then local isFree = false if (not isPause) then isFree = self:IsCardFree(i) end local card = mCards[index + 1] card:SetSortDepth(i + 1000) card:SetCardPosition(self:GetPlayfieldPosition(i), isFree, duration) if (isFree) then card:EnableButton(true) end end end end, ShuffleCards = function (self, numberOfJokers) for _, card in ipairs(mCards) do card:SetObtained(false) end if (numberOfJokers > 2) then numberOfJokers = 2 end self.mCardDistribution:Empty() self.mCardDistribution:SetCount(#mCards - 2 + numberOfJokers, 1) self.mCardsToDeal:Empty() for _ in Murl.ipairs(self.mCardDistribution) do local index index, self.mCardDistribution = self.mRng:DrawNoReplacement(self.mCardDistribution) self.mCardsToDeal:Add(index) end self.mCardStack:Empty() self.mCardTray:Empty() self.mPlayfield:Empty() self.mPlayfield:SetCount(28, -1) end } return Murl.Logic.IAppProcessor.new(logicIAppProcessor)
Dieses Skript wird beim Erzeugen des zugehörigen Ressourcen-Pakets geladen und ausgeführt. Als Rückgabewert wird ein Logic.IAppProcessor
Objekt erwartet.
- Im ersten Block (Zeile 4) werden die Parameter ausgewertet. Das Lua Prozessor Skript bekommt genau zwei Parameter. Der erste Parameter ist der Name der Skript-Ressource und der zweite Parameter ist die Replikations-Nummer.
- Im zweiten Block (Zeile 14) wird eine Lua Tabelle mit Funktionen erzeugt. Diese Funktionen fungieren als Callbacks und haben dieselbe Funktion wie die gleichnamigen C++
Logic::IAppProcessor
Methoden. - An letzter Stelle (Zeile 364) wird eine Instanz der
Logic.IAppProcessor
-Klasse, welche die Lua Callback Tabelle beinhaltet, erzeugt und retourniert.
Die Funktionen und Variablen der logicIAppProcessor
Tabelle entsprechen den Methoden und Membervariablen von card_game_logic.cpp
aus Tutorial #03.
Der auffälligste Unterschied zu der C++ Implementierung besteht in der Initialisierung der Karten-Instanzen, welche durch Verwendung des Skript-Kontrollers automatisch bei der Erstellung der Graph-Knoten stattfindet.