双人坦克大战 - Unity3D

一个简单的双人坦克游戏(3D)。

在这里插入图片描述

GitHub
https://github.com/SSGamble/TanksGame

总结

  • 双人操作
    • 拆分 Horizontal 和 Vertical 轴,为坦克设置编号,区分控制
  • 相机跟随两个物体
    • 让相机跟随两个物体的正中心
  • 添加音效
    • 利用声音组件,设置音效,控制播放。

创建场景

拖入场景模型

创建坦克

拖入坦克模型,并为坦克添加 Box Collider 组件,设置碰撞区域。

添加脚本控制坦克移动

因为是 3 维场景,坦克应在水平面移动,所以应该锁定刚体组件的 y 方向,和 x,z 的转向。即,只能在 xoz 平面移动,只能绕 y 轴旋转。

mark

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 轴,为坦克设置编号,区分控制。

mark

坦克 ID

mark

拆分控制轴

public float tankID = 1; // 坦克的编号,用于控制两种不同的键盘操作
// 通过坦克编号区分控制
float v = Input.GetAxis("VerticalPlayer" + tankID); 
float h = Input.GetAxis("HorizontalPlayer" + tankID);

控制坦克发射子弹

拖入子弹模型,为子弹添加碰撞体和刚体,设为触发器,并将其设为预制体。

创建空物体,设置位置到坦克发射口,此为坦克的开火位置。

mark

坦克的开火位置

找到开火位置,实例化子弹,并为子弹设置速度,实现发射效果,为子弹添加触发检测,实现爆炸效果,最后要记得在适合的时候销毁子弹和爆炸效果。

控制坦克开火

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

mark

音效

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 TypeFilled

public Slider hpSlider; // 血条
private int hpTotal; // 血量


private void Start(){
    hpTotal = hp;
}

// 坦克受到伤害时
hpSlider.value = 1 - ((float)hp / hpTotal); // 设置 Slider 血条 的值

猜你喜欢

转载自blog.csdn.net/SGamble/article/details/89085566