【Unity2D好项目分享】用全是好活制作横版卷轴射击游戏②游戏画面后处理以及制作玩家子弹

学习目标:

前几天放周末一直在摆烂所以教程拖了有点久,今天来学习一下关于URP的画面后处理

  点开Main Camera找到Camera组件的Rendering处理方式,首先修改的是渲染的抗锯齿方式,这里选用的是适合发布于PC平台的SMAA

并在环境Environment那一栏更改Volumes的Mask,将Everything勾选上

背景也不用天空盒因为我们是2D场景而是选用Soild Color

然后再给Main Camera创建一个子对象,用于处理后渲染

首先添加一个叫Volume的脚本组件,这个是URP自带的脚本,点开后 像我这样New一个Profile然后点击下方的Add Override即可修改参数

为了渲染出宇宙冰冷的气氛感,选用偏暗的色调就很合适


子弹生成:

  首先先将老师制作好的子弹的Mesh材质导入,并且更改他们的设置

接下来我们创建一个空对象Player Projectile并将它设置为预制体Prefab,然后再预制体内操作‘

首先要添加一个Partical system更改它的属性

为了更改它的材质,我们还需要创建一个Material 

 然后复制粘贴一份,并把子弹的纹理赋给它

 

 再给子弹一个尾部轨迹Trail

外观设计好后,我们还要让子弹向右飞行 

在此之前我们需要一个其它子弹类能继承的总类即总的Projectile。它所需要实现的功能有:当子弹处于激活状态的时候就一直向右飞行,并且碰到有碰撞体的物体会产生什么什么反应之类的。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Projectile : MonoBehaviour
{
    [SerializeField] float moveSpeed = 10f;

    [SerializeField] protected Vector2 moveDirection;

    [SerializeField] float damage;

    protected GameObject target;
    protected virtual void OnEnable()
    {
        StartCoroutine(MoveDirectly());
    }

    IEnumerator MoveDirectly()
    {
        while (gameObject.activeSelf)
        {
            transform.Translate(moveDirection * moveSpeed * Time.deltaTime);

            yield return null;
        }
    }

    protected virtual void OnCollisionEnter2D(Collision2D collision)
    {
    }
       
}

先写到这里,然后创建一个新的脚本PlayerProjectile

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerProjectile : Projectile
{
    TrailRenderer trail;

    private void Awake()
    {
        trail = GetComponentInChildren<TrailRenderer>();
        if (moveDirection != Vector2.right)
        {
            transform.GetChild(0).rotation = Quaternion.FromToRotation(Vector2.right, moveDirection); //让我们的Parical一直旋转着
        }
    }

    private void OnDisable()
    {
        trail.Clear(); //清楚轨迹
    }

   
}

以及我们还要创建敌人的子弹EnemyProjectile,创建一个子对象叫Enmey Projectile Basic,然后给它一个3D物体Cube并赋予它Material

 给Basic挂载一个脚本

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyProjectile : Projectile
{
    
    private void Awake()
    {
        
        if(moveDirection != Vector2.left)
        {
            transform.rotation = Quaternion.FromToRotation(Vector2.left, moveDirection);
        }
    }

    
}

最后我们要给玩家和敌人挂载子弹,点开Input Actions,我们再创建一个新的Actions,然后把鼠标左键设置为开火箭。

 在PlayerInput脚本中我们选择再创建两个个UnityAction事件

public event UnityAction onFire = delegate { };
    public event UnityAction onStopFire = delegate { };
public void OnFire(InputAction.CallbackContext context)
    {
        if (context.phase == InputActionPhase.Performed) //相当于InputSystems的GetKey
        {
            if (onFire != null) //确认事件不为空
            {
                onFire.Invoke();
            }
        }
        if (context.phase == InputActionPhase.Canceled) //相当于InputSystems的GetKeyUp
        {
            if (onStopFire != null)
            {
                onStopFire.Invoke();
            }
        }
    }

完成以后即可完成输入操作。

同时我们也需要一个对象池,能实时缓存,并将需要生成的游戏对象激活,不需要的设置为非激活,以避免频繁触发垃圾回收机制引起游戏运行延缓,

新创建一个Pool脚本

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[System.Serializable]
public class Pool 
{
    public GameObject Prefab { get => prefab; }

    [SerializeField] GameObject prefab;
    Transform parent;

    public int Size { get => size; }
    public int RuntimeSize { get => queue.Count; }
    [SerializeField] int size = 1;


    Queue<GameObject> queue; //队列

    public void Initialize(Transform parent) //初始化游戏对象
    {
        queue = new Queue<GameObject>();
        this.parent = parent;

        for (int i = 0; i < size; i++)
        {
            queue.Enqueue(Copy());
        }
    }
    GameObject Copy() //在池中生成备用对选哪个
    {
        var copy = GameObject.Instantiate(prefab,parent); //为新生成的Pool对象创建一个父对象

        copy.SetActive(false);

        return copy;
    }
    GameObject AvaliableObject() //从池中取出一个可用对选哪个
    {
        GameObject avaliableObject = null;

        if (queue.Count >0 && !queue.Peek().activeSelf) //而且队列的第一个元素不在启用状态
        {
           avaliableObject = queue.Dequeue();
        }
        else
        {
            avaliableObject = Copy();
        }
        queue.Enqueue(avaliableObject); //让已经完成任务的对象返回对象池(即队列当中)

        return avaliableObject;
    }

    public GameObject PrepareObject() //启用这个可用对象
    {
        GameObject preparedObject = AvaliableObject();

        preparedObject.SetActive(true);

        return preparedObject;
    }
    //写重载函数。
    public GameObject PrepareObject(Vector3 position) //启用这个对象
    {
        GameObject preparedObject = AvaliableObject();

        preparedObject.SetActive(true);
        preparedObject.transform.position = position;

        return preparedObject;
    }
    public GameObject PrepareObject(Vector3 position,Quaternion rotation) //启用这个对象
    {
        GameObject preparedObject = AvaliableObject();

        preparedObject.SetActive(true);
        preparedObject.transform.position = position;
        preparedObject.transform.rotation = rotation;

        return preparedObject;
    }
    public GameObject PrepareObject(Vector3 position, Quaternion rotation,Vector3 localScale) //启用这个对象
    {
        GameObject preparedObject = AvaliableObject();

        preparedObject.SetActive(true);
        preparedObject.transform.position = position;
        preparedObject.transform.rotation = rotation;
        preparedObject.transform.localScale = localScale;

        return preparedObject;
    }

}

新创建一个PoolManager

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PoolManager : MonoBehaviour
{
    [SerializeField] Pool[] enemyPools;

    [SerializeField] Pool[] playerProjectTiles;

    [SerializeField] Pool[] enemyProjectTiles;

    [SerializeField] Pool[] vFXPools;

    static Dictionary<GameObject, Pool> dictionary;

    private void Awake()
    {
        dictionary = new Dictionary<GameObject, Pool>();
        Initialize(enemyPools);
        Initialize(playerProjectTiles);
        Initialize(enemyProjectTiles);
        Initialize(vFXPools);
    }

    void Initialize(Pool[] pools)
    {
        foreach (var pool in pools)
        {
         #if UNITY_EDITOR
            if (dictionary.ContainsKey(pool.Prefab)) //如果包含相同的键就输出错误并跳出本轮循环
            {
                Debug.LogError("Same Prefab in multiple pools! Prefab:" + pool.Prefab.name);

                continue;
            }
         #endif

            dictionary.Add(pool.Prefab, pool);
            Transform poolParent = new GameObject("Pool:" + pool.Prefab.name).transform;

            poolParent.parent = transform;
            pool.Initialize(poolParent);
        }
    }

#if UNITY_EDITOR
    private void OnDesroy()
    {
        CheckPoolSize(enemyPools);
        CheckPoolSize(playerProjectTiles);
        CheckPoolSize(enemyProjectTiles);
        CheckPoolSize(vFXPools);
    }
#endif
    void CheckPoolSize(Pool[] pools)
    {
        foreach (var pool in pools)
        {
            if(pool.RuntimeSize > pool.Size)
            {
                Debug.LogWarning(
                    string.Format("Pool :{0}has a runtime size {1} bigger than its intial size {2}",
                    pool.Prefab.name,
                    pool.RuntimeSize,
                    pool.Size));
            }
        }
    }

    public static GameObject Release(GameObject prefab) //在静态函数中所有引用都必须是静态的
    {
     #if UNITY_EDITOR
        if (!dictionary.ContainsKey(prefab)) //如果包含相同的键就输出错误并跳出本轮循环
        {
            Debug.LogError("Same Prefab in multiple pools! Prefab:" + prefab.name);

            return null;
        }
    #endif


        return dictionary[prefab].PrepareObject();
    }

    public static GameObject Release(GameObject prefab,Vector3 position) //在静态函数中所有引用都必须是静态的
    {
#if UNITY_EDITOR
        if (!dictionary.ContainsKey(prefab)) //如果包含相同的键就输出错误并跳出本轮循环
        {
            Debug.LogError("Same Prefab in multiple pools! Prefab:" + prefab.name);

            return null;
        }
#endif


        return dictionary[prefab].PrepareObject(position);
    }

    public static GameObject Release(GameObject prefab, Vector3 position,Quaternion rotation) //在静态函数中所有引用都必须是静态的
    {
#if UNITY_EDITOR
        if (!dictionary.ContainsKey(prefab)) //如果包含相同的键就输出错误并跳出本轮循环
        {
            Debug.LogError("Same Prefab in multiple pools! Prefab:" + prefab.name);

            return null;
        }
#endif


        return dictionary[prefab].PrepareObject(position,rotation);
    }
    public static GameObject Release(GameObject prefab, Vector3 position, Quaternion rotation,Vector3 localScale) //在静态函数中所有引用都必须是静态的
    {
#if UNITY_EDITOR
        if (!dictionary.ContainsKey(prefab)) //如果包含相同的键就输出错误并跳出本轮循环
        {
            Debug.LogError("Same Prefab in multiple pools! Prefab:" + prefab.name);

            return null;
        }
#endif


        return dictionary[prefab].PrepareObject(position, rotation,localScale);
    }
}

然后在场景中新创建一个PoolManager把上面的脚本挂载上去,这里暂时只用到Projectile相关的

首先点开Player脚本,编写生成子弹的脚本

[Header("--- FIRE ---")]
    [SerializeField] GameObject projectTile1;
    [SerializeField] GameObject projectTile2;
    [SerializeField] GameObject projectTile3;

    [SerializeField] AudioData projectileLaunchSFX;

    [SerializeField, Range(0, 2)] int weaponPower = 0;

    [SerializeField] Transform muzlleMiddle;
    [SerializeField] Transform muzzleTop;
    [SerializeField] Transform muzzleBottom;

    Rigidbody2D rigi2D;
private void Awake()
    {
        rigi2D = GetComponent<Rigidbody2D>();
        collider = GetComponent<Collider2D>();

    }
protected override void OnEnable()
    {
        //增加委托
        base.OnEnable();
        input.onMove += Move;
        input.onStopMove += StopMove;
        input.onFire += Fire;
        input.onStopFire += StopFire;
        
    }
    private void OnDisable()
    {
        //取消委托
        input.onMove -= Move;
        input.onStopMove -= StopMove;
        input.onFire -= Fire;
        input.onStopFire -= StopFire;
        
    }
 #region FIRE

    void Fire()
    {
        StartCoroutine(nameof(FireCoroutine));
    }

    void StopFire()
    {
        StopCoroutine(nameof(FireCoroutine));
    }

    IEnumerator FireCoroutine()
    {
        while (true)
        {
            

            switch (weaponPower)
            {
                case 0:
                    PoolManager.Release(projectTile1, muzlleMiddle.position, Quaternion.identity);
                    break;
                case 1:
                    PoolManager.Release(projectTile1, muzzleTop.position, Quaternion.identity);
                    PoolManager.Release(projectTile1, muzzleBottom.position, Quaternion.identity);
                    break;
                case 2:
                    PoolManager.Release(projectTile1, muzzleTop.position, Quaternion.identity);
                    PoolManager.Release(projectTile1, muzlleMiddle.position, Quaternion.identity);
                    PoolManager.Release(projectTile1, muzzleBottom.position, Quaternion.identity);
                    break;
                default:break;
            }

            yield return waitForFireInterval;
        }
    }

    #endregion

再点开Enemy脚本,需要新增的Fire类脚本如下。

[Header("----- FIRE -----")]
    [SerializeField] GameObject[] projectTiles;
    [SerializeField] Transform muzzle; 
    [SerializeField] float minInterval;
    [SerializeField] float maxInterval;
    [SerializeField] AudioData projectileLaunchSFX;
    private void OnEnable()
    {
        StartCoroutine(nameof(RandomlyMovingCoroutine));
        StartCoroutine(nameof(RandomlyFireCoroutine));
    }

    private void OnDisable()
    {
        StopAllCoroutines();
    }

IEnumerator RandomlyFireCoroutine()
    {
        while (gameObject.activeSelf)
        {
            yield return new WaitForSeconds(Random.Range(minInterval, maxInterval));

            foreach (var projectTile in projectTiles)
            {
                PoolManager.Release(projectTile, muzzle.position);
            }
}
}

我们还需要一个脚本,将子弹在默认的时间内自动清除,并将它挂到和所有和子弹相关的物体。

using System.Collections;
using UnityEngine;

public class AutoDeactive : MonoBehaviour
{
    [SerializeField] bool destoryGameObject;

    [SerializeField] float lifeTime = 3f;

    WaitForSeconds waitLifeTime;

    private void Awake()
    {
        waitLifeTime = new WaitForSeconds(lifeTime);
    }

    private void OnEnable()
    {
        StartCoroutine(nameof(DeactiveCoroutine));
    }
    IEnumerator DeactiveCoroutine()
    {
        yield return waitLifeTime;

        if (destoryGameObject)
        {
            Destroy(gameObject);
        }
        else
        {
            gameObject.SetActive(false);
        }

    }
}

别忘了给子弹和人物,敌人都添加上碰撞体

 


学习产出:

最后我们让每个敌人拥有不同的能力。新复制粘贴三个子弹游戏对象改变他们的Move Direction.

 

 

 

 

 再给敌人子弹创建一个能追踪的

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyProjectile_Aiming : Projectile
{
    private void Awake()
    {
        target = GameObject.FindGameObjectWithTag("Player");
    }

    protected override void OnEnable()
    {
        StartCoroutine(nameof(MoveDirectionCoroutine));
        base.OnEnable();        
    }

    IEnumerator MoveDirectionCoroutine()
    {
        yield return null;

        if (target.activeSelf)
        {
            moveDirection = (transform.position - target.transform.position).normalized;
        }
    }
}

 

猜你喜欢

转载自blog.csdn.net/dangoxiba/article/details/124791078
今日推荐