最近老师布置了一个作业,是做一个21点游戏,既可以跟AI玩,也可以进行网络对战。对于网络这块,因为最近接触了LuaFramework,它在通信方面也进行了一层封装,看了一下,觉得还是挺不错的,所以就打算用它来搞。因为客户端和服务器端的工程体积挺大的,所以就不放出来了。其实模仿框架中的例子已经可以做出很多东西了,所以这里就说一下额外的比较关键的东西。
游戏截图:
服务器端:
1.首先我们需要定义一些消息。在服务端找到Protocal.cs,复制下面的东西。在客户端也有一个Protocal.cs,同时也有一个protocal.lua。都复制复制吧。。。对于客户端来说,如果你想用c#写网络相关逻辑,那肯定要复制到Protocal.cs中;如果想用lua写,那就复制到protocal.lua中。前者优点是写起来很舒服,调试很方便,但不适用于热更,后者就相反。
Wait = 3000, //客户端等待
WantEnter = 3001, //客户端请求进入房间
Enter = 3002, //服务器允许进入房间
HadEnter = 3003, //客户端已进入房间
ShowWantCard = 3004,//服务端显示要牌按钮
WantCard = 3005, //服务端允许要牌
Card = 3006, //服务端发牌
NotWantCard = 3007, //客户端不要牌
ShowResult = 3008, //客户端显示结果
2.然后就是匹配的事情,我们需要把玩家都放在同一间房间中。
using SimpleFramework.Common;
using SimpleFramework.Message;
using SimpleFramework.Utility;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SimpleFramework.MyCode21Game
{
abstract class Room
{
protected int number;//房间号
protected int capacity;//房间容量
protected int hadReadyCount;//已经准备好的玩家数
protected List<ClientSession> playerList;
public Room(int number, int capacity)
{
this.number = number;
this.capacity = capacity;
this.hadReadyCount = 0;
this.playerList = new List<ClientSession>();
}
public void AddReadyPlayerCount()
{
hadReadyCount++;
if (hadReadyCount == capacity) OnAllReady();
}
public void RemoveReadyPlayerCount()
{
hadReadyCount--;
}
public void AddPlayer(ClientSession clientSession)
{
clientSession.roomId = number;//房间ID,或者是地图ID
clientSession.roomIndex = playerList.Count;//房间序号,即表明是第几个玩家
playerList.Add(clientSession);
if (playerList.Count == capacity) OnFull();
else OnNotFull();
}
public void RemovePlayer(ClientSession clientSession)
{
clientSession.roomId = -1;
clientSession.roomIndex = -1;
playerList.Remove(clientSession);
if (playerList.Count == 0) OnClear();
}
public bool IsFull()
{
if (playerList.Count < capacity) return false;
else return true;
}
public virtual void OnAllReady()
{
}
public virtual void OnFull()
{
}
public virtual void OnNotFull()
{
}
public virtual void OnClear()
{
}
//一般为一局游戏结束后调用
public virtual void Reset()
{
hadReadyCount = 0;
}
public virtual void Destroy()
{
}
}
}
3.上面的是抽象类,我们可以继承它,实现一个棋牌房间类。
using SimpleFramework.Common;
using SimpleFramework.Message;
using SimpleFramework.Utility;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace SimpleFramework.MyCode21Game
{
class CardRoom : Room
{
private List<Card> cards;
public CardRoom(int number, int capacity) : base(number, capacity)
{
cards = CardManager.Instance.GetCards();
}
public override void OnAllReady()
{
base.OnAllReady();
Console.WriteLine("所有玩家已经准备好,开始发牌!!!");
foreach (ClientSession client in playerList)
{
Deal(client);
}
Thread.Sleep(1500);
foreach (ClientSession client in playerList)
{
Deal(client);
}
//同时第一个玩家可以要牌
ByteBuffer bb = new ByteBuffer();
bb.WriteByte((byte)ProtocalType.BINARY);
SocketUtil.SendMessage(playerList[0], Protocal.ShowWantCard, bb);
}
public override void OnFull()
{
base.OnFull();
Console.WriteLine("满人!!!");
foreach (ClientSession cs in playerList)
{
ByteBuffer newBuffer = new ByteBuffer();
newBuffer.WriteByte((byte)ProtocalType.BINARY);
newBuffer.WriteString("匹配成功,房间号是:" + number);
newBuffer.WriteInt(cs.roomId);//设置客户端
newBuffer.WriteInt(cs.roomIndex);
SocketUtil.SendMessage(cs, Protocal.Enter, newBuffer);
}
}
public override void OnNotFull()
{
base.OnNotFull();
Console.WriteLine("还没满人!!!");
foreach (ClientSession cs in playerList)
{
ByteBuffer newBuffer = new ByteBuffer();
newBuffer.WriteByte((byte)ProtocalType.BINARY);
newBuffer.WriteString("人数不足,请等待...");
SocketUtil.SendMessage(cs, Protocal.Wait, newBuffer);
}
}
public override void Reset()
{
base.Reset();
Console.WriteLine("重置房间");
cards = CardManager.Instance.GetCards();
}
//抽牌
public void Deal(ClientSession session)
{
Card card = cards[0];
cards.RemoveAt(0);
Console.WriteLine("玩家为:" + session.roomIndex + "牌为:" + card.ToString());
ByteBuffer newBuffer = new ByteBuffer();
newBuffer.WriteByte((byte)ProtocalType.BINARY);
newBuffer.WriteInt(session.roomIndex);//第几个玩家
newBuffer.WriteString(card.ToString());
foreach (ClientSession client in playerList)
{
SocketUtil.SendMessage(client, Protocal.Card, newBuffer);
}
}
//下一位玩家
public void Next(ClientSession session)
{
ByteBuffer bb = new ByteBuffer();
bb.WriteByte((byte)ProtocalType.BINARY);
int nextIndx = session.roomIndex + 1;
if (nextIndx < capacity)
{
SocketUtil.SendMessage(playerList[nextIndx], Protocal.ShowWantCard, bb);
}
else
{
foreach (ClientSession client in playerList)
{
SocketUtil.SendMessage(client, Protocal.ShowResult, bb);
}
Reset();
}
}
}
}
4.因为房间有很多,所以我们需要对它进行管理。
using SimpleFramework.Common;
using SimpleFramework.Message;
using SimpleFramework.Utility;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SimpleFramework.MyCode21Game
{
class RoomManager : CSharpSingletion<RoomManager>
{
public Dictionary<int, Room> roomDir = new Dictionary<int, Room>();//房间号,房间
private int roomNum = 0;//不要使用字典长度作为key
//顺序分配
public int GetDefaultRoomNum()
{
if (!roomDir.ContainsKey(roomNum)) return roomNum;
if (!roomDir[roomNum].IsFull()) return roomNum;
roomNum++;
return roomNum;
}
}
}
客户端:
1.框架默认的是通过SocketCommand.cs将消息分发到lua中,在lua中写逻辑,这样逻辑随便写也没关系,因为可以通过热更新换掉。而我这里为了加快开发效率,直接在c#中写逻辑,因此就需要修改一下框架,将消息在c#中进行分发。
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using LuaFramework;
public class SocketMessageManager : MonoSingletion<SocketMessageManager> {
public delegate void SocketMessage(KeyValuePair<int, ByteBuffer> dir);
public event SocketMessage socketMessage;
public void Dispatch(KeyValuePair<int, ByteBuffer> dir)
{
Debug.Log(dir.Key.ToString());
if (socketMessage != null) socketMessage(dir);
}
}
找到NetworkManager.cs,修改:
void Update() {
if (sEvents.Count > 0) {
while (sEvents.Count > 0) {
KeyValuePair<int, ByteBuffer> _event = sEvents.Dequeue();
//facade.SendMessageCommand(NotiConst.DISPATCH_MESSAGE, _event);
SocketMessageManager.Instance.Dispatch(_event);
}
}
}
2.那么,我们就可以通过订阅事件来响应消息了。例如我的PanelMain.cs(一个panel对应一个.cs,那么转化为.lua时就很方便了)
public void HandleSockMessage(KeyValuePair<int, ByteBuffer> dir)
{
switch (dir.Key)
{
case Protocal.ShowWantCard:
if (CardManager.Instance.CountCard(Game21Manager.Instance.player) < 21)
{
ShowButtonWantCardAndNotWantCard();
}
break;
case Protocal.Card:
dir.Value.ReadByte();
int roomIndex = dir.Value.ReadInt();
string card = dir.Value.ReadString();
if (roomIndex == Game21Manager.Instance.roomIndex)//如果是自己的牌
{
GetCard(card);
}
else
{
CardManager.Instance.GetCard(banker, new Card(card));
}
break;
case Protocal.ShowResult:
ShowCard();
break;
default:
break;
}
}