unity联机FPS-demo
- 0.参考项目
- 1.FPS是什么
- 2.核心代码
- 2.1Character.cs
- 2.2EnemyCharacter.cs
- 2.3FirstPersonControl.cs
- 2.4Network.cs
- 3.效果展示
- 4.TODO
- 5.下载
0.参考项目
1.FPS是什么
第一人称射击类游戏,FPS(First-person shooting game),严格来说第一人称射击游戏属于动作游戏的一个分支,但和RTS类游戏一样,由于其在世界上的迅速风靡,使之发展成了一个单独的类型。
FPS(First-person Shooting game)第一人称视角射击游戏顾名思义就是以玩家的主观视角来进行射击游戏。玩家们不再像别的游戏一样操纵屏幕中的虚拟人物来进行游戏,而是身临其境的体验游戏带来的视觉冲击,这就大大增强了游戏的主动性和真实感。早期第一人称类游戏所带给玩家的一般都是的屏幕光线的刺激,简单快捷的游戏节奏。随着游戏硬件的逐步完善,以及各种游戏的不断结合。第一人称射击类游戏提供了更加丰富的剧情以及精美的画面和生动的音效。
2.核心代码
2.1Character.cs
// 本机角色脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Character : MonoBehaviour {
// Use this for initialization
void Start() {
// 获取摄像头对象
mCamera = transform.Find("Main Camera");
// 获取右手对象
mRightHand = transform.Find("RightHand");
// 获取枪声播放组件
mGunAudio = transform.Find("RightHand/Pistol").GetComponent<AudioSource>();
// 获取火花效果
mFireEffect = transform.Find("RightHand/Pistol/FireEffect").GetComponent<ParticleSystem>();
// 获取网络组件
mNetwork = transform.GetComponent<Network>();
}
// Update is called once per frame
void Update() {
UpdateFire();
// 发送当前状态到服务端,然后服务端就会转发给其他客户
mNetwork.SendStatus(transform.position, transform.eulerAngles,
mCamera.eulerAngles, mRightHand.eulerAngles, _isShooted, _hp);
// 处理服务器发过来的数据包,数据包里装着其他客户的信息
ProcessPackage();
}
private Transform mCamera;
private Transform mRightHand;
private AudioSource mGunAudio;
public GameObject mPiece; // 开枪后撞击产生的碎片
private ParticleSystem mFireEffect; // 开枪后的火花
private bool _isShooted; // 判断是否开了枪
private Network mNetwork; // 网络组件
public GameObject mEnemyCharacter; // 其他客户的实例
private Hashtable _htEnemies = new Hashtable(); // 其他客户的控制脚本
// 开枪
private void UpdateFire() {
if (Input.GetButtonUp("Fire1")) {
// 射击音效与画面
PlayShotSound();
// 播放火花效果
PlayFireEffect();
// 判断射击位置
RaycastHit hit;
if (Physics.Raycast(mCamera.position, mCamera.forward, out hit, 100)) {
// 被枪击中的地方会有碎片弹出
DrawPieces(hit);
}
// 设置开枪判断
_isShooted = true;
} else {
// 设置开枪判断
_isShooted = false;
}
}
// 播放枪声
private void PlayShotSound() {
mGunAudio.PlayOneShot(mGunAudio.clip);
}
// 画碎片
private void DrawPieces(RaycastHit hit) {
for (int i = 0; i < 5; ++i) {
GameObject p = Transform.Instantiate(mPiece);
// 碎片撞击到物体后的反弹位置
Vector3 fwd = mCamera.forward * -1;
p.transform.position = hit.point;
p.GetComponent<Rigidbody>().AddForce(fwd * 100);
// 0.3秒后删除
Destroy(p, 0.3f);
}
}
// 播放火花效果
private void PlayFireEffect() {
mFireEffect.Play();
}
// 人物变量
private int _hp = 100;
// 受到伤害
public void GetHurt() {
_hp -= 10;
if (_hp <= 0) {
// 复活
Revive();
}
}
// 复活
private void Revive() {
_hp = 100;
transform.position = new Vector3(0,1,0);
}
// 处理数据包
private void ProcessPackage() {
Network.Package p;
// 获取数据包直到完毕
while (mNetwork.NextPackage(out p)) {
// 确定不是本机,避免重复
if (mNetwork._id == p.id) {
return;
}
// 获取该客户相对应的人物模组
if (!_htEnemies.Contains(p.id)) {
AddEnemyCharacter(p.id);
}
// 更新客户的人物模型状态
EnemyCharacter ec = (EnemyCharacter)_htEnemies[p.id];
// 血量
ec.SetHP(p.hp);
// 移动动作
ec.Move(p.pos.V3, p.rot.V3, p.cameraRot.V3, p.rightHandRot.V3);
// 开枪
if (p.isShooted) {
ec.Fire();
}
}
}
// 增加客户的人物模组
private EnemyCharacter AddEnemyCharacter(int id) {
GameObject p = GameObject.Instantiate(mEnemyCharacter);
EnemyCharacter ec = p.GetComponent<EnemyCharacter>();
// 修改ID
ec.SetID(id);
// 加入到哈希表
_htEnemies.Add(id, ec);
return ec;
}
// 删除客户的人物模组
private void RemoveEnemyCharacter(int id) {
EnemyCharacter ec = (EnemyCharacter)_htEnemies[id];
ec.Destroy();
_htEnemies.Remove(id);
}
// 删除所有客户的人物模组
public void RemoveAllEnemyCharacter() {
foreach (int id in _htEnemies.Keys) {
EnemyCharacter ec = (EnemyCharacter)_htEnemies[id];
ec.Destroy();
}
_htEnemies.Clear();
}
}
2.2EnemyCharacter.cs
// 敌人角色脚本,敌人也就是其他客户的
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyCharacter : MonoBehaviour {
// Use this for initialization
void Start () {
// 获取本机玩家的对象
mCharacter = GameObject.Find("Character").transform;
mCharacterComponent = mCharacter.GetComponent<Character>();
// 获取摄像头对象
mCamera = transform.Find("Camera");
// 获取右手对象
mRightHand = transform.Find("RightHand");
// 获取枪声播放组件
mGunAudio = transform.Find("RightHand/Pistol").GetComponent<AudioSource>();
// 获取火花效果
mFireEffect = transform.Find("RightHand/Pistol/FireEffect").GetComponent<ParticleSystem>();
// 获取脚步声播放组件
mAudio = transform.GetComponent<AudioSource>();
// 显示血量和ID的组件
txID = transform.Find ("ID");
txIDText = transform.Find ("ID").GetComponent<TextMesh> ();
txHP = transform.Find ("HP");
txHPText = transform.Find ("HP").GetComponent<TextMesh> ();
}
// Update is called once per frame
void Update () {
// 摧毁对象
if (isDestroy) {
Destroy(gameObject);
}
// 更新对象属性
UpdataProperties();
}
private Transform mCharacter;
private Character mCharacterComponent;
private Transform mCamera;
private Transform mRightHand;
private AudioSource mGunAudio;
private AudioSource mAudio;
private ParticleSystem mFireEffect; // 开枪后的火花
public GameObject mPiece; // 开枪后撞击产生的碎片
private bool isDestroy = false;
// 销毁角色
public void Destroy() {
isDestroy = true;
}
// 角色移动动作
public void Move(Vector3 pos, Vector3 rot, Vector3 cameraRot, Vector3 rightHandRot) {
if (pos != transform.position) {
transform.position = pos;
PlayStepSound();
} else {
StopPlayStepSound();
}
mCamera.eulerAngles = cameraRot;
transform.eulerAngles = rot;
mRightHand.eulerAngles = rightHandRot;
}
// 播放脚步声
private void PlayStepSound() {
if (!mAudio.isPlaying) {
mAudio.Play();
}
}
// 停止播放声音
private void StopPlayStepSound() {
if (mAudio.isPlaying) {
mAudio.Stop();
}
}
// 开枪
public void Fire() {
// 射击音效与画面
PlayShotSound();
// 播放火花效果
PlayFireEffect();
// 判断射击位置
RaycastHit hit;
if (Physics.Raycast(mCamera.position, mCamera.forward, out hit, 100)) {
// 被枪击中的地方会有碎片弹出
DrawPieces(hit);
// 判断本机玩家是否中枪如果是就减
print(hit.collider.name);
if (hit.collider.name == mCharacter.name) {
mCharacterComponent.GetHurt();
}
}
}
// 播放枪声
private void PlayShotSound() {
mGunAudio.PlayOneShot(mGunAudio.clip);
}
// 画碎片
private void DrawPieces(RaycastHit hit) {
for (int i = 0; i < 5; ++i) {
GameObject p = Transform.Instantiate(mPiece);
// 碎片撞击到物体后的反弹位置
Vector3 fwd = mCamera.forward * -1;
p.transform.position = hit.point;
p.GetComponent<Rigidbody>().AddForce(fwd * 100);
// 0.3秒后删除
Destroy(p, 0.3f);
}
}
// 播放火花效果
private void PlayFireEffect() {
mFireEffect.Play();
}
// 人物变量
private int _id = 1;
private int _hp = 100;
private Transform txID;
private TextMesh txIDText;
private Transform txHP;
private TextMesh txHPText;
// 角色id
public void SetID(int id) {
_id = id;
}
// 角色血量
public void SetHP(int hp) {
_hp = hp;
}
// 更新角色变量/属性
private void UpdataProperties() {
// 显示血量和ID
txIDText.text = "ID:"+_id.ToString();
txHPText.text = "HP:"+_hp.ToString();
// 血量和ID的方向,面向着本机玩家
txID.rotation = mCharacter.rotation;
txHP.rotation = mCharacter.rotation;
}
}
2.3FirstPersonControl.cs
// 第一人称控制脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FirstPersonControl : MonoBehaviour {
// Use this for initialization
void Start () {
// 获取摄像头对象
mCamera = transform.Find("Main Camera");
// 获取右手对象
mRightHand = transform.Find("RightHand");
// 获取脚步声播放组建
mAudio = transform.GetComponent<AudioSource>();
}
// Update is called once per frame
void Update() {
if (UpdateMovement()) {
PlayStepSound();
} else {
StopPlayStepSound();
}
UpdateLookAt();
}
// 第一人称控制器的常量与变量
private Transform mCamera;
private Transform mRightHand;
private AudioSource mAudio;
public float mMoveSpeed = 8; // 物体移动速度
public float mMouseSensitivity = 5; // 鼠标旋转的敏感度
public float mMinimumX = 325; // 向下望的最大角度
public float mMaximumX = 45; // 向上望的最大角度
public float mMinimumY = -360; // 向左望的最大角度
public float mMaximumY = 360; // 向右望的最大角度
private Vector3 _curRotation = new Vector3(0,0,0); // 当前旋转角度
// 更新移动位置
private bool UpdateMovement() {
float distance = mMoveSpeed * Time.deltaTime; // 移动距离
// 前
if (Input.GetKey(KeyCode.W)) {
float x = Mathf.Sin(transform.eulerAngles.y * Mathf.Deg2Rad) * distance;
float z = Mathf.Cos(transform.eulerAngles.y * Mathf.Deg2Rad) * distance;
transform.Translate(new Vector3(x,0,z), Space.World);
return true;
}
// 后
if (Input.GetKey(KeyCode.S)) {
float x = Mathf.Sin(transform.eulerAngles.y * Mathf.Deg2Rad) * distance;
float z = Mathf.Cos(transform.eulerAngles.y * Mathf.Deg2Rad) * distance;
transform.Translate(new Vector3(-x, 0, -z), Space.World);
return true;
}
// 左
if (Input.GetKey(KeyCode.A)) {
transform.Translate(new Vector3(-distance, 0, 0));
return true;
}
// 右
if (Input.GetKey(KeyCode.D)) {
transform.Translate(new Vector3(distance, 0, 0));
return true;
}
return false;
}
// 更新摄像头指向位置
private void UpdateLookAt() {
// 左右旋转
_curRotation.y = _curRotation.y + Input.GetAxis("Mouse X") * mMouseSensitivity;
_curRotation.y = Mathf.Clamp(_curRotation.y, mMinimumY, mMaximumY);
// 设置身体
Vector3 rotation = transform.eulerAngles;
rotation.y = _curRotation.y;
transform.eulerAngles = rotation;
// 上下旋转
_curRotation.x = _curRotation.x - Input.GetAxis("Mouse Y") * mMouseSensitivity;
_curRotation.x = Mathf.Clamp(_curRotation.x, mMinimumX, mMaximumX);
// 设置摄像头
mCamera.localEulerAngles = new Vector3(_curRotation.x, 0, 0);
// 设置右手
rotation = mRightHand.eulerAngles;
rotation.x = _curRotation.x;
mRightHand.eulerAngles = rotation;
}
// 播放脚步声
private void PlayStepSound() {
if (!mAudio.isPlaying) {
mAudio.Play();
}
}
// 停止播放声音
private void StopPlayStepSound() {
if (mAudio.isPlaying) {
mAudio.Stop();
}
}
}
2.4Network.cs
// 网络脚本,里面有服务端和客户端
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Net;
using System.Net.Sockets;
public class Network : MonoBehaviour {
// Use this for initialization
void Start () {
// 获取人物组件
mCharacter = transform.GetComponent<Character>();
// 数据包大小
_packageSize = PackageSize();
}
// Update is called once per frame
void Update () {
}
// 网络设置UI
void OnGUI() {
GUI.Box (new Rect(0,0,800,60),"网络设置");
if (_connected) {
GUI.Label (new Rect(10,25,100,25), _isServer ? "已建立服务端":"已连接服务端");
} else if (!_connected) {
GUI.Label (new Rect(10,25,100,25), "未连接");
}
GUI.Label (new Rect(130,25,20,25), "IP:");
GUI.Label (new Rect(270,25,40,25), "端口:");
GUI.Label (new Rect(380,25,40,25), "ID:");
if (!_connected && !_isServer) {
_ip = GUI.TextField (new Rect (150, 25, 100, 25), _ip, 100);
_port = System.Convert.ToInt32 (GUI.TextField (new Rect (310, 25, 50, 25), _port.ToString (), 100));
_id = System.Convert.ToInt32 (GUI.TextField (new Rect (420, 25, 100, 25), _id.ToString(), 100));
} else {
GUI.TextField (new Rect (150, 25, 100, 25), _ip, 100);
GUI.TextField (new Rect (310, 25, 50, 25), _port.ToString (), 100);
GUI.TextField (new Rect (420, 25, 100, 25), _id.ToString(), 100);
}
if (!_connected && !_isServer) {
if (GUI.Button(new Rect(540,25,100,25), "开启服务端")) {
StartServer();
ConnectServer();
}
if (GUI.Button(new Rect(660,25,100,25), "连接至服务端")) {
ConnectServer();
}
} else {
if (_isServer) {
if (GUI.Button(new Rect(540,25,100,25), "关闭服务端")) {
StopServer();
}
} else if (_connected) {
if (GUI.Button(new Rect(540,25,100,25), "取消连接")) {
DisconnectServer();
}
}
}
}
// 服务端和客户端的共有变量
private Character mCharacter; // 人物组件
private bool _connected=false; // 判断是否已经建立连接或开启服务端
private bool _isServer=false; // 判断本程序建立的是服务端还是客户端
private string _ip = "127.0.0.1"; // 主机IP
private int _port = 18000; // 端口
public int _id = 1 ; // 人物id
List<Package> _packages=new List<Package>(); // 数据包
private int _packageSize; // 数据包大小
// 数据包
[Serializable]
public struct Package {
public int id;
public Vector3Serializer pos; // 人物位置
public Vector3Serializer rot; // 人物旋转角度
public Vector3Serializer cameraRot; // 摄像头旋转角度
public Vector3Serializer rightHandRot; // 右手旋转角度
public bool isShooted; // 判断是否有开枪
public int hp; // 血量
}
// 获取包大小
private int PackageSize() {
Package p = new Package();
byte[] b;
Serialize(p, out b);
return b.Length;
}
// 可序列化的Vector3
[Serializable]
public struct Vector3Serializer {
public float x;
public float y;
public float z;
public void Fill(Vector3 v3) {
x = v3.x;
y = v3.y;
z = v3.z;
}
public Vector3 V3
{ get { return new Vector3(x, y, z); } }
}
// 序列化数据包
public bool Serialize(object obj, out byte[] result) {
bool ret = false;
result = null;
try {
MemoryStream ms = new MemoryStream();
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(ms, obj);
result = ms.ToArray();
ret = true;
} catch (Exception e) {
ret = false;
Debug.Log(e.Message);
}
return ret;
}
// 反序列化数据包
public bool Deserialize(byte[] data,out object result) {
bool ret = false;
result = new object();
try {
MemoryStream ms = new MemoryStream(data);
BinaryFormatter bf = new BinaryFormatter();
result = bf.Deserialize(ms);
ret = true;
} catch (Exception e) {
ret = false;
Debug.Log(e.Message);
}
return ret;
}
// 服务端变量
TcpListener _listener;
// 储存已连接客户的结构体
private struct Client {
public TcpClient client;
public byte[] buffer; // 接收缓冲区
public List<byte> pendingData; // 还未处理的数据
}
// 客户列表
List<Client> _clients = new List<Client>();
// 开启服务端
private void StartServer() {
try {
_listener = new TcpListener(IPAddress.Any, _port);
_listener.Start();
_listener.BeginAcceptSocket(HandleAccepted, _listener);
_isServer = true;
} catch (Exception e) {
Debug.Log(e.Message);
}
}
// 停止停止服务端
private void StopServer() {
try {
_listener.Stop();
// 清空客户列表
lock (_clients) {
foreach (Client c in _clients) {
RemoveClient(c);
}
_clients.Clear();
}
// 清空数据包
lock (_packages) {
_packages.Clear();
}
_isServer = false;
} catch (Exception e) {
Debug.Log(e.Message);
}
}
// 处理客户端连接的回调函数
private void HandleAccepted(IAsyncResult iar) {
if (_isServer) {
TcpClient tcpClient = _listener.EndAcceptTcpClient(iar);
Client client = new Client();
client.client = tcpClient;
client.buffer = new byte[tcpClient.ReceiveBufferSize];
client.pendingData = new List<byte>();
// 把客户加入到客户列表
lock (_clients) {
AddClient(client);
}
// 开始异步接收从客户端收到的数据
tcpClient.GetStream().BeginRead(
client.buffer,
0,
client.buffer.Length,
HandleClientDataReceived,
client);
// 开始异步接收连接
_listener.BeginAcceptSocket(HandleAccepted, _listener);
}
}
// 从客户端接收数据的回调函数
private void HandleClientDataReceived(IAsyncResult iar) {
try {
if (_isServer) {
Client client = (Client)iar.AsyncState;
NetworkStream ns = client.client.GetStream();
int bytesRead = ns.EndRead(iar);
// 连接中断
if (bytesRead == 0) {
lock (_clients) {
_clients.Remove(client);
}
return;
}
// 保存数据
for (int i=0; i<bytesRead; ++i) {
client.pendingData.Add(client.buffer[i]);
}
// 把数据解析成包
while (client.pendingData.Count >= _packageSize) {
byte[] bp = client.pendingData.GetRange(0, _packageSize).ToArray();
client.pendingData.RemoveRange(0, _packageSize);
// 把数据包分发给所有客户
lock(_clients) {
foreach (Client c in _clients) {
c.client.GetStream().Write(bp, 0, _packageSize);
c.client.GetStream().Flush();
}
}
}
// 开始异步接收从客户端收到的数据
client.client.GetStream().BeginRead(
client.buffer,
0,
client.buffer.Length,
HandleClientDataReceived,
client);
}
} catch (Exception e) {
Debug.Log(e.Message);
}
}
// 加入客户
private void AddClient(Client c) {
_clients.Add(c);
}
// 删除客户
private void RemoveClient(Client c) {
c.client.Client.Disconnect(false);
}
// 客户端变量
Client _server;
// 连接至服务端
private void ConnectServer() {
try {
TcpClient tcpServer = new TcpClient();
tcpServer.Connect(_ip, _port);
_server = new Client();
_server.client = tcpServer;
_server.buffer = new byte[tcpServer.ReceiveBufferSize];
_server.pendingData = new List<byte>();
// 异步接收服务端数据
tcpServer.GetStream().BeginRead(
_server.buffer,
0,
tcpServer.ReceiveBufferSize,
HandleServerDataReceived,
_server);
_connected = true;
} catch (Exception e) {
Debug.Log(e.Message);
}
}
// 从服务端断开
private void DisconnectServer() {
try {
lock (_server.client) {
_server.client.Client.Close();
}
// 清空数据包
lock (_packages) {
_packages.Clear();
}
// 删除所有客户人物模型
mCharacter.RemoveAllEnemyCharacter();
_connected = false;
} catch (Exception e) {
Debug.Log(e.Message);
}
}
// 从服务端读取数据的回调函数
private void HandleServerDataReceived(IAsyncResult iar) {
if (_connected) {
Client server = (Client)iar.AsyncState;
NetworkStream ns = server.client.GetStream();
int bytesRead = ns.EndRead(iar);
// 连接中断
if (bytesRead == 0) {
DisconnectServer();
return;
}
// 保存数据
for (int i=0; i<bytesRead; ++i) {
server.pendingData.Add(server.buffer[i]);
}
// 把数据解析成包
while (server.pendingData.Count >= _packageSize) {
byte[] bp = server.pendingData.GetRange(0, _packageSize).ToArray();
server.pendingData.RemoveRange(0,_packageSize);
// 把数据转换成包然后再储存包列表
object obj;
Deserialize(bp, out obj);
lock (_packages) {
_packages.Add((Package)obj);
}
}
// 异步接收服务端数据
server.client.GetStream().BeginRead(
server.buffer,
0,
server.client.ReceiveBufferSize,
HandleServerDataReceived,
server);
}
}
// 发送自己的当前的状态包给服务端
public void SendStatus(Vector3 pos, Vector3 rot,Vector3 cameraRot,
Vector3 rightHandRot, bool isShooted, int hp) {
try {
if (_connected) {
Package p = new Package();
p.id = _id;
p.pos.Fill(pos);
p.rot.Fill(rot);
p.cameraRot.Fill(cameraRot);
p.rightHandRot.Fill(rightHandRot);
p.isShooted = isShooted;
p.hp = hp;
// 发送包到服务端
byte[] bp;
Serialize(p, out bp);
lock (_server.client) {
_server.client.GetStream().Write(bp, 0, _packageSize);
_server.client.GetStream().Flush();
}
}
} catch (Exception e) {
Debug.Log(e.Message);
// 断开服务端
DisconnectServer();
}
}
// 获取包
public bool NextPackage(out Package p) {
lock (_packages) {
if (_packages.Count == 0) {
p = new Package();
return false;
}
p = _packages[0];
_packages.RemoveAt(0);
}
return true;
}
}
3.效果展示
联网模式选择不同的id连接即可…
4.TODO
摇杆手游版本
简单摇杆unity
5.下载
下载地址