U
tab 02Nhân vật & Chiến đấu9 bài — chạm để mở
05 / 09 · 11 phút
Nhân vật & Chiến đấu

Đạn, sát thương và hiệu ứng

dự án của bạn tới lúc này

Assets/

Scripts/

SkillCaster.csProjectile.csSimplePool.cs

Prefabs/

P_Fireball

Data/

Skill_Slash / Dash / Ultimate

đã có sẵnbài này thêm

Viên đạn là một prefab bay tới trước, trúng thứ gì đó, gây sát thương rồi tự huỷ. Bốn nhiệm vụ, một script nhỏ.

sơ đồ động

Knight bắn dọc theo một hàng ô; bù nhìn ăn đòn; viên đạn quay về đúng cái pool nó vừa đi ra.

Viên đạn

Projectile.cs
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;                 // không bắn trúng người bắn

    public void Launch(GameObject shooter, float dmg)
    {
        owner = shooter;
        damage = dmg;
        Destroy(gameObject, lifeTime);        // tự dọn nếu không trúng gì
    }

    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. 1

    GameObject▸3D Object▸Sphere, scale 0.3, đặt tên P_Fireball.

  2. 2

    Tick Is Trigger trên Collider — trigger báo va chạm mà không bị nảy vật lý.

  3. 3

    Gắn Projectile.cs, cho nó material phát sáng, kéo vào Assets/Prefabs.

  4. 4

    Xoá nó khỏi cảnh. Prefab nằm trong Project, không phải trong Hierarchy.

Bắn ra từ skill

add to SkillData and SkillCaster
//trong SkillData.cs
public GameObject projectilePrefab;

//trong SkillCaster.TryCast
if (s.projectilePrefab != null)
{
    GameObject go = Instantiate(s.projectilePrefab, castPoint.position, castPoint.rotation);
    go.GetComponent<Projectile>().Launch(gameObject, s.damage);
}

Object pool — hết giật khi bắn nhiều

Instantiate và Destroy cấp phát bộ nhớ. Bắn 20 viên mỗi giây là bộ dọn rác sẽ gây khựng. Pool tái sử dụng cùng một nhóm đối tượng mãi mãi.

SimplePool.cs
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);
    }
}
  • Thay Destroy(gameObject) trong viên đạn bằng lệnh gọi ngược pool.Release(gameObject).

  • Unity cũng có sẵn UnityEngine.Pool.ObjectPool<T> nếu bạn thích dùng bản dựng sẵn.

  • Hãy pool mọi thứ sinh ra vài lần mỗi giây: đạn, tia lửa, số sát thương.

Hiệu ứng dễ nhìn, dễ hiểu

  • Tick Stop Action → Destroy trên Particle System để hiệu ứng một lần tự dọn.

  • Tia lửa lúc trúng nên kéo dài 0.2–0.4 giây. Lâu hơn là màn hình rối như canh hẹ.

  • Rung camera nhẹ (0.1 giây) cộng khựng hình 0.05 giây tạo cảm giác trúng đòn hơn mọi hiệu ứng hạt.

khựng hình
using System.Collections;

IEnumerator HitStop(float seconds)
{
    Time.timeScale = 0.05f;
    yield return new WaitForSecondsRealtime(seconds);   // phải dùng Realtime vì timeScale đang đứng
    Time.timeScale = 1f;
}

//gọi như sau
StartCoroutine(HitStop(0.05f));

Lỗi hay gặp

  • Viên đạn trúng người bắn trước: nó sinh ra ngay trong collider của Knight và OnTriggerEnter nổ trên chính người thi triển. Sinh đạn từ castPoint đặt ngay trước thân, và bỏ qua va chạm nào có transform.root là người bắn.

  • Viên đạn lấy lại từ pool mang theo trạng thái cũ: reset vị trí, hướng xoay và mọi bộ đếm trong OnEnable, không thì phát thứ hai chết ngay hoặc bay sai hướng.

  • Destroy(gameObject, lifeTime) vẫn còn đó sau khi chuyển sang pool, nên viên đạn bắn trượt sẽ huỷ luôn object của pool và pool cạn dần. Thay cả hai lệnh Destroy bằng pool.Release, lệnh hẹn giờ thì qua Invoke hoặc coroutine.

  • Đạn bay nhanh xuyên qua collider mỏng: ở 30 m/s và 30 FPS, mỗi khung hình viên đạn đi nguyên một mét và có thể nhảy qua bức tường. Di chuyển qua Rigidbody bằng MovePosition và đặt Collision Detection thành Continuous, hoặc giữ tốc độ vừa phải.