ninja.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. // Copyright (c) 2022-2023 the Dviglo project
  2. // Copyright (c) 2008-2023 the Urho3D project
  3. // License: MIT
  4. #include "ninja.h"
  5. #include "snowball.h"
  6. #include "utilities/spawn.h"
  7. namespace Urho3D
  8. {
  9. static constexpr i32 LAYER_MOVE = 0;
  10. static constexpr i32 LAYER_ATTACK = 1;
  11. static constexpr float NINJA_MOVE_FORCE = 25.f;
  12. static constexpr float NINJA_AIR_MOVE_FORCE = 1.f;
  13. static constexpr float NINJA_DAMPING_FORCE = 5.f;
  14. static constexpr float NINJA_JUMP_FORCE = 450.f;
  15. static const Vector3 NINJA_THROW_VELOCITY(0.f, 4.25f, 20.f);
  16. static const Vector3 NINJA_THROW_POSITION(0.f, 0.2f, 1.f);
  17. static constexpr float NINJA_THROW_DELAY = 0.1f;
  18. static constexpr float NINJA_CORPSE_DURATION = 3.f;
  19. static constexpr i32 NINJA_POINTS = 250;
  20. void Ninja::RegisterObject(Context* context)
  21. {
  22. context->RegisterFactory<Ninja>();
  23. }
  24. Ninja::Ninja(Context* context)
  25. : GameObject(context)
  26. , okToJump(false)
  27. , smoke(false)
  28. , inAirTime(1.f)
  29. , onGroundTime(0.f)
  30. , throwTime(0.f)
  31. , deathTime(0.f)
  32. , deathDir(0.f)
  33. , dirChangeTime(0.f)
  34. , aimX(0.f)
  35. , aimY(0.f)
  36. {
  37. health = maxHealth = 2;
  38. onGround = false;
  39. isSliding = false;
  40. }
  41. void Ninja::DelayedStart()
  42. {
  43. SubscribeToEvent(node_, E_NODECOLLISION, URHO3D_HANDLER(Ninja, HandleNodeCollision));
  44. // Get horizontal aim from initial rotation
  45. aimX = controls.yaw_ = node_->GetRotation().YawAngle();
  46. // Start playing the idle animation immediately, even before the first physics update
  47. AnimationController* animCtrl = node_->GetChild(0)->GetComponent<AnimationController>();
  48. animCtrl->PlayExclusive("Models/NinjaSnowWar/Ninja_Idle3.ani", LAYER_MOVE, true);
  49. }
  50. void Ninja::SetControls(const Controls& newControls)
  51. {
  52. controls = newControls;
  53. }
  54. Quaternion Ninja::GetAim()
  55. {
  56. Quaternion q = Quaternion(aimX, Vector3(0.f, 1.f, 0.f));
  57. q = q * Quaternion(aimY, Vector3(1.f, 0.f, 0.f));
  58. return q;
  59. }
  60. void Ninja::FixedUpdate(float timeStep)
  61. {
  62. // For multiplayer, replicate the health into the node user variables
  63. node_->SetVar("Health", health);
  64. if (health <= 0)
  65. {
  66. DeathUpdate(timeStep);
  67. return;
  68. }
  69. // AI control if controller exists
  70. if (controller)
  71. controller->Control(this, node_, timeStep);
  72. RigidBody* body = node_->GetComponent<RigidBody>();
  73. AnimationController* animCtrl = node_->GetChild(0)->GetComponent<AnimationController>();
  74. // Turning / horizontal aiming
  75. if (aimX != controls.yaw_)
  76. aimX = controls.yaw_;
  77. // Vertical aiming
  78. if (aimY != controls.pitch_)
  79. aimY = controls.pitch_;
  80. // Force the physics rotation
  81. Quaternion q(aimX, Vector3(0.f, 1.f, 0.f));
  82. body->SetRotation(q);
  83. // Movement ground/air
  84. Vector3 vel = body->GetLinearVelocity();
  85. if (onGround)
  86. {
  87. // If landed, play a particle effect at feet (use the AnimatedModel node)
  88. if (inAirTime > 0.5)
  89. SpawnParticleEffect(node_->GetScene(), node_->GetChild(0)->GetWorldPosition(), "Particle/SnowExplosion.xml", 1);
  90. inAirTime = 0;
  91. onGroundTime += timeStep;
  92. }
  93. else
  94. {
  95. onGroundTime = 0;
  96. inAirTime += timeStep;
  97. }
  98. if (inAirTime < 0.3f && !isSliding)
  99. {
  100. bool sideMove = false;
  101. // Movement in four directions
  102. if (controls.IsDown(CTRL_UP | CTRL_DOWN | CTRL_LEFT | CTRL_RIGHT))
  103. {
  104. float animDir = 1.0f;
  105. Vector3 force(0, 0, 0);
  106. if (controls.IsDown(CTRL_UP))
  107. force += q * Vector3(0, 0, 1);
  108. if (controls.IsDown(CTRL_DOWN))
  109. {
  110. animDir = -1.0f;
  111. force += q * Vector3(0, 0, -1);
  112. }
  113. if (controls.IsDown(CTRL_LEFT))
  114. {
  115. sideMove = true;
  116. force += q * Vector3(-1, 0, 0);
  117. }
  118. if (controls.IsDown(CTRL_RIGHT))
  119. {
  120. sideMove = true;
  121. force += q * Vector3(1, 0, 0);
  122. }
  123. // Normalize so that diagonal strafing isn't faster
  124. force.Normalize();
  125. force *= NINJA_MOVE_FORCE;
  126. body->ApplyImpulse(force);
  127. // Walk or sidestep animation
  128. if (sideMove)
  129. {
  130. animCtrl->PlayExclusive("Models/NinjaSnowWar/Ninja_Stealth.ani", LAYER_MOVE, true, 0.2f);
  131. animCtrl->SetSpeed("Models/NinjaSnowWar/Ninja_Stealth.ani", animDir * 2.2f);
  132. }
  133. else
  134. {
  135. animCtrl->PlayExclusive("Models/NinjaSnowWar/Ninja_Walk.ani", LAYER_MOVE, true, 0.2f);
  136. animCtrl->SetSpeed("Models/NinjaSnowWar/Ninja_Walk.ani", animDir * 1.6f);
  137. }
  138. }
  139. else
  140. {
  141. // Idle animation
  142. animCtrl->PlayExclusive("Models/NinjaSnowWar/Ninja_Idle3.ani", LAYER_MOVE, true, 0.2f);
  143. }
  144. // Overall damping to cap maximum speed
  145. body->ApplyImpulse(Vector3(-NINJA_DAMPING_FORCE * vel.x_, 0, -NINJA_DAMPING_FORCE * vel.z_));
  146. // Jumping
  147. if (controls.IsDown(CTRL_JUMP))
  148. {
  149. if (okToJump && inAirTime < 0.1f)
  150. {
  151. // Lift slightly off the ground for better animation
  152. body->SetPosition(body->GetPosition() + Vector3(0.f, 0.03f, 0.f));
  153. body->ApplyImpulse(Vector3(0.f, NINJA_JUMP_FORCE, 0.f));
  154. inAirTime = 1.0f;
  155. animCtrl->PlayExclusive("Models/NinjaSnowWar/Ninja_JumpNoHeight.ani", LAYER_MOVE, false, 0.1f);
  156. animCtrl->SetTime("Models/NinjaSnowWar/Ninja_JumpNoHeight.ani", 0.0f); // Always play from beginning
  157. okToJump = false;
  158. }
  159. }
  160. else okToJump = true;
  161. }
  162. else
  163. {
  164. // Motion in the air
  165. // Note: when sliding a steep slope, control (or damping) isn't allowed!
  166. if (inAirTime > 0.3f && !isSliding)
  167. {
  168. if (controls.IsDown(CTRL_UP | CTRL_DOWN | CTRL_LEFT | CTRL_RIGHT))
  169. {
  170. Vector3 force(0, 0, 0);
  171. if (controls.IsDown(CTRL_UP))
  172. force += q * Vector3(0, 0, 1);
  173. if (controls.IsDown(CTRL_DOWN))
  174. force += q * Vector3(0, 0, -1);
  175. if (controls.IsDown(CTRL_LEFT))
  176. force += q * Vector3(-1, 0, 0);
  177. if (controls.IsDown(CTRL_RIGHT))
  178. force += q * Vector3(1, 0, 0);
  179. // Normalize so that diagonal strafing isn't faster
  180. force.Normalize();
  181. force *= NINJA_AIR_MOVE_FORCE;
  182. body->ApplyImpulse(force);
  183. }
  184. }
  185. // Falling/jumping/sliding animation
  186. if (inAirTime > 0.1f)
  187. animCtrl->PlayExclusive("Models/NinjaSnowWar/Ninja_JumpNoHeight.ani", LAYER_MOVE, false, 0.1f);
  188. }
  189. // Shooting
  190. if (throwTime >= 0)
  191. throwTime -= timeStep;
  192. // Start fading the attack animation after it has progressed past a certain point
  193. if (animCtrl->GetTime("Models/NinjaSnowWar/Ninja_Attack1.ani") > 0.1f)
  194. animCtrl->Fade("Models/NinjaSnowWar/Ninja_Attack1.ani", 0.0f, 0.5f);
  195. if (controls.IsPressed(CTRL_FIRE, prevControls) && throwTime <= 0.f)
  196. {
  197. Vector3 projectileVel = GetAim() * NINJA_THROW_VELOCITY;
  198. animCtrl->Play("Models/NinjaSnowWar/Ninja_Attack1.ani", LAYER_ATTACK, false, 0.0f);
  199. animCtrl->SetTime("Models/NinjaSnowWar/Ninja_Attack1.ani", 0.0f); // Always play from beginning
  200. Node* snowball = SpawnObject(node_->GetScene(), node_->GetPosition() + vel * timeStep + q * NINJA_THROW_POSITION, GetAim(), "snowball");
  201. RigidBody* snowballBody = snowball->GetComponent<RigidBody>();
  202. snowballBody->SetLinearVelocity(projectileVel);
  203. Snowball* snowballObject = snowball->GetComponent<Snowball>();
  204. snowballObject->side = side;
  205. snowballObject->creatorID = node_->GetID();
  206. PlaySound("Sounds/NutThrow.wav");
  207. throwTime = NINJA_THROW_DELAY;
  208. }
  209. prevControls = controls;
  210. ResetWorldCollision();
  211. }
  212. void Ninja::DeathUpdate(float timeStep)
  213. {
  214. RigidBody* body = node_->GetComponent<RigidBody>();
  215. CollisionShape* shape = node_->GetComponent<CollisionShape>();
  216. Node* modelNode = node_->GetChild(0);
  217. AnimationController* animCtrl = modelNode->GetComponent<AnimationController>();
  218. AnimatedModel* model = modelNode->GetComponent<AnimatedModel>();
  219. Vector3 vel = body->GetLinearVelocity();
  220. // Overall damping to cap maximum speed
  221. body->ApplyImpulse(Vector3(-NINJA_DAMPING_FORCE * vel.x_, 0, -NINJA_DAMPING_FORCE * vel.z_));
  222. // Collide only to world geometry
  223. body->SetCollisionMask(2);
  224. // Pick death animation on first death update
  225. if (deathDir == 0)
  226. {
  227. if (Random(1.0f) < 0.5f)
  228. deathDir = -1.f;
  229. else
  230. deathDir = 1.f;
  231. PlaySound("Sounds/SmallExplosion.wav");
  232. VariantMap eventData;
  233. eventData["Points"] = NINJA_POINTS;
  234. eventData["Receiver"] = lastDamageCreatorID;
  235. eventData["DamageSide"] = lastDamageSide;
  236. SendEvent("Points", eventData);
  237. SendEvent("Kill", eventData);
  238. }
  239. deathTime += timeStep;
  240. // Move the model node to center the corpse mostly within the physics cylinder
  241. // (because of the animation)
  242. if (deathDir < 0.f)
  243. {
  244. // Backward death
  245. animCtrl->StopLayer(LAYER_ATTACK, 0.1f);
  246. animCtrl->PlayExclusive("Models/NinjaSnowWar/Ninja_Death1.ani", LAYER_MOVE, false, 0.2f);
  247. animCtrl->SetSpeed("Models/NinjaSnowWar/Ninja_Death1.ani", 0.5f);
  248. if (deathTime >= 0.3f && deathTime < 0.8f)
  249. modelNode->Translate(Vector3(0.f, 0.f, 4.25f * timeStep));
  250. }
  251. else if (deathDir > 0.f)
  252. {
  253. // Forward death
  254. animCtrl->StopLayer(LAYER_ATTACK, 0.1f);
  255. animCtrl->PlayExclusive("Models/NinjaSnowWar/Ninja_Death2.ani", LAYER_MOVE, false, 0.2f);
  256. animCtrl->SetSpeed("Models/NinjaSnowWar/Ninja_Death2.ani", 0.5f);
  257. if (deathTime >= 0.4f && deathTime < 0.8f)
  258. modelNode->Translate(Vector3(0.f, 0.f, -4.25f * timeStep));
  259. }
  260. // Create smokecloud just before vanishing
  261. if (deathTime > NINJA_CORPSE_DURATION - 1.f && !smoke)
  262. {
  263. SpawnParticleEffect(node_->GetScene(), node_->GetPosition() + Vector3(0.f, -0.4f, 0.f), "Particle/Smoke.xml", 8.f);
  264. smoke = true;
  265. }
  266. if (deathTime > NINJA_CORPSE_DURATION)
  267. {
  268. SpawnObject(node_->GetScene(), node_->GetPosition() + Vector3(0.f, -0.5f, 0.f), Quaternion(), "light_flash");
  269. SpawnSound(node_->GetScene(), node_->GetPosition() + Vector3(0.f, -0.5f, 0.f), "Sounds/BigExplosion.wav", 2.f);
  270. node_->Remove();
  271. }
  272. }
  273. bool Ninja::Heal(i32 amount)
  274. {
  275. if (health == maxHealth)
  276. return false;
  277. health += amount;
  278. if (health > maxHealth)
  279. health = maxHealth;
  280. // If player, play the "powerup" sound
  281. if (side == SIDE_PLAYER)
  282. PlaySound("Sounds/Powerup.wav");
  283. return true;
  284. }
  285. } // namespace Urho3D