1500字范文,内容丰富有趣,写作好帮手!
1500字范文 > Unity3D教学 开发简单版第一人称射击游戏 可以多人联机(附源码)

Unity3D教学 开发简单版第一人称射击游戏 可以多人联机(附源码)

时间:2021-04-26 01:07:45

相关推荐

Unity3D教学 开发简单版第一人称射击游戏 可以多人联机(附源码)

简介:

这一篇文章主要是和大家分享如何制作一个属于自己的“第一人称射击游戏”,而且是要可以多人联机的。这个游戏属于比简单的,大神可以直接无视,如果有做错的地方请大家多多指点,我也是刚学如何做游戏。代码是用C#编写,主要实现的功能有三个:第一人称移动控制、角色控制(如射击)、TCP服务端和客户端。我将项目命名为FPS。

游戏运作流程:

服务端

1. 创建服务端

2. 从各个客户端接收数据,然后广播给所有客户

客户端

1. 连接至服务端

2. 从服务端接收到其他客户的数据(比如当前位置,是否有开枪等等),然后更新到游戏上

3. 发送自己当前在游戏的状态给服务端

联机效果图:

服务端

客户端

完整源代码与已编译好的游戏程序:

下载

C#脚本简介:

1. FirstPersonControl.cs

这个脚本是用来做第一人称控制的

using System.Collections;using System.Collections.Generic;using UnityEngine;public class FirstPersonControl : MonoBehaviour {// Use this for initializationvoid Start () {// 获取摄像头对象mCamera = transform.Find("Main Camera");// 获取右手对象mRightHand = transform.Find("RightHand");// 获取脚步声播放组建mAudio = transform.GetComponent<AudioSource>();}// Update is called once per framevoid 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. Character.cs

角色控制脚本,主要是用来控制开枪等角色动作

using System.Collections;using System.Collections.Generic;using UnityEngine;public class Character : MonoBehaviour {// Use this for initializationvoid 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 framevoid 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>();// 修改IDec.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();}}

work.cs

网络组件,用来创建服务端或客户端。服务端负责广播从各个客户端接收的数据给所有客户。客户端则是接收从服务端接收其他客户的数据。

using System.Collections;using System.Collections.Generic;using UnityEngine;using System;using System.IO;using System.Runtime.Serialization.Formatters.Binary;using ;using .Sockets;public class Network : MonoBehaviour {// Use this for initializationvoid Start () {// 获取人物组件mCharacter = transform.GetComponent<Character>();// 数据包大小_packageSize = PackageSize(); }// Update is called once per framevoid Update () {}// 网络设置UIvoid 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"; // 主机IPprivate int _port = 18000; // 端口public int _id = 1 ; // 人物idList<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;}}

4.EnemyCharacter.cs

这个脚本负责控制其他客户的行为,比如从服务端接收到其他客户移动或开枪的数据,就要用这个脚本来更新其他客户的当前行为

using System.Collections;using System.Collections.Generic;using UnityEngine;public class EnemyCharacter : MonoBehaviour {// Use this for initializationvoid 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 framevoid 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;// 角色idpublic void SetID(int id) {_id = id;}// 角色血量public void SetHP(int hp) {_hp = hp;}// 更新角色变量/属性private void UpdataProperties() {// 显示血量和IDtxIDText.text = "ID:"+_id.ToString();txHPText.text = "HP:"+_hp.ToString();// 血量和ID的方向,面向着本机玩家txID.rotation = mCharacter.rotation;txHP.rotation = mCharacter.rotation;}}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。