根据提供的code differences信息,由于没有具体的代码变更内容,我将生成一个通用的commit message模板:
``` docs(readme): 更新文档说明 - 添加项目使用指南 - 完善API接口说明 - 修正错误的配置示例 ``` 注意:由于您提供的code differences部分为空,以上是一个示例模板。当您有具体的代码变更时,请提供详细的变更内容以便生成更准确的commit message。
This commit is contained in:
1 parent
8d58c2223c
commit
11f1af357d
191 files changed
+2315
-1539
No files matched your search
@@ -0,0 +1,53 @@
|
||||
using UnityEngine;
|
||||
|
||||
[CreateAssetMenu(fileName = "NewCharacterData", menuName = "召唤之战/角色数据")]
|
||||
public class CharacterData : ScriptableObject
|
||||
{
|
||||
[Header("基础信息")]
|
||||
public string characterId = "unknown";
|
||||
public string characterName = "未命名角色";
|
||||
|
||||
[TextArea(2, 4)]
|
||||
public string description = "";
|
||||
|
||||
[Header("UI 显示")]
|
||||
public Sprite cardIcon;
|
||||
|
||||
[Header("角色基础配置")]
|
||||
public GameObject unitPrefab;
|
||||
|
||||
[Header("基础属性(1级)")]
|
||||
[Min(0f)]
|
||||
public float baseMaxHealth = 100f;
|
||||
|
||||
[Min(0f)]
|
||||
public float baseAttackPower = 10f;
|
||||
|
||||
[Min(0f)]
|
||||
public float baseMoveSpeed = 3f;
|
||||
|
||||
[Header("升级成长")]
|
||||
[Min(0f)]
|
||||
public float healthPerLevel = 10f;
|
||||
|
||||
[Min(0f)]
|
||||
public float attackPerLevel = 5f;
|
||||
|
||||
[Min(0f)]
|
||||
public float moveSpeedPerLevel = 0f;
|
||||
|
||||
public float GetMaxHealth(int level)
|
||||
{
|
||||
return baseMaxHealth + (level - 1) * healthPerLevel;
|
||||
}
|
||||
|
||||
public float GetAttackPower(int level)
|
||||
{
|
||||
return baseAttackPower + (level - 1) * attackPerLevel;
|
||||
}
|
||||
|
||||
public float GetMoveSpeed(int level)
|
||||
{
|
||||
return baseMoveSpeed + (level - 1) * moveSpeedPerLevel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: W35MtHj8VX6DRkHyDmbyir4vL60Sv0I1rqcWPATEhVf3aQQfsxDbNTA=
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,69 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// 部署网格的几何参数与坐标换算。
|
||||
/// 阵型编排场景和战斗场景共用同一份参数,保证两边的格位完全对齐。
|
||||
/// </summary>
|
||||
public static class DeployGrid
|
||||
{
|
||||
public const int Cols = 6;
|
||||
public const int Rows = 5;
|
||||
public const float ColStep = 2.4f; // 列间距改为2.4,让网格更紧凑,避免最边缘格子太靠外
|
||||
public const float RowStep = 2.8f; // 行间距保持2.8
|
||||
public const float BackZ = -15.0f; // 最靠后一排的中心 Z(调整到靠近摄像机的底部区域)
|
||||
public const float CellWidth = 2.5f;
|
||||
public const float CellDepth = 2.5f; // 格子深度改为2.5,与宽度相同,形成正方形
|
||||
public const float CellHeight = 0.03f;
|
||||
public const float GridY = 0.12f; // 格位可视块的中心高度
|
||||
public const float UnitY = 0.16f; // 单位落点高度(与 NavMesh 高度一致)
|
||||
|
||||
public const int CellCount = Cols * Rows;
|
||||
|
||||
public static int ToIndex(int row, int col) => row * Cols + col;
|
||||
public static int RowOf(int index) => index / Cols;
|
||||
public static int ColOf(int index) => index % Cols;
|
||||
public static bool IsValidIndex(int index) => index >= 0 && index < CellCount;
|
||||
|
||||
/// <summary>格位中心的世界坐标(单位落点高度)。</summary>
|
||||
public static Vector3 CellPosition(int index)
|
||||
{
|
||||
int r = RowOf(index);
|
||||
int c = ColOf(index);
|
||||
float x = (c - (Cols - 1) * 0.5f) * ColStep;
|
||||
float z = BackZ + r * RowStep;
|
||||
return new Vector3(x, UnitY, z);
|
||||
}
|
||||
|
||||
/// <summary>把地面上的世界坐标吸附到最近格位;不在网格范围内返回 -1。</summary>
|
||||
public static int SnapToCell(Vector3 world)
|
||||
{
|
||||
int c = Mathf.RoundToInt(world.x / ColStep + (Cols - 1) * 0.5f);
|
||||
int r = Mathf.RoundToInt((world.z - BackZ) / RowStep);
|
||||
if (c < 0 || c >= Cols || r < 0 || r >= Rows)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
Vector3 center = CellPosition(ToIndex(r, c));
|
||||
if (Mathf.Abs(center.x - world.x) > ColStep * 0.5f) return -1;
|
||||
if (Mathf.Abs(center.z - world.z) > RowStep * 0.5f) return -1;
|
||||
return ToIndex(r, c);
|
||||
}
|
||||
|
||||
/// <summary>把一组单位在网格宽度内均匀铺开,返回列号。</summary>
|
||||
public static int SpreadColumn(int indexInGroup, int groupCount)
|
||||
{
|
||||
if (groupCount <= 1) return Cols / 2;
|
||||
if (groupCount >= Cols) return indexInGroup % Cols;
|
||||
|
||||
float step = (Cols - 1f) / (groupCount + 1f);
|
||||
return Mathf.RoundToInt(step * (indexInGroup + 1));
|
||||
}
|
||||
|
||||
/// <summary>默认阵型格位:近战在最前排(靠近敌人),远程在最后排。</summary>
|
||||
public static int DefaultCell(bool isMelee, int indexInGroup, int groupCount)
|
||||
{
|
||||
int row = isMelee ? Rows - 1 : 0;
|
||||
return ToIndex(row, SpreadColumn(indexInGroup, groupCount));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: XXoXtCz7VXiyQdKhueDTuYKlvhzkgNB45Y2PwopU8MNl9mgUf7KaCPo=
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,24 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// 阵型数据:阵型编排场景写入,战斗场景读取。
|
||||
/// 静态字段在同一个运行会话内跨场景保留;重启游戏会回到「默认阵型」。
|
||||
/// </summary>
|
||||
public static class FormationData
|
||||
{
|
||||
/// <summary>出战英雄的 Prefab(按卡位顺序)。</summary>
|
||||
public static GameObject[] HeroPrefabs = new GameObject[0];
|
||||
|
||||
/// <summary>每个英雄所在的格位索引(-1 表示未放置)。</summary>
|
||||
public static int[] CellIndices = new int[0];
|
||||
|
||||
/// <summary>玩家是否手动编排过阵型。</summary>
|
||||
public static bool HasCustomFormation;
|
||||
|
||||
public static void Clear()
|
||||
{
|
||||
HeroPrefabs = new GameObject[0];
|
||||
CellIndices = new int[0];
|
||||
HasCustomFormation = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: XHtO43yuVy6LtTGJW6cMWRsuX0NZfpi3dDBIk7k/z9U09UsZonVt2P4=
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,469 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.UI;
|
||||
|
||||
/// <summary>
|
||||
/// 阵型编排页控制器:底部英雄卡拖到网格格位放置英雄,「开始战斗」保存阵型并进入战斗场景。
|
||||
/// 什么都不摆直接开始 = 默认阵型(近战最前排 / 远程最后排)。
|
||||
/// </summary>
|
||||
public class FormationManager : MonoBehaviour
|
||||
{
|
||||
[Header("出战英雄名单")]
|
||||
public HeroRoster roster;
|
||||
|
||||
[Header("战斗场景")]
|
||||
public string battleSceneName = "DefenseTest";
|
||||
|
||||
[Header("界面引用")]
|
||||
public Text hintText;
|
||||
public Button defaultButton;
|
||||
public Button startButton;
|
||||
public FormationCardDrag[] cards = new FormationCardDrag[0];
|
||||
|
||||
// 格位索引 -> 放置记录
|
||||
private class Placement
|
||||
{
|
||||
public int heroIndex;
|
||||
public GameObject preview;
|
||||
}
|
||||
|
||||
private readonly Dictionary<int, Placement> placed = new Dictionary<int, Placement>();
|
||||
private readonly HashSet<int> placedHeroes = new HashSet<int>();
|
||||
|
||||
private Camera mainCamera;
|
||||
private GameObject dropHighlight;
|
||||
private Material highlightMaterial;
|
||||
private int draggingHero = -1;
|
||||
|
||||
private static readonly Color ValidColor = new Color(0.2f, 1f, 0.35f, 0.40f);
|
||||
private static readonly Color InvalidColor = new Color(1f, 0.25f, 0.2f, 0.40f);
|
||||
|
||||
private void Start()
|
||||
{
|
||||
mainCamera = Camera.main;
|
||||
CreateDropHighlight();
|
||||
SetupCards();
|
||||
|
||||
if (defaultButton != null) defaultButton.onClick.AddListener(ApplyDefaultFormation);
|
||||
if (startButton != null) startButton.onClick.AddListener(StartBattle);
|
||||
|
||||
UpdateHint();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (draggingHero >= 0)
|
||||
{
|
||||
UpdateDropHighlight(Input.mousePosition);
|
||||
}
|
||||
|
||||
// 点击场上的英雄把它收回卡片
|
||||
if (Input.GetMouseButtonDown(0) && !IsPointerOverUI())
|
||||
{
|
||||
TryRemoveAtPointer();
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 卡片初始化 =====
|
||||
|
||||
private void SetupCards()
|
||||
{
|
||||
if (cards == null) return;
|
||||
|
||||
for (int i = 0; i < cards.Length; i++)
|
||||
{
|
||||
FormationCardDrag card = cards[i];
|
||||
if (card == null) continue;
|
||||
|
||||
card.manager = this;
|
||||
card.heroIndex = i;
|
||||
|
||||
HeroEntry entry = GetHero(i);
|
||||
card.SetContent(entry != null ? entry.displayName : $"英雄{i + 1}",
|
||||
entry != null ? entry.icon : null);
|
||||
card.SetPlaced(false);
|
||||
}
|
||||
}
|
||||
|
||||
private HeroEntry GetHero(int index)
|
||||
{
|
||||
if (roster == null || roster.heroes == null) return null;
|
||||
if (index < 0 || index >= roster.heroes.Length) return null;
|
||||
return roster.heroes[index];
|
||||
}
|
||||
|
||||
private int HeroCount => roster != null && roster.heroes != null ? roster.heroes.Length : 0;
|
||||
|
||||
// ===== 拖拽回调(由 FormationCardDrag 调用) =====
|
||||
|
||||
public bool CanDrag(int heroIndex)
|
||||
{
|
||||
return GetHero(heroIndex) != null && !placedHeroes.Contains(heroIndex);
|
||||
}
|
||||
|
||||
public void OnCardDragStart(int heroIndex)
|
||||
{
|
||||
draggingHero = heroIndex;
|
||||
UpdateDropHighlight(Input.mousePosition);
|
||||
}
|
||||
|
||||
public void OnCardDragging(int heroIndex, Vector2 screenPosition)
|
||||
{
|
||||
draggingHero = heroIndex;
|
||||
UpdateDropHighlight(screenPosition);
|
||||
}
|
||||
|
||||
public void OnCardDragEnd(int heroIndex, Vector2 screenPosition)
|
||||
{
|
||||
draggingHero = -1;
|
||||
if (dropHighlight != null) dropHighlight.SetActive(false);
|
||||
|
||||
int cell = ScreenToCell(screenPosition);
|
||||
if (cell < 0)
|
||||
{
|
||||
return; // 没拖到网格范围内,忽略
|
||||
}
|
||||
|
||||
if (placed.ContainsKey(cell))
|
||||
{
|
||||
Debug.Log("这个格位已经有英雄了");
|
||||
return;
|
||||
}
|
||||
|
||||
PlaceHero(heroIndex, cell);
|
||||
}
|
||||
|
||||
// ===== 放置 / 移除 =====
|
||||
|
||||
private void PlaceHero(int heroIndex, int cell)
|
||||
{
|
||||
HeroEntry entry = GetHero(heroIndex);
|
||||
if (entry == null || entry.prefab == null || !DeployGrid.IsValidIndex(cell)) return;
|
||||
if (placed.ContainsKey(cell)) return;
|
||||
|
||||
GameObject preview = Instantiate(entry.prefab, DeployGrid.CellPosition(cell), Quaternion.identity);
|
||||
preview.name = $"预览_{entry.displayName}";
|
||||
PreparePreview(preview);
|
||||
|
||||
placed[cell] = new Placement { heroIndex = heroIndex, preview = preview };
|
||||
placedHeroes.Add(heroIndex);
|
||||
|
||||
if (cards != null && heroIndex < cards.Length && cards[heroIndex] != null)
|
||||
{
|
||||
cards[heroIndex].SetPlaced(true);
|
||||
}
|
||||
|
||||
UpdateHint();
|
||||
}
|
||||
|
||||
/// <summary>预览体只做展示:关掉 AI 与寻路,避免它自己跑起来。</summary>
|
||||
private static void PreparePreview(GameObject preview)
|
||||
{
|
||||
var unitBase = preview.GetComponent<UnitBase>();
|
||||
if (unitBase != null) unitBase.enabled = false;
|
||||
|
||||
var agent = preview.GetComponent<UnityEngine.AI.NavMeshAgent>();
|
||||
if (agent != null) agent.enabled = false;
|
||||
|
||||
var status = preview.GetComponent<StatusEffectController>();
|
||||
if (status != null) status.enabled = false;
|
||||
|
||||
var animator = preview.GetComponent<Animator>();
|
||||
if (animator != null) animator.applyRootMotion = false;
|
||||
}
|
||||
|
||||
private bool TryRemoveAtPointer()
|
||||
{
|
||||
if (mainCamera == null)
|
||||
{
|
||||
mainCamera = Camera.main;
|
||||
if (mainCamera == null) return false;
|
||||
}
|
||||
|
||||
Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
|
||||
RaycastHit[] hits = Physics.RaycastAll(ray, 200f);
|
||||
|
||||
GameObject closest = null;
|
||||
float closestDistance = float.MaxValue;
|
||||
foreach (RaycastHit hit in hits)
|
||||
{
|
||||
GameObject root = hit.collider.transform.root.gameObject;
|
||||
if (IsPreview(root) && hit.distance < closestDistance)
|
||||
{
|
||||
closestDistance = hit.distance;
|
||||
closest = root;
|
||||
}
|
||||
}
|
||||
|
||||
if (closest == null) return false;
|
||||
|
||||
RemovePreview(closest);
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsPreview(GameObject candidate)
|
||||
{
|
||||
foreach (var pair in placed)
|
||||
{
|
||||
if (pair.Value.preview == candidate) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void RemovePreview(GameObject preview)
|
||||
{
|
||||
int cell = -1;
|
||||
foreach (var pair in placed)
|
||||
{
|
||||
if (pair.Value.preview == preview)
|
||||
{
|
||||
cell = pair.Key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (cell < 0) return;
|
||||
|
||||
int heroIndex = placed[cell].heroIndex;
|
||||
placed.Remove(cell);
|
||||
placedHeroes.Remove(heroIndex);
|
||||
|
||||
if (cards != null && heroIndex < cards.Length && cards[heroIndex] != null)
|
||||
{
|
||||
cards[heroIndex].SetPlaced(false);
|
||||
}
|
||||
|
||||
Destroy(preview);
|
||||
UpdateHint();
|
||||
}
|
||||
|
||||
private void ClearAll()
|
||||
{
|
||||
foreach (var pair in placed)
|
||||
{
|
||||
if (pair.Value.preview != null) Destroy(pair.Value.preview);
|
||||
}
|
||||
placed.Clear();
|
||||
placedHeroes.Clear();
|
||||
|
||||
if (cards != null)
|
||||
{
|
||||
foreach (FormationCardDrag card in cards)
|
||||
{
|
||||
if (card != null) card.SetPlaced(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 默认阵型 =====
|
||||
|
||||
public void ApplyDefaultFormation()
|
||||
{
|
||||
ClearAll();
|
||||
|
||||
int meleeTotal = 0, rangedTotal = 0;
|
||||
for (int i = 0; i < HeroCount; i++)
|
||||
{
|
||||
HeroEntry entry = GetHero(i);
|
||||
if (entry == null) continue;
|
||||
if (entry.isMelee) meleeTotal++; else rangedTotal++;
|
||||
}
|
||||
|
||||
int meleeIndex = 0, rangedIndex = 0;
|
||||
for (int i = 0; i < HeroCount; i++)
|
||||
{
|
||||
HeroEntry entry = GetHero(i);
|
||||
if (entry == null) continue;
|
||||
|
||||
int cell = entry.isMelee
|
||||
? DeployGrid.DefaultCell(true, meleeIndex++, meleeTotal)
|
||||
: DeployGrid.DefaultCell(false, rangedIndex++, rangedTotal);
|
||||
PlaceHero(i, cell);
|
||||
}
|
||||
|
||||
UpdateHint();
|
||||
}
|
||||
|
||||
/// <summary>没摆放的英雄补到默认位置,保证开战时全部出战。</summary>
|
||||
private void FillUnplacedWithDefault()
|
||||
{
|
||||
for (int i = 0; i < HeroCount; i++)
|
||||
{
|
||||
HeroEntry entry = GetHero(i);
|
||||
if (entry == null || placedHeroes.Contains(i)) continue;
|
||||
|
||||
int row = entry.isMelee ? DeployGrid.Rows - 1 : 0;
|
||||
int cell = FindFreeCellInRow(row);
|
||||
if (cell < 0) cell = FindFreeCellInRow(DeployGrid.Rows / 2);
|
||||
if (cell >= 0) PlaceHero(i, cell);
|
||||
}
|
||||
}
|
||||
|
||||
private int FindFreeCellInRow(int row)
|
||||
{
|
||||
for (int c = 0; c < DeployGrid.Cols; c++)
|
||||
{
|
||||
int index = DeployGrid.ToIndex(row, c);
|
||||
if (!placed.ContainsKey(index)) return index;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ===== 保存阵型 =====
|
||||
|
||||
public void StartBattle()
|
||||
{
|
||||
FillUnplacedWithDefault();
|
||||
SaveFormationToJson();
|
||||
}
|
||||
|
||||
private void SaveFormationToJson()
|
||||
{
|
||||
int count = HeroCount;
|
||||
var characterIds = new string[count];
|
||||
var cells = new int[count];
|
||||
|
||||
// 初始化数组
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
HeroEntry entry = GetHero(i);
|
||||
characterIds[i] = entry != null ? GetCharacterId(entry.prefab) : "";
|
||||
cells[i] = -1;
|
||||
}
|
||||
|
||||
// 填充已放置的格位
|
||||
foreach (var pair in placed)
|
||||
{
|
||||
int heroIndex = pair.Value.heroIndex;
|
||||
if (heroIndex >= 0 && heroIndex < count)
|
||||
{
|
||||
cells[heroIndex] = pair.Key;
|
||||
}
|
||||
}
|
||||
|
||||
// 保存到 PlayerData.json
|
||||
if (PlayerDataManager.Instance != null)
|
||||
{
|
||||
PlayerDataManager.Instance.SaveFormation(characterIds, cells);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("PlayerDataManager.Instance 未找到,无法保存阵型");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据 Prefab 获取 characterId
|
||||
/// </summary>
|
||||
private string GetCharacterId(GameObject prefab)
|
||||
{
|
||||
if (prefab == null) return "";
|
||||
|
||||
// 查找对应的 CharacterData
|
||||
var allData = Resources.LoadAll<CharacterData>("Data");
|
||||
foreach (var data in allData)
|
||||
{
|
||||
if (data.unitPrefab == prefab)
|
||||
{
|
||||
return data.characterId;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
private void SaveFormation()
|
||||
{
|
||||
int count = HeroCount;
|
||||
var prefabs = new GameObject[count];
|
||||
var cells = new int[count];
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
HeroEntry entry = GetHero(i);
|
||||
prefabs[i] = entry != null ? entry.prefab : null;
|
||||
cells[i] = -1;
|
||||
}
|
||||
|
||||
foreach (var pair in placed)
|
||||
{
|
||||
int heroIndex = pair.Value.heroIndex;
|
||||
if (heroIndex >= 0 && heroIndex < count)
|
||||
{
|
||||
cells[heroIndex] = pair.Key;
|
||||
}
|
||||
}
|
||||
|
||||
FormationData.HeroPrefabs = prefabs;
|
||||
FormationData.CellIndices = cells;
|
||||
FormationData.HasCustomFormation = placed.Count > 0;
|
||||
}
|
||||
|
||||
// ===== 落点高亮与坐标换算 =====
|
||||
|
||||
private void CreateDropHighlight()
|
||||
{
|
||||
dropHighlight = GameObject.CreatePrimitive(PrimitiveType.Quad);
|
||||
dropHighlight.name = "DropHighlight";
|
||||
Destroy(dropHighlight.GetComponent<Collider>());
|
||||
dropHighlight.transform.rotation = Quaternion.Euler(90f, 0f, 0f);
|
||||
dropHighlight.transform.localScale = new Vector3(DeployGrid.CellWidth * 0.95f, DeployGrid.CellDepth * 0.95f, 1f);
|
||||
|
||||
highlightMaterial = new Material(Shader.Find("Sprites/Default"));
|
||||
dropHighlight.GetComponent<Renderer>().sharedMaterial = highlightMaterial;
|
||||
dropHighlight.SetActive(false);
|
||||
}
|
||||
|
||||
private void UpdateDropHighlight(Vector2 screenPosition)
|
||||
{
|
||||
if (dropHighlight == null) return;
|
||||
|
||||
int cell = ScreenToCell(screenPosition);
|
||||
if (cell < 0)
|
||||
{
|
||||
dropHighlight.SetActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
dropHighlight.SetActive(true);
|
||||
Vector3 position = DeployGrid.CellPosition(cell);
|
||||
dropHighlight.transform.position = new Vector3(position.x, DeployGrid.GridY + 0.02f, position.z);
|
||||
highlightMaterial.color = placed.ContainsKey(cell) ? InvalidColor : ValidColor;
|
||||
}
|
||||
|
||||
/// <summary>屏幕坐标 -> 格位索引(用地平面求交,不受场上单位碰撞体干扰)。</summary>
|
||||
private int ScreenToCell(Vector2 screenPosition)
|
||||
{
|
||||
if (mainCamera == null)
|
||||
{
|
||||
mainCamera = Camera.main;
|
||||
if (mainCamera == null) return -1;
|
||||
}
|
||||
|
||||
Ray ray = mainCamera.ScreenPointToRay(screenPosition);
|
||||
var groundPlane = new Plane(Vector3.up, new Vector3(0f, DeployGrid.UnitY, 0f));
|
||||
if (!groundPlane.Raycast(ray, out float distance)) return -1;
|
||||
|
||||
return DeployGrid.SnapToCell(ray.GetPoint(distance));
|
||||
}
|
||||
|
||||
private static bool IsPointerOverUI()
|
||||
{
|
||||
return EventSystem.current != null && EventSystem.current.IsPointerOverGameObject();
|
||||
}
|
||||
|
||||
private void UpdateHint()
|
||||
{
|
||||
if (hintText == null) return;
|
||||
|
||||
if (placed.Count == 0)
|
||||
{
|
||||
hintText.text = "把下面的英雄卡拖到网格上摆放;不摆放也可以,直接开战 = 默认阵型(近战前排 / 远程后排)";
|
||||
}
|
||||
else
|
||||
{
|
||||
hintText.text = $"已摆放 {placed.Count}/{HeroCount} 点击场上的英雄可以收回";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: WXsZ4S+qAH8QkYuNGjhInfMp6J/A6Q3ZDVjB5sZJlUgtZVMEA9Vbo/g=
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,68 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// 主角金币产出组件,挂载到主角单位上
|
||||
/// </summary>
|
||||
public class HeroGoldProducer : MonoBehaviour
|
||||
{
|
||||
[Header("金币产出")]
|
||||
[Tooltip("每次产出的金币数量")]
|
||||
[SerializeField] private int goldPerProduction = 30;
|
||||
|
||||
[Tooltip("产出间隔(秒)")]
|
||||
[SerializeField] private float productionInterval = 8f;
|
||||
|
||||
[Header("可选:血条和进度条")]
|
||||
[SerializeField] private WorldHealthBar healthBar;
|
||||
|
||||
private float productionTimer = 0f;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// 如果没有手动指定,尝试自动获取
|
||||
if (healthBar == null)
|
||||
{
|
||||
healthBar = GetComponent<WorldHealthBar>();
|
||||
}
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// 初始化进度条(使用能量条显示产出进度)
|
||||
if (healthBar != null)
|
||||
{
|
||||
healthBar.maxEnergy = 100f;
|
||||
healthBar.currentEnergy = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (GoldManager.Instance == null) return;
|
||||
|
||||
productionTimer += Time.deltaTime;
|
||||
|
||||
// 更新进度条
|
||||
if (healthBar != null)
|
||||
{
|
||||
float progress = Mathf.Clamp01(productionTimer / productionInterval);
|
||||
healthBar.currentEnergy = progress * 100f;
|
||||
}
|
||||
|
||||
// 达到产出时间
|
||||
if (productionTimer >= productionInterval)
|
||||
{
|
||||
ProduceGold();
|
||||
productionTimer = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
private void ProduceGold()
|
||||
{
|
||||
if (GoldManager.Instance != null)
|
||||
{
|
||||
GoldManager.Instance.AddGold(goldPerProduction);
|
||||
Debug.Log($"主角 {gameObject.name} 产出金币 +{goldPerProduction}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: DXtJt3ukAH4tf7lqyiIY2BvuX1qD7blxwir1MHXJuADSEvE+JkFXqpU=
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,28 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>一名出战英雄的配置。</summary>
|
||||
[System.Serializable]
|
||||
public class HeroEntry
|
||||
{
|
||||
[Tooltip("显示在卡片上的名字")]
|
||||
public string displayName;
|
||||
|
||||
[Tooltip("战斗场景里生成的单位 Prefab")]
|
||||
public GameObject prefab;
|
||||
|
||||
[Tooltip("卡片头像,可为空")]
|
||||
public Sprite icon;
|
||||
|
||||
[Tooltip("近战 = 默认阵型时放在前排")]
|
||||
public bool isMelee;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 出战英雄名单:阵型编排场景和战斗场景共用同一份数据资产。
|
||||
/// 后续接入卡组系统后,由卡组编辑页写入这里。
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "HeroRoster", menuName = "召唤之战/出战英雄名单")]
|
||||
public class HeroRoster : ScriptableObject
|
||||
{
|
||||
public HeroEntry[] heroes = new HeroEntry[0];
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: CSgesCqoAHoxoXa1Q1+jR8mzKpyfDONP/ph44XUJ1GKkqDmVEQppK4Q=
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
[Serializable]
|
||||
public class LevelConfig
|
||||
{
|
||||
public string levelId;
|
||||
public string levelName;
|
||||
public int difficulty;
|
||||
public WaveConfig[] waves;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class WaveConfig
|
||||
{
|
||||
public int waveIndex;
|
||||
public float triggerZPosition;
|
||||
public EnemyConfig[] enemies;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class EnemyConfig
|
||||
{
|
||||
public string characterDataId;
|
||||
public int count;
|
||||
public int level;
|
||||
public Vector3[] spawnPositions;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: Bywctn+sW3rD7Vfs/oyS08mqQkbyUT1yU67Try45O1adM+WapPU5/Bc=
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,34 @@
|
||||
using UnityEngine;
|
||||
|
||||
[System.Serializable]
|
||||
public class PlayerCharacterData
|
||||
{
|
||||
public string characterId;
|
||||
public int level = 1;
|
||||
public int starLevel = 0;
|
||||
|
||||
public PlayerCharacterData(string id, int lvl = 1, int star = 0)
|
||||
{
|
||||
characterId = id;
|
||||
level = lvl;
|
||||
starLevel = star;
|
||||
}
|
||||
|
||||
public float GetMaxHealth(CharacterData baseData)
|
||||
{
|
||||
if (baseData == null) return 100f;
|
||||
return baseData.GetMaxHealth(level);
|
||||
}
|
||||
|
||||
public float GetAttackPower(CharacterData baseData)
|
||||
{
|
||||
if (baseData == null) return 10f;
|
||||
return baseData.GetAttackPower(level);
|
||||
}
|
||||
|
||||
public float GetMoveSpeed(CharacterData baseData)
|
||||
{
|
||||
if (baseData == null) return 3f;
|
||||
return baseData.GetMoveSpeed(level);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: WitJtyj5ASgYib4ykLWBx1orXnFLNnbEn1ajXTPSEV2Wft8lNSxcKrE=
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in new issue
Block a user