文章目录
- 一、 介绍
- 二、 人物移动、鼠标控制转向
- 三、子弹脚本
- 四、子弹随机抛壳
- 五、 爆炸特效
- 六、 发射子弹
- 七、 子弹、弹壳对象池
- 八、 散弹枪
- 九、 火箭弹、发射火箭
- 十、 下载工程文件
一、 介绍
2d俯视视角游戏。
人物视角跟随鼠标移动
多种枪械
抛壳效果
多种设计效果
对象池
二、 人物移动、鼠标控制转向
获取玩家的输入向量,设置刚体的速度,实现玩家的移动
获取鼠标位置,根据位置设置玩家的朝向
检查是否按下了 Q 或 E 键,根据按下的键切换到新的选中的枪支
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace BulletHell
{
public class PlayerMovement : MonoBehaviour
{
public GameObject[] guns; // 存放玩家枪支的 GameObject 数组
public float speed; // 玩家的速度
private Vector2 input; // 存放玩家输入的 Vector2 变量
private Vector2 mousePos; // 存放鼠标位置的 Vector2 变量
private Animator animator; // 存放动画组件的变量
private Rigidbody2D rigidbody; // 存放刚体组件的变量
private int gunNum; // 存放当前选中的枪支在数组中的索引
void Start()
{
animator = GetComponent<Animator>(); // 获取动画组件
rigidbody = GetComponent<Rigidbody2D>(); // 获取刚体组件
guns[0].SetActive(true); // 激活第一个枪支
}
void Update()
{
SwitchGun(); // 检查是否切换枪支的输入
input.x = Input.GetAxisRaw("Horizontal"); // 获取水平输入轴的值
input.y = Input.GetAxisRaw("Vertical"); // 获取竖直输入轴的值
rigidbody.velocity = input.normalized * speed; // 设置刚体的速度为标准化的输入向量与速度的乘积
mousePos = Input.mousePosition; // 获取鼠标位置
if (mousePos.x > transform.position.x) // 如果鼠标在玩家右侧
{
transform.rotation = Quaternion.Euler(new Vector3(0, 0, 0)); // 玩家朝向右侧
}
else // 如果鼠标在玩家左侧
{
transform.rotation = Quaternion.Euler(new Vector3(0, 180, 0)); // 玩家朝向左侧
}
if (input != Vector2.zero) // 如果输入向量不为零(即玩家正在移动)
animator.SetBool("isMoving", true); // 将动画组件中的 "isMoving" 参数设为 true
else // 如果输入向量为零(即玩家未移动)
animator.SetBool("isMoving", false); // 将动画组件中的 "isMoving" 参数设为 false
}
void SwitchGun()
{
if (Input.GetKeyDown(KeyCode.Q)) // 如果按下了 Q 键
{
guns[gunNum].SetActive(false); // 关闭当前选中的枪支
if (--gunNum < 0) // 索引减一,如果小于零
{
gunNum = guns.Length - 1; // 将索引设为数组中最后一个元素的索引
}
guns[gunNum].SetActive(true); // 激活新的选中的枪支
}
if (Input.GetKeyDown(KeyCode.E)) // 如果按下了 E 键
{
guns[gunNum].SetActive(false); // 关闭当前选中的枪支
if (++gunNum > guns.Length - 1) // 索引加一,如果大于数组中最后一个元素的索引
{
gunNum = 0; // 将索引设为数组中第一个元素的索引
}
guns[gunNum].SetActive(true); // 激活新的选中的枪支
}
}
}
}
三、子弹脚本
这段代码实现了游戏中子弹的飞行、碰撞和销毁功能。
代码使用了 Unity 引擎提供的 MonoBehaviou 类和内置组件,如 Rigidbody2D 和 Collider2D 等。
代码中使用了对象池技术,实现了对子弹对象的回收和重复使用,提高了游戏的性能。
代码的逻辑清晰,注释详细,易于理解和维护。
代码提供了设置子弹速度的公共方法,可以方便地进行修改和调整。
子弹对象在碰撞到其他物体时,可以选择实例化爆炸效果或从对象池中获取爆炸效果对象,并将其位置设置为子弹的位置。
可以选择销毁子弹对象或将其回收到对象池中,以便重复使
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Bullet : MonoBehaviour // 继承自 MonoBehaviour 的类
{
public float speed; // 子弹的速度
public GameObject explosionPrefab; // 爆炸效果的预制体
new private Rigidbody2D rigidbody; // 存放刚体组件的变量
void Awake()
{
rigidbody = GetComponent<Rigidbody2D>(); // 获取刚体组件
}
public void SetSpeed(Vector2 direction) // 设置子弹速度的方法
{
rigidbody.velocity = direction * speed; // 根据方向和速度设置刚体的速度
}
void Update()
{
}
private void OnTriggerEnter2D(Collider2D other) // 当子弹碰撞到其他物体时执行
{
// Instantiate(explosionPrefab, transform.position, Quaternion.identity); // 实例化爆炸效果
// 从对象池中获取爆炸效果对象
GameObject exp = ObjectPool.Instance.GetObject(explosionPrefab);
exp.transform.position = transform.position; // 将爆炸效果的位置设置为子弹的位置
// Destroy(gameObject); // 销毁子弹对象
ObjectPool.Instance.PushObject(gameObject); // 将子弹对象回收到对象池中
}
}
四、子弹随机抛壳
这段代码实现了游戏中子弹的飞行和消失功能,具体包括:
在 OnEnable() 函数中设置子弹的初始速度、透明度和重力系数,并开始协程等待子弹消失
在 Stop() 协程中等待子弹停止一段时间后,将子弹的速度和重力系数设为零,然后不断将子弹的透明度降低,直到消失为止
最后,可以选择销毁子弹对象或将其回收到对象池中,以便重复使用。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BulletShell : MonoBehaviour
{
public float speed;
public float stopTime = .5f;
public float fadeSpeed = .01f;
new private Rigidbody2D rigidbody;
private SpriteRenderer sprite;
void Awake()
{
rigidbody = GetComponent<Rigidbody2D>();
sprite = GetComponent<SpriteRenderer>();
}
private void OnEnable()
{
float angel = Random.Range(-30f, 30f);
rigidbody.velocity = Quaternion.AngleAxis(angel, Vector3.forward) * Vector3.up * speed;
sprite.color = new Color(sprite.color.r, sprite.color.g, sprite.color.b, 1);
rigidbody.gravityScale = 3;
StartCoroutine(Stop());
}
IEnumerator Stop()
{
yield return new WaitForSeconds(stopTime);
rigidbody.velocity = Vector2.zero;
rigidbody.gravityScale = 0;
while (sprite.color.a > 0)
{
sprite.color = new Color(sprite.color.r, sprite.color.g, sprite.color.g, sprite.color.a - fadeSpeed);
yield return new WaitForFixedUpdate();
}
// Destroy(gameObject);
ObjectPool.Instance.PushObject(gameObject);
}
}
五、 爆炸特效
这段代码的作用是控制爆炸动画的播放和销毁,在 Update() 函数中:
获取动画组件
在 Awake() 函数中获取动画组件
获取当前动画状态信息
在 Update() 函数中获取当前动画状态信息
判断动画是否播放完毕
如果动画已经播放完毕,可以选择销毁爆炸对象或将其回收到对象池中,以便重复使用。这里可以通过调用 ObjectPool 类的 PushObject() 方法来将对象回收到对象池中。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Explosion : MonoBehaviour
{
private Animator animator; // 存放动画组件的变量
private AnimatorStateInfo info; // 存放动画状态信息的变量
void Awake()
{
animator = GetComponent<Animator>(); // 获取动画组件
}
void Update()
{
info = animator.GetCurrentAnimatorStateInfo(0); // 获取当前动画状态信息
if (info.normalizedTime >= 1) // 如果动画已经播放完毕
{
// Destroy(gameObject); // 销毁爆炸对象
ObjectPool.Instance.PushObject(gameObject); // 将爆炸对象回收到对象池中
}
}
}
六、 发射子弹
这段代码是一个游戏中的武器类,实现了武器的朝向、射击和弹壳脱落等功能。
代码使用了 Unity 引擎提供的 MonoBehaviou 类和内置组件,如 Animator 和 Transform 等。
代码中使用了对象池技术,实现了对子弹和弹壳对象的回收和重复使用,提高了游戏的性能。
代码的逻辑清晰,注释详细,易于理解和维护。
代码提供了可重写的虚函数,可以方便地扩展和修改功能。
武器在鼠标的位置朝向和翻转时,可以选择翻转武器的 Y 轴。
武器可以通过点击鼠标左键进行射击,通过设置时间间隔来控制射击频率。
射击时会播放射击动画,并从对象池中获取子弹和弹壳对象,并设置它们的位置和速度。
子弹的速度可以根据武器的朝向和一个随机的角度进行调整。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Gun : MonoBehaviour // 继承自 MonoBehaviour 的类
{
public float interval; // 射击间隔时间
public GameObject bulletPrefab; // 子弹预制体
public GameObject shellPrefab; // 弹壳预制体
protected Transform muzzlePos; // 发射口位置
protected Transform shellPos; // 弹壳位置
protected Vector2 mousePos; // 鼠标位置
protected Vector2 direction; // 发射方向
protected float timer; // 计时器
protected float flipY; // Y 轴翻转参数
protected Animator animator; // 动画组件
protected virtual void Start() // 在 Start() 函数中初始化一些变量和组件
{
animator = GetComponent<Animator>(); // 获取动画组件
muzzlePos = transform.Find("Muzzle"); // 获取发射口位置
shellPos = transform.Find("BulletShell"); // 获取弹壳位置
flipY = transform.localScale.y; // 获取 Y 轴翻转参数
}
protected virtual void Update() // 在 Update() 函数中进行鼠标位置朝向、射击和弹壳脱落等操作
{
mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition); // 将鼠标位置从屏幕坐标系转换为世界坐标系
if (mousePos.x < transform.position.x) // 如果鼠标在武器左侧
transform.localScale = new Vector3(flipY, -flipY, 1); // 翻转武器的 Y 轴
else
transform.localScale = new Vector3(flipY, flipY, 1); // 不翻转武器的 Y 轴
Shoot(); // 进行射击操作
}
protected virtual void Shoot() // 进行射击操作
{
direction = (mousePos - new Vector2(transform.position.x, transform.position.y)).normalized; // 计算射击方向
transform.right = direction; // 设置武器的朝向
if (timer != 0) // 如果计时器不为零
{
timer -= Time.deltaTime; // 减少计时器时间
if (timer <= 0)
timer = 0; // 如果计时器小于等于零,将计时器归零
}
if (Input.GetButton("Fire1")) // 如果按下了鼠标左键
{
if (timer == 0) // 如果计时器为零
{
timer = interval; // 重置计时器
Fire(); // 进行射击
}
}
}
protected virtual void Fire() // 进行射击
{
animator.SetTrigger("Shoot"); // 播放射击动画
// GameObject bullet = Instantiate(bulletPrefab, muzzlePos.position, Quaternion.identity); // 在发射口位置实例化子弹
GameObject bullet = ObjectPool.Instance.GetObject(bulletPrefab); // 从对象池中获取子弹对象
bullet.transform.position = muzzlePos.position; // 设置子弹位置
float angel = Random.Range(-5f, 5f); // 随机一个角度
bullet.GetComponent<Bullet>().SetSpeed(Quaternion.AngleAxis(angel, Vector3.forward) * direction); // 根据角度和方向设置子弹速度
// Instantiate(shellPrefab, shellPos.position, shellPos.rotation); // 在弹壳位置实例化弹壳
GameObject shell = ObjectPool.Instance.GetObject(shellPrefab); // 从对象池中获取弹壳对象
shell.transform.position = shellPos.position; // 设置弹壳位置
shell.transform.rotation = shellPos.rotation; // 设置弹壳旋转角度
}
}
七、 子弹、弹壳对象池
使用一个字典来存储预制体,字典的键为预制体的名称,值为一个对象队列。
提供 GetObject() 函数用于从对象池中获取一个预制体,实现如下:
如果对象池中没有该预制体或者预制体的数量为0,则实例化一个新的预制体并加入对象池。
如果对象池为空,则创建一个新的空物体作为对象池的父物体。
查找该预制体的子对象池,如果不存在,则创建一个空物体作为该预制体的子对象池。
从对象池中取出一个对象,并激活该对象。
提供 PushObject() 函数用于将一个预制体放回对象池中,实现如下:
将对象放回对象池,并将对象设置为不激活状态。
注意,为了能够正确地将预制体放回对象池中,预制体的名称不能包含 (Clone),因此在 PushObject() 函数中会将名称中的 (Clone) 替换为空字符串。
提供一个静态的 Instance 属性,用于获取对象池的单例实例。如果该实例为 null,则会创建一个新的对象池实例。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ObjectPool
{
private static ObjectPool instance;
private Dictionary<string, Queue<GameObject>> objectPool = new Dictionary<string, Queue<GameObject>>();
private GameObject pool;
public static ObjectPool Instance
{
get
{
if (instance == null)
{
instance = new ObjectPool();
}
return instance;
}
}
// 从对象池中获取一个对象
public GameObject GetObject(GameObject prefab)
{
GameObject _object;
// 如果对象池中没有该预制体或者预制体的数量为0,则实例化一个新的预制体并加入对象池
if (!objectPool.ContainsKey(prefab.name) || objectPool[prefab.name].Count == 0)
{
_object = GameObject.Instantiate(prefab);
PushObject(_object);
// 如果对象池为空,则创建一个新的空物体作为对象池的父物体
if (pool == null)
pool = new GameObject("ObjectPool");
// 查找该预制体的子对象池,如果不存在,则创建一个空物体作为该预制体的子对象池
GameObject childPool = GameObject.Find(prefab.name + "Pool");
if (!childPool)
{
childPool = new GameObject(prefab.name + "Pool");
childPool.transform.SetParent(pool.transform);
}
_object.transform.SetParent(childPool.transform);
}
// 从对象池中取出一个对象,并激活该对象
_object = objectPool[prefab.name].Dequeue();
_object.SetActive(true);
return _object;
}
// 将对象放回对象池
public void PushObject(GameObject prefab)
{
string _name = prefab.name.Replace("(Clone)", string.Empty);
// 如果对象池中没有该预制体,则新建一个队列用于存储该预制体
if (!objectPool.ContainsKey(_name))
objectPool.Add(_name, new Queue<GameObject>());
// 将对象放回对象池,并将对象设置为不激活状态
objectPool[_name].Enqueue(prefab);
prefab.SetActive(false);
}
}
八、 散弹枪
根据子弹数量和夹角计算出每颗子弹的旋转方向。
播放开枪动画。
从对象池中取出子弹并设置位置和旋转。
从对象池中取出弹壳并设置位置和旋转。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Shotgun : Gun
{
public int bulletNum = 3; // 发射的子弹数量
public float bulletAngle = 15; // 每颗子弹之间的夹角
// 重写基类的 Fire() 函数
protected override void Fire()
{
// 播放开枪动画
animator.SetTrigger("Shoot");
int median = bulletNum / 2; // 子弹数量的中位数(整数除法向下取整)
for (int i = 0; i < bulletNum; i++)
{
GameObject bullet = ObjectPool.Instance.GetObject(bulletPrefab); // 从对象池中获取子弹
bullet.transform.position = muzzlePos.position; // 将子弹位置设置为枪口的位置
if (bulletNum % 2 == 1) // 如果子弹数量是奇数
{
// 计算当前子弹的旋转方向,使用 Quaternion.AngleAxis() 函数
bullet.GetComponent<Bullet>().SetSpeed(Quaternion.AngleAxis(bulletAngle * (i - median), Vector3.forward) * direction);
}
else // 如果子弹数量是偶数
{
// 计算当前子弹的旋转方向,使用 Quaternion.AngleAxis() 函数
bullet.GetComponent<Bullet>().SetSpeed(Quaternion.AngleAxis(bulletAngle * (i - median) + bulletAngle / 2, Vector3.forward) * direction);
}
}
// 从对象池中获取弹壳并设置位置和旋转
GameObject shell = ObjectPool.Instance.GetObject(shellPrefab);
shell.transform.position = shellPos.position;
shell.transform.rotation = shellPos.rotation;
}
}
九、 火箭弹、发射火箭
火箭弹抛物线炸目标点
定义了 Rocket 类,用来控制火箭弹的行为。
定义了 lerp 和 speed 两个公共字段,分别表示插值系数和子弹的速度,以及 explosionPrefab 表示爆炸特效的预制体。
在 Awake() 函数中,获取子弹的刚体组件。
在 SetTarget() 函数中,设置子弹的目标点,并将到达标记重置为 false。
在 FixedUpdate() 函数中,计算子弹的移动方向,根据到达标记判断是否需要插值旋转角度和设置移动速度。如果到达目标点,就将到达标记设置为 true。
在 OnTriggerEnter2D() 函数中,从对象池中获取爆炸特效,设置特效的位置,将子弹的移动速度设置为零,然后使用协程在一定时间后将子弹推回对象池。
这段代码的作用是控制火箭弹的行为。其中,使用插值旋转角度和设置移动速度实现子弹朝着目标点飞行的效果,使用到达标记判断子弹是否到达目标点,使用对象池管理子弹和爆炸特效的创建和销毁,可以有效地减少内存的开销和对象的创建次数,同时使用协程在一定时间后将子弹推回对象池,可以让游戏的操作更加流畅和自然。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Rocket : MonoBehaviour
{
public float lerp; // 插值系数
public float speed = 15; // 子弹速度
public GameObject explosionPrefab; // 爆炸特效预制体
new private Rigidbody2D rigidbody; // 刚体组件
private Vector3 targetPos; // 目标点
private Vector3 direction; // 移动方向
private bool arrived; // 是否到达目标点
private void Awake()
{
rigidbody = GetComponent<Rigidbody2D>(); // 获取刚体组件
}
public void SetTarget(Vector2 _target)
{
arrived = false; // 重置到达标记
targetPos = _target; // 设置目标点
}
private void FixedUpdate()
{
direction = (targetPos - transform.position).normalized; // 计算移动方向
if (!arrived) // 如果没有到达目标点
{
transform.right = Vector3.Slerp(transform.right, direction, lerp / Vector2.Distance(transform.position, targetPos)); // 插值旋转角度
rigidbody.velocity = transform.right * speed; // 设置移动速度
}
if (Vector2.Distance(transform.position, targetPos) < 1f && !arrived) // 如果到达目标点
{
arrived = true; // 设置到达标记
}
}
private void OnTriggerEnter2D(Collider2D other)
{
GameObject exp = ObjectPool.Instance.GetObject(explosionPrefab); // 从对象池中获取爆炸特效
exp.transform.position = transform.position; // 设置特效位置
rigidbody.velocity = Vector2.zero; // 停止移动
StartCoroutine(Push(gameObject, .3f)); // 在一定时间后将子弹推回对象池
}
IEnumerator Push(GameObject _object, float time)
{
yield return new WaitForSeconds(time); // 等待一段时间
ObjectPool.Instance.PushObject(_object); // 将子弹推回对象池
}
}
发射火箭弹
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RocketLauncher : Gun
{
public int rocketNum = 3; // 每次发射的子弹数量
public float rocketAngle = 15; // 子弹之间的夹角
protected override void Fire()
{
animator.SetTrigger("Shoot"); // 播放射击动画
StartCoroutine(DelayFire(.2f)); // 延迟一定时间后发射子弹
}
IEnumerator DelayFire(float delay)
{
yield return new WaitForSeconds(delay); // 等待一定时间
int median = rocketNum / 2; // 计算中位数
for (int i = 0; i < rocketNum; i++) // 循环生成子弹
{
GameObject bullet = ObjectPool.Instance.GetObject(bulletPrefab); // 从对象池中获取子弹
bullet.transform.position = muzzlePos.position; // 设置子弹的初始位置
if (rocketNum % 2 == 1) // 如果子弹数量是奇数
{
bullet.transform.right = Quaternion.AngleAxis(rocketAngle * (i - median), Vector3.forward) * direction; // 计算子弹的旋转方向
}
else // 如果子弹数量是偶数
{
bullet.transform.right = Quaternion.AngleAxis(rocketAngle * (i - median) + rocketAngle / 2, Vector3.forward) * direction; // 计算子弹的旋转方向
}
bullet.GetComponent<Rocket>().SetTarget(mousePos); // 设置子弹的目标点
}
}
}
定义了 RocketLauncher 类,用来控制火箭发射器的行为。
定义了 rocketNum 和 rocketAngle 两个公共字段,分别表示每次发射的子弹数量和子弹之间的夹角。
在 Fire() 函数中,播放射击动画并调用 DelayFire() 函数延迟一定时间后发射子弹。
在 DelayFire() 函数中,首先计算子弹数量的中位数,然后使用循环来生成子弹。在生成每个子弹时,从对象池中获取子弹,并设置子弹的初始位置和旋转方向。如果子弹数量是奇数,就使用 (i - median) * rocketAngle 计算子弹的旋转方向。如果子弹数量是偶数,就使用 (i - median) * rocketAngle + rocketAngle / 2 计算子弹的旋转方向。最后,调用 bullet.GetComponent().SetTarget(mousePos) 函数设置子弹的目标点,让子弹朝着鼠标指向的方向飞行。
这段代码的作用是控制火箭发射器的行为。其中,使用子弹数量和子弹之间的夹角来决定生成子弹的位置和旋转方向,使用对象池管理子弹的创建和销毁,可以有效地减少内存的开销和对象的创建次数,同时使用协程来实现延迟发射子弹的效果,可以让游戏的操作更加流畅和自然。
十、 下载工程文件
https://wwez.lanzoul.com/izVuU0tyzffe