diff --git a/.codely/clipboard/clipboard-1791530764779.png b/.codely/clipboard/clipboard-1791530764779.png new file mode 100644 index 0000000..b582a7e Binary files /dev/null and b/.codely/clipboard/clipboard-1791530764779.png differ diff --git a/Assets/Materials/CoreBattleGround.mat b/Assets/Materials/CoreBattleGround.mat index 47fff8e..525ddfa 100644 --- a/Assets/Materials/CoreBattleGround.mat +++ b/Assets/Materials/CoreBattleGround.mat @@ -46,7 +46,7 @@ Material: m_Scale: {x: 3, y: 4} m_Offset: {x: 0, y: 0} - _MainTex: - m_Texture: {fileID: 2800000, guid: 594180e608ded448d8751c39bca40633, type: 3} + m_Texture: {fileID: 2800000, guid: b424ea763d6a34320bae85b1d85ef820, type: 3} m_Scale: {x: 1, y: 1} m_Offset: {x: 0, y: 0} - _MetallicGlossMap: diff --git a/Assets/Materials/UI/GoldTextMaterial.mat b/Assets/Materials/UI/GoldTextMaterial.mat index 6293aed..3da8563 100644 --- a/Assets/Materials/UI/GoldTextMaterial.mat +++ b/Assets/Materials/UI/GoldTextMaterial.mat @@ -30,13 +30,15 @@ Material: m_Ints: [] m_Floats: - _ColorMask: 15 + - _MidPoint: 0.5 - _Stencil: 0 - _StencilComp: 8 - _StencilOp: 0 - _StencilReadMask: 255 - _StencilWriteMask: 255 m_Colors: - - _BottomColor: {r: 0.88, g: 0.58, b: 0.08, a: 1} + - _BottomColor: {r: 1, g: 0.8272859, b: 0.62578607, a: 1} - _Color: {r: 1, g: 1, b: 1, a: 1} - - _TopColor: {r: 1, g: 0.96, b: 0.65, a: 1} + - _MiddleColor: {r: 1, g: 0.8353073, b: 0.35534585, a: 1} + - _TopColor: {r: 1, g: 0.98, b: 0.93, a: 1} m_BuildTextureStacks: [] diff --git a/Assets/Picture/UI/按钮.meta b/Assets/Picture/UI/按钮.meta new file mode 100644 index 0000000..61b5a73 --- /dev/null +++ b/Assets/Picture/UI/按钮.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: XHsWsyKuVn29xwSVE/3Ug9GpdtrPk+ZRNROucmeIFIhu6+A9ilqnymY= +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Picture/UI/按钮/按钮1.png b/Assets/Picture/UI/按钮/按钮1.png new file mode 100644 index 0000000..e5f3f28 Binary files /dev/null and b/Assets/Picture/UI/按钮/按钮1.png differ diff --git a/Assets/Picture/UI/按钮/按钮1.png.meta b/Assets/Picture/UI/按钮/按钮1.png.meta new file mode 100644 index 0000000..ebd5523 --- /dev/null +++ b/Assets/Picture/UI/按钮/按钮1.png.meta @@ -0,0 +1,177 @@ +fileFormatVersion: 2 +guid: CnxM43+qB3mU/IJVjpBlqtDrEW5/D+SiAMmbk4HP1rVTuGsPSJz7Hnc= +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 14 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + webStreaming: 0 + priorityLevel: 0 + uploadedMode: 2 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 128 + maxPlaceholderSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + maxPlaceholderSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + maxPlaceholderSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: WebGL + maxTextureSize: 2048 + maxPlaceholderSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: WeixinMiniGame + maxTextureSize: 2048 + maxPlaceholderSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: PlayableAds + maxTextureSize: 2048 + maxPlaceholderSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + doOverrideTextureManagerOperations: 0 + platformOperationGroupSettings: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Prefabs/第一章/盾卫.prefab b/Assets/Prefabs/第一章/盾卫.prefab index 2e28cc2..9d7aa44 100644 --- a/Assets/Prefabs/第一章/盾卫.prefab +++ b/Assets/Prefabs/第一章/盾卫.prefab @@ -313,8 +313,10 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 15c30b8bf55ed47c38c74d59aa9fd021, type: 3} m_Name: m_EditorClassIdentifier: - maxHealth: 100 - currentHealth: 100 + maxHealth: 700 + currentHealth: 700 + maxShield: 0 + currentShield: 0 showEnergyBar: 1 maxEnergy: 100 _currentEnergy: 0 @@ -329,6 +331,7 @@ MonoBehaviour: damageNumberRiseHeight: 1.2 damageNumberSpawnHeight: 1.5 damageNumberSize: 0.09 + unitLevel: 1 --- !u!114 &832567817104069704 MonoBehaviour: m_ObjectHideFlags: 0 @@ -364,6 +367,11 @@ MonoBehaviour: attackRange: 2 modelFacingOffset: 45 attackPower: 10 + critRate: 0 + critMultiplier: 1.5 + lifestealRate: 0 + damageReductionRate: 0 + skillChargeMultiplier: 1 attackDuration: 0 attackType: 0 projectilePrefab: {fileID: 0} @@ -376,6 +384,8 @@ MonoBehaviour: returnToAnchorWhenIdle: 0 anchorArrivalDistance: 0.35 useAnchorLeash: 0 + useEngagementZLine: 1 + engagementZThreshold: 10 target: {fileID: 0} --- !u!114 &7519013316474627281 MonoBehaviour: @@ -389,11 +399,8 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 91c4440b51dd744e9857b9a5b3a0f19b, type: 3} m_Name: m_EditorClassIdentifier: - shieldDurability: 220 - shieldDuration: 4 - damageReduction: 0.35 - shieldLocalPosition: {x: 0, y: 1, z: 0.7} - shieldSize: {x: 1.5, y: 1.8, z: 0.12} + shieldDuration: 5 + shieldVfxPrefab: {fileID: 106386, guid: 439cf2d390c444d6b428b530f5b7eec3, type: 3} --- !u!4 &1906187853415385385 stripped Transform: m_CorrespondingSourceObject: {fileID: -8679921383154817045, guid: 17f7a1b7ae92f0948a5bebde8a11fc2b, diff --git a/Assets/Scenes/CharacterTest.scene b/Assets/Scenes/CharacterTest.scene index acee576..e7516fa 100644 Binary files a/Assets/Scenes/CharacterTest.scene and b/Assets/Scenes/CharacterTest.scene differ diff --git a/Assets/Scenes/DefenseTest.scene b/Assets/Scenes/DefenseTest.scene index 13dde4f..2329125 100644 Binary files a/Assets/Scenes/DefenseTest.scene and b/Assets/Scenes/DefenseTest.scene differ diff --git a/Assets/Scripts/UI脚本/GoldUI.cs b/Assets/Scripts/UI脚本/GoldUI.cs index 53d52ca..69837b0 100644 --- a/Assets/Scripts/UI脚本/GoldUI.cs +++ b/Assets/Scripts/UI脚本/GoldUI.cs @@ -51,7 +51,7 @@ public class GoldUI : MonoBehaviour { if (goldText != null) { - goldText.text = $"金币 {newGold}"; + goldText.text = newGold.ToString(); } } } diff --git a/Assets/Scripts/UI脚本/WorldHealthBar.cs b/Assets/Scripts/UI脚本/WorldHealthBar.cs index ec3013d..d53222f 100644 --- a/Assets/Scripts/UI脚本/WorldHealthBar.cs +++ b/Assets/Scripts/UI脚本/WorldHealthBar.cs @@ -10,6 +10,7 @@ public class WorldHealthBar : MonoBehaviour [Min(0f)] public float currentHealth = 100f; [Header("护盾")] + public float maxShield = 0f; public float currentShield = 0f; /// @@ -18,9 +19,32 @@ public class WorldHealthBar : MonoBehaviour public void AddShield(float amount) { if (amount <= 0f || isDead) return; + if (currentShield <= 0f) + { + maxShield = amount; + } + else + { + maxShield += amount; + } currentShield += amount; UpdateFill(); - Debug.Log($"{gameObject.name} 获得护盾 +{amount:F1},当前护盾: {currentShield:F1}"); + UpdateShieldFill(); + Debug.Log($"{gameObject.name} 获得护盾 +{amount:F1},当前护盾: {currentShield:F1}/{maxShield:F1}"); + } + + /// + /// 减少或清空护盾 + /// + public void RemoveShield(float amount) + { + if (amount <= 0f || currentShield <= 0f) return; + currentShield = Mathf.Max(0f, currentShield - amount); + if (currentShield <= 0f) + { + maxShield = 0f; + } + UpdateShieldFill(); } // 技能能量上限,英雄默认使用100点;没有技能的小怪可关闭能量条。 @@ -74,7 +98,8 @@ public class WorldHealthBar : MonoBehaviour // 描边宽度设为私有常量,所有角色统一使用 private const float DamageNumberOutlineWidth = 0.025f; // 从 0.015 增加到 0.025,描边更粗 - // 血条 UI 的像素尺寸,实际显示大小由 worldWidth 控制。 + // 护盾条高 10 像素,置于血条最上方。 + private const int ShieldPixelHeight = 10; private const int PixelWidth = 100; private const int PixelHeight = 14; @@ -82,7 +107,7 @@ public class WorldHealthBar : MonoBehaviour private const int EnergyPixelWidth = 100; // 改为和血条一样宽 private const int EnergyPixelHeight = 12; // 从 8 增加到 12,更明显 private const int BarGap = 0; // 改为 0,让血条和能量条紧贴在一起 - private const int TotalPixelHeight = PixelHeight + BarGap + EnergyPixelHeight; + private const int TotalPixelHeight = ShieldPixelHeight + BarGap + PixelHeight + BarGap + EnergyPixelHeight; // 世界空间 Canvas,用来承载血条 UI。 private Canvas canvas; @@ -90,9 +115,17 @@ public class WorldHealthBar : MonoBehaviour // 血条整体的 RectTransform。 private RectTransform barRect; + // 整体 UI 行节点引用 + private RectTransform shieldRow; + private RectTransform healthRow; + private RectTransform energyRow; + // 绿色血量填充 Image。 private Image fillImage; + // 浅蓝色护盾填充 Image。 + private Image shieldFillImage; + // 金色技能能量填充 Image。 private Image energyFillImage; @@ -218,6 +251,7 @@ public class WorldHealthBar : MonoBehaviour float absorbed = Mathf.Min(currentShield, amount); currentShield -= absorbed; amount -= absorbed; + UpdateShieldFill(); Debug.Log($"{gameObject.name} 护盾吸收了 {absorbed:F1} 伤害,剩余护盾: {currentShield:F1}"); } @@ -472,16 +506,32 @@ public class WorldHealthBar : MonoBehaviour canvas.sortingOrder = 100; barObject.GetComponent().dynamicPixelsPerUnit = 10f; barRect = barObject.GetComponent(); - barRect.sizeDelta = new Vector2(PixelWidth, showEnergyBar ? TotalPixelHeight : PixelHeight); + barRect.sizeDelta = new Vector2(PixelWidth, showEnergyBar ? TotalPixelHeight : (ShieldPixelHeight + PixelHeight)); barRect.localScale = Vector3.one * (worldWidth / PixelWidth); barRect.localPosition = GetBarRootPosition(); ApplyScaleCompensation(); - RectTransform healthRow = CreateRow(barObject.transform, "HealthBar", PixelWidth, PixelHeight, 0); + // 1. 顶层独立护盾条 (ShieldBar) + shieldRow = CreateRow(barObject.transform, "ShieldBar", PixelWidth, ShieldPixelHeight, 0); + Image shieldBackground = CreateImage("ShieldBorder", shieldRow, RoundedSprite(), borderColor); // 加回黑框 + SetFullRect(shieldBackground.rectTransform); + + Image shieldInner = CreateImage("ShieldFill", shieldRow, RoundedSprite(), new Color(0f, 0.88f, 1f, 0.95f)); // 亮天蓝色 + shieldInner.rectTransform.anchorMin = Vector2.zero; + shieldInner.rectTransform.anchorMax = Vector2.one; + shieldInner.rectTransform.offsetMin = new Vector2(2f, 1f); + shieldInner.rectTransform.offsetMax = new Vector2(-2f, -1f); + shieldInner.type = Image.Type.Filled; + shieldInner.fillMethod = Image.FillMethod.Horizontal; + shieldInner.fillOrigin = 0; + shieldFillImage = shieldInner; + shieldRow.gameObject.SetActive(false); // 默认没有护盾时整体收起隐藏 + + // 2. 中层主血条 (HealthBar) + healthRow = CreateRow(barObject.transform, "HealthBar", PixelWidth, PixelHeight, ShieldPixelHeight + BarGap); Image background = CreateImage("Border", healthRow, RoundedSprite(), borderColor); SetFullRect(background.rectTransform); - // 创建填充 Image,颜色暂时使用默认绿色,在 Start() 中根据最终标签设置 Image inner = CreateImage("Fill", healthRow, RoundedSprite(), Color.green); inner.rectTransform.anchorMin = Vector2.zero; inner.rectTransform.anchorMax = Vector2.one; @@ -492,6 +542,7 @@ public class WorldHealthBar : MonoBehaviour inner.fillOrigin = 0; fillImage = inner; + // 3. 底层技能能量条 (EnergyBar) if (!showEnergyBar) { energyFillImage = null; @@ -499,12 +550,12 @@ public class WorldHealthBar : MonoBehaviour return; } - RectTransform energyRow = CreateRow( + energyRow = CreateRow( barObject.transform, "EnergyBar", EnergyPixelWidth, EnergyPixelHeight, - PixelHeight + BarGap); + ShieldPixelHeight + BarGap + PixelHeight + BarGap); Image energyBackground = CreateImage("EnergyBorder", energyRow, RoundedSprite(), borderColor); SetFullRect(energyBackground.rectTransform); @@ -619,7 +670,7 @@ public class WorldHealthBar : MonoBehaviour // 创建血条左侧的圆圈等级徽章 private void CreateLevelBadge(Transform parent) { - int badgeSize = 26; // 圆圈徽章直径 + int badgeSize = 39; // 调整为原来的 1.5 倍 (从 26 增大至 39) GameObject badgeObj = new GameObject("LevelBadge", typeof(RectTransform)); badgeObj.transform.SetParent(parent, false); @@ -628,7 +679,7 @@ public class WorldHealthBar : MonoBehaviour badgeRect.anchorMax = new Vector2(0f, 1f); badgeRect.pivot = new Vector2(1f, 0.5f); // 右边缘切齐,紧贴血条左边缘 badgeRect.sizeDelta = new Vector2(badgeSize, badgeSize); - badgeRect.anchoredPosition = new Vector2(2f, -PixelHeight * 0.5f); // 总是对齐第一行血条中心 + badgeRect.anchoredPosition = new Vector2(2f, -15f); // 居中对齐主血条中心 (posY = -15f) // 外层黑边圆圈 Image outerCircle = CreateImage("OuterCircle", badgeRect, CircleSprite(), new Color(0.05f, 0.05f, 0.05f, 0.95f)); @@ -652,7 +703,7 @@ public class WorldHealthBar : MonoBehaviour levelText = textObj.GetComponent(); levelText.text = unitLevel.ToString(); levelText.font = GetBuiltinFont(); - levelText.fontSize = 18; + levelText.fontSize = 26; // 字体同步放大 levelText.fontStyle = FontStyle.Bold; levelText.alignment = TextAnchor.MiddleCenter; levelText.color = new Color(0.95f, 0.98f, 0.95f, 1f); @@ -701,6 +752,38 @@ public class WorldHealthBar : MonoBehaviour } } + // 根据当前护盾值更新天蓝色填充比例与显隐。 + private void UpdateShieldFill() + { + if (shieldRow != null && shieldFillImage != null) + { + if (currentShield > 0f) + { + shieldRow.gameObject.SetActive(true); + + // 1. 护盾条左对齐:锚点改为左上角 + shieldRow.anchorMin = new Vector2(0f, 1f); + shieldRow.anchorMax = new Vector2(0f, 1f); + shieldRow.pivot = new Vector2(0f, 1f); + shieldRow.anchoredPosition = new Vector2(0f, 0f); + + // 2. 护盾条的物理宽度按 (maxShield / maxHealth) 比例,代表护盾容量占血条的比例 + float shieldRatio = maxHealth > 0f ? Mathf.Clamp01(maxShield / maxHealth) : 1.0f; + float targetWidth = PixelWidth * Mathf.Max(0.05f, shieldRatio); + shieldRow.sizeDelta = new Vector2(targetWidth, ShieldPixelHeight); + + // 3. 天蓝色填充条按 (currentShield / maxShield) 动态扣减 + float fillRatio = maxShield > 0f ? Mathf.Clamp01(currentShield / maxShield) : 1.0f; + shieldFillImage.fillAmount = fillRatio; + shieldFillImage.SetVerticesDirty(); + } + else + { + shieldRow.gameObject.SetActive(false); + } + } + } + // 创建并缓存一个直角矩形白色 Sprite,供边框和填充复用。 private static Sprite RoundedSprite() { diff --git a/Assets/Scripts/技能脚本/ShieldGuardSkill.cs b/Assets/Scripts/技能脚本/ShieldGuardSkill.cs index 94d6a32..6d53370 100644 --- a/Assets/Scripts/技能脚本/ShieldGuardSkill.cs +++ b/Assets/Scripts/技能脚本/ShieldGuardSkill.cs @@ -1,62 +1,134 @@ +using System.Collections; using UnityEngine; +/// +/// 盾卫技能:坚固壁垒 +/// 1级:生成相当于自身 25% 最大生命值的护盾(持续 5 秒),播放 MagicShield 护盾特效 +/// 3级:护盾值提升至 35% 最大生命值,护盾期间减伤 +15% +/// public class ShieldGuardSkill : UnitSkill { - [Header("盾牌参数")] - [Min(1f)] - public float shieldDurability = 220f; + [Header("护盾配置")] + [Tooltip("护盾持续时间(秒)")] + public float shieldDuration = 5.0f; - [Min(0.1f)] - public float shieldDuration = 4f; + [Tooltip("护盾特效预制体")] + public GameObject shieldVfxPrefab; - [Range(0f, 1f)] - public float damageReduction = 0.35f; + private GameObject activeVfxInstance; + private Coroutine activeShieldCoroutine; - public Vector3 shieldLocalPosition = new Vector3(0f, 1f, 0.7f); - - public Vector3 shieldSize = new Vector3(1.5f, 1.8f, 0.12f); - - private ShieldBarrier shield; - - public ShieldBarrier ActiveShield => shield; + private void EnsureUnit() + { + if (unit == null) + { + unit = GetComponent(); + } + } public override void ReleaseSkill() { - EnsureShield(); - shield.Refresh(shieldDurability, shieldDuration, shieldLocalPosition, shieldSize); + EnsureUnit(); + if (unit == null) return; + + WorldHealthBar healthBar = unit.GetComponent(); + if (healthBar == null || healthBar.IsDead) return; + + int level = unit.level; // 从 UnitBase 字段读取 level + // 1级:25% maxHealth,3级+:35% maxHealth + float ratio = level >= 3 ? 0.35f : 0.25f; + float shieldAmount = healthBar.maxHealth * ratio; + + // 1. 添加护盾值 + healthBar.AddShield(shieldAmount); + + // 2. 播放 MagicShield 护盾特效 + PlayShieldVfx(); + + // 3. 开启超时自动清盾协程 + if (activeShieldCoroutine != null) + { + StopCoroutine(activeShieldCoroutine); + } + activeShieldCoroutine = StartCoroutine(ShieldTimeoutRoutine(shieldDuration, shieldAmount)); + + Debug.Log($"盾卫释放技能【坚固壁垒】 获得 {shieldAmount:F1} 护盾(比例: {ratio * 100}%,等级: {level})"); } public override float ModifyIncomingDamage(float damage, GameObject attacker) { - if (shield == null || !shield.IsActive) + EnsureUnit(); + // 3级解锁:护盾激活期间享受额外 15% 减伤 + if (unit != null && unit.level >= 3) { - return damage; + WorldHealthBar healthBar = unit.GetComponent(); + if (healthBar != null && healthBar.currentShield > 0f) + { + return damage * (1f - 0.15f); + } } - - if (shield.TryBlock(attacker, damage)) - { - return damage * (1f - damageReduction); - } - return damage; } - private void EnsureShield() + private void PlayShieldVfx() { - if (shield != null) + if (activeVfxInstance != null) { - return; + Destroy(activeVfxInstance); } - GameObject shieldObject = new GameObject("ShieldBarrier"); - shieldObject.transform.SetParent(unit.transform, false); + GameObject prefab = shieldVfxPrefab; + if (prefab == null) + { + prefab = Resources.Load("Prefabs/Combat/Magic/Shield/MagicShieldBlue"); + } +#if UNITY_EDITOR + if (prefab == null) + { + prefab = UnityEditor.AssetDatabase.LoadAssetAtPath("Assets/TJGeneratorLibEffects/Prefabs/Combat/Magic/Shield/MagicShieldBlue.prefab"); + } +#endif - BoxCollider boxCollider = shieldObject.AddComponent(); - boxCollider.isTrigger = true; - boxCollider.size = shieldSize; + if (prefab != null) + { + activeVfxInstance = Instantiate(prefab, unit.transform.position + Vector3.up * 1.0f, Quaternion.identity); + activeVfxInstance.transform.SetParent(unit.transform, true); + } + } - shield = shieldObject.AddComponent(); - shield.Configure(unit, boxCollider); - shieldObject.SetActive(false); + private IEnumerator ShieldTimeoutRoutine(float duration, float amount) + { + float timer = duration; + WorldHealthBar healthBar = unit != null ? unit.GetComponent() : null; + + while (timer > 0f) + { + if (unit == null || healthBar == null || healthBar.IsDead || healthBar.currentShield <= 0f) + { + break; + } + timer -= Time.deltaTime; + yield return null; + } + + // 超时或护盾为 0 清理特效与护盾值 + if (activeVfxInstance != null) + { + Destroy(activeVfxInstance); + activeVfxInstance = null; + } + + if (healthBar != null && healthBar.currentShield > 0f && timer <= 0f) + { + healthBar.RemoveShield(amount); + } + } + + private void OnDestroy() + { + if (activeVfxInstance != null) + { + Destroy(activeVfxInstance); + } } } diff --git a/Assets/Scripts/系统脚本/BuffSelectionController.cs b/Assets/Scripts/系统脚本/BuffSelectionController.cs index d0ccf5f..3e4d55f 100644 --- a/Assets/Scripts/系统脚本/BuffSelectionController.cs +++ b/Assets/Scripts/系统脚本/BuffSelectionController.cs @@ -45,9 +45,10 @@ public class BuffSelectionController : MonoBehaviour public enum BuffQuality { - Common, // 初级 (白色 55%) - Rare, // 中级 (紫色 35%) - Epic // 高级 (金色 10%) + Common, // 初级 (白色 85%) + Rare, // 中级 (紫色 10%) + Epic, // 高级 (金色 5%) + Legendary // 顶级 (红色/酒红 预留) } public class BuffOption @@ -68,18 +69,24 @@ public class BuffSelectionController : MonoBehaviour [Header("UI 节点引用")] public GameObject buffSelectionWindow; public Transform cardsContainer; + public Button rollButton; // 刷新按钮 + public int rollCost = 2; // 每次刷新消耗金币 [Header("品质边框 Sprites")] public Sprite commonBorderSprite; // 铜 (初级) public Sprite rareBorderSprite; // 银 (中级) public Sprite epicBorderSprite; // 金 (高级) - public Sprite legendaryBorderSprite; // 红 (最高级) + public Sprite legendaryBorderSprite; // 红 (顶级) [Header("头像 Sprites")] public Sprite allIconSprite; // 全体/全局头像 (all.png) private bool isWindowShowing = false; + // 3 个卡槽各自独立追踪的品质与递进未升阶计数 + private BuffQuality[] slotQualities = new BuffQuality[3]; + private int[] slotNonUpgradeCounts = new int[3]; + private void Awake() { if (_instance == null) @@ -102,15 +109,111 @@ public class BuffSelectionController : MonoBehaviour return; } - // 生成 3 个随机卡片 - List options = GenerateThreeOptions(); + // 初始弹窗时重置 3 个卡槽的累积计数,并按初始基础概率独立抽品质 (初级 85% / 中级 10% / 高级 5%) + for (int i = 0; i < 3; i++) + { + slotNonUpgradeCounts[i] = 0; + float roll = Random.value; + if (roll < 0.85f) + { + slotQualities[i] = BuffQuality.Common; + } + else if (roll < 0.95f) + { + slotQualities[i] = BuffQuality.Rare; + } + else + { + slotQualities[i] = BuffQuality.Epic; + } + } + + // 生成 3 个卡槽的增益选项 + List options = GenerateOptionsForSlots(); RenderOptionsUI(options); + // 绑定刷新按钮事件 + if (rollButton != null) + { + rollButton.onClick.RemoveAllListeners(); + rollButton.onClick.AddListener(OnRollButtonClicked); + } + buffSelectionWindow.SetActive(true); isWindowShowing = true; Time.timeScale = 0f; // 暂停游戏让玩家做选择 } + /// + /// 点击刷新按钮 (耗费 2 金币),3 个卡槽独立计算升阶概率并重新生成选项 + /// + private void OnRollButtonClicked() + { + if (GoldManager.Instance != null && !GoldManager.Instance.TrySpendGold(rollCost)) + { + Debug.LogWarning($"金币不足 {rollCost},无法刷新!"); + return; + } + + // 3 个卡槽独立判定升级逻辑 + for (int i = 0; i < 3; i++) + { + BuffQuality currentQ = slotQualities[i]; + bool upgraded = false; + BuffQuality targetQ = currentQ; + + if (currentQ == BuffQuality.Common) + { + // 初级 升 中级 10% 每次 +10% (第2次20%、第3次30%...) + float chance = 0.10f + slotNonUpgradeCounts[i] * 0.10f; + if (Random.value < chance) + { + targetQ = BuffQuality.Rare; + upgraded = true; + } + } + else if (currentQ == BuffQuality.Rare) + { + // 中级 升 高级 3% 每次 +5% (第2次8%、第3次13%...) + float chance = 0.03f + slotNonUpgradeCounts[i] * 0.05f; + if (Random.value < chance) + { + targetQ = BuffQuality.Epic; + upgraded = true; + } + } + else if (currentQ == BuffQuality.Epic) + { + // 高级 升 顶级 1% (预留) 每次 +2% + float chance = 0.01f + slotNonUpgradeCounts[i] * 0.02f; + if (Random.value < chance) + { + targetQ = BuffQuality.Legendary; + upgraded = true; + } + } + + if (upgraded) + { + slotQualities[i] = targetQ; + slotNonUpgradeCounts[i] = 0; // 刷出升阶,该槽位的追加概率重置 + } + else + { + if (currentQ != BuffQuality.Legendary) + { + slotNonUpgradeCounts[i]++; // 未升阶,追加概率增加 + } + } + } + + Debug.Log($"玩家消耗 {rollCost} 金币刷新卡牌! 当前槽位品质: [{slotQualities[0]}], [{slotQualities[1]}], [{slotQualities[2]}]"); + + // 重新渲染 3 个卡槽 + List newOptions = GenerateOptionsForSlots(); + RenderOptionsUI(newOptions); + } + private void RenderOptionsUI(List options) { int cardCount = cardsContainer.childCount; @@ -120,7 +223,7 @@ public class BuffSelectionController : MonoBehaviour if (i < options.Count) { card.gameObject.SetActive(true); - SetupCardUI(card, options[i]); + SetupCardUI(card, options[i], i); } else { @@ -129,10 +232,32 @@ public class BuffSelectionController : MonoBehaviour } } - private void SetupCardUI(Transform cardTransform, BuffOption option) + private float GetNextUpgradeChance(int slotIndex) + { + if (slotIndex < 0 || slotIndex >= 3) return 0f; + BuffQuality currentQ = slotQualities[slotIndex]; + int count = slotNonUpgradeCounts[slotIndex]; + + if (currentQ == BuffQuality.Common) + { + return 0.10f + count * 0.10f; // 10% + n * 10% + } + else if (currentQ == BuffQuality.Rare) + { + return 0.03f + count * 0.05f; // 3% + n * 5% + } + else if (currentQ == BuffQuality.Epic) + { + return 0.01f + count * 0.02f; // 1% + n * 2% + } + return 0f; // 顶级无升级 + } + + private void SetupCardUI(Transform cardTransform, BuffOption option, int slotIndex) { Text buffNameText = cardTransform.Find("BuffName")?.GetComponent(); Text descText = cardTransform.Find("Description")?.GetComponent(); + Text upgradeRateText = cardTransform.Find("UpgradeRateText")?.GetComponent(); if (buffNameText != null) { @@ -145,6 +270,19 @@ public class BuffSelectionController : MonoBehaviour descText.text = option.descText; } + if (upgradeRateText != null) + { + if (option.quality == BuffQuality.Legendary) + { + upgradeRateText.text = "已达最高品质"; + } + else + { + float chance = GetNextUpgradeChance(slotIndex); + upgradeRateText.text = $"升阶概率: {Mathf.Min(100f, chance * 100f):F0}%"; + } + } + // 设置目标英雄 / 全体 Icon 头像 Image iconImage = cardTransform.Find("Icon")?.GetComponent(); if (iconImage != null && option.iconSprite != null) @@ -191,6 +329,8 @@ public class BuffSelectionController : MonoBehaviour return rareBorderSprite != null ? rareBorderSprite : Resources.Load("Picture/UI/增益边框/银"); case BuffQuality.Epic: return epicBorderSprite != null ? epicBorderSprite : Resources.Load("Picture/UI/增益边框/金"); + case BuffQuality.Legendary: + return legendaryBorderSprite != null ? legendaryBorderSprite : Resources.Load("Picture/UI/增益边框/红"); default: return commonBorderSprite; } @@ -335,7 +475,7 @@ public class BuffSelectionController : MonoBehaviour } } - private List GenerateThreeOptions() + private List GenerateOptionsForSlots() { List list = new List(); HashSet usedTypes = new HashSet(); @@ -346,7 +486,7 @@ public class BuffSelectionController : MonoBehaviour int maxAttempts = 10; do { - opt = CreateRandomOption(i); + opt = CreateOptionForSlot(i, slotQualities[i]); maxAttempts--; } while (usedTypes.Contains(opt.type) && maxAttempts > 0); @@ -370,42 +510,42 @@ public class BuffSelectionController : MonoBehaviour BuffType.EmergencyHeal // 14. 紧急救急 }; - private BuffOption CreateRandomOption(int seedOffset) + private BuffOption CreateOptionForSlot(int slotIndex, BuffQuality quality) { BuffOption opt = new BuffOption(); // 从当前开放的词条池中随机选择 opt.type = AvailableBuffTypes[Random.Range(0, AvailableBuffTypes.Length)]; + opt.quality = quality; - // 随机品质 (初级55%, 中级35%, 高级10%) - float rollQuality = Random.value; - if (rollQuality < 0.55f) + switch (quality) { - opt.quality = BuffQuality.Common; - opt.qualityName = "初级"; - opt.qualityColor = new Color(0.85f, 0.85f, 0.85f); // 白色 - } - else if (rollQuality < 0.90f) - { - opt.quality = BuffQuality.Rare; - opt.qualityName = "中级"; - opt.qualityColor = new Color(0.7f, 0.35f, 0.95f); // 紫色 - } - else - { - opt.quality = BuffQuality.Epic; - opt.qualityName = "高级"; - opt.qualityColor = new Color(1f, 0.8f, 0.15f); // 金色 + case BuffQuality.Common: + opt.qualityName = "初级"; + opt.qualityColor = new Color(0.85f, 0.85f, 0.85f); // 白色 + break; + case BuffQuality.Rare: + opt.qualityName = "中级"; + opt.qualityColor = new Color(0.7f, 0.35f, 0.95f); // 紫色 + break; + case BuffQuality.Epic: + opt.qualityName = "高级"; + opt.qualityColor = new Color(1f, 0.8f, 0.15f); // 金色 + break; + case BuffQuality.Legendary: + opt.qualityName = "顶级"; + opt.qualityColor = new Color(1f, 0.25f, 0.25f); // 红色/酒红 + break; } - // 判断是否为全局属性 + // 判断是否为全局属性 (20% 全体,80% 指定单体) bool isGlobalOnly = (opt.type == BuffType.SummonerCD || opt.type == BuffType.SummonerPower || opt.type == BuffType.KillGold || opt.type == BuffType.GoldBonus); List activeHeroes = GetLocalPlayerHeroes(); - bool isAll = isGlobalOnly || (Random.value < 0.5f) || (activeHeroes.Count == 0); + bool isAll = isGlobalOnly || (Random.value < 0.20f) || (activeHeroes.Count == 0); string heroDisplayName = ""; if (isAll) @@ -530,33 +670,33 @@ public class BuffSelectionController : MonoBehaviour switch (type) { case BuffType.AttackPower: - return quality == BuffQuality.Common ? 0.06f : (quality == BuffQuality.Rare ? 0.12f : 0.20f); + return quality == BuffQuality.Common ? 0.06f : (quality == BuffQuality.Rare ? 0.12f : (quality == BuffQuality.Epic ? 0.20f : 0.32f)); case BuffType.MaxHealth: - return quality == BuffQuality.Common ? 0.10f : (quality == BuffQuality.Rare ? 0.18f : 0.30f); + return quality == BuffQuality.Common ? 0.10f : (quality == BuffQuality.Rare ? 0.18f : (quality == BuffQuality.Epic ? 0.30f : 0.45f)); case BuffType.AttackSpeed: - return quality == BuffQuality.Common ? 0.05f : (quality == BuffQuality.Rare ? 0.10f : 0.16f); + return quality == BuffQuality.Common ? 0.05f : (quality == BuffQuality.Rare ? 0.10f : (quality == BuffQuality.Epic ? 0.16f : 0.25f)); case BuffType.CritRate: - return quality == BuffQuality.Common ? 0.06f : (quality == BuffQuality.Rare ? 0.12f : 0.20f); + return quality == BuffQuality.Common ? 0.06f : (quality == BuffQuality.Rare ? 0.12f : (quality == BuffQuality.Epic ? 0.20f : 0.30f)); case BuffType.Lifesteal: - return quality == BuffQuality.Common ? 0.04f : (quality == BuffQuality.Rare ? 0.08f : 0.14f); + return quality == BuffQuality.Common ? 0.04f : (quality == BuffQuality.Rare ? 0.08f : (quality == BuffQuality.Epic ? 0.14f : 0.20f)); case BuffType.SkillCharge: - return quality == BuffQuality.Common ? 0.08f : (quality == BuffQuality.Rare ? 0.15f : 0.25f); + return quality == BuffQuality.Common ? 0.08f : (quality == BuffQuality.Rare ? 0.15f : (quality == BuffQuality.Epic ? 0.25f : 0.40f)); case BuffType.SummonerCD: - return quality == BuffQuality.Common ? 0.05f : (quality == BuffQuality.Rare ? 0.10f : 0.18f); + return quality == BuffQuality.Common ? 0.05f : (quality == BuffQuality.Rare ? 0.10f : (quality == BuffQuality.Epic ? 0.18f : 0.28f)); case BuffType.SummonerPower: - return quality == BuffQuality.Common ? 0.08f : (quality == BuffQuality.Rare ? 0.15f : 0.25f); + return quality == BuffQuality.Common ? 0.08f : (quality == BuffQuality.Rare ? 0.15f : (quality == BuffQuality.Epic ? 0.25f : 0.40f)); case BuffType.AttackRange: - return quality == BuffQuality.Common ? 0.5f : (quality == BuffQuality.Rare ? 1.0f : 1.8f); + return quality == BuffQuality.Common ? 0.5f : (quality == BuffQuality.Rare ? 1.0f : (quality == BuffQuality.Epic ? 1.8f : 2.5f)); case BuffType.DamageReduction: - return quality == BuffQuality.Common ? 0.05f : (quality == BuffQuality.Rare ? 0.10f : 0.16f); + return quality == BuffQuality.Common ? 0.05f : (quality == BuffQuality.Rare ? 0.10f : (quality == BuffQuality.Epic ? 0.16f : 0.25f)); case BuffType.Shield: - return quality == BuffQuality.Common ? 0.15f : (quality == BuffQuality.Rare ? 0.25f : 0.40f); + return quality == BuffQuality.Common ? 0.15f : (quality == BuffQuality.Rare ? 0.25f : (quality == BuffQuality.Epic ? 0.40f : 0.60f)); case BuffType.KillGold: - return quality == BuffQuality.Common ? 0.15f : (quality == BuffQuality.Rare ? 0.30f : 0.50f); + return quality == BuffQuality.Common ? 0.15f : (quality == BuffQuality.Rare ? 0.30f : (quality == BuffQuality.Epic ? 0.50f : 0.80f)); case BuffType.GoldBonus: - return quality == BuffQuality.Common ? 30f : (quality == BuffQuality.Rare ? 60f : 100f); + return quality == BuffQuality.Common ? 30f : (quality == BuffQuality.Rare ? 60f : (quality == BuffQuality.Epic ? 100f : 160f)); case BuffType.EmergencyHeal: - return quality == BuffQuality.Common ? 0.25f : (quality == BuffQuality.Rare ? 0.40f : 0.60f); + return quality == BuffQuality.Common ? 0.25f : (quality == BuffQuality.Rare ? 0.40f : (quality == BuffQuality.Epic ? 0.60f : 0.80f)); default: return 0.10f; } @@ -595,10 +735,19 @@ public class BuffSelectionController : MonoBehaviour if (winTrans != null) buffSelectionWindow = winTrans.gameObject; } - if (buffSelectionWindow != null && cardsContainer == null) + if (buffSelectionWindow != null) { - var containerTrans = buffSelectionWindow.transform.Find("BuffPanel/BuffCardsContainer"); - if (containerTrans != null) cardsContainer = containerTrans; + if (cardsContainer == null) + { + var containerTrans = buffSelectionWindow.transform.Find("BuffPanel/BuffCardsContainer"); + if (containerTrans != null) cardsContainer = containerTrans; + } + + if (rollButton == null) + { + var rollTrans = buffSelectionWindow.transform.Find("BuffPanel/RollButton"); + if (rollTrans != null) rollButton = rollTrans.GetComponent