ai_controller.cpp 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. // Copyright (c) 2022-2023 the Dviglo project
  2. // Copyright (c) 2008-2023 the Urho3D project
  3. // License: MIT
  4. #include "ai_controller.h"
  5. #include "ninja.h"
  6. namespace Urho3D
  7. {
  8. static constexpr float INITIAL_AGGRESSION = 0.0020f;
  9. static constexpr float INITIAL_PREDICTION = 30.f;
  10. static constexpr float INITIAL_AIM_SPEED = 10.f;
  11. static constexpr float DELTA_AGGRESSION = 0.000025f;
  12. static constexpr float DELTA_PREDICTION = -0.15f;
  13. static constexpr float DELTA_AIM_SPEED = 0.30f;
  14. static constexpr float MAX_AGGRESSION = 0.01f;
  15. static constexpr float MAX_PREDICTION = 20.f;
  16. static constexpr float MAX_AIM_SPEED = 40.f;
  17. float aiAggression = INITIAL_AGGRESSION;
  18. float aiPrediction = INITIAL_PREDICTION;
  19. float aiAimSpeed = INITIAL_AIM_SPEED;
  20. void ResetAI()
  21. {
  22. aiAggression = INITIAL_AGGRESSION;
  23. aiPrediction = INITIAL_PREDICTION;
  24. aiAimSpeed = INITIAL_AIM_SPEED;
  25. }
  26. void MakeAIHarder()
  27. {
  28. aiAggression += DELTA_AGGRESSION;
  29. if (aiAggression > MAX_AGGRESSION)
  30. aiAggression = MAX_AGGRESSION;
  31. aiPrediction += DELTA_PREDICTION;
  32. if (aiPrediction < MAX_PREDICTION)
  33. aiPrediction = MAX_PREDICTION;
  34. aiAimSpeed += DELTA_AIM_SPEED;
  35. if (aiAimSpeed > MAX_AIM_SPEED)
  36. aiAimSpeed = MAX_AIM_SPEED;
  37. }
  38. void AIController::Control(Ninja* ownNinja, Node* ownNode, float timeStep)
  39. {
  40. // Get new target if none. Do not constantly scan for new targets to conserve CPU time
  41. if (!currentTarget)
  42. {
  43. newTargetTimer += timeStep;
  44. if (newTargetTimer > 0.5)
  45. GetNewTarget(ownNode);
  46. }
  47. Node* targetNode = currentTarget.Get();
  48. if (targetNode)
  49. {
  50. // Check that current target is still alive. Otherwise choose new
  51. Ninja* targetNinja = targetNode->GetComponent<Ninja>();
  52. if (!targetNinja || targetNinja->health <= 0.f)
  53. {
  54. currentTarget = nullptr;
  55. return;
  56. }
  57. RigidBody* targetBody = targetNode->GetComponent<RigidBody>();
  58. ownNinja->controls.Set(CTRL_FIRE, false);
  59. ownNinja->controls.Set(CTRL_JUMP, false);
  60. float deltaX = 0.0f;
  61. float deltaY = 0.0f;
  62. // Aim from own head to target's feet
  63. Vector3 ownPos(ownNode->GetPosition() + Vector3(0.f, 0.9f, 0.f));
  64. Vector3 targetPos(targetNode->GetPosition() + Vector3(0.f, -0.9f, 0.f));
  65. float distance = (targetPos - ownPos).Length();
  66. // Use prediction according to target distance & estimated snowball speed
  67. Vector3 currentAim(ownNinja->GetAim() * Vector3(0.f, 0.f, 1.f));
  68. float predictDistance = distance;
  69. if (predictDistance > 50.f)
  70. predictDistance = 50.f;
  71. Vector3 predictedPos = targetPos + targetBody->GetLinearVelocity() * predictDistance / aiPrediction;
  72. Vector3 targetAim = predictedPos - ownPos;
  73. // Add distance/height compensation
  74. float compensation = Max(targetAim.Length() - 15.f, 0.0f);
  75. targetAim += Vector3(0.f, 0.6f, 0.f) * compensation;
  76. // X-aiming
  77. targetAim.Normalize();
  78. Vector3 currentYaw(currentAim.x_, 0.f, currentAim.z_);
  79. Vector3 targetYaw(targetAim.x_, 0.f, targetAim.z_);
  80. currentYaw.Normalize();
  81. targetYaw.Normalize();
  82. deltaX = Clamp(Quaternion(currentYaw, targetYaw).YawAngle(), -aiAimSpeed, aiAimSpeed);
  83. // Y-aiming
  84. Vector3 currentPitch(0.f, currentAim.y_, 1.f);
  85. Vector3 targetPitch(0.f, targetAim.y_, 1.f);
  86. currentPitch.Normalize();
  87. targetPitch.Normalize();
  88. deltaY = Clamp(Quaternion(currentPitch, targetPitch).PitchAngle(), -aiAimSpeed, aiAimSpeed);
  89. ownNinja->controls.yaw_ += 0.1f * deltaX;
  90. ownNinja->controls.pitch_ += 0.1f * deltaY;
  91. // Firing? if close enough and relatively correct aim
  92. if (distance < 25.f && currentAim.DotProduct(targetAim) > 0.75f)
  93. {
  94. if (Random(1.0f) < aiAggression)
  95. ownNinja->controls.Set(CTRL_FIRE, true);
  96. }
  97. // Movement
  98. ownNinja->dirChangeTime -= timeStep;
  99. if (ownNinja->dirChangeTime <= 0.f)
  100. {
  101. ownNinja->dirChangeTime = 0.5f + Random(1.0f);
  102. ownNinja->controls.Set(CTRL_UP | CTRL_DOWN | CTRL_LEFT | CTRL_RIGHT, false);
  103. // Far distance: go forward
  104. if (distance > 30.f)
  105. ownNinja->controls.Set(CTRL_UP, true);
  106. else if (distance > 6)
  107. {
  108. // Medium distance: random strafing, predominantly forward
  109. float v = Random(1.0f);
  110. if (v < 0.8f)
  111. ownNinja->controls.Set(CTRL_UP, true);
  112. float h = Random(1.0f);
  113. if (h < 0.3f)
  114. ownNinja->controls.Set(CTRL_LEFT, true);
  115. if (h > 0.7f)
  116. ownNinja->controls.Set(CTRL_RIGHT, true);
  117. }
  118. else
  119. {
  120. // Close distance: random strafing backwards
  121. float v = Random(1.0f);
  122. if (v < 0.8f)
  123. ownNinja->controls.Set(CTRL_DOWN, true);
  124. float h = Random(1.0f);
  125. if (h < 0.4f)
  126. ownNinja->controls.Set(CTRL_LEFT, true);
  127. if (h > 0.6f)
  128. ownNinja->controls.Set(CTRL_RIGHT, true);
  129. }
  130. }
  131. // Random jump, if going forward
  132. if (ownNinja->controls.IsDown(CTRL_UP) && distance < 1000.f)
  133. {
  134. if (Random(1.0f) < aiAggression / 5.0f)
  135. ownNinja->controls.Set(CTRL_JUMP, true);
  136. }
  137. }
  138. else
  139. {
  140. // If no target, walk idly
  141. ownNinja->controls.Set(CTRL_ALL, false);
  142. ownNinja->controls.Set(CTRL_UP, true);
  143. ownNinja->dirChangeTime -= timeStep;
  144. if (ownNinja->dirChangeTime <= 0.f)
  145. {
  146. ownNinja->dirChangeTime = 1.f + Random(2.f);
  147. ownNinja->controls.yaw_ += 0.1f * (Random(600.f) - 300.f);
  148. }
  149. if (ownNinja->isSliding)
  150. ownNinja->controls.yaw_ += 0.2f;
  151. }
  152. }
  153. void AIController::GetNewTarget(Node* ownNode)
  154. {
  155. newTargetTimer = 0;
  156. Vector<Node*> nodes = ownNode->GetScene()->GetChildrenWithComponent("Ninja", true);
  157. float closestDistance = M_INFINITY;
  158. for (i32 i = 0; i < nodes.Size(); ++i)
  159. {
  160. Node* otherNode = nodes[i];
  161. Ninja* otherNinja = otherNode->GetComponent<Ninja>();
  162. if (otherNinja->side == SIDE_PLAYER && otherNinja->health > 0.f)
  163. {
  164. float distance = (ownNode->GetPosition() - otherNode->GetPosition()).LengthSquared();
  165. if (distance < closestDistance)
  166. {
  167. currentTarget = otherNode;
  168. closestDistance = distance;
  169. }
  170. }
  171. }
  172. }
  173. } // namespace Urho3D