双人3D坦克实现
player1: WSAD控制上下左右 空格键发射炮弹
player2: IKJL可控制上下左右 B键发射炮弹
每个坦克只有100hp,子弹击中1次扣30hp,hp时时显示在坦克上
当一辆坦克hp低于0时,游戏结束
Main Camera聚焦两辆坦克中心点
游戏项目已托管到github上(里面有个32bit可执行文件) 传送门
(官方demo 素材包有点大。。)
游戏界面
(觉得子弹射程短,可以适当提高游戏子弹速度)


using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement;public class FollowCamera : MonoBehaviour {public GameObject tank1;public GameObject tank2;public float size= 0.58f;Vector3 p;// Use this for initializationvoid Start () {Vector3 target=(tank1.transform.position + tank2.transform.position) / 2;p = target - transform.position;}// Update is called once per framevoid Update () {if (tank2 != null&&tank1!=null){//计算出两个坦克之间的距离transform.position = (tank1.transform.position + tank2.transform.position) / 2 - p;//找到两个坦克的中点//让摄像机对准中心点//根据两点之间的距离,调整摄像机的尺寸float distance = Vector3.Distance(tank1.transform.position, tank2.transform.position);GetComponent<Camera>().orthographicSize = distance * size + 2f;}if(tank2 == null || tank1 == null){SceneManager.LoadScene("SampleScene");}} }


using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI;public class TankHealth : MonoBehaviour {public int hp = 100;public GameObject ex;public Slider slider;// Use this for initializationvoid Start () {}// Update is called once per framevoid Update () {}public void TakeDamage(){hp -= 30;if (hp < 0){Destroy(gameObject);GameObject go = Instantiate(ex, transform.position, transform.rotation);}slider.value = hp;}}


using System.Collections; using System.Collections.Generic; using UnityEngine;public class DestroyForTime : MonoBehaviour {// Use this for initializationvoid Start () {Destroy(gameObject,2);}// Update is called once per framevoid Update () {} }


using System.Collections; using System.Collections.Generic; using UnityEngine;public class Shell : MonoBehaviour {public GameObject ex;// Use this for initializationvoid Start () {}// Update is called once per framevoid Update () {}private void OnCollisionEnter(Collision collision){Destroy(gameObject);GameObject go = Instantiate(ex,transform.position,transform.rotation);//判断是否打中坦克if(collision.gameObject.tag == "Tank"){collision.gameObject.GetComponent<TankHealth>().TakeDamage();}//扣除坦克的血量 } }


using System.Collections; using System.Collections.Generic; using UnityEngine;public class Shoot : MonoBehaviour {public GameObject shell;Transform pos;public KeyCode key = KeyCode.Space;public float speed = 180;// Use this for initializationvoid Start () {//找到子对象pos = transform.Find("FirstPosition");}// Update is called once per framevoid Update () {//按下一个按键if (Input.GetKeyDown(key)){GameObject go = Instantiate(shell,pos.position,pos.rotation);go.GetComponent<Rigidbody>().velocity = go.transform.forward * speed;//go.GetComponent<Rigidbody>().velocity = Vector3.forward * speed; }} }


using System.Collections; using System.Collections.Generic; using UnityEngine;public class Movement : MonoBehaviour {public float speed = 10;public float angularSpeed = 5;public string player = "1";// Use this for initializationvoid Start () {}// Update is called once per framevoid Update () {//获得玩家的键盘输入float v = Input.GetAxis("Vertical"+player);float h = Input.GetAxis("Horizontal"+ player);//让坦克前后移动GetComponent<Rigidbody>().velocity = transform.forward * v * speed;GetComponent<Rigidbody>().angularVelocity = transform.up * h * angularSpeed;} }
实现过程
创建场景
导入坦克资源场景


Lightmapping光照贴图技术是一种增强静态场景光照效果的技术,其优点是可以通过较少的性能消耗使静态场景看上去更加真实,丰富,更加具有立体感;缺点是不能用来实时地处理动态光照。当游戏场景包含了大量的多边形时,实时光源和阴影对游戏的性能的影响会很大。这时使用Lightmapping技术,将光线效果预渲染成贴图使用到多边形上模拟光影效果。
加载光照贴图有时候会造成场景卡顿
Auto Generate :设置成不自动生成
Generate Lighting :生成一次场景光照
坦克的前后移动
添加坦克预设体进入游戏场景,DustTrail粒子特效预设体放到坦克上
(GameObject ->Align With View 将Game视图对准当前Scene视图)
给坦克添加碰撞器和刚体
(碰撞器慢慢移,拖动数值调整到刚好包围完坦克)
添加移动坦克脚本


using System.Collections; using System.Collections.Generic; using UnityEngine;public class Movement : MonoBehaviour {public float speed = 5;// Use this for initializationvoid Start () {}// Update is called once per framevoid Update () {//获得玩家的键盘输入float v = Input.GetAxis("Vertical");//让坦克前后移动GetComponent<Rigidbody>().velocity = transform.forward * v * speed;} }
设置坦克移动速度
public float speed = 5;
获得玩家的键盘输入
float v = Input.GetAxis("Vertical");
让坦克前后移动
GetComponent<Rigidbody>().velocity = transform.forward * v * speed;
(将相机放到坦克里面,实现相机和坦克一起移动)
坦克的旋转
查看Unity输入按键值
水平移动:Vertical
左右移动:Horizontal


using System.Collections; using System.Collections.Generic; using UnityEngine;public class Movement : MonoBehaviour {public float speed = 10;public float angularSpeed = 5;// Use this for initializationvoid Start () {}// Update is called once per framevoid Update () {//获得玩家的键盘输入float v = Input.GetAxis("Vertical");float h = Input.GetAxis("Horizontal");//让坦克前后移动GetComponent<Rigidbody>().velocity = transform.forward * v * speed;GetComponent<Rigidbody>().angularVelocity = transform.up * h * angularSpeed;} }
设置坦克移动转动角速度
public float angularSpeed = 5;
获得玩家的键盘输入
float h = Input.GetAxis("Horizontal");
让坦克前后移动
GetComponent<Rigidbody>().angularVelocity = transform.up * h * angularSpeed;
为实现双人坦克效果,提高按键灵活性,在InputManager管理器中添加两个Axes
修改Axes值Horizontal1、Horizontal2、Vertical1、Vertical2
我把玩家二上下左右WSAD按键修改为IKJL,实现双人笑游戏效果
默认角色是1号玩家
public string player = "1";//获得玩家的键盘输入float v = Input.GetAxis("Vertical"+player);float h = Input.GetAxis("Horizontal"+ player);
新建双人坦克修改Player值
坦克子弹
给子弹添加刚体和碰撞器
(将子弹设置为预设体,保存到Gary文件夹中)
给坦克添加一个空物体,每次坦克都是从这个点动态生成子弹发射出去的
添加Shoot脚本,挂载到坦克物体上


using System.Collections; using System.Collections.Generic; using UnityEngine;public class Shoot : MonoBehaviour {public GameObject shell;Transform pos;// Use this for initializationvoid Start () {//找到子对象pos = transform.Find("FirstPosition");}// Update is called once per framevoid Update () {//按下一个按键if (Input.GetKeyDown(KeyCode.Space)){//发射子弹 实例化 Instantiate(shell,pos.position,pos.rotation);}} }
引用子弹实例化
public GameObject shell;
找到子弹发射的位置
pos = transform.Find("FirstPosition");
按下一个按键,,将子弹实例化
if (Input.GetKeyDown(KeyCode.Space)){Instantiate(shell,pos.position,pos.rotation);}


using System.Collections; using System.Collections.Generic; using UnityEngine;public class Shoot : MonoBehaviour {public GameObject shell;Transform pos;public KeyCode key = KeyCode.Space;// Use this for initializationvoid Start () {//找到子对象pos = transform.Find("FirstPosition");}// Update is called once per framevoid Update () {//按下一个按键if (Input.GetKeyDown(key)){Instantiate(shell,pos.position,pos.rotation);}} }
在外添加一个key,按任意键可以射出子弹
public KeyCode key = KeyCode.Space;//按下一个按键if (Input.GetKeyDown(key)){Instantiate(shell,pos.position,pos.rotation);}
定义子弹发射速度
public float speed = 180;
按下一个按键,子弹发射出去
if (Input.GetKeyDown(key)){GameObject go = Instantiate(shell,pos.position,pos.rotation);go.GetComponent<Rigidbody>().velocity = go.transform.forward * speed;go.GetComponent<Rigidbody>().velocity = Vector3.forward * speed;}
子弹爆炸动画
添加脚本shell,挂载到子弹物体对象上
private void OnCollisionEnter(Collision collision){Destroy(gameObject);}
当子弹碰到刚体时销毁自己
添加子弹碰到物体触发爆炸效果
引用粒子特效
public GameObject ex;
private void OnCollisionEnter(Collision collision){Destroy(gameObject);GameObject go = Instantiate(ex,transform.position,transform.rotation);}


using System.Collections; using System.Collections.Generic; using UnityEngine;public class Shell : MonoBehaviour {public GameObject ex;// Use this for initializationvoid Start () {}// Update is called once per framevoid Update () {}private void OnCollisionEnter(Collision collision){Destroy(gameObject);GameObject go = Instantiate(ex,transform.position,transform.rotation);} }
子弹血量的减少
给坦克添加标签


using System.Collections; using System.Collections.Generic; using UnityEngine;public class TankHealth : MonoBehaviour {public int hp = 100;public GameObject ex;// Use this for initializationvoid Start () {}// Update is called once per framevoid Update () {}public void TakeDamage(){hp -= 30;if (hp < 0){Destroy(gameObject);GameObject go = Instantiate(ex, transform.position, transform.rotation);}}}
初始化血量hp=100
public int hp = 100;
设置坦克爆炸粒子效果
public GameObject ex;
当血量低于0时,坦克销毁,爆炸粒子效果出现
public void TakeDamage(){hp -= 30;if (hp < 0){Destroy(gameObject);GameObject go = Instantiate(ex, transform.position, transform.rotation);}}
相机跟随
将照相机改为正交
在摄像机中添加并挂载FollowCameral脚本
两个坦克的引用
public GameObject tank1;public GameObject tank2;
两个坦克之间的中点
Vector3 p;Vector3 target=(tank1.transform.position + tank2.transform.position) / 2;p = target - transform.position;
计算出两个坦克之间的距离
transform .position= (tank1.transform.position + tank2.transform.position) / 2 - p;
找到两个坦克的中点并让摄像机对准中心点,根据两点之间的距离,调整摄像机的尺寸
float distance = Vector3.Distance(tank1.transform.position,tank2.transform.position);GetComponent<Camera>().orthographicSize = distance * size+ 2f;


using System.Collections; using System.Collections.Generic; using UnityEngine;public class FollowCamera : MonoBehaviour {public GameObject tank1;public GameObject tank2;public float size= 0.58f;Vector3 p;// Use this for initializationvoid Start () {Vector3 target=(tank1.transform.position + tank2.transform.position) / 2;p = target - transform.position;}// Update is called once per framevoid Update () {//计算出两个坦克之间的距离transform .position= (tank1.transform.position + tank2.transform.position) / 2 - p;//找到两个坦克的中点//让摄像机对准中心点//根据两点之间的距离,调整摄像机的尺寸float distance = Vector3.Distance(tank1.transform.position,tank2.transform.position);GetComponent<Camera>().orthographicSize = distance * size+ 2f;} }
添加音效
在Main Camera上绑定Audio Source音乐播放器
血条
创建一个Slider,把Canvas移动到坦克上
设置Slider控件的 PosX=0 PosX=0 PosZ=0
参数下如下配置
设置完成后效果如下
设置成垂直


using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI;public class TankHealth : MonoBehaviour {public int hp = 100;public GameObject ex;public Slider slider;// Use this for initializationvoid Start () {}// Update is called once per framevoid Update () {}public void TakeDamage(){hp -= 30;if (hp < 0){Destroy(gameObject);GameObject go = Instantiate(ex, transform.position, transform.rotation);}slider.value = hp;}}
添加Slider控件
public Slider slider;
坦克攻击时,时时刷新slider.value值
public void TakeDamage(){hp -= 30;if (hp < 0){Destroy(gameObject);GameObject go = Instantiate(ex, transform.position, transform.rotation);}slider.value = hp;}
完结!