3d遊戲設計 Homework10

3d遊戲設計 Homework10


這裏寫圖片描述

總結

這次的作業是使用Unet完成一個p2p的聯網遊戲。主要難點不在於代碼上,而在於搞清楚Unet的p2p簡單原理,知道代碼運行在哪裏,執行在哪裏,下面就來捋下知識點。

首先要明白服務端和客戶端的概念,其中一臺主機作爲服務端,但同時他也是客戶端(因爲他也要玩遊戲),明白什麼是運行在本地的,什麼是運行在服務端上的,通過if(!isLocalPlayer)orif(!isServer)進行運行。但是有的時候客戶端產生的行爲效果想讓其他客戶端知道,這個時候就需要將客戶端運行的代碼交給服務端執行,就需要使用到c#特性【command】,命令服務端執行。由於一些具有本地權限的網絡對象,服務端想對客戶端的網絡對象進行修改的時候,修改之後會被本地權限覆蓋,因此就需要用特性【clientRPC】標識在服務端運行的代碼,要在客戶端執行,下面上代碼。

代碼修改自unity官方的network tutorial,這次代碼的質量不是太高(耦合度太高),也沒有使用設計風格。

PlayerMove

PlayerMove完成了攝像頭跟蹤,船舶移動,蓄力攻擊。要提到的是,蓄力攻擊的加速度是在客戶端運行的,要放到服務端執行,其中accelation是在本地進行執行的,但是服務端的accelation一直爲0,所以我們還需要在完成CmdPrepareAccelation()在服務端進行蓄力,這裏應該創建一個clientAccelation,但是我沒有這樣做,這樣就會出現其他客戶端玩家會幫助服務端玩家蓄力,也是我留的一個小坑,房主就會像開掛一樣蓄力超快。

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

將本地的血量顯示成血條。

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是移動到原地。

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

完成了碰撞檢測,碰撞時減少血量。

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);
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章