弹道、伤害与特效
Assets/
Scripts/
Prefabs/
Data/
你已经有的这一课新增
弹体就是一个向前飞、撞上东西、造成伤害然后消失的 prefab。四个职责,一个小脚本。
Knight 沿着斜向的一排格子开火,靶子挨了一下,弹体又回到它出来的那个 pool 里。
弹体
using UnityEngine;
[RequireComponent(typeof(Collider))]
public class Projectile : MonoBehaviour
{
[SerializeField] private float speed = 18f;
[SerializeField] private float lifeTime = 4f;
[SerializeField] private GameObject hitVfx;
private float damage;
private GameObject owner; // do not hit the shooter
public void Launch(GameObject shooter, float dmg)
{
owner = shooter;
damage = dmg;
Destroy(gameObject, lifeTime); // clean up if it never hits
}
void Update()
{
transform.position += transform.forward * speed * Time.deltaTime;
}
void OnTriggerEnter(Collider other)
{
if (other.gameObject == owner) return;
if (other.TryGetComponent(out CharacterStats target))
target.TakeDamage(damage);
if (hitVfx != null)
Instantiate(hitVfx, transform.position, Quaternion.identity);
Destroy(gameObject);
}
}- 1
,scale 设为 0.3,命名为 P_Fireball。
- 2
在它的 Collider 上勾选 Is Trigger —— trigger 只报告重叠,不会发生物理反弹。
- 3
挂上 Projectile.cs,给它一个自发光的 material,再拖进 Assets/Prefabs。
- 4
把它从场景里删掉。prefab 应该待在 Project 里,而不是 Hierarchy。
从技能里发射
// in SkillData.cs
public GameObject projectilePrefab;
// in SkillCaster.TryCast
if (s.projectilePrefab != null)
{
GameObject go = Instantiate(s.projectilePrefab, castPoint.position, castPoint.rotation);
go.GetComponent<Projectile>().Launch(gameObject, s.damage);
}对象池 —— 告别连射时的卡顿
Instantiate 和 Destroy 都要分配内存。每秒打 20 发,垃圾回收就会带来卡顿。用池就能一直重复利用同一批对象。
using System.Collections.Generic;
using UnityEngine;
public class SimplePool : MonoBehaviour
{
[SerializeField] private GameObject prefab;
[SerializeField] private int prewarm = 20;
private readonly Queue<GameObject> pool = new Queue<GameObject>();
void Awake()
{
for (int i = 0; i < prewarm; i++)
{
GameObject go = Instantiate(prefab, transform);
go.SetActive(false);
pool.Enqueue(go);
}
}
public GameObject Get(Vector3 pos, Quaternion rot)
{
GameObject go = pool.Count > 0 ? pool.Dequeue() : Instantiate(prefab, transform);
go.transform.SetPositionAndRotation(pos, rot);
go.SetActive(true);
return go;
}
public void Release(GameObject go)
{
go.SetActive(false);
pool.Enqueue(go);
}
}把弹体里的 Destroy(gameObject) 换成回调 pool.Release(gameObject)。
如果喜欢用现成的实现,Unity 也自带 UnityEngine.Pool.ObjectPool<T>。
凡是每秒要生成好几次的一切都做成池:子弹、命中火花、伤害数字。
看得清楚的特效
在 Particle System 上把 Stop Action 设为 Destroy,一次性特效就会自己清理干净。
命中火花持续 0.2–0.4 秒。再长下去屏幕就糊成一锅粥了。
一点点镜头震动(0.1 秒)加上 0.05 秒顿帧,比任何粒子效果都更能卖出打击感。
using System.Collections;
IEnumerator HitStop(float seconds)
{
Time.timeScale = 0.05f;
yield return new WaitForSecondsRealtime(seconds); // Realtime! timeScale is frozen
Time.timeScale = 1f;
}
// call it
StartCoroutine(HitStop(0.05f));常见错误
弹体先打中开枪的人:它在 Knight 自己的碰撞体内部生成,OnTriggerEnter 就在施法者身上触发了。改从身体前方一点的 castPoint 生成,并忽略 transform.root 是施法者的命中。
从池里取回的弹体带着上一次的状态:要在 OnEnable 里重置位置、旋转和所有计时器,否则第二发要么立刻消失,要么飞错方向。
换成对象池之后 Destroy(gameObject, lifeTime) 还留着,打空的弹体就会把池里的对象销毁掉,池子越来越空。两处 Destroy 都换成 pool.Release,定时的那一处用 Invoke 或协程来调。
飞得快的弹体会穿过薄碰撞体:30 m/s 配 30 FPS,子弹一帧就走整整一米,可能直接跳过一堵墙。通过 Rigidbody 的 MovePosition 移动并把 Collision Detection 设为 Continuous,或者把速度控制在合理范围。