局域網多人對戰飛行棋的實現

在項目之間有段「空項期」,上個項目剛剛完成,下個項目還沒落實,時間比較充裕。去年9月份就經歷了這麼一次短暫的「空項期」,那時偶仍是一名前端工做者,C#使用起來絕不含糊,還本身整過一個類SCSF的MVP框架AngelFrame(詳見以前博客:http://www.cnblogs.com/wgp13x/p/99c2adc52d8f0dff30a038841ac32872.html)。在那段「空項期」以前,有位朋友託我作個小遊戲,偶也滿口的答應,只惋惜以前項目太忙沒時間作,就一直耽擱了,正好有這段「空項期」,因此作了一下,如今回想起來,作這個小遊戲的過程當中仍是學習到了很多東西的,由於作遊戲跟作項目的經常使用技術不一樣,因而在這裏總結一下,別把這麼寶貴的經驗給弄丟了。html

 
這個小遊戲的需求很簡單,就是在局域網的環境裏可以自組織一個飛行棋平臺,多個玩家在裏面你一步我一步的玩,看誰先飛徹底程。在作這個遊戲以前,偶連什麼是飛行棋,飛行棋怎麼玩的都不懂,就先在網上試玩了一小把,查了查飛行棋的規則。會玩兒了,就要想一想怎麼作了,規則實現確定要有,用戶交互是個問題。在網上搜索了一下現成的C#飛行棋實現例子,發現不是給自家女兒作的就是給自家兒子作的,單機版的,手機版的,就是沒有局域網版的。好吧,只有看我來創造了。
 
關鍵詞:飛行棋, C#, 局域網, 多人對戰
摘要:好久以前就有個朋友 託我作個遊戲了,這個遊戲 的需求很簡單,就是在局域網的環境裏可以自組織一個飛行棋平臺,多個玩家在裏面你一步我一步的玩 。遊戲規則有了,實現的難點在於自建多人對戰平臺, C#飛行棋實現例子有不少,可就是沒有局域網版的,下面就是我抽出時間寫的一個 局域網多人對戰飛行棋,在這裏總結一下。

 
先看一下我作的遊戲的運行界面,網上有個單機版本的飛行棋,我借鑑了它的界面及遊戲邏輯,因爲它是開源的,也不知道源代碼提供者是誰,這裏就不作相關連接了。
解釋一下,打開主界面就是上面這個樣子,若是局域網內同時在線的遊戲客戶端多,那麼在當前在線列表中會顯示同時在線的客戶端IP和用戶名,這時你先選擇要進行遊戲的客戶端,再點擊建立遊戲,就能夠開創一輪新遊戲了,遊戲者在兩到四之間。若是你要添加跨網段的客戶端,那就要點擊邀請好友按鈕,填寫IP,若是它們在線就會添加到當前在線列表中,這時你再選擇它們,點擊建立遊戲,就可以在開創的新遊戲裏 他們玩 了。
建立遊戲成功後,在左下角處會出現一個色子,遊戲面板上會出現各種顏色的飛機,每一個遊戲者對應一類 顏色。由一個遊戲者先擲色子, 擲到6後纔可以起飛,其它的遊戲者輪循着來,每一個遊戲者的動做都可以被其它遊戲者收到,在下方文字欄中,會做出說明。
 
網絡版的遊戲,要作只能作成一個Server和多個Client,每一個Client的每一步都要通知Server,由Server來運行遊戲邏輯,指導Client的運行流程;或只是多個Client,每一個Client都要維護其它的Client信息,Client的運行流程都要通知其它全部的Client,每一個Client都運行遊戲邏輯。我這裏陰差陽錯的選擇了第二種,每一個Client都是平等的,沒有Server。通知用的是UDP,局域網的用戶交互就全靠它了,下面是網絡通知相關的代碼,使用單例模式,它是局域網遊戲運行起來的核。
 
public class Network
    {
        private static Network _instace;
        private const int Port = 100;
        public static UdpClient UdpClient;
        private static  Encoding encoding = Encoding.GetEncoding("gb2312");
        public static string HostName;
        public static IPAddress IpAddress;
        private static Thread listener;
 
        public event EventHandler<GameMsg> ReceivedMsg;
        
        public static Network Instance
        {
            get
            {
                if (_instace == null
                    _instace = new Network();
                return _instace;
            }
        }
 
        private Network()
        {
            UdpClient = new UdpClient(Port);
            HostName = Dns.GetHostName();
            foreach (IPAddress ip in Dns.GetHostAddresses(HostName))
            {
                if (ip.AddressFamily == AddressFamily.InterNetwork)
                {
                    IpAddress = ip;
                    break;
                }
            }
        }
       
        //局域網內廣播,在上線時調用,通知其它Client有人上線了
        public void Broadcast(GameMsg msg)
        {
            IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Broadcast, Port);
            byte[] bytes = Tools.Serialize(msg);
            UdpClient.Send(bytes, bytes.Length, ipEndPoint);
        }
 
        //接收消息線程入口,收到消息後觸發各種事件
        public void ReceiveMsg()
        {
            IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Broadcast, Port);
            while (true)
            {
                byte[] bytes = null;
                try
                {
                    bytes = UdpClient.Receive(ref ipEndPoint);
                }
                catch (SocketException ex)
                {
                    return;
                }
                GameMsg msg = (GameMsg)Tools.Deserialize(bytes);
                if (ReceivedMsg != null && !msg.IpAddress.Equals(IpAddress))
                    ReceivedMsg(this, msg);
            }
        }
 
        //向某IP定向發送消息
        public void Send(IPAddress ip, GameMsg msg)
        {
            IPEndPoint ipEndPoint = new IPEndPoint(ip, Port);
            byte[] bytes = Tools.Serialize(msg);
            UdpClient.Send(bytes, bytes.Length, ipEndPoint);
        }
 
        //向一些IP定向發送消息
        public void Send(IPAddress[] ips, GameMsg msg)
        {
            foreach (IPAddress ipAddress in ips)
            {
                if (!ipAddress.Equals(IpAddress))
                    Send(ipAddress, msg);
            }
        }
    }
下面是遊戲中, Client之間 須要交互的一些消息體定義。 GameMsg 是消息體,裏面包含消息類型,IP地址,消息內容,消息內容有多是 LandGameMsg 對象,也有多是 CreateGameMsg 對象...
[Serializable]
    public class GameMsg : EventArgs
    {
        public MsgTypeEnum MsgType;   
        public IPAddress IpAddress;
        public object MsgContent;
    }
 
    [Serializable]
    public class LandGameMsg    //OnlineReply, Hello
    {
        public string HostName;
        public string UserName;
    }
 
    [Serializable]
    public class CreateGameMsg
    {
        public string CreaterHostName;
        public string CreaterUserName;
        public IPAddress[] SelectedIpAddresses;
        public string[] SelectedHostNames;
    }
......
在主界面中,有些對接收到的消息共同的處理邏輯,在這裏也列出來給你們看一下吧。
public partial class MainForm : Form
    {
        public static Thread Listener = new Thread(new ThreadStart(Network.Instance.ReceiveMsg)) { Name = "receiveMsg", Priority = ThreadPriority.Highest };
        public static CurrStatEnum CurrStat = CurrStatEnum.Idle;
        private static readonly MsgTypeEnum[] interestMsgTypes = new MsgTypeEnum[] { MsgTypeEnum.QuitGame };
 
            public  MainForm()
        {
            Listener.Start();
            Network.Instance.ReceivedMsg += new EventHandler<GameMsg>(_network_ReceivedMsg);
            Network.Instance.Broadcast(new GameMsg() { MsgType = MsgTypeEnum.LandGame, IpAddress = Network.IpAddress, MsgContent = new LandGameMsg() { HostName = Network.HostName } });
        }
 
            private   delegate   void  Delegate_ReceivedMsg(GameMsg msg);
        void _network_ReceivedMsg(object sender, GameMsg e)
        {
            Delegate_ReceivedMsg myDelegate = new Delegate_ReceivedMsg(handleReceivedMsg);
            if (interestMsgTypes.Contains(e.MsgType))
                Invoke(myDelegate, e);
        }
 
        void handleReceivedMsg(GameMsg msg)
        {
            switch (msg.MsgType)
            {
                case MsgTypeEnum.QuitGame:    //收到某人退出遊戲請求
                    MessageBox.Show("有小夥伴要求退出遊戲");
                    ucUsersInGame_QuitGame(nullnull);
                    break;
            }
        }
}
 
如上所示,在主界面中主要是對用戶退出遊戲請求作出一些邏輯處理,這裏的線程監控網絡發來的全部消息,但只對退出遊戲請求感興趣,提示用戶,本局遊戲結束。
在其它界面也是相似的過程,下面再列出邀請好友界面裏的,對其它遊戲者的反饋信息作出邏輯處理的代碼段。
public partial class InviteOthers : Form
    {
        private static readonly MsgTypeEnum[] interestMsgTypes = new MsgTypeEnum[] { MsgTypeEnum.OnlineReply };
        public List<GameMsg> OnlineReplys = new List<GameMsg>();
 
         void  _network_ReceivedMsg( object  sender, GameMsg msg)
        {
            if (interestMsgTypes.Contains(msg.MsgType))
            {
                switch (msg.MsgType)
                {
                    case MsgTypeEnum.OnlineReply:
                         OnlineReplys.Add(msg);
                        break;
                }
            }
        }
  }
遊戲引擎,遊戲邏輯處理在這裏就很少列了,那是在單機飛行棋裏的實現了的。
遊戲的基本功能實現了後,還實現了一些添喜的功能,好比隱藏到桌面上方、下方、旁邊,就是你能夠把遊戲窗口拖動到屏幕的最上方,而後松鼠標,遊戲會縮到屏幕上方,當你再把鼠標移動到屏幕上方時,它還會出來,就跟掛QQ的功能同樣。它是這樣實現的,在主界面中添加3個Windows.Forms.Timer,timer1的Enabled=True,Interval=100;timer二、timer3的Enabled=False,Interval=1,再各自添加以下事件處理邏輯。
        /// 監控鼠標和窗口位置
        private void timer1_Tick(object sender, EventArgs e)
        {
            int mouse_x = Cursor.Position.X, mouse_y = Cursor.Position.Y;
            int window_x = this.Location.X, window_y = this.Location.Y;
            int window_width = this.Size.Width, window_height = this.Size.Height;
            if (isHiding == false && window_y == 0)
            {
                if (window_x - mouse_x > 10 || mouse_x - window_x - window_width > 10
                    || mouse_y - window_y - window_height > 10)
                {
                    timer1.Enabled = false;
                    timer2.Enabled = true;
                }
            }
            if (isHiding == true && mouse_y <= 1 && mouse_x > window_x &&
                mouse_x < window_x + window_width)
            {
                timer1.Enabled = false;
                timer3.Enabled = true;
            }
        }
        /// 隱藏界面
        private void timer2_Tick(object sender, EventArgs e)
        {
            int window_height = this.Size.Height;
            startY += window_height / 8;
            if (startY < window_height)
            {
                this.Location = new Point(this.Location.X, -startY);
            }
            else
            {
                this.Location = new Point(this.Location.X, 1 - window_height);
                isHiding = true;
                timer2.Enabled = false;
                timer1.Enabled = true;
            }
        }
        /// 顯示界面
        private void timer3_Tick(object sender, EventArgs e)
        {
            int window_height = this.Size.Height;
            startY -= window_height / 8;
            if (startY > 0)
            {
                this.Location = new Point(this.Location.X, -startY);
            }
            else
            {
                this.Location = new Point(this.Location.X, 0);
                isHiding = false;
                timer3.Enabled = false;
                timer1.Enabled = true;
            }
        }
 
就這樣把局域網多人對戰飛行棋給實現了,回看此次的編程設計經歷,以爲這種P2P式的,無Server式的遊戲設計是個問題。這種設計影響到遊戲編碼方式,使得遊戲編碼雜亂無章,沒有一個主心骨來對遊戲步驟進行統一管理,這樣如若數據在網絡中丟失,很容易致使多客戶端不一樣步的現象。應該在選取完遊戲夥伴建立新遊戲時,自主選擇一個Server來處理遊戲邏輯,這樣,8我的、10我的、更多的人同時在線均可以自組織遊戲平臺了,不一樣步也能夠避免了。
 
其它的一些收穫:
一、System.Windows.Forms.Application.DoEvents();能夠督促主線程處理當前在消息隊列中的全部Windows消息。
二、朋友機器上的操做系統是WinXP,自帶的沒有.net4.0,自帶.net3.0,剛開始運行不起來,後來所有換到 .net3.0調試、改代碼,才運行得起來。
三、作完後,在家裏跟老婆兩我的打對戰,玩了一夜這個遊戲都不累,還挺好玩的。
很久沒來博客園更新博客了,最近新項目來了。新項目作完,又有好多新知識能夠總結嘍!
 
 



相關文章
相關標籤/搜索