ソースを参照

Add healer animation: face healing target and channeling pose during heal

Co-authored-by: djeada <[email protected]>
copilot-swe-agent[bot] 1 週間 前
コミット
097186d26a

+ 2 - 0
game/core/component.h

@@ -285,6 +285,8 @@ public:
   float healing_cooldown{2.0F};
   float time_since_last_heal{0.0F};
   bool is_healing_active{false};
+  float healing_target_x{0.0F};
+  float healing_target_z{0.0F};
 };
 
 class CatapultLoadingComponent : public Component {

+ 11 - 0
game/systems/healing_system.cpp

@@ -79,6 +79,17 @@ void HealingSystem::process_healing(Engine::Core::World *world,
           target_unit->health = target_unit->max_health;
         }
 
+        // Store healing target position for animation
+        healer_comp->healing_target_x = target_transform->position.x;
+        healer_comp->healing_target_z = target_transform->position.z;
+
+        // Make healer face the target being healed
+        if (dist > 0.1F) {
+          float const target_yaw = std::atan2(dx, dz) * 180.0F / 3.14159265F;
+          healer_transform->desired_yaw = target_yaw;
+          healer_transform->has_desired_yaw = true;
+        }
+
         if (healing_beam_system != nullptr) {
           QVector3D const healer_pos(healer_transform->position.x,
                                      healer_transform->position.y + 1.2F,

+ 42 - 9
render/entity/nations/carthage/healer_renderer.cpp

@@ -150,15 +150,48 @@ public:
     float const arm_height_jitter = (hash_01(seed ^ 0xABCDU) - 0.5F) * 0.03F;
     float const arm_asymmetry = (hash_01(seed ^ 0xDEF0U) - 0.5F) * 0.04F;
 
-    QVector3D const idle_hand_l(-0.10F + arm_asymmetry,
-                                HP::SHOULDER_Y + 0.10F + arm_height_jitter,
-                                0.45F);
-    QVector3D const idle_hand_r(
-        0.10F - arm_asymmetry * 0.5F,
-        HP::SHOULDER_Y + 0.10F + arm_height_jitter * 0.8F, 0.45F);
-
-    controller.placeHandAt(true, idle_hand_l);
-    controller.placeHandAt(false, idle_hand_r);
+    if (anim.is_healing) {
+      // Healing animation: mystical channeling pose with staff-like gestures
+      float const healing_time = anim.time * 2.2F;
+      float const sway_phase = std::sin(healing_time);
+      float const sway_phase_offset = std::sin(healing_time + 0.7F);
+
+      // One arm raised high (channeling), one arm extended forward
+      float const high_arm_height =
+          HP::SHOULDER_Y + 0.18F + 0.04F * sway_phase + arm_height_jitter;
+      float const extended_arm_height =
+          HP::SHOULDER_Y + 0.05F + 0.02F * sway_phase_offset + arm_height_jitter;
+
+      // Gentle circular motion for mystical effect
+      float const circle_x = 0.03F * std::sin(healing_time * 0.8F);
+      float const circle_z = 0.02F * std::cos(healing_time * 0.8F);
+
+      QVector3D const heal_hand_l(-0.08F + arm_asymmetry + circle_x,
+                                   high_arm_height,
+                                   0.28F + circle_z);
+      QVector3D const heal_hand_r(0.15F - arm_asymmetry * 0.5F,
+                                   extended_arm_height,
+                                   0.40F + 0.03F * sway_phase_offset);
+
+      controller.placeHandAt(true, heal_hand_l);
+      controller.placeHandAt(false, heal_hand_r);
+
+      // Subtle body sway during channeling
+      float const torso_sway = 0.012F * sway_phase;
+      controller.tilt_torso(torso_sway, 0.008F * sway_phase_offset);
+
+    } else {
+      // Normal idle pose with staff
+      QVector3D const idle_hand_l(-0.10F + arm_asymmetry,
+                                  HP::SHOULDER_Y + 0.10F + arm_height_jitter,
+                                  0.45F);
+      QVector3D const idle_hand_r(
+          0.10F - arm_asymmetry * 0.5F,
+          HP::SHOULDER_Y + 0.10F + arm_height_jitter * 0.8F, 0.45F);
+
+      controller.placeHandAt(true, idle_hand_l);
+      controller.placeHandAt(false, idle_hand_r);
+    }
   }
 
   void add_attachments(const DrawContext &ctx, const HumanoidVariant &v,

+ 51 - 9
render/entity/nations/roman/healer_renderer.cpp

@@ -98,15 +98,57 @@ public:
     float const arm_height_jitter = (hash_01(seed ^ 0xABCDU) - 0.5F) * 0.03F;
     float const arm_asymmetry = (hash_01(seed ^ 0xDEF0U) - 0.5F) * 0.06F;
 
-    float const forward_offset = 0.16F + (anim.is_moving ? 0.05F : 0.0F);
-    float const hand_height = HP::WAIST_Y + 0.04F + arm_height_jitter;
-    QVector3D const idle_hand_l(-0.16F + arm_asymmetry, hand_height,
-                                forward_offset);
-    QVector3D const idle_hand_r(0.12F - arm_asymmetry * 0.6F,
-                                hand_height + 0.01F, forward_offset * 0.9F);
-
-    controller.placeHandAt(true, idle_hand_l);
-    controller.placeHandAt(false, idle_hand_r);
+    if (anim.is_healing) {
+      // Healing animation: raised arms with gentle swaying motion
+      float const healing_time = anim.time * 2.5F;
+      float const sway_phase = std::sin(healing_time);
+      float const sway_phase_offset = std::sin(healing_time + 0.5F);
+
+      // Arms raised outward in channeling pose
+      float const base_arm_height = HP::SHOULDER_Y - 0.02F + arm_height_jitter;
+      float const sway_height = 0.03F * sway_phase;
+
+      // Arms extended toward target direction
+      float const target_dist =
+          std::sqrt(anim.healing_target_dx * anim.healing_target_dx +
+                    anim.healing_target_dz * anim.healing_target_dz);
+      float target_dir_x = 0.0F;
+      float target_dir_z = 1.0F;
+      if (target_dist > 0.01F) {
+        target_dir_x = anim.healing_target_dx / target_dist;
+        target_dir_z = anim.healing_target_dz / target_dist;
+      }
+
+      // Hands positioned forward and outward for channeling
+      float const arm_spread = 0.18F + 0.02F * sway_phase_offset;
+      float const forward_reach = 0.22F + 0.03F * std::sin(healing_time * 0.7F);
+
+      QVector3D const heal_hand_l(-arm_spread + arm_asymmetry * 0.3F,
+                                   base_arm_height + sway_height,
+                                   forward_reach);
+      QVector3D const heal_hand_r(arm_spread - arm_asymmetry * 0.3F,
+                                   base_arm_height + sway_height + 0.01F,
+                                   forward_reach * 0.95F);
+
+      controller.placeHandAt(true, heal_hand_l);
+      controller.placeHandAt(false, heal_hand_r);
+
+      // Gentle torso sway during healing
+      float const torso_sway = 0.015F * sway_phase;
+      controller.tilt_torso(torso_sway, 0.0F);
+
+    } else {
+      // Normal idle pose
+      float const forward_offset = 0.16F + (anim.is_moving ? 0.05F : 0.0F);
+      float const hand_height = HP::WAIST_Y + 0.04F + arm_height_jitter;
+      QVector3D const idle_hand_l(-0.16F + arm_asymmetry, hand_height,
+                                  forward_offset);
+      QVector3D const idle_hand_r(0.12F - arm_asymmetry * 0.6F,
+                                  hand_height + 0.01F, forward_offset * 0.9F);
+
+      controller.placeHandAt(true, idle_hand_l);
+      controller.placeHandAt(false, idle_hand_r);
+    }
   }
 
   void add_attachments(const DrawContext &ctx, const HumanoidVariant &v,

+ 8 - 0
render/gl/humanoid/animation/animation_inputs.cpp

@@ -79,6 +79,14 @@ auto sample_anim_state(const DrawContext &ctx) -> AnimationInputs {
   }
   anim.is_moving = ((movement != nullptr) && movement->has_target);
 
+  // Check for healing state
+  auto *healer = ctx.entity->get_component<Engine::Core::HealerComponent>();
+  if (healer != nullptr && healer->is_healing_active && transform != nullptr) {
+    anim.is_healing = true;
+    anim.healing_target_dx = healer->healing_target_x - transform->position.x;
+    anim.healing_target_dz = healer->healing_target_z - transform->position.z;
+  }
+
   if (combat_state != nullptr) {
     anim.combat_phase = map_combat_state_to_phase(combat_state->animation_state);
     if (combat_state->state_duration > 0.0F) {

+ 3 - 0
render/gl/humanoid/humanoid_types.h

@@ -29,6 +29,9 @@ struct AnimationInputs {
   std::uint8_t attack_variant{0};
   bool is_hit_reacting{false};
   float hit_reaction_intensity{0.0F};
+  bool is_healing{false};
+  float healing_target_dx{0.0F};
+  float healing_target_dz{0.0F};
 };
 
 struct FormationParams {

+ 21 - 0
render/humanoid/pose_controller.cpp

@@ -639,4 +639,25 @@ void HumanoidPoseController::spear_thrust_variant(float attack_phase,
   placeHandAt(true, hand_l_target);
 }
 
+void HumanoidPoseController::tilt_torso(float side_tilt, float forward_tilt) {
+  // Apply subtle tilt to upper body components
+  // side_tilt: positive = lean right, negative = lean left
+  // forward_tilt: positive = lean forward, negative = lean back
+
+  QVector3D const right = m_anim_ctx.heading_right();
+  QVector3D const forward = m_anim_ctx.heading_forward();
+
+  QVector3D const offset = right * side_tilt + forward * forward_tilt;
+
+  // Apply offset to upper body positions
+  m_pose.shoulder_l += offset;
+  m_pose.shoulder_r += offset;
+  m_pose.neck_base += offset * 1.2F;
+  m_pose.head_pos += offset * 1.5F;
+
+  // Also adjust body frames if they're initialized
+  m_pose.body_frames.torso.origin += offset;
+  m_pose.body_frames.head.origin += offset * 1.5F;
+}
+
 } // namespace Render::GL

+ 1 - 0
render/humanoid/pose_controller.h

@@ -31,6 +31,7 @@ public:
   void hold_sword_and_shield();
   void look_at(const QVector3D &target);
   void hit_flinch(float intensity);
+  void tilt_torso(float side_tilt, float forward_tilt);
 
   auto solve_elbow_ik(bool is_left, const QVector3D &shoulder,
                       const QVector3D &hand, const QVector3D &outward_dir,