Unet联网小游戏——简易版坦克动荡

前言

这一次,使用Unity3D的Network模块实现一个联网的小游戏,当然,资源有限,只是在本地模拟实现一个联网的小游戏,小游戏的名字为——坦克动荡。在开始实现这个游戏之前,我们需要了解一些关于Unet的基本知识,可参考博客——Unity5网络模块UNet介绍。

场景构造

在了解了Unet的基础概念后,就可以开始做游戏了。

第一步,创建一个空对象,命名为NetworkManager,然后,为其添加NetworkManager组件和NetworkManager HUD组件,事实上,当你添加了NetworkManager HUD组件的时候,会自动添加NetworkManager组件。
NetworkManager是一个组件,它是完全使用HLAPI实现,用来管理网络多人游戏的状态。NetworkManager封装好了很多有用的功能到一个地方,使创建、运行和调试多人游戏尽可能简单。
NetworkManagerHUD属性面板显示网络在运行时的状态额外的信息。
NetworkManager的Inspector面板如下图:
Unet联网小游戏——简易版坦克动荡_第1张图片

第二步,构建游戏地图。在这里,我只构建了一幅地图,使用plane作为地面,用cube作为墙,然后为地面和墙增加点颜色,再根据自己想要的样子,将地图中的墙面堆砌好。构建好地图后,将地图拉入资源栏,制成预制。我完成的地图如下:
Unet联网小游戏——简易版坦克动荡_第2张图片
第三步,建立玩家预制和子弹预制。在这里,我只是简单地使用了一个cube和一个Capsule组成坦克,其中,Capsule用来标记坦克的前面,使用一个Sphere作为子弹,并为他们加上颜色,如下:
Unet联网小游戏——简易版坦克动荡_第3张图片
为了使玩家对象能够联网产生,需要给预制体加上NetworkIdentity组件【网络物体最基本的组件,客户端与服务器确认是否是一个物体(netID),也用来表示各个状态,如是否是服务器,是否是客户端,是否有权限,是否是本地玩家等】,并勾上LocalPlayerAuthority。然后,由于涉及对象的移动,需要给玩家对象加上NetworkTransform组件。则Player对象的组件如下:
Unet联网小游戏——简易版坦克动荡_第4张图片
同理,bullet预制也要加上NetworkIdentify组件和NetworkTransform组件,如下图:
Unet联网小游戏——简易版坦克动荡_第5张图片
在这里,为了子弹可以无损能量反弹,我创建了一个Physic Material,并将其Bounciness属性设置为1,然后将该Material添加到了bullet的Collider组件中,如下:
Unet联网小游戏——简易版坦克动荡_第6张图片
然后,回到NetworkManager,在Inspector面板中,找到NetworkManager组件里的Spawn Info,将Player预制体拖入Player Prefab槽中,并增添一个Registered Spawnable Prefab,将bullet预制体拖入槽中,如下:
Unet联网小游戏——简易版坦克动荡_第7张图片
我们可以看到,在Spawn Info中,有一个Player Spawn Method,在这里,我选了Random,表示玩家在开始点中随机选一个点出现,Round Robin则是按开始点的顺序出现。
现在,我们来开始设置开始点。创建一个空对象,命名为startPoint,然后,Add Component -> Network Start Position。将开始点拖到你想玩家对象开始出现的位置即可,在这里,我设置了六个开始点,如下:(为了方便观察,我将开始点的图标改成了蓝色。)
Unet联网小游戏——简易版坦克动荡_第8张图片

至此,场景设计部分就完成了。

代码编写

首先是玩家的移动和开火,其中,开火要使用command命令,在服务端执行动作,并用Spawn同步到每一个玩家。脚本如下:

using UnityEngine;
using UnityEngine.Networking;

public class PlayerMove : NetworkBehaviour
{
    public GameObject bulletPrefab;

    private float movementInputValue;
    private float turnInputValue;
    private float speed = 4f;//the moving speed
    private Rigidbody m_rigidbody;
    private float turnSpeed = 150f; //the rotating speed

    private void Awake()
    {
        m_rigidbody = GetComponent();
    }
    //distinguish self from others
    public override void OnStartLocalPlayer()
    {
        GetComponent().material.color = Color.red;
    }
    //fire
    [Command]
    void CmdFire()
    {
        // This [Command] code is run on the server!

        // create the bullet object locally
        var bullet = (GameObject)Instantiate(
             bulletPrefab,
             transform.position + transform.forward * 0.7f,
             Quaternion.identity);

        bullet.GetComponent().velocity = transform.forward * 5;

        // spawn the bullet on the clients
        NetworkServer.Spawn(bullet);

        // when the bullet is destroyed on the server it will automaticaly be destroyed on clients after 6s
        Destroy(bullet, 6.0f);
    }

    void Update()
    {
        if (!isLocalPlayer)
            return;
        //get the movement value and rotation value
        movementInputValue = Input.GetAxis("Vertical");
        turnInputValue = Input.GetAxis("Horizontal");

        if (Input.GetKeyDown(KeyCode.Space))
        {
            // Command function is called from the client, but invoked on the server
            CmdFire();
        }
    }
    //move and turn
    private void FixedUpdate()
    {
        Move();
        Turn();
    }

    private void Move()
    {
        Vector3 movement = transform.forward * movementInputValue * speed * Time.deltaTime;
        m_rigidbody.MovePosition(m_rigidbody.position + movement);
    }

    private void Turn()
    {
        float turn = turnInputValue * turnSpeed * Time.deltaTime;
        Quaternion turnRotation = Quaternion.Euler(0f, turn, 0f);
        m_rigidbody.MoveRotation(m_rigidbody.rotation * turnRotation);
    }
}

编写完后,将这个脚本挂载到Player预制上,然后将bullet预制拖拉到脚本的BulletPrefab上即可。
Unet联网小游戏——简易版坦克动荡_第9张图片
然后,再写一个Combat类,用来处理玩家的生命值和重生处理。

using UnityEngine;
using UnityEngine.Networking;

public class Combat : NetworkBehaviour
{
    public const int maxHealth = 100; //the max health
    public AudioSource audio;    //the audio when the player died
    [SyncVar]
    public int health = maxHealth; //the current health
    //deal with the damage, this job belongs to the server
    public void TakeDamage(int amount)
    {
        if (!isServer)
            return;

        health -= amount;
        //if the player is dead
        if (health <= 0)
        {
            health = maxHealth;
            // called on the server, will be invoked on the clients
            RpcRespawn();
        }
    }

    [ClientRpc]
    void RpcRespawn()
    {
        if (isLocalPlayer)
        {
        //play the audio and reset the player
            audio.Play();
            transform.position = new Vector3(-8, 0.5f, -4);
        }
    }
}

将上述脚本挂载在Player预制中,同时,增加一个AudioSource组件,并将爆炸音效拖拉到AudioClip槽中。
上面既然说了要处理伤害,那么,就得有个血条,这里,弄了一个很简单的血条,将下面的脚本直接挂载到Player中即可。

using UnityEngine;
using System.Collections;

public class HealthBar : MonoBehaviour
{
    GUIStyle healthStyle;
    GUIStyle backStyle;
    Combat combat;

    void Awake()
    {
        combat = GetComponent();
    }

    void OnGUI()
    {
        InitStyles();

        // Draw a Health Bar

        Vector3 pos = Camera.main.WorldToScreenPoint(transform.position);

        // draw health bar background
        GUI.color = Color.grey;
        GUI.backgroundColor = Color.grey;
        GUI.Box(new Rect(pos.x - 26, Screen.height - pos.y + 20, Combat.maxHealth / 2, 7), ".", backStyle);

        // draw health bar amount
        GUI.color = Color.green;
        GUI.backgroundColor = Color.green;
        GUI.Box(new Rect(pos.x - 25, Screen.height - pos.y + 21, combat.health / 2, 5), ".", healthStyle);
    }

    void InitStyles()
    {
        if (healthStyle == null)
        {
            healthStyle = new GUIStyle(GUI.skin.box);
            healthStyle.normal.background = MakeTex(2, 2, new Color(0f, 1f, 0f, 1.0f));
        }

        if (backStyle == null)
        {
            backStyle = new GUIStyle(GUI.skin.box);
            backStyle.normal.background = MakeTex(2, 2, new Color(0f, 0f, 0f, 1.0f));
        }
    }

    Texture2D MakeTex(int width, int height, Color col)
    {
        Color[] pix = new Color[width * height];
        for (int i = 0; i < pix.Length; ++i)
        {
            pix[i] = col;
        }
        Texture2D result = new Texture2D(width, height);
        result.SetPixels(pix);
        result.Apply();
        return result;
    }
}

下面,就是处理子弹碰撞的问题,就子弹碰撞到玩家的时候,产生伤害,并销毁子弹即可。将下面的代码挂载到bullet预制体上即可。

using UnityEngine;

public class Bullet : MonoBehaviour
{
    void OnCollisionEnter(Collision collision)
    {
        var hit = collision.gameObject;
        var hitPlayer = hit.GetComponent();
        if (hitPlayer != null)
        {
            // Subscribe and Publish model may be good here!
            var combat = hit.GetComponent();
            combat.TakeDamage(20);

            Destroy(gameObject);
        }
    }
}

至此,代码部分就完成了。由于没有使用工厂模式之类的,整体的代码量还是比较少的,也比较容易理解。

成品

然后,再给游戏增加一点儿背景音乐,在地图上增加一个AudioSource组件,将背景音乐拖入AudioClip中,并勾选PlayOnAwake和Loop即可,如下:
Unet联网小游戏——简易版坦克动荡_第10张图片
最后,就可以运行游戏了。先生成exe文件,操作为file->Build setting,或者file->Build&Run。然后,就可以点击exe文件运行游戏了。
Unet联网小游戏——简易版坦克动荡_第11张图片
在开始游戏的时候,第一个玩家必须建立服务器,即以Host模式启动,后面的玩家则以Client模式启动。
Unet联网小游戏——简易版坦克动荡_第12张图片
至此,这个非常简易的坦克动荡就完成了。当然,还有很多不足的地方,例如没有设置玩家的GUI,没有设置胜利和计分以及多幅地图等。但是,这一次主要是熟悉联网游戏的制作过程,其他的处理,等期末考完了,再加上去吧。。。

其他

视频
Github地址

你可能感兴趣的:(Unity3D,C#,Unity3D,C#,Unity3D学习笔记)