Multiplayer Snake játék C# nyelven
2021-05-23T22:11:55+02:00
2021-05-24T15:22:33+02:00
2022-08-12T02:50:32+02:00
raketamernok
Sziasztok!
Én egy multiplayer Snake játékot szeretnék elkészíteni C# nyelven. Maga a játék már implementálásra került, és jól is működik, mindaddig, amíg el nem indítom multiplayer üzemmódban.

A játék koncepciója, a következő: az alkalmazást úgy csináltam meg, hogy a játék indításakor az egyik számítógépen hostGamet lehet indítani, vagyis ő inicializálja fel a játékot, és egy másik számítógépről hozzá lehet csatlakozni, ha egy menüstrip elembe beírja a felhasználó, hogy milyen IP címhez szeretne csatlakozni, ha ez megtörtént, akkor elindul a játék. A szerver és kliens is implementálva van az alkalmazásban, a szerver figyeli a beérkező TCP kéréséket, majd azokat kiszolgálja. Az alkalmazás akkor küld üzenetet TCP-n, ha a játékos megváltoztatta a kígyója mozgását, vagy megevett egy kaját a kígyó.

A szerver indításáért felelős kódrészek:

public void Start() { Thread thread = new Thread(ServerThreadFunc); thread.IsBackground = true; thread.Start(); } private void ServerThreadFunc() { try { int port = int.Parse(ConfigurationManager.AppSettings["port"]); listener = new TcpListener(port); listener.Start(); } catch (SocketException ex) { MessageBox.Show(ex.Message); return; } while (true) { try { TcpClient newClient = listener.AcceptTcpClient(); ThreadPool.QueueUserWorkItem(HandleClient, newClient); } catch (SocketException ex) { MessageBox.Show(ex.Message); } } }
A kliens kiszolgálása:

private void HandleClient(object client) { TcpClient tcpClient = null; NetworkStream netStream = null; try { tcpClient = (TcpClient)client; netStream = tcpClient.GetStream(); string line = new StreamReader(netStream).ReadLine(); Game receivedGame = JsonConvert.DeserializeObject<Game>(line); ProcessIncomingMessage(currGame,receivedGame , tcpClient.Client); } catch (Exception ex) { MessageBox.Show(ex.Message); } finally { if (netStream != null) netStream.Close(); if (tcpClient != null) tcpClient.Close(); } } private delegate void ProcessIncomingMessageDelegate(Game currGame, Game receivedGame, Socket client); private void ProcessIncomingMessage(Game currentGame, Game receivedGame, Socket client) { if (App.Instance.GamePanel.InvokeRequired) { App.Instance.GamePanel.Invoke(new ProcessIncomingMessageDelegate(ProcessIncomingMessage),currentGame, receivedGame, client); } else { if (firstStart) { if (!receivedGame.HostGame) { currentGame.RemoteHost = "192.168.50.22"; currentGame.SendMessageAsync(); currentGame.Timer.Start(); } else { currentGame.Items = receivedGame.Items; currentGame.Timer.Start(); firstStart = false; } } else { lock (App.Instance.syncObject) { currentGame.Items = receivedGame.Items; currentGame.EnemySnake.DeltaX = receivedGame.MySnake.DeltaX; currentGame.EnemySnake.DeltaY = receivedGame.MySnake.DeltaY; } } } }
A játék állapotot leíró Game típusú objektumot JSON-nel sorosítom és küldöm ki, amelyet a szerver fog desorosítani. Amikor legelőször indul el a játék a nem-host alkalmazás, azaz aki csatlakozik a host alkalmazáshoz küld egy üzenetet a host-nak és az kimenti magának az IP-címét. Az én kódomban ez fixre van állítva, mert a client.RemoteEndPoint a port számot is belerakta, de ez most a kisebbik gondom. Ha megtörtént az első üzenet küldése, a host-nál elindul a játék, egy timer elindításával, ami lépteti a játék teret (20ms-onként). A hostnak vissza kell küldenie a nem-host felé, hogy hova helyezte fel az ételt, ez egy Item típusú változó a későbbi fejleszthetőség érdekében. Ezután ha üzenetet kap bármelyik alkalmazás, akkor az ellenfél kígyó sebesség vektorát kell állítani, illetve az Item-eket, hogy az közös legyen mindkét oldalon.

A kliens így küldi ki a játék állapotot:

public void SendMessageAsync() { ThreadPool.QueueUserWorkItem(SendMessage); } private void SendMessage(object state) { TcpClient client = null; NetworkStream netStream = null; try { int port = int.Parse(ConfigurationManager.AppSettings["port"]); client = new TcpClient(); client.Connect(remoteHost, port); netStream = client.GetStream(); using (StreamWriter sw = new StreamWriter(netStream)) { string dataToSend = JsonConvert.SerializeObject(this); sw.WriteLine(dataToSend); } Trace.TraceInformation("Message sent by client"); } catch (Exception ex) { Trace.TraceError(ex.Message); MessageBox.Show(ex.Message); } finally { if (netStream != null) netStream.Close(); if (client != null) client.Close(); }
Na most a játék elindulásakor elkezd akadozni a képernyő, és a játékteren lenyomatott hagynak a  kígyók. Mivel sohasem csináltam ilyen alkalmazást nem igazán tudom mik a jól bevált praktikák, de nagyon sok mindennel próbálkoztam már. Pl azzal, hogy a játékteret csak a host oldalon léptetem, és minden léptetésnél (20ms) kiküldöm a játékteret, amit a másik oldalon csak beolvasok ás megjelenítek. Ebben a megoldásban a nem-host oldalon akkor küldök adatot, ha a felhasználó változtatta a kígyója mozgását. Mivel ezzel a megoldással eléggé akadozott a kijelző, arra jutottam, hogy ez nem túl jó megoldás, mert lehet, hogy a TCP alapú kommunikáció nem képes ilyen sebességű átvitelre.
Mivel az alkalmazás elég erősen multi-thread alapokon nyugszik én arra tudok gondolni, hogy lehet közös változó írás probléma áll fent, de ezt abszolút nem látom át, hogy hol lehet a hiba és hogyan kéne megoldani.

Még a végére ide teszem a timer_tick handlert, mert az fontos lehet:

void timer_Tick(object sender, EventArgs e) { if (mySnake.Iterate(items) == false) { App.Instance.EndGame(); MessageBox.Show("Game over, you lost!"); } if (enemySnake.Iterate(items) == false) { App.Instance.EndGame(); MessageBox.Show("Game over, you win!"); } lock(App.Instance.syncObject) App.Instance.GamePanel.Invalidate(); }
Az iterate függvény lépteti a kígyót, és ha meghal akkor tér vissza hamissal. Egy képet csatoltam a hiba jelenségről. Játék futása közben nem tudom megmutatni a hibát, de játszhatatlan, azt se látni mi történik a kijelzőn. Van olyan, hogy a kígyók egy helyben maradnak, és ilyen "árnyék" szerűen haladnak a pályán. Ez alatt azt értem, hogy nem a kígyókat reprezentáló fekete négyzetek haladnak, hanem a játékteren a fehér négyzetrácsok feketék lesznek, mint a lenyomat esetén a képen, és úgy halad ez az "árnyék". A fekete oválissal a kígyók által hagyott lenyomatokat akartam mutatni.
Ha valaki segítene, de nem elég a forráskód a megoldáshoz, akkor tudom küldeni a projektet.
Mutasd a teljes hozzászólást!
Csatolt állomány
Van sokféle szinkronizációs megoldás itt  illetve itt is de alapvetően UDP-vel kellene ilyen jellegű hálózatos játékot megoldani.  Ráadásul az ipv4 miatt ez csak localban fog menni, szóval még egy hole punching nevű technikát is meg kell oldani, amihez majd kelleni fog egy szerver is. 1v1 játékhoz az se anyira bonyi, csak át kell küldeni a 2 játékosnak egymás publikus ip-jét és portját, onnantól egymással kommunikálhatnak is.
Mutasd a teljes hozzászólást!

abcd