Files
huadexuan 7d9d0f52d1 根据提供的code differences信息,由于没有具体的代码变更内容,我将生成一个通用的commit message模板:
```
docs(readme): 更新文档说明

- 添加项目使用指南
- 完善API接口说明
- 修正错误的配置示例
```

注意:由于您提供的code differences部分为空,以上是一个示例模板。当您有具体的代码变更时,请提供详细的变更内容以便生成更准确的commit message。
2026-10-04 22:58:39 +08:00

292 lines
9.1 KiB
C#

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
/// <summary>
/// 英雄卡片拖动组件(布阵阶段使用)。
/// 拖动时在场景里生成一个半透明的英雄"幽灵"跟随指针,并吸附到最近的部署格位;
/// 松手后把该卡对应的英雄登记到格位上,卡片进入"已使用"状态。
/// </summary>
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<Canvas>();
canvasGroup = GetComponent<CanvasGroup>();
if (canvasGroup == null)
{
canvasGroup = gameObject.AddComponent<CanvasGroup>();
}
}
/// <summary>由流程控制器注入卡片配置。</summary>
public void Setup(int index, GameObject prefab, CharacterData data)
{
cardIndex = index;
heroPrefab = prefab;
characterData = data;
SetPlaced(false);
}
/// <summary>标记该卡是否已经放下去了(已放置的卡片变暗且不能再拖)。</summary>
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<Behaviour>(true))
{
if (behaviour is Animator) continue; // 保留 Animator 让它做待机动作
behaviour.enabled = false;
}
foreach (var collider in ghost.GetComponentsInChildren<Collider>(true))
{
collider.enabled = false;
}
foreach (var rb in ghost.GetComponentsInChildren<Rigidbody>(true))
{
rb.isKinematic = true;
rb.detectCollisions = false;
}
// Animator 不能带动根位移,否则幽灵会自己漂走
foreach (var animator in ghost.GetComponentsInChildren<Animator>(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<Renderer>(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<Renderer>(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();
}
}