根据提供的code differences信息,由于没有具体的代码变更内容,我将生成一个通用的commit message模板:
``` docs(readme): 更新文档说明 - 添加项目使用指南 - 完善API接口说明 - 修正错误的配置示例 ```
This commit is contained in:
1 parent
a74d1e62d7
commit
8d58c2223c
167 files changed
+1119
-15679
No files matched your search
@@ -0,0 +1,127 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class Barracks : MonoBehaviour
|
||||
{
|
||||
[Header("角色数据")]
|
||||
public CharacterData characterData;
|
||||
|
||||
[Header("阵营")]
|
||||
public string unitTag = "Friend";
|
||||
|
||||
[Header("生产设置")]
|
||||
public Transform spawnPoint; // 单位生成位置
|
||||
|
||||
[Header("UI显示")]
|
||||
public Image progressBar; // 生产进度条
|
||||
public Image characterIcon; // 角色头像
|
||||
|
||||
private float spawnTimer = 0f;
|
||||
private float spawnInterval = 5f;
|
||||
private bool isProducing = true;
|
||||
private BuildingHealth buildingHealth;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
buildingHealth = GetComponent<BuildingHealth>();
|
||||
|
||||
if (characterData != null)
|
||||
{
|
||||
spawnInterval = 5f;
|
||||
|
||||
// 设置角色头像
|
||||
if (characterIcon != null && characterData.cardIcon != null)
|
||||
{
|
||||
characterIcon.sprite = characterData.cardIcon;
|
||||
}
|
||||
|
||||
Debug.Log($"兵营初始化:{characterData.characterName},生产间隔{spawnInterval}秒");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("兵营缺少角色数据!");
|
||||
}
|
||||
|
||||
// 如果没有spawnPoint,使用兵营自己的位置
|
||||
if (spawnPoint == null)
|
||||
{
|
||||
spawnPoint = transform;
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!isProducing || characterData == null) return;
|
||||
|
||||
spawnTimer += Time.deltaTime;
|
||||
|
||||
// 更新进度条
|
||||
if (buildingHealth != null)
|
||||
{
|
||||
buildingHealth.SetProductionProgress(spawnTimer / spawnInterval);
|
||||
}
|
||||
else if (progressBar != null)
|
||||
{
|
||||
progressBar.fillAmount = spawnTimer / spawnInterval;
|
||||
}
|
||||
|
||||
// 时间到,生产单位
|
||||
if (spawnTimer >= spawnInterval)
|
||||
{
|
||||
SpawnUnit();
|
||||
spawnTimer = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
private void SpawnUnit()
|
||||
{
|
||||
if (characterData == null || characterData.unitPrefab == null)
|
||||
{
|
||||
Debug.LogError("无法生产单位:缺少unitPrefab");
|
||||
return;
|
||||
}
|
||||
|
||||
// 在spawnPoint位置生成单位
|
||||
Vector3 spawnPosition = spawnPoint.position;
|
||||
|
||||
GameObject unit = Instantiate(characterData.unitPrefab, spawnPosition, Quaternion.identity);
|
||||
unit.name = characterData.characterName;
|
||||
unit.tag = unitTag;
|
||||
|
||||
// 设置阵营 ID(根据建筑的阵营)
|
||||
var buildingTeam = GetComponent<UnitTeam>();
|
||||
var unitTeam = unit.GetComponent<UnitTeam>();
|
||||
if (buildingTeam != null && unitTeam != null)
|
||||
{
|
||||
unitTeam.teamId = buildingTeam.teamId;
|
||||
}
|
||||
else if (unitTeam != null)
|
||||
{
|
||||
// 兜底:根据 tag 判断阵营
|
||||
unitTeam.teamId = unitTag == "Friend" ? 0 : 1;
|
||||
}
|
||||
|
||||
// 如果单位有UnitBase组件,使用1级属性初始化
|
||||
var unitBase = unit.GetComponent<UnitBase>();
|
||||
if (unitBase != null)
|
||||
{
|
||||
float health = characterData.GetMaxHealth(1);
|
||||
float attack = characterData.GetAttackPower(1);
|
||||
float moveSpeed = characterData.GetMoveSpeed(1);
|
||||
|
||||
unitBase.InitializeFromPlayerData(health, attack, moveSpeed);
|
||||
}
|
||||
|
||||
Debug.Log($"兵营生产了单位:{characterData.characterName}");
|
||||
}
|
||||
|
||||
public void StopProduction()
|
||||
{
|
||||
isProducing = false;
|
||||
}
|
||||
|
||||
public void StartProduction()
|
||||
{
|
||||
isProducing = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: W3JKtSP/Ai5L068yM/KfmfMqMzxNuuwNH7682DCQ2pexDdqL0sqt1zY=
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,42 @@
|
||||
using UnityEngine;
|
||||
|
||||
[RequireComponent(typeof(WorldHealthBar))]
|
||||
public class BuildingHealth : MonoBehaviour
|
||||
{
|
||||
[Header("建筑生命")]
|
||||
[Min(1f)] public float maxHealth = 500f;
|
||||
|
||||
private WorldHealthBar healthBar;
|
||||
|
||||
public float ProductionProgress => healthBar != null
|
||||
? Mathf.Clamp01(healthBar.currentEnergy / healthBar.maxEnergy)
|
||||
: 0f;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
healthBar = GetComponent<WorldHealthBar>();
|
||||
healthBar.maxHealth = maxHealth;
|
||||
healthBar.currentHealth = maxHealth;
|
||||
healthBar.maxEnergy = 1f;
|
||||
healthBar.currentEnergy = 0f;
|
||||
}
|
||||
|
||||
public void SetProductionProgress(float normalizedProgress)
|
||||
{
|
||||
if (healthBar != null)
|
||||
{
|
||||
healthBar.SetEnergy(Mathf.Clamp01(normalizedProgress) * healthBar.maxEnergy);
|
||||
}
|
||||
}
|
||||
|
||||
public bool ReceiveDamage(float damage, GameObject attacker = null)
|
||||
{
|
||||
if (damage <= 0f || healthBar == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
healthBar.TakeDamage(damage);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: BihN5y6lVy1x4afDhOwE5/fM5e8mWQXxroZfuMUij3yrzemt9JIHQDA=
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,242 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class BuildingPlacementManager : MonoBehaviour
|
||||
{
|
||||
public static BuildingPlacementManager Instance { get; private set; }
|
||||
|
||||
[Header("建筑预制体")]
|
||||
public GameObject barracksBasePrefab;
|
||||
public GameObject goldMinePrefab;
|
||||
|
||||
[Header("虚拟网格")]
|
||||
[Min(0.1f)] public float gridSize = 1f;
|
||||
[Min(0f)] public float placementHeight;
|
||||
public Vector2 xLimits = new Vector2(-7f, 7f);
|
||||
public Vector2 zLimits = new Vector2(-10f, 20f);
|
||||
|
||||
[Header("放置检测")]
|
||||
public LayerMask groundLayer;
|
||||
[Min(0.1f)] public float buildingRadius = 1.25f;
|
||||
public Material previewMaterialValid;
|
||||
public Material previewMaterialInvalid;
|
||||
|
||||
private readonly List<GameObject> placedBuildings = new List<GameObject>();
|
||||
private CharacterData currentCharacterData;
|
||||
private GameObject currentPrefab;
|
||||
private GameObject previewObject;
|
||||
private Camera mainCamera;
|
||||
private bool isPlacing;
|
||||
private bool previewPositionValid;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
mainCamera = Camera.main;
|
||||
RegisterExistingBuilding(GameObject.Find("主要基地"));
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!isPlacing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UpdatePreview();
|
||||
|
||||
if (Input.GetMouseButtonDown(0))
|
||||
{
|
||||
TryPlaceBuilding();
|
||||
}
|
||||
else if (Input.GetMouseButtonDown(1) || Input.GetKeyDown(KeyCode.Escape))
|
||||
{
|
||||
CancelPlacement();
|
||||
}
|
||||
}
|
||||
|
||||
public void StartPlacingBuilding(CharacterData data)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StartPlacingBarracks(data);
|
||||
}
|
||||
|
||||
public void StartPlacingBarracks(CharacterData data)
|
||||
{
|
||||
if (data == null || barracksBasePrefab == null)
|
||||
{
|
||||
Debug.LogError("兵营预制体或角色数据为空");
|
||||
return;
|
||||
}
|
||||
|
||||
BeginPlacement(barracksBasePrefab, data);
|
||||
}
|
||||
|
||||
public void StartPlacingGoldMine()
|
||||
{
|
||||
if (goldMinePrefab == null)
|
||||
{
|
||||
Debug.LogError("金矿预制体为空");
|
||||
return;
|
||||
}
|
||||
|
||||
BeginPlacement(goldMinePrefab, null);
|
||||
}
|
||||
|
||||
private void BeginPlacement(GameObject prefab, CharacterData data)
|
||||
{
|
||||
CancelPlacement();
|
||||
currentPrefab = prefab;
|
||||
currentCharacterData = data;
|
||||
previewObject = Instantiate(prefab);
|
||||
previewObject.name = "建筑预览";
|
||||
SetCollidersEnabled(previewObject, false);
|
||||
isPlacing = true;
|
||||
mainCamera = Camera.main;
|
||||
}
|
||||
|
||||
private void UpdatePreview()
|
||||
{
|
||||
if (previewObject == null || mainCamera == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
|
||||
int mask = groundLayer.value == 0 ? Physics.DefaultRaycastLayers : groundLayer.value;
|
||||
if (!Physics.Raycast(ray, out RaycastHit hit, 1000f, mask))
|
||||
{
|
||||
previewPositionValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 position = hit.point;
|
||||
position.x = Mathf.Round(position.x / gridSize) * gridSize;
|
||||
position.y = placementHeight;
|
||||
position.z = Mathf.Round(position.z / gridSize) * gridSize;
|
||||
previewObject.transform.position = position;
|
||||
previewPositionValid = IsValidPosition(position);
|
||||
UpdatePreviewMaterial(previewPositionValid);
|
||||
}
|
||||
|
||||
private bool IsValidPosition(Vector3 position)
|
||||
{
|
||||
if (position.x < xLimits.x || position.x > xLimits.y ||
|
||||
position.z < zLimits.x || position.z > zLimits.y)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (GameObject building in placedBuildings)
|
||||
{
|
||||
if (building != null && Vector3.Distance(position, building.transform.position) < buildingRadius * 2f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void TryPlaceBuilding()
|
||||
{
|
||||
if (!previewPositionValid || previewObject == null || currentPrefab == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int cost = 100;
|
||||
if (GoldSystem.Instance == null || !GoldSystem.Instance.TrySpend(cost))
|
||||
{
|
||||
Debug.Log($"金币不足,需要{cost}");
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 position = previewObject.transform.position;
|
||||
GameObject building = Instantiate(currentPrefab, position, Quaternion.identity);
|
||||
building.name = currentCharacterData != null
|
||||
? "兵营_" + currentCharacterData.characterName
|
||||
: "金矿";
|
||||
building.tag = "Friend";
|
||||
if (building.GetComponent<BuildingHealth>() == null)
|
||||
{
|
||||
building.AddComponent<BuildingHealth>();
|
||||
}
|
||||
|
||||
if (currentCharacterData != null)
|
||||
{
|
||||
Barracks barracks = building.GetComponent<Barracks>();
|
||||
if (barracks == null)
|
||||
{
|
||||
barracks = building.AddComponent<Barracks>();
|
||||
}
|
||||
|
||||
barracks.characterData = currentCharacterData;
|
||||
}
|
||||
else if (building.GetComponent<GoldMine>() == null)
|
||||
{
|
||||
building.AddComponent<GoldMine>();
|
||||
}
|
||||
|
||||
placedBuildings.Add(building);
|
||||
CancelPlacement();
|
||||
}
|
||||
|
||||
private void UpdatePreviewMaterial(bool valid)
|
||||
{
|
||||
Material material = valid ? previewMaterialValid : previewMaterialInvalid;
|
||||
if (material == null || previewObject == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (Renderer renderer in previewObject.GetComponentsInChildren<Renderer>())
|
||||
{
|
||||
renderer.sharedMaterial = material;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetCollidersEnabled(GameObject target, bool enabled)
|
||||
{
|
||||
foreach (Collider collider in target.GetComponentsInChildren<Collider>())
|
||||
{
|
||||
collider.enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
public void RegisterExistingBuilding(GameObject building)
|
||||
{
|
||||
if (building != null && !placedBuildings.Contains(building))
|
||||
{
|
||||
placedBuildings.Add(building);
|
||||
}
|
||||
}
|
||||
|
||||
public void CancelPlacement()
|
||||
{
|
||||
if (previewObject != null)
|
||||
{
|
||||
Destroy(previewObject);
|
||||
}
|
||||
|
||||
previewObject = null;
|
||||
currentPrefab = null;
|
||||
currentCharacterData = null;
|
||||
isPlacing = false;
|
||||
previewPositionValid = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: C31KvXn8U39bDHLWfJ9jSgBhWvoLhWw7Xvmn2WmpmrFDtWho+cg1kak=
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,75 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// 拆家版出兵管理:从城堡前方出兵点生成单位,消耗能量。
|
||||
/// 替代旧的召唤师身边召唤逻辑。
|
||||
/// </summary>
|
||||
public class CastleSpawnManager : MonoBehaviour
|
||||
{
|
||||
public static CastleSpawnManager Instance { get; private set; }
|
||||
|
||||
[Header("出兵点(城堡前方)")]
|
||||
public Transform blueSpawnPoint;
|
||||
public Transform redSpawnPoint;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this) { Destroy(gameObject); return; }
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
/// <summary>从指定阵营城堡出兵,消耗能量。teamId: 0=蓝, 1=红。</summary>
|
||||
public bool TrySpawn(CharacterData data, int teamId)
|
||||
{
|
||||
if (data == null || data.unitPrefab == null) return false;
|
||||
Transform spawnPoint = teamId == 0 ? blueSpawnPoint : redSpawnPoint;
|
||||
if (spawnPoint == null)
|
||||
{
|
||||
Debug.LogError($"找不到 teamId={teamId} 的出兵点");
|
||||
return false;
|
||||
}
|
||||
|
||||
Vector3 direction = spawnPoint.forward;
|
||||
direction.y = 0f;
|
||||
if (direction.sqrMagnitude < 0.001f) direction = Vector3.forward;
|
||||
direction.Normalize();
|
||||
|
||||
GameObject unit = Instantiate(
|
||||
data.unitPrefab,
|
||||
spawnPoint.position,
|
||||
Quaternion.LookRotation(direction, Vector3.up));
|
||||
|
||||
// 设置阵营
|
||||
UnitTeam unitTeam = unit.GetComponent<UnitTeam>();
|
||||
if (unitTeam != null)
|
||||
{
|
||||
unitTeam.teamId = teamId;
|
||||
unit.tag = teamId == 0 ? "Friend" : "Enemy";
|
||||
}
|
||||
|
||||
// 初始化属性(默认 1 级)
|
||||
UnitBase unitBase = unit.GetComponent<UnitBase>();
|
||||
if (unitBase != null)
|
||||
{
|
||||
PlayerCharacterData playerData = new PlayerCharacterData("castle_spawn", 1, 0);
|
||||
float hp = playerData.GetMaxHealth(data);
|
||||
float atk = playerData.GetAttackPower(data);
|
||||
float spd = playerData.GetMoveSpeed(data);
|
||||
unitBase.InitializeFromPlayerData(hp, atk, spd);
|
||||
|
||||
// 如果是敌人(teamId != 0),增大寻敌距离
|
||||
if (teamId != 0)
|
||||
{
|
||||
unitBase.detectionRange = 100f; // 从默认30增加到100
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Log($"城堡出兵: {data.characterName} teamId={teamId} pos={spawnPoint.position}");
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanAfford(CharacterData data)
|
||||
{
|
||||
return data != null && data.unitPrefab != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: Wysc5i6rUC+IV5FZ0/o3UMuAsbu+kExxLM4sa7XnkVGb+y85mKtxqMU=
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,277 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class CrystalMineCapture : MonoBehaviour
|
||||
{
|
||||
[Header("争夺进度")]
|
||||
[Range(0f, 100f)] public float blueProgress = 50f;
|
||||
[Min(0f)] public float capturePerDamage = 1f;
|
||||
|
||||
[Tooltip("是否自动添加中立阵营(teamId=2)。拆家版关闭以避免单位自动锁敌水晶矿")]
|
||||
public bool autoSetNeutralTeam = true;
|
||||
|
||||
[Header("资源产出")]
|
||||
[Tooltip("占领后是否按固定间隔给所属阵营增加能量")]
|
||||
public bool producesMana = true;
|
||||
[Min(1f)] public float manaInterval = 8f;
|
||||
[Min(1)] public int manaAmount = 1;
|
||||
|
||||
// [SerializeField] private AdjustableGlowOrb glowOrb; // 已删除,视觉效果脚本未使用
|
||||
// [Tooltip("混沌裂缝传送门(随机出兵点),随争夺进度同步变色,可留空")]
|
||||
// [SerializeField] private RiftPortalVisual riftVisual; // 已删除,视觉效果脚本未使用
|
||||
[SerializeField] private Color neutralColor = Color.white;
|
||||
[SerializeField] private Color blueColor = new Color(0.1f, 0.65f, 1f, 1f);
|
||||
[SerializeField] private Color redColor = new Color(1f, 0.12f, 0.08f, 1f);
|
||||
|
||||
[Header("世界血条")]
|
||||
[Tooltip("血条相对模型渲染包围盒顶部的额外高度")]
|
||||
[Min(0f)] public float heightOffset = 1.1f;
|
||||
[Min(0.1f)] public float barWidth = 5f;
|
||||
|
||||
private float? barHeightCache;
|
||||
|
||||
private Canvas canvas;
|
||||
private RectTransform barRect;
|
||||
private Image blueFill;
|
||||
private Image redFill;
|
||||
private RectTransform marker;
|
||||
private UnitTeam unitTeam;
|
||||
private float manaTimer;
|
||||
private static Sprite roundedSprite;
|
||||
private static readonly int BaseColorId = Shader.PropertyToID("_Color");
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (autoSetNeutralTeam)
|
||||
{
|
||||
unitTeam = GetComponent<UnitTeam>();
|
||||
if (unitTeam == null)
|
||||
{
|
||||
unitTeam = gameObject.AddComponent<UnitTeam>();
|
||||
}
|
||||
unitTeam.teamId = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
unitTeam = GetComponent<UnitTeam>();
|
||||
}
|
||||
|
||||
// if (glowOrb == null)
|
||||
// {
|
||||
// glowOrb = GetComponentInChildren<AdjustableGlowOrb>(true);
|
||||
// }
|
||||
|
||||
// if (riftVisual == null)
|
||||
// {
|
||||
// riftVisual = GetComponentInChildren<RiftPortalVisual>(true);
|
||||
// }
|
||||
|
||||
CreateCaptureBar();
|
||||
RefreshVisuals();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// 强制重置光球颜色为初始状态
|
||||
// if (glowOrb != null)
|
||||
// {
|
||||
// glowOrb.SetGlowColor(neutralColor);
|
||||
// }
|
||||
RefreshVisuals();
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
UpdateManaProduction();
|
||||
|
||||
if (barRect == null) return;
|
||||
|
||||
barRect.position = transform.position + Vector3.up * GetBarHeight();
|
||||
barRect.localScale = Vector3.one * (barWidth / 100f);
|
||||
|
||||
Camera camera = Camera.main;
|
||||
if (camera != null)
|
||||
{
|
||||
barRect.rotation = Quaternion.LookRotation(camera.transform.position - barRect.position, camera.transform.up);
|
||||
}
|
||||
}
|
||||
|
||||
// 血条高度 = 所有子 Renderer 包围盒顶部 + heightOffset。
|
||||
// 水晶矿模型根节点是 100 倍缩放,高度必须按世界包围盒算,否则血条会埋进模型内部被遮挡。
|
||||
private float GetBarHeight()
|
||||
{
|
||||
if (barHeightCache.HasValue) return barHeightCache.Value;
|
||||
|
||||
float top = 0f;
|
||||
Renderer[] renderers = GetComponentsInChildren<Renderer>();
|
||||
if (renderers.Length > 0)
|
||||
{
|
||||
Bounds bounds = renderers[0].bounds;
|
||||
for (int i = 1; i < renderers.Length; i++)
|
||||
{
|
||||
bounds.Encapsulate(renderers[i].bounds);
|
||||
}
|
||||
top = bounds.max.y;
|
||||
}
|
||||
barHeightCache = top - transform.position.y + heightOffset;
|
||||
return barHeightCache.Value;
|
||||
}
|
||||
|
||||
private void UpdateManaProduction()
|
||||
{
|
||||
if (!producesMana || unitTeam == null || (unitTeam.teamId != 0 && unitTeam.teamId != 1))
|
||||
{
|
||||
manaTimer = 0f;
|
||||
return;
|
||||
}
|
||||
|
||||
manaTimer += Time.deltaTime;
|
||||
while (manaTimer >= manaInterval)
|
||||
{
|
||||
manaTimer -= manaInterval;
|
||||
if (unitTeam.teamId == 0)
|
||||
{
|
||||
ManaSystem.Instance?.AddMana(manaAmount);
|
||||
}
|
||||
else
|
||||
{
|
||||
EnemyCastleAI enemyAI = FindObjectOfType<EnemyCastleAI>();
|
||||
enemyAI?.AddMana(manaAmount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ReceiveCaptureDamage(float damage, GameObject attacker)
|
||||
{
|
||||
if (damage <= 0f || attacker == null) return false;
|
||||
|
||||
UnitTeam attackerTeam = attacker.GetComponentInParent<UnitTeam>();
|
||||
if (attackerTeam == null) return false;
|
||||
|
||||
float direction = attackerTeam.teamId == 0 ? 1f : -1f;
|
||||
blueProgress = Mathf.Clamp(blueProgress + direction * damage * capturePerDamage, 0f, 100f);
|
||||
RefreshVisuals();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetCaptureProgress(float value)
|
||||
{
|
||||
blueProgress = Mathf.Clamp(value, 0f, 100f);
|
||||
RefreshVisuals();
|
||||
}
|
||||
|
||||
private void RefreshVisuals()
|
||||
{
|
||||
if (blueFill == null || redFill == null) return;
|
||||
|
||||
float blue = blueProgress / 100f;
|
||||
blueFill.fillAmount = blue;
|
||||
redFill.fillAmount = 1f - blue;
|
||||
marker.anchorMin = new Vector2(blue, 0f);
|
||||
marker.anchorMax = new Vector2(blue, 1f);
|
||||
marker.anchoredPosition = Vector2.zero;
|
||||
marker.gameObject.SetActive(blueProgress > 0f && blueProgress < 100f);
|
||||
|
||||
if (unitTeam != null)
|
||||
{
|
||||
unitTeam.teamId = blueProgress >= 100f ? 0 : blueProgress <= 0f ? 1 : 2;
|
||||
}
|
||||
|
||||
// if (glowOrb != null)
|
||||
// {
|
||||
// Color color = blueProgress >= 100f ? blueColor : blueProgress <= 0f ? redColor : neutralColor;
|
||||
// glowOrb.SetGlowColor(color);
|
||||
// }
|
||||
|
||||
// if (riftVisual != null)
|
||||
// {
|
||||
// Color riftColor = blueProgress >= 100f ? blueColor : blueProgress <= 0f ? redColor : neutralColor;
|
||||
// riftVisual.SetRiftColor(riftColor);
|
||||
// }
|
||||
}
|
||||
|
||||
private void CreateCaptureBar()
|
||||
{
|
||||
GameObject root = new GameObject("CrystalCaptureBar", typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler));
|
||||
// 血条 Canvas 不要作为水晶矿的子对象,直接放在场景根目录避免被 100 倍缩放影响
|
||||
root.transform.parent = null;
|
||||
canvas = root.GetComponent<Canvas>();
|
||||
canvas.renderMode = RenderMode.WorldSpace;
|
||||
canvas.sortingOrder = 110;
|
||||
root.GetComponent<CanvasScaler>().dynamicPixelsPerUnit = 10f;
|
||||
barRect = root.GetComponent<RectTransform>();
|
||||
barRect.sizeDelta = new Vector2(100f, 16f);
|
||||
barRect.localScale = Vector3.one * (barWidth / 100f);
|
||||
|
||||
RectTransform row = CreateRow(root.transform, "CaptureBar", 100f, 14f);
|
||||
Image background = CreateImage(row, "Background", new Color(0.03f, 0.03f, 0.03f, 0.95f));
|
||||
SetFullRect(background.rectTransform);
|
||||
|
||||
blueFill = CreateImage(row, "BlueFill", blueColor);
|
||||
blueFill.type = Image.Type.Filled;
|
||||
blueFill.fillMethod = Image.FillMethod.Horizontal;
|
||||
blueFill.fillOrigin = 0;
|
||||
blueFill.rectTransform.anchorMin = new Vector2(0f, 0f);
|
||||
blueFill.rectTransform.anchorMax = new Vector2(1f, 1f);
|
||||
blueFill.rectTransform.offsetMin = new Vector2(2f, 2f);
|
||||
blueFill.rectTransform.offsetMax = new Vector2(-2f, -2f);
|
||||
|
||||
redFill = CreateImage(row, "RedFill", redColor);
|
||||
redFill.type = Image.Type.Filled;
|
||||
redFill.fillMethod = Image.FillMethod.Horizontal;
|
||||
redFill.fillOrigin = 1;
|
||||
redFill.rectTransform.anchorMin = new Vector2(0f, 0f);
|
||||
redFill.rectTransform.anchorMax = new Vector2(1f, 1f);
|
||||
redFill.rectTransform.offsetMin = new Vector2(2f, 2f);
|
||||
redFill.rectTransform.offsetMax = new Vector2(-2f, -2f);
|
||||
|
||||
GameObject markerObject = new GameObject("CenterMarker", typeof(RectTransform), typeof(Image));
|
||||
markerObject.transform.SetParent(row, false);
|
||||
marker = markerObject.GetComponent<RectTransform>();
|
||||
marker.sizeDelta = new Vector2(2f, 14f);
|
||||
marker.anchorMin = new Vector2(0.5f, 0f);
|
||||
marker.anchorMax = new Vector2(0.5f, 1f);
|
||||
marker.GetComponent<Image>().color = Color.white;
|
||||
}
|
||||
|
||||
private RectTransform CreateRow(Transform parent, string name, float width, float height)
|
||||
{
|
||||
GameObject rowObject = new GameObject(name, typeof(RectTransform));
|
||||
rowObject.transform.SetParent(parent, false);
|
||||
RectTransform rect = rowObject.GetComponent<RectTransform>();
|
||||
rect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
rect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
rect.pivot = new Vector2(0.5f, 0.5f);
|
||||
rect.sizeDelta = new Vector2(width, height);
|
||||
return rect;
|
||||
}
|
||||
|
||||
private Image CreateImage(Transform parent, string name, Color color)
|
||||
{
|
||||
GameObject imageObject = new GameObject(name, typeof(RectTransform), typeof(Image));
|
||||
imageObject.transform.SetParent(parent, false);
|
||||
Image image = imageObject.GetComponent<Image>();
|
||||
image.sprite = RoundedSprite();
|
||||
image.color = color;
|
||||
return image;
|
||||
}
|
||||
|
||||
private void SetFullRect(RectTransform rect)
|
||||
{
|
||||
rect.anchorMin = Vector2.zero;
|
||||
rect.anchorMax = Vector2.one;
|
||||
rect.offsetMin = Vector2.zero;
|
||||
rect.offsetMax = Vector2.zero;
|
||||
}
|
||||
|
||||
private static Sprite RoundedSprite()
|
||||
{
|
||||
if (roundedSprite != null) return roundedSprite;
|
||||
Texture2D texture = new Texture2D(1, 1, TextureFormat.RGBA32, false);
|
||||
texture.SetPixel(0, 0, Color.white);
|
||||
texture.Apply();
|
||||
roundedSprite = Sprite.Create(texture, new Rect(0f, 0f, 1f, 1f), new Vector2(0.5f, 0.5f));
|
||||
roundedSprite.name = "CrystalCaptureBarSprite";
|
||||
return roundedSprite;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: Ci8f4Sz/WnrXjU/p2xyMiRtWkh/2zF5+zBmBE7FvT1PHEvOb80JVbxc=
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,284 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// 红方城堡自动出兵 AI:独立能量、三路目标评估、低费试探与高费推进。
|
||||
/// </summary>
|
||||
public class EnemyCastleAI : MonoBehaviour
|
||||
{
|
||||
[Header("AI 阵营")]
|
||||
[SerializeField] private int teamId = 1;
|
||||
[SerializeField] private int maxMana = 10;
|
||||
[SerializeField] private float manaRegenInterval = 3f;
|
||||
[SerializeField] private float decisionInterval = 1.2f;
|
||||
[SerializeField] private float minimumSpawnInterval = 1.4f;
|
||||
|
||||
[Header("三路目标")]
|
||||
[SerializeField] private float leftLaneX = -10f;
|
||||
[SerializeField] private float centerLaneX = 0f;
|
||||
[SerializeField] private float rightLaneX = 10f;
|
||||
[SerializeField] private float laneHalfWidth = 4f;
|
||||
[SerializeField] private float resourceThreatWeight = 2.5f;
|
||||
[SerializeField] private float enemyUnitWeight = 1.5f;
|
||||
[SerializeField] private float laneRotationWeight = 0.7f;
|
||||
|
||||
[Header("卡组轮转")]
|
||||
[SerializeField] private int visibleSlotCount = 4;
|
||||
[SerializeField] private string[] deckDataNames =
|
||||
{
|
||||
"人类士兵Data", "弓箭手Data", "盾卫Data", "精锐士兵Data",
|
||||
"法师Data", "狂暴狼人Data", "石巨人Data", "弓箭猎人Data"
|
||||
};
|
||||
[SerializeField] private CharacterData[] deck;
|
||||
|
||||
private readonly List<CharacterData> hand = new List<CharacterData>();
|
||||
private readonly Queue<CharacterData> drawQueue = new Queue<CharacterData>();
|
||||
private Transform redSpawnPoint;
|
||||
private AttackTargetController targetController;
|
||||
private float currentMana;
|
||||
private float manaTimer;
|
||||
private float decisionTimer;
|
||||
private float spawnTimer;
|
||||
private int lastLane = 1;
|
||||
private int consecutiveLaneSpawns;
|
||||
private bool initialized;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
redSpawnPoint = FindRedSpawnPoint();
|
||||
targetController = AttackTargetController.Instance;
|
||||
currentMana = maxMana;
|
||||
BuildDeck();
|
||||
BuildHand();
|
||||
initialized = redSpawnPoint != null && hand.Count > 0;
|
||||
if (!initialized)
|
||||
Debug.LogWarning("EnemyCastleAI 初始化失败:缺少红方出兵点或角色卡组。");
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!initialized) return;
|
||||
|
||||
manaTimer += Time.deltaTime;
|
||||
while (manaTimer >= manaRegenInterval)
|
||||
{
|
||||
manaTimer -= manaRegenInterval;
|
||||
currentMana = Mathf.Min(maxMana, currentMana + 1f);
|
||||
}
|
||||
|
||||
decisionTimer -= Time.deltaTime;
|
||||
spawnTimer -= Time.deltaTime;
|
||||
if (decisionTimer > 0f || spawnTimer > 0f) return;
|
||||
decisionTimer = decisionInterval;
|
||||
|
||||
int lane = ChooseLane();
|
||||
CharacterData card = ChooseCard(lane);
|
||||
if (card == null) return;
|
||||
if (Spawn(card, lane))
|
||||
{
|
||||
currentMana -= 1f;
|
||||
RotatePlayedCard(card);
|
||||
spawnTimer = minimumSpawnInterval;
|
||||
if (lane == lastLane) consecutiveLaneSpawns++;
|
||||
else consecutiveLaneSpawns = 1;
|
||||
lastLane = lane;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddMana(int amount)
|
||||
{
|
||||
if (amount <= 0) return;
|
||||
currentMana = Mathf.Min(maxMana, currentMana + amount);
|
||||
}
|
||||
|
||||
public int CurrentMana => Mathf.FloorToInt(currentMana);
|
||||
|
||||
public bool TrySpawnFromPortal(Vector3 spawnPosition, Quaternion spawnRotation)
|
||||
{
|
||||
if (!initialized) return false;
|
||||
|
||||
List<CharacterData> affordable = new List<CharacterData>();
|
||||
foreach (CharacterData card in hand)
|
||||
{
|
||||
if (card != null && card.unitPrefab != null)
|
||||
affordable.Add(card);
|
||||
}
|
||||
|
||||
if (affordable.Count == 0) return false;
|
||||
|
||||
CharacterData selected = affordable[Random.Range(0, affordable.Count)];
|
||||
if (!SpawnAt(selected, spawnPosition, spawnRotation, teamId)) return false;
|
||||
|
||||
currentMana -= 1f;
|
||||
RotatePlayedCard(selected);
|
||||
return true;
|
||||
}
|
||||
|
||||
private CharacterData SelectWeightedCard(List<CharacterData> cards)
|
||||
{
|
||||
return cards == null || cards.Count == 0 ? null : cards[Random.Range(0, cards.Count)];
|
||||
}
|
||||
|
||||
private int ChooseLane()
|
||||
{
|
||||
float left = ScoreLane(leftLaneX, 0);
|
||||
float center = ScoreLane(centerLaneX, 1);
|
||||
float right = ScoreLane(rightLaneX, 2);
|
||||
|
||||
if (consecutiveLaneSpawns >= 2)
|
||||
{
|
||||
if (lastLane == 0) left -= 3f;
|
||||
if (lastLane == 1) center -= 3f;
|
||||
if (lastLane == 2) right -= 3f;
|
||||
}
|
||||
|
||||
float best = Mathf.Max(left, Mathf.Max(center, right));
|
||||
if (Mathf.Approximately(best, left)) return 0;
|
||||
if (Mathf.Approximately(best, right)) return 2;
|
||||
return 1;
|
||||
}
|
||||
|
||||
private float ScoreLane(float laneX, int lane)
|
||||
{
|
||||
float score = 1f;
|
||||
score += lane == 1 ? 0.8f : 0f;
|
||||
score += lane == lastLane ? laneRotationWeight : 0f;
|
||||
|
||||
foreach (var unit in FindObjectsOfType<UnitTeam>())
|
||||
{
|
||||
if (unit == null || unit.teamId != 0) continue;
|
||||
Vector3 p = unit.transform.position;
|
||||
if (Mathf.Abs(p.x - laneX) <= laneHalfWidth)
|
||||
score += enemyUnitWeight;
|
||||
}
|
||||
|
||||
foreach (var mine in FindObjectsOfType<CrystalMineCapture>())
|
||||
{
|
||||
if (mine == null) continue;
|
||||
Vector3 p = mine.transform.position;
|
||||
if (Mathf.Abs(p.x - laneX) > laneHalfWidth * 1.8f) continue;
|
||||
UnitTeam team = mine.GetComponent<UnitTeam>();
|
||||
if (team == null || team.teamId != teamId)
|
||||
score += resourceThreatWeight;
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
private CharacterData ChooseCard(int lane)
|
||||
{
|
||||
CharacterData best = null;
|
||||
float bestScore = float.NegativeInfinity;
|
||||
foreach (var card in hand)
|
||||
{
|
||||
if (card == null || card.unitPrefab == null) continue;
|
||||
float score = Random.Range(0f, 1f);
|
||||
if (score > bestScore)
|
||||
{
|
||||
bestScore = score;
|
||||
best = card;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
private void BuildDeck()
|
||||
{
|
||||
if (deck != null && deck.Length > 0) return;
|
||||
|
||||
var loadedDeck = new List<CharacterData>();
|
||||
foreach (string dataName in deckDataNames)
|
||||
{
|
||||
CharacterData data = Resources.Load<CharacterData>("Data/" + dataName);
|
||||
if (data == null)
|
||||
{
|
||||
Debug.LogWarning($"敌方卡组缺少角色数据: Resources/Data/{dataName}.asset");
|
||||
continue;
|
||||
}
|
||||
loadedDeck.Add(data);
|
||||
}
|
||||
deck = loadedDeck.ToArray();
|
||||
}
|
||||
|
||||
private void BuildHand()
|
||||
{
|
||||
hand.Clear();
|
||||
drawQueue.Clear();
|
||||
if (deck == null) return;
|
||||
|
||||
int handCount = Mathf.Min(visibleSlotCount, deck.Length);
|
||||
for (int i = 0; i < handCount; i++)
|
||||
hand.Add(deck[i]);
|
||||
for (int i = handCount; i < deck.Length; i++)
|
||||
drawQueue.Enqueue(deck[i]);
|
||||
}
|
||||
|
||||
private void RotatePlayedCard(CharacterData playedCard)
|
||||
{
|
||||
int handIndex = hand.IndexOf(playedCard);
|
||||
if (handIndex < 0 || drawQueue.Count == 0) return;
|
||||
|
||||
hand[handIndex] = drawQueue.Dequeue();
|
||||
drawQueue.Enqueue(playedCard);
|
||||
}
|
||||
|
||||
private bool SpawnAt(CharacterData data, Vector3 position, Quaternion rotation, int spawnTeamId)
|
||||
{
|
||||
GameObject unit = Instantiate(data.unitPrefab, position, rotation);
|
||||
UnitTeam team = unit.GetComponent<UnitTeam>();
|
||||
if (team != null)
|
||||
{
|
||||
team.teamId = spawnTeamId;
|
||||
unit.tag = spawnTeamId == 0 ? "Friend" : "Enemy";
|
||||
}
|
||||
|
||||
UnitBase unitBase = unit.GetComponent<UnitBase>();
|
||||
if (unitBase != null)
|
||||
{
|
||||
unitBase.InitializeFromPlayerData(data.GetMaxHealth(1), data.GetAttackPower(1), data.GetMoveSpeed(1));
|
||||
|
||||
// 如果是敌人(teamId != 0),增大寻敌距离
|
||||
if (spawnTeamId != 0)
|
||||
{
|
||||
unitBase.detectionRange = 100f; // 从默认30增加到100
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool Spawn(CharacterData data, int lane)
|
||||
{
|
||||
if (!SpawnAt(data, redSpawnPoint.position, redSpawnPoint.rotation, teamId)) return false;
|
||||
|
||||
if (targetController != null)
|
||||
{
|
||||
targetController.redTarget = FindLaneTarget(lane);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private GameObject FindLaneTarget(int lane)
|
||||
{
|
||||
float x = lane == 0 ? leftLaneX : lane == 2 ? rightLaneX : centerLaneX;
|
||||
GameObject best = null;
|
||||
float bestDistance = float.PositiveInfinity;
|
||||
foreach (var mine in FindObjectsOfType<CrystalMineCapture>())
|
||||
{
|
||||
if (Mathf.Abs(mine.transform.position.x - x) < laneHalfWidth * 2f)
|
||||
{
|
||||
float d = Mathf.Abs(mine.transform.position.z - redSpawnPoint.position.z);
|
||||
if (d < bestDistance) { bestDistance = d; best = mine.gameObject; }
|
||||
}
|
||||
}
|
||||
return best != null ? best : targetController?.GetEnemyCastleOfTeam(teamId);
|
||||
}
|
||||
|
||||
private Transform FindRedSpawnPoint()
|
||||
{
|
||||
var manager = GetComponent<CastleSpawnManager>();
|
||||
if (manager != null && manager.redSpawnPoint != null) return manager.redSpawnPoint;
|
||||
var castles = GameObject.Find("红色城堡");
|
||||
return castles != null ? castles.transform.Find("出兵点") : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: Di4e5COsW3MaOIKjAZpkNB3DV96stk4sKbaXXg7lgL4M9IOQyMJld6Y=
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,78 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// 金矿建筑,定时产出金币
|
||||
/// </summary>
|
||||
public class GoldMine : MonoBehaviour
|
||||
{
|
||||
[Header("金币产出")]
|
||||
[Tooltip("每次产出的金币数量")]
|
||||
[SerializeField] private int goldPerProduction = 50;
|
||||
|
||||
[Tooltip("产出间隔(秒)")]
|
||||
[SerializeField] private float productionInterval = 5f;
|
||||
|
||||
[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}");
|
||||
|
||||
// 可以在这里播放特效和音效
|
||||
// TODO: 添加金币产出特效
|
||||
}
|
||||
}
|
||||
|
||||
// 在编辑器中显示信息
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
Gizmos.color = Color.yellow;
|
||||
Gizmos.DrawWireSphere(transform.position + Vector3.up * 2f, 0.5f);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: DykfvC7+VnysnFVe0RuuhqYblOX8b3TqKpWkys/vBAvo9QxqFltLb0g=
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in new issue
Block a user