点开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更改它的属性
然后复制粘贴一份,并把子弹的纹理赋给它
外观设计好后,我们还要让子弹向右飞行
在此之前我们需要一个其它子弹类能继承的总类即总的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();
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 queue; //队列
public void Initialize(Transform parent) //初始化游戏对象
{
queue = new Queue();
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 dictionary;
private void Awake()
{
dictionary = new Dictionary();
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();
collider = GetComponent();
}
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;
}
}
}