454 lines
16 KiB
C#
454 lines
16 KiB
C#
using UnityEngine;
|
|
using UnityEngine.InputSystem;
|
|
using System.Collections.Generic;
|
|
|
|
/// <summary>
|
|
/// TeamControlManager - Manages player control switching between team members.
|
|
/// Inspired by FRAG Pro Shooter's instant character switching system.
|
|
/// </summary>
|
|
public class TeamControlManager : MonoBehaviour
|
|
{
|
|
public static TeamControlManager Instance { get; private set; }
|
|
|
|
[Header("Current Control")]
|
|
[SerializeField] private GameObject currentControlledCharacter;
|
|
[SerializeField] private int currentCharacterIndex = 0;
|
|
|
|
[Header("Team References")]
|
|
[SerializeField] private List<GameObject> playerTeamCharacters = new List<GameObject>();
|
|
|
|
[Header("Camera Settings")]
|
|
[SerializeField] private bool autoFollowCamera = true;
|
|
|
|
// Events
|
|
public event System.Action<GameObject, GameObject> OnControlSwitched; // (old, new)
|
|
|
|
private void Awake()
|
|
{
|
|
if (Instance != null && Instance != this)
|
|
{
|
|
Destroy(gameObject);
|
|
return;
|
|
}
|
|
Instance = this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register a character to the player team
|
|
/// </summary>
|
|
public void RegisterPlayerTeamCharacter(GameObject character, bool isPlayerControlled = false)
|
|
{
|
|
if (!playerTeamCharacters.Contains(character))
|
|
{
|
|
playerTeamCharacters.Add(character);
|
|
DevLog.Log($"[TeamControlManager] Registered {character.name} to player team");
|
|
|
|
if (isPlayerControlled)
|
|
{
|
|
SetControlledCharacter(character);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove a character from the team (e.g., on death)
|
|
/// </summary>
|
|
public void UnregisterCharacter(GameObject character)
|
|
{
|
|
if (playerTeamCharacters.Contains(character))
|
|
{
|
|
playerTeamCharacters.Remove(character);
|
|
DevLog.Log($"[TeamControlManager] Unregistered {character.name}");
|
|
|
|
// If this was the controlled character, switch to another
|
|
if (currentControlledCharacter == character)
|
|
{
|
|
SwitchToNextAvailableCharacter();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set which character is player-controlled using the unified ControlType system
|
|
/// </summary>
|
|
public void SetControlledCharacter(GameObject newCharacter)
|
|
{
|
|
if (newCharacter == null || newCharacter == currentControlledCharacter) return;
|
|
|
|
GameObject oldCharacter = currentControlledCharacter;
|
|
|
|
// Get TeamMember components
|
|
TeamMember oldMember = oldCharacter?.GetComponent<TeamMember>();
|
|
TeamMember newMember = newCharacter.GetComponent<TeamMember>();
|
|
|
|
// Switch control using ControlType system (handles all component config!)
|
|
if (oldMember != null)
|
|
{
|
|
// Old character becomes PlayerAI (still on player team, but AI controlled)
|
|
oldMember.SetControlType(TeamMember.ControlType.PlayerAI);
|
|
DevLog.Log($"[TeamControlManager] {oldCharacter.name} -> PlayerAI");
|
|
}
|
|
|
|
if (newMember != null)
|
|
{
|
|
// New character becomes HumanControlled
|
|
newMember.SetControlType(TeamMember.ControlType.HumanControlled);
|
|
DevLog.Log($"[TeamControlManager] {newCharacter.name} -> HumanControlled");
|
|
|
|
// CRITICAL: Bind input actions for the new controlled character
|
|
BindPlayerInputActions(newCharacter);
|
|
}
|
|
else
|
|
{
|
|
// Fallback for characters without TeamMember (shouldn't happen)
|
|
DevLog.LogWarning($"[TeamControlManager] {newCharacter.name} has no TeamMember component!");
|
|
EnablePlayerControl(newCharacter);
|
|
}
|
|
|
|
currentControlledCharacter = newCharacter;
|
|
currentCharacterIndex = playerTeamCharacters.IndexOf(newCharacter);
|
|
|
|
// Update camera using CameraManager (preferred) or legacy UpdateCameraTarget
|
|
if (CameraManager.Instance != null)
|
|
{
|
|
CameraManager.Instance.SetTarget(newMember);
|
|
}
|
|
else if (autoFollowCamera)
|
|
{
|
|
UpdateCameraTarget(newCharacter);
|
|
}
|
|
|
|
DevLog.Log($"[TeamControlManager] Switched control to {newCharacter.name}");
|
|
OnControlSwitched?.Invoke(oldCharacter, newCharacter);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Switch to the next character in the team
|
|
/// </summary>
|
|
public void SwitchToNextCharacter()
|
|
{
|
|
if (playerTeamCharacters.Count <= 1) return;
|
|
|
|
int nextIndex = (currentCharacterIndex + 1) % playerTeamCharacters.Count;
|
|
|
|
// Find next alive character
|
|
for (int i = 0; i < playerTeamCharacters.Count; i++)
|
|
{
|
|
int checkIndex = (currentCharacterIndex + 1 + i) % playerTeamCharacters.Count;
|
|
GameObject candidate = playerTeamCharacters[checkIndex];
|
|
|
|
if (candidate != null && candidate.activeInHierarchy && IsCharacterAlive(candidate))
|
|
{
|
|
SetControlledCharacter(candidate);
|
|
return;
|
|
}
|
|
}
|
|
|
|
DevLog.LogWarning("[TeamControlManager] No other alive characters to switch to!");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Switch to a specific character by index
|
|
/// </summary>
|
|
public void SwitchToCharacter(int index)
|
|
{
|
|
if (index >= 0 && index < playerTeamCharacters.Count)
|
|
{
|
|
GameObject character = playerTeamCharacters[index];
|
|
if (character != null && character.activeInHierarchy && IsCharacterAlive(character))
|
|
{
|
|
SetControlledCharacter(character);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Switch to next available character when current dies
|
|
/// </summary>
|
|
private void SwitchToNextAvailableCharacter()
|
|
{
|
|
foreach (var character in playerTeamCharacters)
|
|
{
|
|
if (character != null && character.activeInHierarchy && IsCharacterAlive(character))
|
|
{
|
|
SetControlledCharacter(character);
|
|
return;
|
|
}
|
|
}
|
|
|
|
Debug.LogError("[TeamControlManager] No available characters to control!");
|
|
currentControlledCharacter = null;
|
|
}
|
|
|
|
private bool IsCharacterAlive(GameObject character)
|
|
{
|
|
// Check if character has health and is alive
|
|
HealthNew health = character.GetComponent<HealthNew>();
|
|
if (health != null)
|
|
{
|
|
return !health.isDead;
|
|
}
|
|
return true; // Assume alive if no health component
|
|
}
|
|
|
|
private void EnablePlayerControl(GameObject character)
|
|
{
|
|
// Update TeamMember
|
|
TeamMember teamMember = character.GetComponent<TeamMember>();
|
|
if (teamMember != null)
|
|
{
|
|
teamMember.IsPlayerControlled = true;
|
|
}
|
|
|
|
// Enable PlayerScript
|
|
PlayerScript playerScript = character.GetComponent<PlayerScript>();
|
|
if (playerScript != null)
|
|
{
|
|
playerScript.enabled = true;
|
|
playerScript.attack = true;
|
|
}
|
|
|
|
// Enable PlayerInput
|
|
PlayerInput playerInput = character.GetComponent<PlayerInput>();
|
|
if (playerInput != null)
|
|
{
|
|
playerInput.enabled = true;
|
|
try
|
|
{
|
|
playerInput.SwitchCurrentActionMap("Player Controls");
|
|
}
|
|
catch (System.Exception) { }
|
|
}
|
|
|
|
// Update tag
|
|
character.tag = "Player";
|
|
|
|
DevLog.Log($"[TeamControlManager] Enabled player control on {character.name}");
|
|
}
|
|
|
|
private void DisablePlayerControl(GameObject character)
|
|
{
|
|
// Update TeamMember
|
|
TeamMember teamMember = character.GetComponent<TeamMember>();
|
|
if (teamMember != null)
|
|
{
|
|
teamMember.IsPlayerControlled = false;
|
|
}
|
|
|
|
// Disable PlayerScript (attacks, not movement animation)
|
|
PlayerScript playerScript = character.GetComponent<PlayerScript>();
|
|
if (playerScript != null)
|
|
{
|
|
playerScript.attack = false;
|
|
// Keep enabled for animation but disable input
|
|
}
|
|
|
|
// Disable PlayerInput
|
|
PlayerInput playerInput = character.GetComponent<PlayerInput>();
|
|
if (playerInput != null)
|
|
{
|
|
playerInput.enabled = false;
|
|
}
|
|
|
|
DevLog.Log($"[TeamControlManager] Disabled player control on {character.name}");
|
|
}
|
|
|
|
private void EnableTeamAI(GameObject character)
|
|
{
|
|
TeamAIController aiController = character.GetComponent<TeamAIController>();
|
|
if (aiController != null)
|
|
{
|
|
aiController.enabled = true;
|
|
}
|
|
|
|
// Also enable CashSystemAI if present
|
|
CashSystemAI cashAI = character.GetComponent<CashSystemAI>();
|
|
if (cashAI != null)
|
|
{
|
|
cashAI.enabled = true;
|
|
}
|
|
|
|
DevLog.Log($"[TeamControlManager] Enabled AI on {character.name}");
|
|
}
|
|
|
|
private void DisableTeamAI(GameObject character)
|
|
{
|
|
TeamAIController aiController = character.GetComponent<TeamAIController>();
|
|
if (aiController != null)
|
|
{
|
|
aiController.enabled = false;
|
|
}
|
|
|
|
// Also disable CashSystemAI if present (player makes own decisions)
|
|
CashSystemAI cashAI = character.GetComponent<CashSystemAI>();
|
|
if (cashAI != null)
|
|
{
|
|
cashAI.enabled = false;
|
|
}
|
|
|
|
DevLog.Log($"[TeamControlManager] Disabled AI on {character.name}");
|
|
}
|
|
|
|
private void UpdateCameraTarget(GameObject character)
|
|
{
|
|
// Correct separation: Follow = root (orbit center), LookAt = head (aim point)
|
|
Transform followTarget = character.transform;
|
|
Transform lookAtTarget = FindCharacterHead(character);
|
|
|
|
// Update Cinemachine FreeLook with correct separation
|
|
Cinemachine.CinemachineFreeLook[] freeLookCameras = FindObjectsOfType<Cinemachine.CinemachineFreeLook>();
|
|
foreach (var cam in freeLookCameras)
|
|
{
|
|
if (cam.gameObject.activeInHierarchy)
|
|
{
|
|
cam.Follow = followTarget;
|
|
cam.LookAt = lookAtTarget;
|
|
}
|
|
}
|
|
|
|
// Update Cinemachine Virtual Cameras
|
|
Cinemachine.CinemachineVirtualCamera[] virtualCameras = FindObjectsOfType<Cinemachine.CinemachineVirtualCamera>();
|
|
foreach (var cam in virtualCameras)
|
|
{
|
|
if (cam.gameObject.activeInHierarchy)
|
|
{
|
|
cam.Follow = followTarget;
|
|
cam.LookAt = lookAtTarget;
|
|
}
|
|
}
|
|
|
|
DevLog.Log($"[TeamControlManager] Updated camera to follow {character.name} (Follow=root, LookAt=head)");
|
|
}
|
|
|
|
private Transform FindCharacterHead(GameObject character)
|
|
{
|
|
string[] headNames = { "Head", "head", "Bip001 Head", "mixamorig:Head", "Bip01 Head" };
|
|
|
|
foreach (string headName in headNames)
|
|
{
|
|
Transform head = FindChildRecursive(character.transform, headName);
|
|
if (head != null) return head;
|
|
}
|
|
|
|
// Fallback to character root
|
|
return character.transform;
|
|
}
|
|
|
|
private Transform FindChildRecursive(Transform parent, string name)
|
|
{
|
|
foreach (Transform child in parent)
|
|
{
|
|
if (child.name == name) return child;
|
|
Transform found = FindChildRecursive(child, name);
|
|
if (found != null) return found;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the currently controlled character
|
|
/// </summary>
|
|
public GameObject GetCurrentControlledCharacter() => currentControlledCharacter;
|
|
|
|
/// <summary>
|
|
/// Get all player team characters
|
|
/// </summary>
|
|
public List<GameObject> GetPlayerTeamCharacters() => playerTeamCharacters;
|
|
|
|
/// <summary>
|
|
/// Clear all registered characters
|
|
/// </summary>
|
|
public void ClearTeam()
|
|
{
|
|
playerTeamCharacters.Clear();
|
|
currentControlledCharacter = null;
|
|
currentCharacterIndex = 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bind input action callbacks to PlayerScript methods.
|
|
/// This mirrors what Practice mode does in SelectionOptions.SetUpPlayerInputAI.
|
|
/// </summary>
|
|
private void BindPlayerInputActions(GameObject character)
|
|
{
|
|
PlayerInput playerInput = character.GetComponent<PlayerInput>();
|
|
PlayerScript playerScript = character.GetComponent<PlayerScript>();
|
|
|
|
if (playerInput == null || playerScript == null)
|
|
{
|
|
DevLog.LogWarning($"[TeamControlManager] Cannot bind input - PlayerInput: {playerInput != null}, PlayerScript: {playerScript != null}");
|
|
return;
|
|
}
|
|
|
|
// Ensure PlayerInput is enabled
|
|
playerInput.enabled = true;
|
|
try { playerInput.SwitchCurrentActionMap("Player Controls"); } catch { }
|
|
|
|
var actionMap = playerInput.currentActionMap;
|
|
if (actionMap == null)
|
|
{
|
|
Debug.LogError("[TeamControlManager] No current action map found!");
|
|
return;
|
|
}
|
|
|
|
// CRITICAL: Bind the Move action!
|
|
TryBindAction(actionMap, "Move", playerScript.OnMove);
|
|
|
|
// Bind attack actions
|
|
TryBindAction(actionMap, "Jab", playerScript.OnJab);
|
|
TryBindAction(actionMap, "Right", playerScript.OnRight);
|
|
TryBindAction(actionMap, "LeftHook", playerScript.OnLeftHook);
|
|
TryBindAction(actionMap, "RightHook", playerScript.OnRightHook);
|
|
TryBindAction(actionMap, "LeftElbow", playerScript.OnLeftElbow);
|
|
TryBindAction(actionMap, "RightElbow", playerScript.OnRightElbow);
|
|
TryBindAction(actionMap, "RightBody", playerScript.OnRightBody);
|
|
TryBindAction(actionMap, "PowerPunchLeft", playerScript.OnPowerPunchLeft);
|
|
TryBindAction(actionMap, "SupermanPunch", playerScript.OnSupermanPunch);
|
|
|
|
// Kick actions
|
|
TryBindAction(actionMap, "LegKickRight", playerScript.OnLegKickRight);
|
|
TryBindAction(actionMap, "BodyKickRight", playerScript.OnBodyKickRight);
|
|
TryBindAction(actionMap, "KneeRight", playerScript.OnKneeRight);
|
|
TryBindAction(actionMap, "BackSideKick", playerScript.OnBackSideKick);
|
|
TryBindAction(actionMap, "FrontKickRight", playerScript.OnFrontKickRight);
|
|
TryBindAction(actionMap, "LegPowerKickRight", playerScript.OnLegPowerKickRight);
|
|
|
|
// Dodge actions
|
|
TryBindAction(actionMap, "JumpBack", playerScript.OnJumpBack);
|
|
TryBindAction(actionMap, "JumpLeft", playerScript.OnJumpLeft);
|
|
TryBindAction(actionMap, "JumpRight", playerScript.OnJumpRight);
|
|
|
|
// Block actions
|
|
TryBindAction(actionMap, "BlockStepBack", playerScript.OnBlockStepBack);
|
|
TryBindAction(actionMap, "BlockBodyLeft", playerScript.OnBlockBodyLeft);
|
|
TryBindAction(actionMap, "BlockLeg", playerScript.OnBlockLeg);
|
|
|
|
// Special attacks
|
|
TryBindAction(actionMap, "Chokeslam", playerScript.OnChokeslam);
|
|
TryBindAction(actionMap, "Suplex", playerScript.OnSuplex);
|
|
TryBindAction(actionMap, "GiantSwing", playerScript.OnGiantSwing);
|
|
TryBindAction(actionMap, "DiamondCrusher", playerScript.OnDiamondCrusher);
|
|
TryBindAction(actionMap, "SumoSlap", playerScript.OnSumoSlap);
|
|
TryBindAction(actionMap, "JavelinTackle", playerScript.OnJavelinTackle);
|
|
TryBindAction(actionMap, "RKO", playerScript.OnRKO);
|
|
TryBindAction(actionMap, "Spear", playerScript.OnSpear);
|
|
TryBindAction(actionMap, "RockBottom", playerScript.OnRockBottom);
|
|
TryBindAction(actionMap, "F5", playerScript.OnF5);
|
|
|
|
DevLog.Log($"[TeamControlManager] Input actions bound for {character.name}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper to safely bind an action
|
|
/// </summary>
|
|
private void TryBindAction(InputActionMap actionMap, string actionName, System.Action<InputAction.CallbackContext> callback)
|
|
{
|
|
InputAction action = actionMap.FindAction(actionName);
|
|
if (action != null)
|
|
{
|
|
action.performed += callback;
|
|
if (actionName == "Move") action.canceled += callback;
|
|
}
|
|
}
|
|
}
|