敌人追踪
This commit is contained in:
1 parent
e839c158ac
commit
1aeaebcddb
17 files changed
+297
-2365
No files matched your search
@@ -3,8 +3,9 @@ using UnityEngine;
|
||||
/// <summary>
|
||||
/// 敌人追踪:朝目标(玩家)移动,进入销毁半径后自我销毁。
|
||||
///
|
||||
/// 目标引用在 Inspector / 预制体实例里接好——预制体资产本身存不下场景对象,
|
||||
/// 所以每个场景实例各自接一次;代码里不按名字 / 标签查找节点。
|
||||
/// 目标来源两种:场景里手摆的敌人在 Inspector / 预制体实例里接好(预制体资产本身存不下场景对象,
|
||||
/// 所以每个场景实例各自接一次);运行时生成的敌人由生成方(PrefabSpawner)在 Instantiate 之后
|
||||
/// 调 SetTarget 传进来。代码里不按名字 / 标签查找节点。
|
||||
/// 移动直接改 transform,不依赖 CharacterController / Rigidbody,只在水平面内走,
|
||||
/// 高度保持不变。
|
||||
///
|
||||
@@ -56,13 +57,25 @@ public class EnemyChaseController : MonoBehaviour
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (target == null)
|
||||
Debug.LogError("[EnemyChaseController] 没有指定追踪目标(target),请在 Inspector / 预制体实例里拖入玩家。", this);
|
||||
|
||||
if (animator == null)
|
||||
Debug.LogError("[EnemyChaseController] 没有指定 Animator(animator),死亡动画不会播。", this);
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// 放在 Start 而不是 Awake:敌人是 Instantiate 出来的,生成方在实例化之后才调 SetTarget,
|
||||
// 而 Instantiate 会立刻跑 Awake,那时目标还没递进来。
|
||||
if (target == null)
|
||||
Debug.LogError("[EnemyChaseController] 没有追踪目标(target):请在 Inspector / 预制体实例里拖入玩家," +
|
||||
"或由生成方(PrefabSpawner)在实例化后调 SetTarget 传入。", this);
|
||||
}
|
||||
|
||||
/// <summary>由生成方在实例化之后调用,把追踪目标接进来;场景里手摆的敌人直接在 Inspector 接。</summary>
|
||||
public void SetTarget(Transform value)
|
||||
{
|
||||
target = value;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (_dying)
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// 每隔 x 秒,在自身半径 n 米的范围内随机生成一个预制体,并把追踪目标递给它。
|
||||
///
|
||||
/// 用法:挂在场景里任意物体上,把要生成的预制体拖到 prefab 字段,把玩家根节点拖到 target 字段,
|
||||
/// 再填间隔和半径。
|
||||
/// 生成位置在以自身为圆心、半径 n 米的**水平圆面**内(Y 跟自身一致),不会掉到地下或飘到天上。
|
||||
/// 生成的物体放在场景根节点下,朝向用 Identity。
|
||||
///
|
||||
/// 目标传递:生成出来的敌人拿不到场景里的玩家引用(预制体资产存不下场景对象),
|
||||
/// 所以在 Instantiate 之后把 target 赋给它身上的 EnemyChaseController。
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public class PrefabSpawner : MonoBehaviour
|
||||
{
|
||||
[Header("敌人引用(必须在 Inspector / 预制体里指定)")]
|
||||
[Tooltip("要生成的预制体,根节点上必须有 EnemyChaseController")]
|
||||
[SerializeField] private GameObject prefab;
|
||||
|
||||
[Tooltip("生成后赋给敌人的追踪目标,拖玩家根节点进来")]
|
||||
[SerializeField] private Transform target;
|
||||
|
||||
[Header("生成")]
|
||||
[Tooltip("生成间隔(秒),必须大于 0")]
|
||||
[SerializeField] private float interval = 1f;
|
||||
|
||||
[Tooltip("随机范围半径(米),以自身位置为圆心")]
|
||||
[SerializeField] private float radius = 5f;
|
||||
|
||||
private float _timer;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (prefab == null)
|
||||
{
|
||||
Debug.LogError("[PrefabSpawner] 没有指定要生成的预制体(prefab),请在 Inspector 里拖入。", this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (target == null)
|
||||
Debug.LogError("[PrefabSpawner] 没有指定目标(target),生成出来的敌人不会有追踪目标。", this);
|
||||
|
||||
if (prefab.GetComponent<EnemyChaseController>() == null)
|
||||
Debug.LogError("[PrefabSpawner] 这个预制体上没有 EnemyChaseController,目标传不进去。", this);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (prefab == null)
|
||||
return; // Awake 已经报过错
|
||||
|
||||
_timer += Time.deltaTime;
|
||||
if (_timer < interval)
|
||||
return;
|
||||
|
||||
_timer -= interval;
|
||||
Spawn();
|
||||
}
|
||||
|
||||
/// <summary>在自身半径 radius 米的水平圆面内随机取一点,生成预制体。</summary>
|
||||
private void Spawn()
|
||||
{
|
||||
Vector2 offset = Random.insideUnitCircle * radius;
|
||||
Vector3 position = transform.position + new Vector3(offset.x, 0f, offset.y);
|
||||
|
||||
GameObject instance = Instantiate(prefab, position, Quaternion.identity);
|
||||
|
||||
// 实例拿不到场景里的玩家引用,这里递进去
|
||||
EnemyChaseController enemy = instance.GetComponent<EnemyChaseController>();
|
||||
if (enemy != null)
|
||||
enemy.SetTarget(target);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2aef3dd8d016749eba828b4711807579
|
||||
@@ -0,0 +1,69 @@
|
||||
using Cinemachine;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// 按屏幕方向切换虚拟相机的跟随距离。
|
||||
///
|
||||
/// 挂在 Canvas 或任意「拉伸铺满」的 UI 节点上(这类节点的尺寸会随屏幕变化,Unity 才会回调),
|
||||
/// 再把要改的虚拟相机拖到 virtualCamera 字段。该虚拟相机的 Body 必须是 Framing Transposer
|
||||
/// (Camera Distance 是 Body 上的字段,不在虚拟相机组件本身)。
|
||||
///
|
||||
/// 触发时机:Unity 只在 RectTransform 尺寸变化时调用 OnRectTransformDimensionsChange,
|
||||
/// 所以这里没有 Update、不轮询、初始化时不写值;只有屏幕方向真的翻转那一次才写一次距离。
|
||||
/// (尺寸变了但方向没变——比如布局调整——也不会写。)
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public class CameraDistanceByOrientation : MonoBehaviour
|
||||
{
|
||||
[Header("引用(必须在 Inspector / 预制体里指定)")]
|
||||
[Tooltip("要改跟随距离的虚拟相机;它的 Body 必须是 Framing Transposer")]
|
||||
[SerializeField] private CinemachineVirtualCamera virtualCamera;
|
||||
|
||||
[Header("相机距离")]
|
||||
[Tooltip("横屏时的 Camera Distance")]
|
||||
[SerializeField] private float landscapeDistance = 13f;
|
||||
|
||||
[Tooltip("竖屏时的 Camera Distance")]
|
||||
[SerializeField] private float portraitDistance = 30f;
|
||||
|
||||
private CinemachineFramingTransposer _framingTransposer;
|
||||
private bool _isPortrait;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (GetComponent<RectTransform>() == null)
|
||||
Debug.LogError("[CameraDistanceByOrientation] 这个物体没有 RectTransform,收不到尺寸变化回调;" +
|
||||
"请挂到 Canvas 或任意拉伸铺满的 UI 节点上。", this);
|
||||
|
||||
if (virtualCamera == null)
|
||||
{
|
||||
Debug.LogError("[CameraDistanceByOrientation] 没有指定 virtualCamera,请在 Inspector 里拖入。", this);
|
||||
return;
|
||||
}
|
||||
|
||||
_framingTransposer = virtualCamera.GetCinemachineComponent<CinemachineFramingTransposer>();
|
||||
if (_framingTransposer == null)
|
||||
Debug.LogError("[CameraDistanceByOrientation] 虚拟相机的 Body 不是 Framing Transposer,改不了 Camera Distance。", this);
|
||||
|
||||
// 只记下开局方向,不写相机
|
||||
_isPortrait = IsPortrait();
|
||||
}
|
||||
|
||||
/// <summary>RectTransform 尺寸变化时由 Unity 调用(屏幕旋转、分辨率变化都会触发)。</summary>
|
||||
private void OnRectTransformDimensionsChange()
|
||||
{
|
||||
bool portrait = IsPortrait();
|
||||
if (portrait == _isPortrait)
|
||||
return; // 尺寸变了但方向没变,不动相机
|
||||
|
||||
_isPortrait = portrait; // 只在翻转的这一次写值
|
||||
|
||||
if (_framingTransposer == null)
|
||||
return; // Awake 已经报过错
|
||||
|
||||
_framingTransposer.m_CameraDistance = portrait ? portraitDistance : landscapeDistance;
|
||||
}
|
||||
|
||||
/// <summary>屏幕高度大于宽度算竖屏。编辑器里拖动 Game 视图尺寸也能触发,方便验证。</summary>
|
||||
private static bool IsPortrait() => Screen.height > Screen.width;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 508bb4f2c6ebb49ffbe7c3cb6ee43239
|
||||
Reference in new issue
Block a user