using UnityEngine; using UnityEngine.UI; public class WorldHealthBar : MonoBehaviour { // 最大生命值。 [Min(1f)] public float maxHealth = 100f; // 当前生命值,范围会自动限制在 0 到 maxHealth 之间。 [Min(0f)] public float currentHealth = 100f; // 技能能量上限,英雄默认使用100点;没有技能的小怪可关闭能量条。 [Header("技能能量")] public bool showEnergyBar = true; [Min(1f)] public float maxEnergy = 100f; // 当前技能能量,范围会自动限制在 0 到 maxEnergy 之间。 [Min(0f)] [SerializeField] private float _currentEnergy = 0f; public float currentEnergy { get => _currentEnergy; set { _currentEnergy = Mathf.Clamp(value, 0f, maxEnergy); UpdateEnergyFill(); } } public bool IsEnergyFull => !isDead && currentEnergy >= maxEnergy; // 血条相对于单位头顶的额外高度。 [Min(0f)] public float heightOffset = 0.25f; // 所有单位使用统一的血条世界宽度。 [Min(0.1f)] public float barWidth = 1.2f; // 血条边框颜色。 public Color borderColor = new Color(0.03f, 0.03f, 0.03f, 0.95f); // 死亡特效持续时间,单位为秒。 [Min(0.1f)] public float deathEffectDuration = 1.1f; // 死亡时使用的浅蓝色粒子颜色。 public Color deathEffectColor = new Color(0.55f, 0.9f, 1f, 1f); [Header("伤害数字")] public bool showDamageNumbers = true; [Min(0.1f)] public float damageNumberDuration = 0.8f; [Min(0f)] public float damageNumberRiseHeight = 1.2f; [Min(0f)] public float damageNumberSpawnHeight = 1.5f; [Min(0.01f)] public float damageNumberSize = 0.09f; // 描边宽度设为私有常量,所有角色统一使用 private const float DamageNumberOutlineWidth = 0.025f; // 从 0.015 增加到 0.025,描边更粗 // 血条 UI 的像素尺寸,实际显示大小由 worldWidth 控制。 private const int PixelWidth = 100; private const int PixelHeight = 14; // 能量条紧贴血条下方,宽度更窄,高度更小。 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; // 世界空间 Canvas,用来承载血条 UI。 private Canvas canvas; // 血条整体的 RectTransform。 private RectTransform barRect; // 绿色血量填充 Image。 private Image fillImage; // 金色技能能量填充 Image。 private Image energyFillImage; // 血条相对于单位的本地位置。 private Vector3 localBarPosition; // 血条最终使用的世界宽度。 private float worldWidth; // 所有血条共用的圆角 Sprite。 private static Sprite roundedSprite; // 防止死亡逻辑重复执行。 private bool isDead; // 公开死亡状态供外部查询(用于胜负判定) public bool IsDead => isDead; // 初始化血量和血条 UI。 private void Awake() { maxHealth = Mathf.Max(1f, maxHealth); currentHealth = Mathf.Clamp(currentHealth, 0f, maxHealth); maxEnergy = Mathf.Max(1f, maxEnergy); _currentEnergy = Mathf.Clamp(_currentEnergy, 0f, maxEnergy); CalculateLayout(); CreateBar(); // 创建血条 UI 结构;无技能单位只创建血条 UpdateFill(); UpdateEnergyFill(); } // 在 Start() 中根据最终标签设置血条颜色,确保 Instantiate 后的标签修改生效。 private void Start() { if (fillImage != null) { fillImage.color = GetHealthFillColor(); } } // 每帧让血条保持在单位头顶,并朝向主摄像机。 private void LateUpdate() { if (barRect == null) return; barRect.localPosition = GetBarRootPosition(); ApplyScaleCompensation(); // 优先使用场景中名称完全为 Main Camera 的摄像机。 Camera camera = FindMainCamera(); if (camera == null) return; canvas.worldCamera = camera; if (camera.gameObject.name == "Main Camera") { // 使用摄像机的上方向,保证血条在摄像机画面中保持水平。 barRect.rotation = Quaternion.LookRotation( camera.transform.position - barRect.position, camera.transform.up); } else { // 找不到 Main Camera 时,保留原来的展示逻辑。 barRect.rotation = Quaternion.LookRotation( camera.transform.position - barRect.position, Vector3.up); } } // 查找场景中名称为 Main Camera 的摄像机;找不到时回退到 Camera.main。 private Camera FindMainCamera() { GameObject mainCameraObject = GameObject.Find("Main Camera"); if (mainCameraObject != null) { Camera namedCamera = mainCameraObject.GetComponent(); if (namedCamera != null && namedCamera.isActiveAndEnabled) { return namedCamera; } } return Camera.main; } // 直接设置当前生命值,并刷新绿色填充比例。 public void SetHealth(float value) { if (isDead) return; currentHealth = Mathf.Clamp(value, 0f, maxHealth); UpdateFill(); if (currentHealth <= 0f) { Die(); } } // 让单位受到伤害,并弹出伤害数字。 // attacker: 攻击者,用于判断受击者(自己)是敌是友,决定数字颜色和浮动方向 public void TakeDamage(float amount, GameObject attacker = null) { bool wasDead = isDead; SetHealth(currentHealth - amount); if (showDamageNumbers && amount > 0f && !wasDead) { // 判断受击者(自己)是否是友方(Friend tag) bool isVictimFriend = gameObject.CompareTag("Friend"); SpawnDamageNumber(amount, false, isVictimFriend); } } // 不再需要这个方法,直接用 CompareTag 判断 // 让单位恢复生命值,并弹出绿色治疗数字。 public void Heal(float amount) { bool wasDead = isDead; float healed = Mathf.Min(amount, maxHealth - currentHealth); SetHealth(currentHealth + amount); if (showDamageNumbers && healed > 0f && !wasDead) { SpawnDamageNumber(healed, true); } } // 增加技能能量,并返回本次增加后是否达到满能量。 public bool AddEnergy(float amount) { if (isDead || amount <= 0f) { return false; } float previousEnergy = currentEnergy; currentEnergy = Mathf.Clamp(currentEnergy + amount, 0f, maxEnergy); UpdateEnergyFill(); return previousEnergy < maxEnergy && currentEnergy >= maxEnergy; } // 清空技能能量并刷新能量条。 public void ResetEnergy() { SetEnergy(0f); } // 设置下方条的当前填充值;单位用于技能能量,建筑用于生产进度。 public void SetEnergy(float value) { if (isDead) { return; } currentEnergy = Mathf.Clamp(value, 0f, maxEnergy); UpdateEnergyFill(); } // 处理单位死亡:隐藏模型和血条,播放蓝色向上爆散特效。 private void Die() { isDead = true; currentHealth = 0f; UpdateFill(); foreach (Renderer renderer in GetComponentsInChildren()) { renderer.enabled = false; } // 立即从敌我扫描中移除,避免死亡等待期间继续被攻击。 gameObject.SetActive(false); if (barRect != null) { barRect.gameObject.SetActive(false); } GameObject effect = CreateDeathEffect(); Destroy(effect, deathEffectDuration); Destroy(gameObject, deathEffectDuration); } // 创建蓝色粒子爆散特效。 private GameObject CreateDeathEffect() { GameObject effect = new GameObject("DeathEffect"); effect.transform.position = transform.position + Vector3.up * 1.35f; ParticleSystem particles = effect.AddComponent(); particles.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear); var main = particles.main; main.playOnAwake = false; main.duration = deathEffectDuration; main.startLifetime = new ParticleSystem.MinMaxCurve(0.9f, deathEffectDuration); main.startSpeed = new ParticleSystem.MinMaxCurve(0.05f, 0.18f); main.startSize = new ParticleSystem.MinMaxCurve(0.09f, 0.2f); main.startRotation3D = true; main.startRotationX = new ParticleSystem.MinMaxCurve(0f, 6.28f); main.startRotationY = new ParticleSystem.MinMaxCurve(0f, 6.28f); main.startRotationZ = new ParticleSystem.MinMaxCurve(0f, 6.28f); main.startColor = deathEffectColor; main.maxParticles = 32; main.gravityModifier = new ParticleSystem.MinMaxCurve(1.6f); main.simulationSpace = ParticleSystemSimulationSpace.World; main.stopAction = ParticleSystemStopAction.None; var emission = particles.emission; emission.rateOverTime = 0f; emission.SetBursts(new[] { new ParticleSystem.Burst(0f, 18) }); var shape = particles.shape; shape.shapeType = ParticleSystemShapeType.Sphere; shape.radius = 0.12f; // 只保留轻微水平散落,主要依靠重力向下坠落。 var velocity = particles.velocityOverLifetime; velocity.enabled = true; velocity.space = ParticleSystemSimulationSpace.World; velocity.x = new ParticleSystem.MinMaxCurve(-0.08f, 0.08f); velocity.y = new ParticleSystem.MinMaxCurve(-0.25f, -0.05f); velocity.z = new ParticleSystem.MinMaxCurve(-0.08f, 0.08f); var collision = particles.collision; collision.enabled = true; collision.type = ParticleSystemCollisionType.World; collision.mode = ParticleSystemCollisionMode.Collision3D; collision.bounce = 0f; collision.lifetimeLoss = 0.15f; var size = particles.sizeOverLifetime; size.enabled = true; AnimationCurve fade = new AnimationCurve( new Keyframe(0f, 1f), new Keyframe(0.75f, 1f), new Keyframe(1f, 0f)); size.size = new ParticleSystem.MinMaxCurve(1f, fade); var renderer = effect.GetComponent(); renderer.renderMode = ParticleSystemRenderMode.Mesh; renderer.mesh = CreateCrystalMesh(); renderer.material = CreateParticleMaterial(); particles.Play(); return effect; } // 创建低模菱形水晶碎片 Mesh。 private Mesh CreateCrystalMesh() { Mesh mesh = new Mesh { name = "DeathCrystal" }; mesh.vertices = new[] { new Vector3(0f, 0.5f, 0f), new Vector3(0.5f, 0f, 0f), new Vector3(0f, 0f, 0.5f), new Vector3(-0.5f, 0f, 0f), new Vector3(0f, 0f, -0.5f), new Vector3(0f, -0.5f, 0f) }; mesh.triangles = new[] { 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 1, 5, 2, 1, 5, 3, 2, 5, 4, 3, 5, 1, 4 }; mesh.RecalculateNormals(); mesh.RecalculateBounds(); return mesh; } // 创建带浅蓝发光效果的水晶材质。 private Material CreateParticleMaterial() { Shader shader = Shader.Find("Standard"); Material material = new Material(shader); material.color = deathEffectColor; material.SetFloat("_Metallic", 0.15f); material.SetFloat("_Glossiness", 0.85f); material.SetColor("_EmissionColor", deathEffectColor * 1.6f); material.EnableKeyword("_EMISSION"); return material; } // 根据单位模型或碰撞体计算血条位置和宽度。 private void CalculateLayout() { Bounds bounds = new Bounds(transform.position, new Vector3(1f, 2f, 1f)); bool found = false; foreach (Renderer renderer in GetComponentsInChildren(true)) { if (!found) { bounds = renderer.bounds; found = true; } else { bounds.Encapsulate(renderer.bounds); } } if (!found) { CharacterController controller = GetComponent(); if (controller != null) bounds = controller.bounds; } // 血条尺寸固定,不随单位模型大小变化;只根据模型顶部调整位置。 worldWidth = barWidth; // 水平方向固定对齐单位根节点,只使用模型顶部计算血条高度。 Vector3 top = transform.InverseTransformPoint(new Vector3(transform.position.x, bounds.max.y, transform.position.z)); float localHeightOffset = transform.lossyScale.y != 0f ? heightOffset / transform.lossyScale.y : heightOffset; localBarPosition = new Vector3(0f, top.y + localHeightOffset, 0f); } // 创建世界空间 Canvas、边框和绿色血量填充。 private void CreateBar() { GameObject barObject = new GameObject("WorldHealthBar", typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler)); barObject.transform.SetParent(transform, false); barObject.layer = gameObject.layer; canvas = barObject.GetComponent(); canvas.renderMode = RenderMode.WorldSpace; canvas.sortingOrder = 100; barObject.GetComponent().dynamicPixelsPerUnit = 10f; barRect = barObject.GetComponent(); barRect.sizeDelta = new Vector2(PixelWidth, showEnergyBar ? TotalPixelHeight : PixelHeight); barRect.localScale = Vector3.one * (worldWidth / PixelWidth); barRect.localPosition = GetBarRootPosition(); ApplyScaleCompensation(); RectTransform healthRow = CreateRow(barObject.transform, "HealthBar", PixelWidth, PixelHeight, 0); 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; inner.rectTransform.offsetMin = new Vector2(2f, 2f); inner.rectTransform.offsetMax = new Vector2(-2f, -2f); inner.type = Image.Type.Filled; inner.fillMethod = Image.FillMethod.Horizontal; inner.fillOrigin = 0; fillImage = inner; if (!showEnergyBar) { energyFillImage = null; return; } RectTransform energyRow = CreateRow( barObject.transform, "EnergyBar", EnergyPixelWidth, EnergyPixelHeight, PixelHeight + BarGap); Image energyBackground = CreateImage("EnergyBorder", energyRow, RoundedSprite(), borderColor); SetFullRect(energyBackground.rectTransform); Image energyInner = CreateImage( "EnergyFill", energyRow, RoundedSprite(), GetEnergyFillColor()); energyInner.rectTransform.anchorMin = Vector2.zero; energyInner.rectTransform.anchorMax = Vector2.one; energyInner.rectTransform.offsetMin = new Vector2(2f, 1f); energyInner.rectTransform.offsetMax = new Vector2(-2f, -1f); energyInner.type = Image.Type.Filled; energyInner.fillMethod = Image.FillMethod.Horizontal; energyInner.fillOrigin = 0; energyFillImage = energyInner; } private void ApplyScaleCompensation() { Vector3 parentScale = transform.lossyScale; float targetScale = worldWidth / PixelWidth; barRect.localScale = new Vector3( parentScale.x != 0f ? targetScale / parentScale.x : targetScale, parentScale.y != 0f ? targetScale / parentScale.y : targetScale, parentScale.z != 0f ? targetScale / parentScale.z : targetScale); } // 返回整体 Canvas 的位置,使血条中心保持原来的高度。 private Vector3 GetBarRootPosition() { float pixelToWorld = worldWidth / PixelWidth; int barHeight = showEnergyBar ? TotalPixelHeight : PixelHeight; float worldEnergyOffset = (barHeight - PixelHeight) * 0.5f * pixelToWorld; float localEnergyOffset = transform.lossyScale.y != 0f ? worldEnergyOffset / transform.lossyScale.y : worldEnergyOffset; return localBarPosition - Vector3.up * localEnergyOffset; } // 创建一行固定像素高度的世界空间 UI。 private static RectTransform CreateRow( Transform parent, string name, int width, int height, int topOffset) { GameObject rowObject = new GameObject(name, typeof(RectTransform)); rowObject.transform.SetParent(parent, false); RectTransform rowRect = rowObject.GetComponent(); rowRect.anchorMin = new Vector2(0.5f, 1f); rowRect.anchorMax = new Vector2(0.5f, 1f); rowRect.pivot = new Vector2(0.5f, 1f); rowRect.sizeDelta = new Vector2(width, height); rowRect.anchoredPosition = new Vector2(0f, -topOffset); return rowRect; } private Color GetHealthFillColor() { // 使用阵营 ID 判断敌我(支持 PVP) var unitTeam = GetComponent(); if (unitTeam != null && unitTeam.IsLocalPlayerTeam()) { return new Color(0.1f, 0.8f, 0.18f, 1f); // 绿色(我方) } else { return new Color(0.88f, 0.12f, 0.12f, 1f); // 红色(敌方) } } private Color GetEnergyFillColor() { return new Color(1f, 0.72f, 0.08f, 1f); } // 创建一个用于边框或填充的 UI Image。 private static Image CreateImage(string name, Transform parent, Sprite sprite, Color color) { GameObject imageObject = new GameObject(name, typeof(RectTransform), typeof(Image)); imageObject.transform.SetParent(parent, false); Image image = imageObject.GetComponent(); image.sprite = sprite; image.color = color; image.raycastTarget = false; return image; } // 让 UI 元素铺满父级 RectTransform。 private static void SetFullRect(RectTransform rect) { rect.anchorMin = Vector2.zero; rect.anchorMax = Vector2.one; rect.offsetMin = Vector2.zero; rect.offsetMax = Vector2.zero; } // 根据当前生命值更新绿色填充比例。 private void UpdateFill() { if (fillImage != null) { fillImage.fillAmount = Mathf.Clamp01(currentHealth / maxHealth); } } // 根据当前技能能量更新金色填充比例。 private void UpdateEnergyFill() { if (energyFillImage != null) { energyFillImage.fillAmount = Mathf.Clamp01(currentEnergy / maxEnergy); } } // 创建并缓存一个直角矩形白色 Sprite,供边框和填充复用。 private static Sprite RoundedSprite() { if (roundedSprite != null) return roundedSprite; const int width = 64; const int height = 16; Texture2D texture = new Texture2D(width, height, TextureFormat.RGBA32, false); texture.wrapMode = TextureWrapMode.Clamp; texture.filterMode = FilterMode.Bilinear; texture.hideFlags = HideFlags.HideAndDontSave; // 填充纯白色,无圆角 for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { texture.SetPixel(x, y, Color.white); } } texture.Apply(); roundedSprite = Sprite.Create(texture, new Rect(0, 0, width, height), new Vector2(0.5f, 0.5f), width); roundedSprite.hideFlags = HideFlags.HideAndDontSave; return roundedSprite; } // 在单位头顶生成一个漂浮伤害数字;数字独立于单位存活,死亡单位也能看到最后一击。 // TextMesh 不支持原生描边,用 4 个偏移的黑色副本 + 1 个白色主字组合出黑边效果。 private void SpawnDamageNumber(float amount, bool isHeal, bool isVictimFriend = false) { if (DamageNumber.ActiveCount >= DamageNumber.MaxActive) { return; } GameObject rootObject = new GameObject("DamageNumber"); Vector3 spawnPosition = transform.position + Vector3.up * damageNumberSpawnHeight; spawnPosition.x += Random.Range(-0.3f, 0.3f); rootObject.transform.position = spawnPosition; string text = Mathf.RoundToInt(amount).ToString(); float outline = DamageNumberOutlineWidth; // 8 方向黑色描边:上下左右 + 四个对角,形成更粗更均匀的描边 CreateNumberText(rootObject.transform, text, new Vector3(-outline, 0f, 0f), Color.black, damageNumberSize, 60); // 左 CreateNumberText(rootObject.transform, text, new Vector3(outline, 0f, 0f), Color.black, damageNumberSize, 60); // 右 CreateNumberText(rootObject.transform, text, new Vector3(0f, -outline, 0f), Color.black, damageNumberSize, 60); // 下 CreateNumberText(rootObject.transform, text, new Vector3(0f, outline, 0f), Color.black, damageNumberSize, 60); // 上 CreateNumberText(rootObject.transform, text, new Vector3(-outline, -outline, 0f), Color.black, damageNumberSize, 60); // 左下 CreateNumberText(rootObject.transform, text, new Vector3(outline, -outline, 0f), Color.black, damageNumberSize, 60); // 右下 CreateNumberText(rootObject.transform, text, new Vector3(-outline, outline, 0f), Color.black, damageNumberSize, 60); // 左上 CreateNumberText(rootObject.transform, text, new Vector3(outline, outline, 0f), Color.black, damageNumberSize, 60); // 右上 // 主字颜色:治疗=绿色,友方受伤=红色,敌方受伤=白色 Color mainColor = isHeal ? Color.green : (isVictimFriend ? Color.red : Color.white); CreateNumberText(rootObject.transform, text, Vector3.zero, mainColor, damageNumberSize, 61); DamageNumber number = rootObject.AddComponent(); number.duration = damageNumberDuration; // 友方受伤向下浮动(负值),敌方受伤向上浮动(正值) number.riseHeight = isVictimFriend ? -damageNumberRiseHeight : damageNumberRiseHeight; } // 创建一个配置好的数字 TextMesh 子物体。 private static TextMesh CreateNumberText(Transform parent, string text, Vector3 localOffset, Color color, float characterSize, int sortingOrder) { GameObject textObject = new GameObject("Text"); textObject.transform.SetParent(parent, false); textObject.transform.localPosition = localOffset; Font font = GetBuiltinFont(); TextMesh textMesh = textObject.AddComponent(); textMesh.text = text; textMesh.font = font; textMesh.fontSize = 40; textMesh.characterSize = characterSize; textMesh.anchor = TextAnchor.MiddleCenter; textMesh.alignment = TextAlignment.Center; textMesh.fontStyle = FontStyle.Bold; textMesh.richText = false; textMesh.color = color; MeshRenderer textRenderer = textObject.GetComponent(); if (font != null) { textRenderer.sharedMaterial = font.material; } // 渲染在单位之上、血条(100)之下;白主字必须比黑边高一级, // 同材质同 sortingOrder 的渲染顺序不确定,后画的黑边会盖住白字。 textRenderer.sortingOrder = sortingOrder; return textMesh; } // 缓存引擎内置字体;Tuanjie 2022.3 使用 LegacyRuntime,旧版本回退 Arial。 private static Font cachedBuiltinFont; private static Font GetBuiltinFont() { if (cachedBuiltinFont == null) { try { cachedBuiltinFont = Resources.GetBuiltinResource("LegacyRuntime.ttf"); } catch { cachedBuiltinFont = null; } if (cachedBuiltinFont == null) { try { cachedBuiltinFont = Resources.GetBuiltinResource("Arial.ttf"); } catch { cachedBuiltinFont = null; } } } return cachedBuiltinFont; } } // 漂浮伤害数字:上升、缩放回弹、淡出后自动销毁。 // 独立于所属单位存活,即使单位死亡仍会完整播放动画。 public class DamageNumber : MonoBehaviour { // 全场同时存在的伤害数字上限,防止极端情况下刷屏。 public const int MaxActive = 60; public static int ActiveCount { get; private set; } public float duration = 0.8f; public float riseHeight = 1.2f; // 一个数字由主字 + 8 方向描边副本共 9 个 TextMesh 组成,统一驱动透明度。 private TextMesh[] textMeshes; private Color[] startColors; private Vector3 startPosition; private Camera targetCamera; private float elapsed; private void Awake() { ActiveCount++; textMeshes = GetComponentsInChildren(); startColors = new Color[textMeshes.Length]; for (int i = 0; i < textMeshes.Length; i++) { startColors[i] = textMeshes[i].color; } startPosition = transform.position; } private void Start() { GameObject mainCameraObject = GameObject.Find("Main Camera"); if (mainCameraObject != null) { targetCamera = mainCameraObject.GetComponent(); } if (targetCamera == null) { targetCamera = Camera.main; } } private void Update() { elapsed += Time.deltaTime; if (elapsed >= duration) { Destroy(gameObject); return; } float t = elapsed / duration; // 匀加速上升。 Vector3 position = startPosition; position.y += riseHeight * Mathf.SmoothStep(0f, 1f, t); transform.position = position; // 前20%从1.35倍缩放回弹到1倍。 float scale = t < 0.2f ? Mathf.Lerp(1.35f, 1f, t / 0.2f) : 1f; transform.localScale = Vector3.one * scale; // 后30%淡出。 float alpha = t < 0.7f ? 1f : 1f - (t - 0.7f) / 0.3f; for (int i = 0; i < textMeshes.Length; i++) { if (textMeshes[i] != null) { textMeshes[i].color = new Color(startColors[i].r, startColors[i].g, startColors[i].b, alpha); } } // 每帧朝向摄像机。TextMesh 可读面朝本地-Z,所以 +Z 要背向摄像机, // 否则文字从背面被看到会左右镜像。 if (targetCamera != null) { transform.rotation = Quaternion.LookRotation( transform.position - targetCamera.transform.position, targetCamera.transform.up); } } private void OnDestroy() { ActiveCount--; } }