feat(Enemy): 添加敌人攻击动画速度控制功能

- 新增attackDuration属性用于控制完整攻击动作时长
- 实现攻击动画速度动态调整,支持自定义攻击持续时间
- 添加健康条显示组件到敌人类
- 增加物理材质配置优化碰撞效果

- 添加微信小游戏平台支持和适配
- 集成自适应性能包和微信小游戏SDK
- 配置项目构建设置支持多平台发布
- 更新质量设置以适配不同目标平台

- 调整场景中物体位置和旋转参数
- 修复角色控制器材质引用问题
- 优化敌人AI移动和攻击逻辑
```
This commit is contained in:
huadexuan committed 2026-08-23 22:23:15 +08:00
1 parent b807bb80ec
commit 48dfa0d95d
38 files changed
+2295 -36

No files matched your search

+229
View File
@@ -0,0 +1,229 @@
using UnityEngine;
using UnityEngine.UI;
public class WorldHealthBar : MonoBehaviour
{
// 最大生命值。
[Min(1f)] public float maxHealth = 100f;
// 当前生命值,范围会自动限制在 0 到 maxHealth 之间。
[Min(0f)] public float currentHealth = 100f;
// 血条相对于单位头顶的额外高度。
[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);
// 血条内部填充颜色。
public Color fillColor = new Color(0.1f, 0.8f, 0.18f, 1f);
// 血条 UI 的像素尺寸,实际显示大小由 worldWidth 控制。
private const int PixelWidth = 100;
private const int PixelHeight = 14;
// 世界空间 Canvas,用来承载血条 UI。
private Canvas canvas;
// 血条整体的 RectTransform。
private RectTransform barRect;
// 绿色血量填充 Image。
private Image fillImage;
// 血条相对于单位的本地位置。
private Vector3 localBarPosition;
// 血条最终使用的世界宽度。
private float worldWidth;
// 所有血条共用的圆角 Sprite。
private static Sprite roundedSprite;
// 初始化血量和血条 UI。
private void Awake()
{
maxHealth = Mathf.Max(1f, maxHealth);
currentHealth = Mathf.Clamp(currentHealth, 0f, maxHealth);
CalculateLayout();
CreateBar();
UpdateFill();
}
// 每帧让血条保持在单位头顶,并朝向主摄像机。
private void LateUpdate()
{
if (barRect == null) return;
barRect.localPosition = localBarPosition;
// 优先使用场景中名称完全为 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<Camera>();
if (namedCamera != null && namedCamera.isActiveAndEnabled)
{
return namedCamera;
}
}
return Camera.main;
}
// 直接设置当前生命值,并刷新绿色填充比例。
public void SetHealth(float value)
{
currentHealth = Mathf.Clamp(value, 0f, maxHealth);
UpdateFill();
}
// 让单位受到伤害。
public void TakeDamage(float amount) => SetHealth(currentHealth - amount);
// 让单位恢复生命值。
public void Heal(float amount) => SetHealth(currentHealth + amount);
// 根据单位模型或碰撞体计算血条位置和宽度。
private void CalculateLayout()
{
Bounds bounds = new Bounds(transform.position, new Vector3(1f, 2f, 1f));
bool found = false;
foreach (Renderer renderer in GetComponentsInChildren<Renderer>(true))
{
if (!found)
{
bounds = renderer.bounds;
found = true;
}
else
{
bounds.Encapsulate(renderer.bounds);
}
}
if (!found)
{
CharacterController controller = GetComponent<CharacterController>();
if (controller != null) bounds = controller.bounds;
}
// 血条尺寸固定,不随单位模型大小变化;只根据模型顶部调整位置。
worldWidth = barWidth;
// 水平方向固定对齐单位根节点,只使用模型顶部计算血条高度。
Vector3 top = transform.InverseTransformPoint(new Vector3(transform.position.x, bounds.max.y, transform.position.z));
localBarPosition = new Vector3(0f, top.y + heightOffset, 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>();
canvas.renderMode = RenderMode.WorldSpace;
canvas.sortingOrder = 100;
barObject.GetComponent<CanvasScaler>().dynamicPixelsPerUnit = 10f;
barRect = barObject.GetComponent<RectTransform>();
barRect.sizeDelta = new Vector2(PixelWidth, PixelHeight);
barRect.localScale = Vector3.one * (worldWidth / PixelWidth);
barRect.localPosition = localBarPosition;
Image background = CreateImage("Border", barObject.transform, RoundedSprite(), borderColor);
SetFullRect(background.rectTransform);
Image inner = CreateImage("Fill", barObject.transform, RoundedSprite(), fillColor);
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;
}
// 创建一个用于边框或填充的 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>();
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);
}
}
// 创建并缓存一个带圆角的白色 Sprite,供边框和填充复用。
private static Sprite RoundedSprite()
{
if (roundedSprite != null) return roundedSprite;
const int width = 64;
const int height = 16;
const float radius = 5f;
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++)
{
float dx = Mathf.Max(Mathf.Max(radius - x, 0f), x - (width - 1 - radius));
float dy = Mathf.Max(Mathf.Max(radius - y, 0f), y - (height - 1 - radius));
bool inside = dx * dx + dy * dy <= radius * radius;
texture.SetPixel(x, y, inside ? Color.white : Color.clear);
}
}
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;
}
}