网络游戏制作---坦克大战(1)

 

这个游戏可能有点大,我们一步步来实现。

 

一、模型的导入和坦克的移动逻辑

首先给坦克模型添加Rigidbody组件,设置mass=2000;添加Box Collider组件,调整触发器的大小。

编写模型移动的脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TankMove : MonoBehaviour
{
    //表示坦克移动和旋转的变量
    public float moveSpeed = 20.0f;
    public float rotSpeed = 50.0f;

    //要分配各组件的变量
    private Rigidbody rbody;
    private Transform tr;

    //保存键盘输入值的变量
    private float h, v;

    // Start is called before the first frame update
    void Start()
    {
        //初始化各组件
        rbody = GetComponent<Rigidbody>();
        tr = GetComponent<Transform>();
        //将Rigidbody的重心设置为较低的值
        rbody.centerOfMass = new Vector3(0.0f, -0.5f, 0.0f);
    }

    // Update is called once per frame
    void Update()
    {
        h = Input.GetAxis("Horizontal");
        v = Input.GetAxis("Vertical");

        //旋转和移动处理
        tr.Rotate(Vector3.up * rotSpeed * h * Time.deltaTime);
        tr.Translate(Vector3.forward * v * moveSpeed * Time.deltaTime);
    }
}

以上脚本的编写和普通游戏物体的移动相似,通过获取X和Y轴进行旋转和移动。

***这里要注意一点是坦克的重心相对较低,较低重心的方式为:

rbody.centerOfMass = new Vector3(0.0f, -0.5f, 0.0f);

将获取到的刚体组件进行调用centerOfMass函数进行实现。

 

二、实现坦克的履带动画

整体思路我们是通过改变履带纹理的偏移量实现履带旋转。

纹理的理解是如果现在有一个物体,给它增加一个材质(Matiral),其实这个材质就是一个纹理,我们可以设置其的shader方式,包括纹理贴图Diffuse、Normal map等

具体的代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TrackAnim : MonoBehaviour
{
    //纹理旋转速度
    private float scrollSpeed = 1000.0f;
    private Renderer _renderer;

    // Start is called before the first frame update
    void Start()
    {
        _renderer = GetComponent<Renderer>();
    }

    // Update is called once per frame
    void Update()
    {
        var offset = Time.time * scrollSpeed * Input.GetAxisRaw("Vertical");

        //更改默认纹理的Y偏移量值
        _renderer.material.SetTextureOffset("_MainTex", new Vector2(0, offset));
        //更改常规纹理的Y偏移量值
        _renderer.material.SetTextureOffset("_BumpMap", new Vector2(0, offset));
    }
}

这里需要说明的是Renderer是渲染组件,如果需要对其材质改变,需要先获取渲染组件,在调用材质,再设置材质的偏移量。

这里的所有操作都是在update函数中进行,而时间的改变主要在于Time.time:其表示总共花费的时间。

这里的函数重点再说一句:

void SetTextureOffset(string propertyName, Vecter2 offset)

纹理属性名称“_MainTex”--Diffuse类型、“_BimpMap”--Normal map类型、"_Cube"--Cubemap类型。

单词:Renderer---渲染、offset---偏移量。

 

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

三、摄像机追随

主要用到是Unity内置的资源包SmoothFollow-------路径Standard Assets/Utility/SmoothFollow

将脚本SmoothFollow添加到Main Camera上,并且设置追随目标(Target)

 

四、旋转炮塔

 炮塔旋转这一部分可能有点复杂,我们慢慢来看。

我们主要根据鼠标的旋转方向进行旋转,如何实现这个过程:

1、首先创建一条又摄像机到鼠标位置的一条射线

 //通过主摄像机生成向鼠标光标指示位置发射的射线
 Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

2、接下来对射线进行处理

先通过下面函数进行判断,具体而言ray是我们生成的射线,hit是碰撞体信息,Infinity是射线距离,1<<8是过滤层

***1<<8这个参数相对较为重要,它决定了射线碰撞的对象是第8层,我们将Terrain设置为了第8层,碰撞对象就是Terrain。

Physics.Raycast(ray, out hit, Mathf.Infinity, 1 << 8)

3、将射线的位置转化为本地坐标

  //将射线击中的位置转换为本地坐标
  Vector3 relative = tr.InverseTransformPoint(hit.point);

4、根据本地坐标计算炮塔旋转的角度

  //用反正切函数Atan2计算炮塔要旋转的角度
  float angle = Mathf.Atan2(relative.x, relative.z) * Mathf.Rad2Deg;

 上面是对x,z的坐标进行了反正切运算,求出一个弧度值,在乘以Mathf.Rad2Deg转化为了角度值。

5、对炮塔进行旋转

   //以rotSpeed变量作为炮塔旋转角度
   tr.Rotate(0, angle * Time.deltaTime * rotSpeed, 0);

整体代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TurretCtrl : MonoBehaviour
{
    private Transform tr;
    //保存光线击中地面的位置的变量
    private RaycastHit hit;

    //炮塔的旋转速度
    public float rotSpeed = 5.0f;

    // Start is called before the first frame update
    void Start()
    {
        tr = GetComponent<Transform>();
    }

    // Update is called once per frame
    void Update()
    {
        //通过主摄像机生成向鼠标光标指示位置发射的射线
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

        //在场景视图中以绿色光线表示射线
        Debug.DrawRay(ray.origin, ray.direction * 100.0f, Color.green);

        if (Physics.Raycast(ray, out hit, Mathf.Infinity, 1 << 8))
        {
            //将射线击中的位置转换为本地坐标
            Vector3 relative = tr.InverseTransformPoint(hit.point);
            //用反正切函数Atan2计算炮塔要旋转的角度
            float angle = Mathf.Atan2(relative.x, relative.z) * Mathf.Rad2Deg;
            //以rotSpeed变量作为炮塔旋转角度
            tr.Rotate(0, angle * Time.deltaTime * rotSpeed, 0);
        }
    }
}

将脚本添加到Turret上,就实现了移动鼠标光标进行炮塔旋转。

 

五、调整炮身角度

使用鼠标滚轮实现炮弹的发射角度,就是上下移动

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CannonCtrl : MonoBehaviour
{
    private Transform tr;
    public float rotSpeed = 100.0f;

    // Start is called before the first frame update
    void Start()
    {
        tr = GetComponent<Transform>();
    }

    // Update is called once per frame
    void Update()
    {
        float angle = -Input.GetAxis("Mouse ScrollWheel") * Time.deltaTime * rotSpeed;
        tr.Rotate(angle, 0, 0);
    }
}

 

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------

 

六、建立炮弹预设和发射逻辑

1、建立炮弹预设

  1) 创建一个空对象,添加Capsule Collider组件和Rigidbody组件,

        另外还需要添加一个Trail Renderer组件,主要实现炮弹发射后的视觉效果,其需要添加一个纹理。

  2)添加脚本,实现子弹的向前发射

//炮弹速度
public float speed = 6000.0f;

GetComponent<Rigidbody>().AddForce(transform.forward * speed);

  3)完成预设之后,我们将其放置在Resources文件夹下,用以脚本中调动预设资源。

 

2、设置炮弹的起始位置

       Tank--->Turrent(炮台)--->Cannon(大炮)--->FirePos,在Cannon下创建空子对象FirePos,将其移动到合适的位置。

 

3、点击鼠标左键实现发射子弹

  1)定义炮弹预设和炮弹初始点的变量

    //炮弹预设
    public GameObject cannon = null;

    //炮弹发射初始点
    public Transform firePos;

  2)加载预设资源到变量

    void Awake()
    {
        //加载Resources文件夹中的Cannon预设
        cannon = (GameObject)Resources.Load("Cannon");
    }

  3)点击鼠标左键实现发射子弹

    void Update()
    {
        //点击鼠标左键时发射逻辑
        if (Input.GetMouseButtonDown(0))
        {
            Fire();
        }
    }

    void Fire()
    {
        Instantiate(cannon, firePos.position, firePos.rotation);
    }
}

  克隆,首先应该克隆预设、克隆发射位置、克隆旋转。

整体代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FireCannon : MonoBehaviour
{
    //炮弹预设
    public GameObject cannon = null;

    //炮弹发射初始点
    public Transform firePos;

    void Awake()
    {
        //加载Resources文件夹中的Cannon预设
        cannon = (GameObject)Resources.Load("Cannon");
    }

    void Update()
    {
        //点击鼠标左键时发射逻辑
        if (Input.GetMouseButtonDown(0))
        {
            Fire();
        }
    }
void Fire() { Instantiate(cannon, firePos.position, firePos.rotation); } }

 

4、子弹和物体发生碰撞时产生爆炸效果

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Cannon : MonoBehaviour
{
    //炮弹速度
    public float speed = 6000.0f;

    //爆炸效果预设
    public GameObject expEffect;

    private CapsuleCollider _collider;
    private Rigidbody _rigidbody;

    // Start is called before the first frame update
    void Start()
    {
        _collider = GetComponent<CapsuleCollider>();
        _rigidbody = GetComponent<Rigidbody>();

        GetComponent<Rigidbody>().AddForce(transform.forward * speed);

        //3秒后执行自动爆炸的协程函数
        StartCoroutine(this.ExplosionCannon(3.0f));
    }

    void OnTriggerEnter()
    {
        //撞击地面或坦克时立即爆炸
        StartCoroutine(this.ExplosionCannon(0.0f));
    }

    IEnumerator ExplosionCannon(float tm)
    {
        yield return new WaitForSeconds(tm);

        //禁用Collider组件
        _collider.enabled = false;
        //无需再受物理引擎影响
        _rigidbody.isKinematic = true;

        //动态生成爆炸预设
        GameObject obj = (GameObject)Instantiate(expEffect, transform.position, Quaternion.identity);

        Destroy(obj, 1.0f);

        //Trail Renderer消失并等待一段时间后删除炮弹
        Destroy(this.gameObject, 1.0f);
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

 

 

七、炮弹发射音效

首先为Tank添加AudioSource组件

具体过程:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FireCannon : MonoBehaviour
{
    //炮弹预设
    private GameObject cannon = null;

    //炮弹发射声音
    private AudioClip fireSfx = null;
    //AudioSource组件
    private AudioSource sfx = null;

    //炮弹发射初始点
    public Transform firePos;

    void Awake()
    {
        //加载Resources文件夹中的Cannon预设
        cannon = (GameObject)Resources.Load("Cannon");

        //从Resources文件夹中加载炮弹声音文件
        fireSfx = Resources.Load<AudioClip>("CannonFire");
        //声明AudioSource变量
        sfx = GetComponent<AudioSource>();
    }

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        //点击鼠标左键时发射逻辑
        if (Input.GetMouseButtonDown(0))
        {
            Fire();
        }
    }

    void Fire()
    {
        sfx.PlayOneShot(fireSfx, 1.0f);
        Instantiate(cannon, firePos.position, firePos.rotation);
    }
}

 

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------

 

八、安装Photon Unity Networking插件

1、在资源商店搜索PUN---Photon Unity Networking Free,下载安装

2、需要在网站www.exitgames.com注册会员得到Application ID

3、下载好PUN后,将AppId填入,选择地区,协议选择TCP协议稳定一点

4、选择Auto-Join Lobby(自动加入大厅)

单词:Photon(光子)、Lobby(游戏大厅)

 

这里先介绍一下网络游戏的基本过程:

玩家--->首先连接光子云(Photon Cloud)---->连接成功后会进入到游戏大厅(Lobby)--->接着光子云创建游戏房间(room)--->玩家进入房间就可以开始游戏了

这里需要说明的是第一个创建房间的玩家会成为主客户端,如果玩家退出,其他玩家成为主客户端。

 

8.1连接Photon Cloud

层次视图创建空对象(PhotonInit)

主要的代码如下:

    public string version = "v1.0";

    void Awake()
    {
        //连接Photo Cloud
        PhotonNetwork.ConnectUsingSettings(version);
    }
PhotonNetwork.ConnectUsingSettings(),通过这个函数就可连接到光子云,参数是版本号,实现同一版本号的玩家进行游戏。

完整代码:
    public string version = "v1.0";

    void Awake()
    {
        //连接Photo Cloud
        PhotonNetwork.ConnectUsingSettings(version);
    }

    //正常连接Photo Cloud并进入大厅后调用回调函数
    void OnJoinedLobby()
    {
        Debug.Log("Entered Lobby!");
    }

 

8.2随机配对

随机加入房间

   //正常连接Photo Cloud并进入大厅后调用回调函数
    void OnJoinedLobby()
    {
        Debug.Log("Entered Lobby!");
        PhotonNetwork.JoinRandomRoom();
    }

随机连接房间失败

 //随机连接房间失败时调用回调函数
    void OnPhotonRandomJoinFailed()
    {
        Debug.Log("No rooms!");
    }

 

 

8.3制作房间

 

   //随机连接房间失败时调用回调函数
    void OnPhotonRandomJoinFailed()
    {
        Debug.Log("No rooms!");
        //建立房间
        PhotonNetwork.CreateRoom("MyRoom");
    }

    void OnJoinedRoom()
    {
        Debug.Log("Enter Room");
    }

完整代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PhotonInit : MonoBehaviour
{
    //App版本信息
    public string version = "v1.0";

    void Awake()
    {
        //连接Photo Cloud
        PhotonNetwork.ConnectUsingSettings(version);
    }

    //正常连接Photo Cloud并进入大厅后调用回调函数
    void OnJoinedLobby()
    {
        Debug.Log("Entered Lobby!");
        PhotonNetwork.JoinRandomRoom();
    }

    //随机连接房间失败时调用回调函数
    void OnPhotonRandomJoinFailed()
    {
        Debug.Log("No rooms!");
        //建立房间
        PhotonNetwork.CreateRoom("MyRoom");
    }

    void OnJoinedRoom()
    {
        Debug.Log("Enter Room");
    }

    void OnGUI()
    {
        //画面左上角出现连接过程日志
        GUILayout.Label(PhotonNetwork.connectionStateDetailed.ToString());
    }
}
posted @ 2019-06-17 21:48  彩色的梦  阅读(700)  评论(0编辑  收藏  举报