49_Urho2DIsometricDemo.lua 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. -- Tile map example.
  2. -- This sample demonstrates:
  3. -- - Creating an isometric 2D scene with tile map
  4. -- - Displaying the scene using the Renderer subsystem
  5. -- - Handling keyboard to move a character and zoom 2D camera
  6. -- - Generating physics shapes from the tmx file's objects
  7. -- - Displaying debug geometry for physics and tile map
  8. -- Note that this sample uses some functions from Sample2D utility class.
  9. require "LuaScripts/Utilities/Sample"
  10. require "LuaScripts/Utilities/2D/Sample2D"
  11. function Start()
  12. -- Set filename for load/save functions
  13. demoFilename = "Isometric2D"
  14. -- Execute the common startup for samples
  15. SampleStart()
  16. -- Create the scene content
  17. CreateScene()
  18. -- Create the UI content
  19. CreateUIContent("ISOMETRIC 2.5D DEMO")
  20. -- Hook up to the frame update events
  21. SubscribeToEvents()
  22. end
  23. function CreateScene()
  24. scene_ = Scene()
  25. -- Create the Octree, DebugRenderer and PhysicsWorld2D components to the scene
  26. scene_:CreateComponent("Octree")
  27. scene_:CreateComponent("DebugRenderer")
  28. local physicsWorld = scene_:CreateComponent("PhysicsWorld2D")
  29. physicsWorld.gravity = Vector2.ZERO -- Neutralize gravity as the character will always be grounded
  30. -- Create camera
  31. cameraNode = Node()
  32. local camera = cameraNode:CreateComponent("Camera")
  33. camera.orthographic = true
  34. camera.orthoSize = graphics.height * PIXEL_SIZE
  35. zoom = 2 * Min(graphics.width / 1280, graphics.height / 800) -- Set zoom according to user's resolution to ensure full visibility (initial zoom (2) is set for full visibility at 1280x800 resolution)
  36. camera.zoom = zoom
  37. -- Setup the viewport for displaying the scene
  38. renderer:SetViewport(0, Viewport:new(scene_, camera))
  39. renderer.defaultZone.fogColor = Color(0.2, 0.2, 0.2) -- Set background color for the scene
  40. -- Create tile map from tmx file
  41. local tmxFile = cache:GetResource("TmxFile2D", "Urho2D/Tilesets/atrium.tmx")
  42. local tileMapNode = scene_:CreateChild("TileMap")
  43. local tileMap = tileMapNode:CreateComponent("TileMap2D")
  44. tileMap.tmxFile = tmxFile
  45. local info = tileMap.info
  46. -- Create Spriter Imp character (from sample 33_SpriterAnimation)
  47. CreateCharacter(info, true, 0, Vector3(-5, 11, 0), 0.15)
  48. -- Generate physics collision shapes from the tmx file's objects located in "Physics" (top) layer
  49. local tileMapLayer = tileMap:GetLayer(tileMap.numLayers - 1)
  50. CreateCollisionShapesFromTMXObjects(tileMapNode, tileMapLayer, info)
  51. -- Instantiate enemies and moving platforms at each placeholder of "MovingEntities" layer (placeholders are Poly Line objects defining a path from points)
  52. PopulateMovingEntities(tileMap:GetLayer(tileMap.numLayers - 2))
  53. -- Instantiate coins to pick at each placeholder of "Coins" layer (placeholders for coins are Rectangle objects)
  54. PopulateCoins(tileMap:GetLayer(tileMap.numLayers - 3))
  55. -- Check when scene is rendered
  56. SubscribeToEvent("EndRendering", HandleSceneRendered)
  57. end
  58. function HandleSceneRendered()
  59. UnsubscribeFromEvent("EndRendering")
  60. SaveScene(true) -- Save the scene so we can reload it later
  61. scene_.updateEnabled = false -- Pause the scene as long as the UI is hiding it
  62. end
  63. function SubscribeToEvents()
  64. -- Subscribe HandleUpdate() function for processing update events
  65. SubscribeToEvent("Update", "HandleUpdate")
  66. -- Subscribe HandlePostUpdate() function for processing post update events
  67. SubscribeToEvent("PostUpdate", "HandlePostUpdate")
  68. -- Subscribe to PostRenderUpdate to draw physics shapes
  69. SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate")
  70. -- Subscribe to Box2D contact listeners
  71. SubscribeToEvent("PhysicsBeginContact2D", "HandleCollisionBegin")
  72. -- Unsubscribe the SceneUpdate event from base class to prevent camera pitch and yaw in 2D sample
  73. UnsubscribeFromEvent("SceneUpdate")
  74. end
  75. function HandleUpdate(eventType, eventData)
  76. -- Zoom in/out
  77. Zoom(cameraNode:GetComponent("Camera"))
  78. -- Toggle debug geometry with spacebar
  79. if input:GetKeyPress(KEY_Z) then drawDebug = not drawDebug end
  80. -- Check for loading / saving the scene
  81. if input:GetKeyPress(KEY_F5) then
  82. SaveScene()
  83. end
  84. if input:GetKeyPress(KEY_F7) then
  85. ReloadScene(false)
  86. end
  87. end
  88. function HandlePostUpdate(eventType, eventData)
  89. if character2DNode == nil or cameraNode == nil then
  90. return
  91. end
  92. cameraNode.position = Vector3(character2DNode.position.x, character2DNode.position.y, -10) -- Camera tracks character
  93. end
  94. function HandlePostRenderUpdate(eventType, eventData)
  95. if drawDebug then
  96. scene_:GetComponent("PhysicsWorld2D"):DrawDebugGeometry(true)
  97. local tileMapNode = scene_:GetChild("TileMap", true)
  98. local map = tileMapNode:GetComponent("TileMap2D")
  99. map:DrawDebugGeometry(scene_:GetComponent("DebugRenderer"), false)
  100. end
  101. end
  102. function HandleCollisionBegin(eventType, eventData)
  103. -- Get colliding node
  104. local hitNode = eventData["NodeA"]:GetPtr("Node")
  105. if hitNode.name == "Imp" then
  106. hitNode = eventData["NodeB"]:GetPtr("Node")
  107. end
  108. local nodeName = hitNode.name
  109. local character = character2DNode:GetScriptObject("Character2D")
  110. -- Handle coins picking
  111. if nodeName == "Coin" then
  112. hitNode:Remove()
  113. character.remainingCoins = character.remainingCoins - 1
  114. if character.remainingCoins == 0 then
  115. local instructions = ui.root:GetChild("Instructions", true)
  116. instructions.text = "!!! You got all the coins !!!"
  117. end
  118. local coinsText = ui.root:GetChild("CoinsText", true)
  119. coinsText.text = character.remainingCoins -- Update coins UI counter
  120. PlaySound("Powerup.wav")
  121. end
  122. -- Handle interactions with enemies
  123. if nodeName == "Orc" then
  124. local animatedSprite = character2DNode:GetComponent("AnimatedSprite2D")
  125. local deltaX = character2DNode.position.x - hitNode.position.x
  126. -- Orc killed if character is fighting in its direction when the contact occurs
  127. if animatedSprite.animation == "attack" and deltaX < 0 == animatedSprite.flipX then
  128. hitNode:GetScriptObject("Mover").emitTime = 1
  129. if hitNode:GetChild("Emitter", true) == nil then
  130. hitNode:GetComponent("RigidBody2D"):Remove() -- Remove Orc's body
  131. SpawnEffect(hitNode)
  132. PlaySound("BigExplosion.wav")
  133. end
  134. -- Player killed if not fighting in the direction of the Orc when the contact occurs
  135. else
  136. if character2DNode:GetChild("Emitter", true) == nil then
  137. character.wounded = true
  138. if nodeName == "Orc" then
  139. hitNode:GetScriptObject("Mover").fightTimer = 1
  140. end
  141. SpawnEffect(character2DNode)
  142. PlaySound("BigExplosion.wav")
  143. end
  144. end
  145. end
  146. end
  147. -- Character2D script object class
  148. Character2D = ScriptObject()
  149. function Character2D:Start()
  150. self.wounded = false
  151. self.killed = false
  152. self.timer = 0
  153. self.maxCoins = 0
  154. self.remainingCoins = 0
  155. self.remainingLifes = 3
  156. end
  157. function Character2D:Update(timeStep)
  158. if character2DNode == nil then
  159. return
  160. end
  161. -- Handle wounded/killed states
  162. if self.killed then
  163. return
  164. end
  165. if self.wounded then
  166. self:HandleWoundedState(timeStep)
  167. return
  168. end
  169. local node = self.node
  170. local animatedSprite = node:GetComponent("AnimatedSprite2D")
  171. -- Set direction
  172. local moveDir = Vector3.ZERO -- Reset
  173. local speedX = Clamp(MOVE_SPEED_X / zoom, 0.4, 1)
  174. local speedY = speedX
  175. if input:GetKeyDown(KEY_LEFT) or input:GetKeyDown(KEY_A) then
  176. moveDir = moveDir + Vector3.LEFT * speedX
  177. animatedSprite.flipX = false -- Flip sprite (reset to default play on the X axis)
  178. end
  179. if input:GetKeyDown(KEY_RIGHT) or input:GetKeyDown(KEY_D) then
  180. moveDir = moveDir + Vector3.RIGHT * speedX
  181. animatedSprite.flipX = true -- Flip sprite (flip animation on the X axis)
  182. end
  183. if not moveDir:Equals(Vector3.ZERO) then
  184. speedY = speedX * MOVE_SPEED_SCALE
  185. end
  186. if input:GetKeyDown(KEY_UP) or input:GetKeyDown(KEY_W) then
  187. moveDir = moveDir + Vector3.UP * speedY
  188. end
  189. if input:GetKeyDown(KEY_DOWN) or input:GetKeyDown(KEY_S) then
  190. moveDir = moveDir + Vector3.DOWN * speedY
  191. end
  192. -- Move
  193. if not moveDir:Equals(Vector3.ZERO) then
  194. node:Translate(moveDir * timeStep)
  195. end
  196. -- Animate
  197. if input:GetKeyDown(KEY_SPACE) then
  198. if animatedSprite.animation ~= "attack" then
  199. animatedSprite:SetAnimation("attack", LM_FORCE_LOOPED)
  200. end
  201. elseif not moveDir:Equals(Vector3.ZERO) then
  202. if animatedSprite.animation ~= "run" then
  203. animatedSprite:SetAnimation("run")
  204. end
  205. elseif animatedSprite.animation ~= "idle" then
  206. animatedSprite:SetAnimation("idle")
  207. end
  208. end
  209. function Character2D:HandleWoundedState(timeStep)
  210. local node = self.node
  211. local body = node:GetComponent("RigidBody2D")
  212. local animatedSprite = node:GetComponent("AnimatedSprite2D")
  213. -- Play "hit" animation in loop
  214. if animatedSprite.animation ~= "hit" then
  215. animatedSprite:SetAnimation("hit", LM_FORCE_LOOPED)
  216. end
  217. -- Update timer
  218. self.timer = self.timer + timeStep
  219. if self.timer > 2.0 then
  220. -- Reset timer
  221. self.timer = 0.0
  222. -- Clear forces (should be performed by setting linear velocity to zero, but currently doesn't work)
  223. body.linearVelocity = Vector2(0.0, 0.0)
  224. body.awake = false
  225. body.awake = true
  226. -- Remove particle emitter
  227. node:GetChild("Emitter", true):Remove()
  228. -- Update lifes UI and counter
  229. self.remainingLifes = self.remainingLifes - 1
  230. local lifeText = ui.root:GetChild("LifeText", true)
  231. lifeText.text = self.remainingLifes -- Update lifes UI counter
  232. -- Reset wounded state
  233. self.wounded = false
  234. -- Handle death
  235. if self.remainingLifes == 0 then
  236. self:HandleDeath()
  237. return
  238. end
  239. -- Re-position the character to the nearest point
  240. if node.position.x < 15.0 then
  241. node.position = Vector3(1.0, 8.0, 0.0)
  242. else
  243. node.position = Vector3(18.8, 9.2, 0.0)
  244. end
  245. end
  246. end
  247. function Character2D:HandleDeath()
  248. local node = self.node
  249. local animatedSprite = node:GetComponent("AnimatedSprite2D")
  250. -- Set state to 'killed'
  251. self.killed = true
  252. -- Update UI elements
  253. local instructions = ui.root:GetChild("Instructions", true)
  254. instructions.text = "!!! GAME OVER !!!"
  255. ui.root:GetChild("ExitButton", true).visible = true
  256. ui.root:GetChild("PlayButton", true).visible = true
  257. -- Show mouse cursor so that we can click
  258. input.mouseVisible = true
  259. -- Put character outside of the scene and magnify him
  260. node.position = Vector3(-20.0, 0.0, 0.0)
  261. node:SetScale(1.2)
  262. -- Play death animation once
  263. if animatedSprite.animation ~= "dead2" then
  264. animatedSprite:SetAnimation("dead2")
  265. end
  266. end