游戏开发日志 (其六):武器、投射物

发表于: 4/18/2023

Weapon

Projectile

好了,我们已经有了一个系统来把输入指令传递给我们的舰队了,那么,我们的舰队又将如何执行命令呢? 更具体一点,进行攻击? 是以豪迈的气魄向敌人发起冲锋,胆怯的人将迎来粉身碎骨的命运! 既然我们都已经登上太空了,当然要避免这种原始低效的杀戮方式。真理部的工程师们给我们带来了更好的选择。

万物皆可发射的终极“火炮”

先考虑一些基本参数

class Weapon : MonoBehaviour{
    string Name { get; }
    float BaseDamage { get; }
    float BaseRange { get; } 
    // 攻击间隔,防止过热,需要冷却才能进行下次攻击
    float BaseCooldown { get; } 
    // “热身”过程,而当我们停止攻击,又逐渐变得需要重新进行预热
    float BasePrepareTime { get; } 
}

改改参数就可以说自己推出了一款新武器

为了保护武器延长使用寿命,为其加上限制

class Weapon : MonoBehaviour{
    // ...
    public float IsCoolingDown() => cooldown;
    float Cooldown()
    {
        return cooldown = Mathf.Clamp(cooldown - Time.dltaTime, 0, BaseCooldown);
    }
    float cooldown = 0;

    bool Prepared()
    {
        prepared = true;
        prepareTime = Mathf.Clamp(prepareTime + Time.deltaTime, 0, BasePrepareTime);
        return prepareTime >= BasePrepareTime;
    }
    bool prepared = false;
    float prepareTime = 0;

    void Update()
    {
        Cooldown();
    }
    void LateUpdate()
    {
        if(prepared)
            prepared = false;
        else
            if(IsCoolingDown() == 0)
                prepareTime = Mathf.Clamp(prepareTime - Time.deltaTime*0.3f, 0, BasePrepareTime);
    }
}

我们在“开火!”时检测Prepare和Cooldown (顺便进行了Prepare),当可以发射后就发射并设置Cooldown。

public void Fire(Fleet self, Fleet target)
{
    if (!Prepared() || IsCoolingDown() > 0)
        return;

    LaunchProjectile(self, target);
    cooldown = BaseCooldown;
}
void LaunchProjectile(Fleet self, Fleet target)
{
}

枪已经准备好了,只需要子弹了,那这就简单了,我们实际上只需要两个要素:

  1. 移动路径
  2. 命中判定
public class LaserProjectile : Projectile // 继承自 MonoBehaviour
{
    Vector2 from;
    Vector2 to;
    Action<Fleet> OnHit;
    // Update is called once per frame
    void LateUpdate()
    {
        var collider = GetComponent<Collider2D>();
        var hitList = new Collider2D[1];
        if (collider.OverlapCollider(new() { layerMask = LayerMask.GetMask("PlayerControls") }, hitList) > 0)
        {
            if (hitList[0].gameObject.GetComponent<Fleet>() is Fleet fleet && fleet != self && fleet.Team != self.Team/*friendly fire*/)
            {
                OnHit?.Invoke(fleet);

                Destroy(gameObject);
            }
        }
    }
    void Update()
    {
        transform.position = Vector2.MoveTowards(transform.position, to, speed * Time.deltaTime);
    }

    public static LaserProjectile Create(Fleet self, Fleet target, Action<Fleet> onHit)
    {
        var r = Quaternion.LookRotation(Vector3.forward, target.transform.position - self.transform.position);

        var projectile = Instantiate(LaserProjectilePrefab, self.transform.position, r).GetComponent<LaserProjectile>();

        projectile.transform.position = self.transform.position;
        projectile.self = self;

        projectile.from = self.transform.position;
        projectile.to = target.transform.position;
        projectile.OnHit = onHit;
        return projectile;
    }
}

这里简单制作了一枚弹药:直线运动,不能追踪目标 在Update中进行移动,在LateUpdate中进行命中判定,在创建时设置目标等一系列参数。 命中时触发命中事件进行诸如造成伤害之类的行为。

外题

近来有对Shader进行一定了解,将投射物整体运动也交给Shader不知道是否可行? 使用一张覆盖整个地图的材质,在上面绘制弹道,对大量的,规则运动的 “弹幕” 应该是个很好的优化办法。

——由此延申,战争迷雾也可以直接在Shader中计算范围,而非是基于网格的循环遍历,不过遮挡的处理或许略有复杂。 这大概能挽回我丢失的200多帧吧,是的,之前的实现要消耗掉200多帧,甚至略有卡顿。