using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems; /// /// 英雄卡片拖动组件(布阵阶段使用)。 /// 拖动时在场景里生成一个半透明的英雄"幽灵"跟随指针,并吸附到最近的部署格位; /// 松手后把该卡对应的英雄登记到格位上,卡片进入"已使用"状态。 /// public class HeroCardDrag : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler { [Header("配置(由 DefenseGameFlowController 注入)")] public int cardIndex; // 卡片序号,0 起 public GameObject heroPrefab; // 对应英雄 Prefab public CharacterData characterData; // 角色数据 [Header("幽灵外观")] [Range(0.1f, 1f)] public float ghostAlpha = 0.55f; private Canvas canvas; private CanvasGroup canvasGroup; private GameObject ghost; private Material ghostHiddenMaterial; private bool dragging; private int currentCell = -1; private const float PlacedCardAlpha = 0.35f; private void Awake() { canvas = GetComponentInParent(); canvasGroup = GetComponent(); if (canvasGroup == null) { canvasGroup = gameObject.AddComponent(); } } /// 由流程控制器注入卡片配置。 public void Setup(int index, GameObject prefab, CharacterData data) { cardIndex = index; heroPrefab = prefab; characterData = data; SetPlaced(false); } /// 标记该卡是否已经放下去了(已放置的卡片变暗且不能再拖)。 public void SetPlaced(bool placed) { if (canvasGroup != null) { canvasGroup.alpha = placed ? PlacedCardAlpha : 1f; } } public void OnBeginDrag(PointerEventData eventData) { var flow = DefenseGameFlowController.Instance; if (flow == null || !flow.IsDeployPhase) return; if (flow.IsCardPlaced(cardIndex)) return; // 已经放置过了 if (heroPrefab == null) return; dragging = true; currentCell = -1; if (canvasGroup != null) { canvasGroup.alpha = 0.5f; canvasGroup.blocksRaycasts = false; } CreateGhost(); FollowPointer(eventData.position); } public void OnDrag(PointerEventData eventData) { if (!dragging) return; FollowPointer(eventData.position); } public void OnEndDrag(PointerEventData eventData) { if (!dragging) return; dragging = false; if (canvasGroup != null) { canvasGroup.alpha = 1f; canvasGroup.blocksRaycasts = true; } if (DeployHighlight.Instance != null) DeployHighlight.Instance.Hide(); int cell = currentCell; currentCell = -1; DestroyGhost(); if (cell >= 0) { var flow = DefenseGameFlowController.Instance; if (flow != null && flow.PlaceHeroFromCard(cardIndex, cell)) { SetPlaced(true); } } } // ── 幽灵 ──────────────────────────────────────────────── private void CreateGhost() { ghost = Instantiate(heroPrefab); ghost.name = "Ghost_" + heroPrefab.name; // 只保留外观:关掉所有逻辑组件与碰撞体 foreach (var behaviour in ghost.GetComponentsInChildren(true)) { if (behaviour is Animator) continue; // 保留 Animator 让它做待机动作 behaviour.enabled = false; } foreach (var collider in ghost.GetComponentsInChildren(true)) { collider.enabled = false; } foreach (var rb in ghost.GetComponentsInChildren(true)) { rb.isKinematic = true; rb.detectCollisions = false; } // Animator 不能带动根位移,否则幽灵会自己漂走 foreach (var animator in ghost.GetComponentsInChildren(true)) { animator.applyRootMotion = false; } // 血条 UI 是 WorldHealthBar.Awake 生成的,幽灵不需要 var healthBar = ghost.transform.Find("WorldHealthBar"); if (healthBar != null) { Destroy(healthBar.gameObject); } BuildGhostMaterials(); } private void BuildGhostMaterials() { if (ghostHiddenMaterial == null) { var hiddenShader = Shader.Find("Sprites/Default") ?? Shader.Find("Unlit/Color"); ghostHiddenMaterial = new Material(hiddenShader); ghostHiddenMaterial.name = "GhostHidden"; ghostHiddenMaterial.color = new Color(1f, 1f, 1f, 0f); } var bodyShader = Shader.Find("Standard"); foreach (var renderer in ghost.GetComponentsInChildren(true)) { var source = renderer.sharedMaterials; var result = new Material[source.Length]; for (int i = 0; i < source.Length; i++) { // 第 0 个材质是本体,做成半透明;其余(描边等附加材质)隐藏掉 if (i == 0 && source[i] != null && bodyShader != null) { result[i] = MakeTransparent(source[i], ghostAlpha, bodyShader); } else { result[i] = ghostHiddenMaterial; } } renderer.sharedMaterials = result; renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; } } private static Material MakeTransparent(Material source, float alpha, Shader standard) { var mat = new Material(source); mat.name = source.name + "_Ghost"; // 统一用 Standard 的透明混合模式,保证任何贴图都能变成半透明 if (mat.shader != standard) { var mainTex = mat.HasProperty("_MainTex") ? mat.GetTexture("_MainTex") : null; var color = mat.HasProperty("_Color") ? mat.GetColor("_Color") : Color.white; mat.shader = standard; if (mainTex != null) mat.SetTexture("_MainTex", mainTex); mat.SetColor("_Color", color); } if (mat.HasProperty("_Mode")) { mat.SetFloat("_Mode", 3f); // Transparent mat.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha); mat.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha); mat.SetInt("_ZWrite", 0); mat.DisableKeyword("_ALPHATEST_ON"); mat.EnableKeyword("_ALPHABLEND_ON"); mat.DisableKeyword("_ALPHAPREMULTIPLY_ON"); mat.renderQueue = 3000; } if (mat.HasProperty("_Color")) { var c = mat.GetColor("_Color"); c.a = alpha; mat.SetColor("_Color", c); } return mat; } private void FollowPointer(Vector2 screenPosition) { if (ghost == null) return; var cam = Camera.main; if (cam == null) return; var ray = cam.ScreenPointToRay(screenPosition); var ground = new Plane(Vector3.up, new Vector3(0f, DeployGrid.UnitY, 0f)); if (!ground.Raycast(ray, out float distance)) { return; } Vector3 world = ray.GetPoint(distance); int cell = DeployGrid.SnapToCellClamped(world); if (cell == currentCell) return; currentCell = cell; var flow = DefenseGameFlowController.Instance; bool blocked = cell < 0 || (flow != null && flow.IsCellOccupied(cell)); if (cell >= 0) { ghost.transform.position = DeployGrid.CellPosition(cell); if (DeployHighlight.Instance != null) DeployHighlight.Instance.Show(cell, blocked); } else { // 没吸附到格位:幽灵跟着指针但压暗提示不可放置 ghost.transform.position = new Vector3(world.x, DeployGrid.UnitY, world.z); if (DeployHighlight.Instance != null) DeployHighlight.Instance.Hide(); } } private void DestroyGhost() { if (ghost == null) return; foreach (var renderer in ghost.GetComponentsInChildren(true)) { if (renderer.sharedMaterials == null) continue; foreach (var mat in renderer.sharedMaterials) { if (mat != null && mat != ghostHiddenMaterial) Destroy(mat); } } Destroy(ghost); ghost = null; } private void OnDisable() { if (dragging) { dragging = false; if (canvasGroup != null) { canvasGroup.alpha = 1f; canvasGroup.blocksRaycasts = true; } if (DeployHighlight.Instance != null) DeployHighlight.Instance.Hide(); } DestroyGhost(); } }