``` docs(readme): 更新文档说明 - 添加项目使用指南 - 完善API接口说明 - 修正错误的配置示例 ``` 注意:由于您提供的code differences部分为空,以上是一个示例模板。当您有具体的代码变更时,请提供详细的变更内容以便生成更准确的commit message。
292 lines
9.1 KiB
C#
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();
|
|
}
|
|
}
|