49_Urho2DIsometricDemo.as 12 KB

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