Character2D.cpp 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. //
  2. // Copyright (c) 2008-2020 the Urho3D project.
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to deal
  6. // in the Software without restriction, including without limitation the rights
  7. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. // copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20. // THE SOFTWARE.
  21. //
  22. #include <Urho3D/Urho2D/AnimatedSprite2D.h>
  23. #include <Urho3D/Urho2D/AnimationSet2D.h>
  24. #include <Urho3D/Core/Context.h>
  25. #include <Urho3D/Input/Input.h>
  26. #include <Urho3D/IO/MemoryBuffer.h>
  27. #include <Urho3D/Urho2D/PhysicsWorld2D.h>
  28. #include <Urho3D/Urho2D/RigidBody2D.h>
  29. #include <Urho3D/Scene/Scene.h>
  30. #include <Urho3D/Scene/SceneEvents.h>
  31. #include <Urho3D/UI/Text.h>
  32. #include <Urho3D/UI/UI.h>
  33. #include <Urho3D/DebugNew.h>
  34. #include "Character2D.h"
  35. // Character2D logic component
  36. Character2D::Character2D(Context* context) :
  37. LogicComponent(context),
  38. wounded_(false),
  39. killed_(false),
  40. timer_(0.0f),
  41. maxCoins_(0),
  42. remainingCoins_(0),
  43. remainingLifes_(3),
  44. isClimbing_(false),
  45. climb2_(false),
  46. aboveClimbable_(false),
  47. onSlope_(false)
  48. {
  49. }
  50. void Character2D::RegisterObject(Context* context)
  51. {
  52. context->RegisterFactory<Character2D>();
  53. // These macros register the class attributes to the Context for automatic load / save handling.
  54. // We specify the 'Default' attribute mode which means it will be used both for saving into file, and network replication.
  55. URHO3D_ATTRIBUTE("Wounded", bool, wounded_, false, AM_DEFAULT);
  56. URHO3D_ATTRIBUTE("Killed", bool, killed_, false, AM_DEFAULT);
  57. URHO3D_ATTRIBUTE("Timer", float, timer_, 0.0f, AM_DEFAULT);
  58. URHO3D_ATTRIBUTE("Coins In Level", int, maxCoins_, 0, AM_DEFAULT);
  59. URHO3D_ATTRIBUTE("Remaining Coins", int, remainingCoins_, 0, AM_DEFAULT);
  60. URHO3D_ATTRIBUTE("Remaining Lifes", int, remainingLifes_, 3, AM_DEFAULT);
  61. // Note that we don't load/save isClimbing_ as the contact listener already sets this bool.
  62. URHO3D_ATTRIBUTE("Is Climbing Rope", bool, climb2_, false, AM_DEFAULT);
  63. URHO3D_ATTRIBUTE("Is Above Climbable", bool, aboveClimbable_, false, AM_DEFAULT);
  64. URHO3D_ATTRIBUTE("Is On Slope", bool, onSlope_, false, AM_DEFAULT);
  65. }
  66. void Character2D::Update(float timeStep)
  67. {
  68. // Handle wounded/killed states
  69. if (killed_)
  70. return;
  71. if (wounded_)
  72. {
  73. HandleWoundedState(timeStep);
  74. return;
  75. }
  76. // Set temporary variables
  77. auto* input = GetSubsystem<Input>();
  78. auto* body = GetComponent<RigidBody2D>();
  79. auto* animatedSprite = GetComponent<AnimatedSprite2D>();
  80. bool onGround = false;
  81. bool jump = false;
  82. // Collision detection (AABB query)
  83. Vector2 characterHalfSize = Vector2(0.16f, 0.16f);
  84. auto* physicsWorld = GetScene()->GetComponent<PhysicsWorld2D>();
  85. PODVector<RigidBody2D*> collidingBodies;
  86. physicsWorld->GetRigidBodies(collidingBodies, Rect(node_->GetWorldPosition2D() - characterHalfSize - Vector2(0.0f, 0.1f), node_->GetWorldPosition2D() + characterHalfSize));
  87. if (collidingBodies.Size() > 1 && !isClimbing_)
  88. onGround = true;
  89. // Set direction
  90. Vector2 moveDir = Vector2::ZERO; // Reset
  91. if (input->GetKeyDown(KEY_A) || input->GetKeyDown(KEY_LEFT))
  92. {
  93. moveDir = moveDir + Vector2::LEFT;
  94. animatedSprite->SetFlipX(false); // Flip sprite (reset to default play on the X axis)
  95. }
  96. if (input->GetKeyDown(KEY_D) || input->GetKeyDown(KEY_RIGHT))
  97. {
  98. moveDir = moveDir + Vector2::RIGHT;
  99. animatedSprite->SetFlipX(true); // Flip sprite (flip animation on the X axis)
  100. }
  101. // Jump
  102. if ((onGround || aboveClimbable_) && (input->GetKeyPress(KEY_W) || input->GetKeyPress(KEY_UP)))
  103. jump = true;
  104. // Climb
  105. if (isClimbing_)
  106. {
  107. if (!aboveClimbable_ && (input->GetKeyDown(KEY_UP) || input->GetKeyDown(KEY_W)))
  108. moveDir = moveDir + Vector2(0.0f, 1.0f);
  109. if (input->GetKeyDown(KEY_DOWN) || input->GetKeyDown(KEY_S))
  110. moveDir = moveDir + Vector2(0.0f, -1.0f);
  111. }
  112. // Move
  113. if (!moveDir.Equals(Vector2::ZERO) || jump)
  114. {
  115. if (onSlope_)
  116. body->ApplyForceToCenter(moveDir * MOVE_SPEED / 2, true); // When climbing a slope, apply force (todo: replace by setting linear velocity to zero when will work)
  117. else
  118. node_->Translate(Vector3(moveDir.x_, moveDir.y_, 0) * timeStep * 1.8f);
  119. if (jump)
  120. body->ApplyLinearImpulse(Vector2(0.0f, 0.17f) * MOVE_SPEED, body->GetMassCenter(), true);
  121. }
  122. // Animate
  123. if (input->GetKeyDown(KEY_SPACE))
  124. {
  125. if (animatedSprite->GetAnimation() != "attack")
  126. {
  127. animatedSprite->SetAnimation("attack", LM_FORCE_LOOPED);
  128. animatedSprite->SetSpeed(1.5f);
  129. }
  130. }
  131. else if (!moveDir.Equals(Vector2::ZERO))
  132. {
  133. if (animatedSprite->GetAnimation() != "run")
  134. animatedSprite->SetAnimation("run");
  135. }
  136. else if (animatedSprite->GetAnimation() != "idle")
  137. {
  138. animatedSprite->SetAnimation("idle");
  139. }
  140. }
  141. void Character2D::HandleWoundedState(float timeStep)
  142. {
  143. auto* body = GetComponent<RigidBody2D>();
  144. auto* animatedSprite = GetComponent<AnimatedSprite2D>();
  145. // Play "hit" animation in loop
  146. if (animatedSprite->GetAnimation() != "hit")
  147. animatedSprite->SetAnimation("hit", LM_FORCE_LOOPED);
  148. // Update timer
  149. timer_ += timeStep;
  150. if (timer_ > 2.0f)
  151. {
  152. // Reset timer
  153. timer_ = 0.0f;
  154. // Clear forces (should be performed by setting linear velocity to zero, but currently doesn't work)
  155. body->SetLinearVelocity(Vector2::ZERO);
  156. body->SetAwake(false);
  157. body->SetAwake(true);
  158. // Remove particle emitter
  159. node_->GetChild("Emitter", true)->Remove();
  160. // Update lifes UI and counter
  161. remainingLifes_ -= 1;
  162. auto* ui = GetSubsystem<UI>();
  163. Text* lifeText = static_cast<Text*>(ui->GetRoot()->GetChild("LifeText", true));
  164. lifeText->SetText(String(remainingLifes_)); // Update lifes UI counter
  165. // Reset wounded state
  166. wounded_ = false;
  167. // Handle death
  168. if (remainingLifes_ == 0)
  169. {
  170. HandleDeath();
  171. return;
  172. }
  173. // Re-position the character to the nearest point
  174. if (node_->GetPosition().x_ < 15.0f)
  175. node_->SetPosition(Vector3(1.0f, 8.0f, 0.0f));
  176. else
  177. node_->SetPosition(Vector3(18.8f, 9.2f, 0.0f));
  178. }
  179. }
  180. void Character2D::HandleDeath()
  181. {
  182. auto* body = GetComponent<RigidBody2D>();
  183. auto* animatedSprite = GetComponent<AnimatedSprite2D>();
  184. // Set state to 'killed'
  185. killed_ = true;
  186. // Update UI elements
  187. auto* ui = GetSubsystem<UI>();
  188. Text* instructions = static_cast<Text*>(ui->GetRoot()->GetChild("Instructions", true));
  189. instructions->SetText("!!! GAME OVER !!!");
  190. static_cast<Text*>(ui->GetRoot()->GetChild("ExitButton", true))->SetVisible(true);
  191. static_cast<Text*>(ui->GetRoot()->GetChild("PlayButton", true))->SetVisible(true);
  192. // Show mouse cursor so that we can click
  193. auto* input = GetSubsystem<Input>();
  194. input->SetMouseVisible(true);
  195. // Put character outside of the scene and magnify him
  196. node_->SetPosition(Vector3(-20.0f, 0.0f, 0.0f));
  197. node_->SetScale(1.2f);
  198. // Play death animation once
  199. if (animatedSprite->GetAnimation() != "dead2")
  200. animatedSprite->SetAnimation("dead2");
  201. }