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