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.luadata/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::IAppGraphMethoden. - Im dritten Block (Zeile 175) wird die Callback-Tabelle einer globalen Tabelle
mCardshinzugefü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::IAppProcessorMethoden. - 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.