3d游戏设计 Homework10

3d游戏设计 Homework10


这里写图片描述

总结

此次的做业是使用Unet完成一个p2p的联网游戏。主要难点不在于代码上,而在于搞清楚Unet的p2p简单原理,知道代码运行在哪里,执行在哪里,下面就来捋下知识点。web

首先要明白服务端和客户端的概念,其中一台主机做为服务端,但同时他也是客户端(由于他也要玩游戏),明白什么是运行在本地的,什么是运行在服务端上的,经过if(!isLocalPlayer)orif(!isServer)进行运行。可是有的时候客户端产生的行为效果想让其余客户端知道,这个时候就须要将客户端运行的代码交给服务端执行,就须要使用到c#特性【command】,命令服务端执行。因为一些具备本地权限的网络对象,服务端想对客户端的网络对象进行修改的时候,修改以后会被本地权限覆盖,所以就须要用特性【clientRPC】标识在服务端运行的代码,要在客户端执行,下面上代码。c#

代码修改自unity官方的network tutorial,此次代码的质量不是过高(耦合度过高),也没有使用设计风格。网络

PlayerMove

PlayerMove完成了摄像头跟踪,船舶移动,蓄力攻击。要提到的是,蓄力攻击的加速度是在客户端运行的,要放到服务端执行,其中accelation是在本地进行执行的,可是服务端的accelation一直为0,因此咱们还须要在完成CmdPrepareAccelation()在服务端进行蓄力,这里应该建立一个clientAccelation,可是我没有这样作,这样就会出现其余客户端玩家会帮助服务端玩家蓄力,也是我留的一个小坑,房主就会像开挂同样蓄力超快。app

using UnityEngine;
using UnityEngine.Networking;

public class PlayerMove : NetworkBehaviour
{
    [Header("Movement Variables")]
    [SerializeField] float turnSpeed = 45.0f;
    [SerializeField] float movementSpeed = 5.0f;
    [Header("Camera Position Variables")]
    [SerializeField] float cameraDistance = 5f;
    [SerializeField] float cameraHeight = 2f;


    public GameObject bulletPrefab;
    public Rigidbody localRigidBody;
    private Transform mainCamera;
    private Vector3 cameraOffset;
    private float accelation;

    public override void OnStartLocalPlayer()
    {
        GetComponent<MeshRenderer>().material.color = Color.blue;
    }

    private void Start()
    {
        if (!isLocalPlayer)
        {
            return;
        }

        localRigidBody = this.GetComponent<Rigidbody>();

        Debug.Log(transform.position);

        cameraOffset = new Vector3(0f, cameraHeight, -cameraDistance);

        mainCamera = Camera.main.transform;

        MoveCamera();
    }

    private void FixedUpdate()
    {
        if (!isLocalPlayer)
            return;

        /*var turnAmount = Input.GetAxis("Horizontal") * 0.1f; var moveAmount = Input.GetAxis("Vertical") * 0.1f; transform.Translate(x, 0, z);*/

        var turnAmount = Input.GetAxis("Horizontal") * 0.1f;
        var moveAmount = Input.GetAxis("Vertical") * 0.1f;

        Vector3 deltaTranslation = transform.position + transform.forward * movementSpeed * moveAmount * Time.deltaTime;
        localRigidBody.MovePosition(deltaTranslation);

        Quaternion deltaRotation = Quaternion.Euler(turnSpeed * new Vector3(0, turnAmount, 0) * Time.deltaTime);
        localRigidBody.MoveRotation(deltaRotation * localRigidBody.rotation);

        if (Input.GetKey(KeyCode.Space))
        {
            accelation += Time.deltaTime * 2;
            CmdPrepareAccelation();
        }

        if (Input.GetKeyDown(KeyCode.Space))
        {
            accelation = 0;
            CmdZeroAccelation();
        }

        if (Input.GetKeyUp(KeyCode.Space))
        {
            CmdFire();
        }
        MoveCamera();
    }

    [Command]
    private void CmdPrepareAccelation()
    {
        accelation += Time.deltaTime * 2;
    }

    [Command]
    private void CmdZeroAccelation()
    {
        accelation = 0;
    }

    private void MoveCamera()
    {
        mainCamera.position = transform.position;
        mainCamera.rotation = transform.rotation;
        mainCamera.Translate(cameraOffset);
        mainCamera.LookAt(transform);
    }

    [Command]
    void CmdFire()
    {
        // This [Command] code is run on the server!

        // create the bullet object locally
        Transform emitTransform = transform.GetChild(1);
        var bullet = (GameObject)Instantiate(
             bulletPrefab,
             emitTransform.position,
             Quaternion.identity);
        Debug.Log(accelation);
        bullet.GetComponent<Rigidbody>().velocity = transform.forward * 5 * accelation;

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

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

    private void OnGUI()
    {
        if (!isLocalPlayer) return;
        GUIStyle gUIStyle = new GUIStyle();
        gUIStyle.fontSize = 14;
        gUIStyle.normal.textColor = Color.cyan;
        GUI.Label(new Rect(Screen.width / 2 - 130, Screen.height / 2 + 140, 200, 100), "[w.a.s.d]:move " +
            "the boat.\n[space]:attack, and you can accumulate attacking distance by holding [space].\n" +
            "if your health bar disappear, you will move to another place to play again.", gUIStyle);
    }
}

HealthBar

将本地的血量显示成血条。ide

using UnityEngine;
using System.Collections;

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

    void Awake()
    {
        combat = GetComponent<Combat>();
    }

    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;
    }
}

Combat

Combat完成了血量减小,和血量低于0是移动到原地。svg

using UnityEngine;
using UnityEngine.Networking;

public class Combat : NetworkBehaviour
{
    public const int maxHealth = 100;

    [SyncVar]
    public int health = maxHealth;

    public void TakeDamage(int amount)
    {
        if (!isServer)
            return;

        health -= amount;
        if (health <= 0)
        {
            health = maxHealth;

            // called on the server, will be invoked on the clients
            RpcRespawn();
        }
    }

    [ClientRpc]
    void RpcRespawn()
    {
        if (isLocalPlayer)
        {
            // move back to zero location
            transform.position = new Vector3(0,1,0);
        }
    }
}

Bullet

完成了碰撞检测,碰撞时减小血量。this

using UnityEngine;

public class Bullet : MonoBehaviour
{
    void OnCollisionEnter(Collision collision)
    {
        var hit = collision.gameObject;
        var hitPlayer = hit.GetComponent<PlayerMove>();
        if (hitPlayer != null)
        {
            // Subscribe and Publish model may be good here!
            Debug.Log("test");
            var combat = hit.GetComponent<Combat>();
            combat.TakeDamage(30);

            //Destroy(gameObject);
        }
    }
}