程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 局域網多人對戰飛行棋的實現

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

編輯:C#入門知識

在項目之間有段“空項期”,上個項目剛剛完成,下個項目還沒落實,時間比較充裕。去年9月份就經歷了這麼一次短暫的“空項期”,那時偶還是一名前端工作者,C#使用起來毫不含糊,還自己整過一個類SCSF的MVP框架AngelFrame(詳見之前博客:http://www.cnblogs.com/wgp13x/p/99c2adc52d8f0dff30a038841ac32872.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(null, null);                     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;timer2、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個人、更多的人同時在線都可以自組織游戲平台了,不同步也可以避免了。   其它的一些收獲: 1、System.Windows.Forms.Application.DoEvents();可以督促主線程處理當前在消息隊列中的所有Windows消息。 2、朋友機器上的操作系統是WinXP,自帶的沒有.net4.0,自帶.net3.0,剛開始運行不起來,後來全部換到.net3.0調試、改代碼,才運行得起來。 3、做完後,在家裡跟老婆兩個人打對戰,玩了一晚上這個游戲都不累,還挺好玩的。 好久沒來博客園更新博客了,最近新項目來了。新項目做完,又有好多新知識可以總結喽!    



來自為知筆記(Wiz)



  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved