文章目录
这章主要是实现一个大乱斗的游戏,搭场景部分省略,人物动作状态机省略。
第一版角色类Human
BaseHuman
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BaseHuman : MonoBehaviour {
//是否正在移动
internal bool isMoving = false;
//移动目标点
private Vector3 targetPosition;
//移动速度
public float speed = 1.2f;
//动画组件
private Animator animator;
//是否正在攻击
internal bool isAttacking = false;
internal float attackTime = float.MinValue;
//描述
public string desc = "";
//移动到某处
public void MoveTo(Vector3 pos){
targetPosition = pos;
isMoving = true;
animator.SetBool("isMoving", true);
}
//移动Update
public void MoveUpdate(){
if(isMoving == false) {
return;
}
Vector3 pos = transform.position;
transform.position = Vector3.MoveTowards(pos, targetPosition, speed*Time.deltaTime);
transform.LookAt(targetPosition);
if(Vector3.Distance(pos, targetPosition) < 0.05f){
isMoving = false;
animator.SetBool("isMoving", false);
}
}
//攻击动作
public void Attack(){
isAttacking = true;
attackTime = Time.time;
animator.SetBool("isAttacking", true);
}
//攻击Update
public void AttackUpdate(){
if(!isAttacking) return;
if(Time.time - attackTime < 1.2f) return;
isAttacking = false;
animator.SetBool("isAttacking", false);
}
// Use this for initialization
internal void Start () {
animator = GetComponent<Animator>();
}
// Update is called once per frame
internal void Update () {
MoveUpdate();
AttackUpdate();
}
}
BaseHuman
作为基类,这部分主要实现的逻辑是,给定一个目标地点,那么人物就会朝目标地点移动,不涉及任何网络部分。
CtrlHuman
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CtrlHuman : BaseHuman {
// Use this for initialization
new void Start () {
base.Start();
}
// Update is called once per frame
new void Update () {
base.Update();
//移动
if(Input.GetMouseButtonDown(0)){
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
Physics.Raycast(ray, out hit);
if (hit.collider.tag == "Terrain"){
MoveTo(hit.point);
}
}
}
}
暂时也不涉及网络部分,主要逻辑是鼠标点击到Terrain
,玩家就会向点击的地方移动。
SyncHuman
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SyncHuman : BaseHuman {
// Use this for initialization
new void Start () {
base.Start();
}
// Update is called once per frame
new void Update () {
base.Update();
}
}
暂时不做逻辑处理。
通信协议
目前的通信协议采用字符串协议,形式如下
消息名|参数1,参数2,参数3,...
例如Move|127.0.0.1:1234,10,0,8
可以代表127.0.0.1:1234
这位用户向(10,0,8)
坐标移动。
消息队列
将通信记录保存下来,采用数据结构List<String>
,主要是在Update
里面读取消息处理。
NetManager类
辅助方法类,便于客户端对网络的操作。
主要提供三个主要接口
- Connect 发起连接
- AddListener 消息监听
- Send 发送消息给服务端
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Net.Sockets;
using UnityEngine.UI;
using System;
public static class NetManager {
//定义套接字
static Socket socket;
//接收缓冲区
static byte[] readBuff = new byte[1024];
//委托类型
public delegate void MsgListener(String str);
//监听列表
private static Dictionary<string, MsgListener> listeners = new Dictionary<string, MsgListener>();
//消息列表
static List<String> msgList = new List<string>();
//添加监听
public static void AddListener(string msgName, MsgListener listener){
listeners[msgName] = listener;
}
//获取描述
public static string GetDesc(){
if(socket == null) return "";
if(!socket.Connected) return "";
return socket.LocalEndPoint.ToString();
}
//连接
public static void Connect(string ip, int port)
{
//Socket
socket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
//Connect
socket.Connect(ip, port);
//BeginReceive
socket.BeginReceive( readBuff, 0, 1024, 0,
ReceiveCallback, socket);
}
//Receive回调
private static void ReceiveCallback(IAsyncResult ar){
try {
Socket socket = (Socket) ar.AsyncState;
int count = socket.EndReceive(ar);
string recvStr = System.Text.Encoding.Default.GetString(readBuff, 0, count);
msgList.Add(recvStr);
socket.BeginReceive( readBuff, 0, 1024, 0,
ReceiveCallback, socket);
}
catch (SocketException ex){
Debug.Log("Socket Receive fail" + ex.ToString());
}
}
//点击发送按钮
public static void Send(string sendStr)
{
if(socket == null) return;
if(!socket.Connected)return;
byte[] sendBytes = System.Text.Encoding.Default.GetBytes(sendStr);
socket.BeginSend(sendBytes, 0, sendBytes.Length, 0, SendCallback, socket);
}
//Send回调
private static void SendCallback(IAsyncResult ar){
try {
Socket socket = (Socket) ar.AsyncState;
//int count = socket.EndSend(ar);
}
catch (SocketException ex){
Debug.Log("Socket Send fail" + ex.ToString());
}
}
//Update
public static void Update(){
if(msgList.Count <= 0)
return;
String msgStr = msgList[0];
msgList.RemoveAt(0);
string[] split = msgStr.Split('|');
string msgName = split[0];
string msgArgs = split[1];
//监听回调;
if(listeners.ContainsKey(msgName)){
listeners[msgName](msgArgs);
}
}
}
再解释一下上面的代码,首先用到了delegate
,不解释。用private static Dictionary<string, MsgListener> listeners = new Dictionary<string, MsgListener>();
表示了字符串和要调用函数之间的映射关系,这个功能很强大,在Update
函数中的最后一个判断就用到了这个映射关系
if(listeners.ContainsKey(msgName)){