chunk 1: core gameplay scripts scenes runtime assets
This commit is contained in:
453
Assets/Scripts/Character/TeamControlManager.cs
Normal file
453
Assets/Scripts/Character/TeamControlManager.cs
Normal file
@@ -0,0 +1,453 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user