一个简单的双人坦克游戏(3D)。
总结
- 双人操作
- 拆分 Horizontal 和 Vertical 轴,为坦克设置编号,区分控制
- 相机跟随两个物体
- 让相机跟随两个物体的正中心
- 添加音效
- 利用声音组件,设置音效,控制播放。
创建场景
拖入场景模型
创建坦克
拖入坦克模型,并为坦克添加 Box Collider
组件,设置碰撞区域。
添加脚本控制坦克移动
因为是 3 维场景,坦克应在水平面移动,所以应该锁定刚体组件的 y 方向,和 x,z 的转向。即,只能在 xoz 平面移动,只能绕 y 轴旋转。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TankMovement : MonoBehaviour {
private Rigidbody rigidbody;
public float speed = 15; // 坦克的移动速度
public float angularSpeed = 10; // 坦克的旋转速度
void Start () {
rigidbody = this.GetComponent<Rigidbody>();
}
// MonoBehaviour.FixedUpdate():在每个固定帧帧数被调用,在处理 Rigidbody 时,应该使用 FixedUpdate 而不是 Update
private void FixedUpdate()
{
// 控制坦克前后移动
float v = Input.GetAxis("Vertical");
rigidbody.velocity = transform.forward * v * speed; // 修改刚体的速度,这种方式并不符合物理规律
// 控制坦克左右旋转
float h = Input.GetAxis("Horizontal");
// angularVelocity 刚体角速度矢量,单位为弧度/秒。
rigidbody.angularVelocity = transform.up * h * angularSpeed; // 绕 Y 轴旋转
}
}
双人操作坦克
edit -> project settings -> input,拆分 Horizontal 和 Vertical 轴,为坦克设置编号,区分控制。
坦克 ID
拆分控制轴
public float tankID = 1; // 坦克的编号,用于控制两种不同的键盘操作
// 通过坦克编号区分控制
float v = Input.GetAxis("VerticalPlayer" + tankID);
float h = Input.GetAxis("HorizontalPlayer" + tankID);
控制坦克发射子弹
拖入子弹模型,为子弹添加碰撞体和刚体,设为触发器,并将其设为预制体。
创建空物体,设置位置到坦克发射口,此为坦克的开火位置。
坦克的开火位置
找到开火位置,实例化子弹,并为子弹设置速度,实现发射效果,为子弹添加触发检测,实现爆炸效果,最后要记得在适合的时候销毁子弹和爆炸效果。
控制坦克开火
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 坦克
/// 控制坦克开火
/// </summary>
public class TankAttack : MonoBehaviour {
private Transform firePosition; // 开火位置
public GameObject shell; // 子弹,需要赋值
public KeyCode fireKey = KeyCode.Space; // 开火按键
public float shellSpeed = 15; // 子弹发射速度
// Use this for initialization
void Start () {
// Transform.Find,public Transform Find(string name); 按名称查找子元素并返回它。
firePosition = transform.Find("FirePosition"); // 查找开火位置
}
// Update is called once per frame
void Update () {
if (Input.GetKeyDown(fireKey))
{
GameObject obj = GameObject.Instantiate(shell, firePosition.position, firePosition.rotation) as GameObject; // 实例化子弹
obj.GetComponent<Rigidbody>().velocity = obj.transform.forward * shellSpeed;
}
}
}
子弹的触发检测,产生爆炸效果
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 子弹
/// 子弹的触发检测,产生爆炸效果
/// </summary>
public class Shell : MonoBehaviour {
public GameObject shellExplosion; // 爆炸效果,需勾选爆炸效果的 Play On Awake,即生成就开始
// 触发检测
private void OnTriggerEnter(Collider collider)
{
GameObject.Instantiate(shellExplosion, transform.position, transform.rotation); // 实例化爆炸效果,位置就是当前子弹的位置
GameObject.Destroy(this.gameObject); // 把自身销毁
}
}
爆炸效果的销毁
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 爆炸效果
/// 爆炸效果的销毁
/// </summary>
public class DestroyFotTime : MonoBehaviour {
public float delayTime; // 爆炸效果延迟销毁时间
void Start () {
Destroy(this.gameObject, delayTime); // 爆炸效果,延迟销毁
}
}
设置坦克生命值并控制死亡
当子弹碰到坦克时,发送一条信息,坦克收到消息,做出相应的处理。
在子弹的触发检测里设置
// 如果子弹碰撞到了 Tank,给 Tank 发送一条消息,Tank 里会创建一个 TankDamage 方法来处理消息
if (collider.tag == "Tank")
{
collider.SendMessage("TankDamage");
}
坦克生命处理
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 坦克,坦克的生命处理
/// </summary>
public class TankHealth : MonoBehaviour {
public int hp = 100; // 坦克的生命值
public GameObject tankExplosion; // 坦克死亡的爆炸效果
/// <summary>
/// 收到消息,坦克受到伤害
/// </summary>
void TankDamage()
{
if (hp <= 0) return;
hp = hp - Random.Range(20, 30); // 随机减少生命值
if (hp <= 0) // 收到伤害后,如果死亡
{
GameObject.Instantiate(tankExplosion, transform.position, transform.rotation); // 生成爆炸效果
GameObject.Destroy(this.gameObject); // 销毁坦克
}
}
}
设置相机跟随
让相机跟随两个坦克的正中心。首先获得两个坦克的距离,让相机处在一个合适的位置,此时相机的视野距离应该和两个坦克的距离成一个倍数,比如:
视野 = 18,坦克1 = 0.44,坦克2 = 9.57,此时倍数应该是 2 左右。
让相机一直保持在这个距离即可。这样一来,两个坦克走近的时候,视野会变小,走远的时候,视野会变大。应设置一个最小视野,否则坦克距离很近的时候,视野会变得非常小,可能只看得见两个坦克了,这是很不科学的。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 相机:让相机跟随两个坦克的正中心
/// </summary>
public class Follow : MonoBehaviour {
public Transform player1; // 坦克 1
public Transform player2; //坦克 2
private Camera mainCamera; // 主相机
private Vector3 offect; // 偏移,相机和两个坦克正中心
private float size;
void Start () {
offect = transform.position - (player1.position + player2.position) / 2; // 两个坦克的正中心
mainCamera = this.GetComponent<Camera>(); // 获取相机
mainCamera.orthographic = true; // 将相机设置为正交法
}
void Update () {
if (player1 == null || player2 == null) return; // 如果任何一个坦克消失,游戏结束,取消相机跟随
transform.position = offect + (player1.position + player2.position) / 2; // 两个坦克的中心位置
float distance = Vector3.Distance(player1.position, player2.position); // 两个坦克的距离
if (distance < 9) // 即使两个坦克的距离很近,相机的最小视野也应是一个最佳倍数
size = 9f; // 相机视野的距离 = 初始倍数
else // 两个坦克距离很远
size = distance * 1; // 相机视野的距离 = 两个坦克的距离 * 一个合适的倍数
mainCamera.orthographicSize = size; // 设置相机视野
}
}
添加音效
BGM
音效
private AudioSource audioSource; // 声音组件
public AudioClip idleAudio; // 坦克空闲时的音效
public AudioClip drivingAudio; // 坦克移动时的音效
// 获取 AudioSource 的组件
audioSource = this.GetComponent<AudioSource>();
// 在适合的时候播放音效
if (Mathf.Abs(h) > 0.1 || Mathf.Abs(v) > 0.1) // 坦克移动时
{
audioSource.clip = drivingAudio; // 设置音效
if (audioSource.isPlaying == false) // 如果没有播放,播放
audioSource.Play();
}
else // 坦克空闲时
{
audioSource.clip = idleAudio;
if (audioSource.isPlaying == false)
audioSource.Play();
}
增加血条 - 游戏结束
添加 UI Slider,设置背景和填充图片,设置 Image Type
为 Filled
public Slider hpSlider; // 血条
private int hpTotal; // 血量
private void Start(){
hpTotal = hp;
}
// 坦克受到伤害时
hpSlider.value = 1 - ((float)hp / hpTotal); // 设置 Slider 血条 的值
转载:https://blog.csdn.net/SGamble/article/details/89085566