ขณะนี้เวอร์ชันขั้นสูงของผึ้งน้อยกำลังแยกตรรกะการซิงโครไนซ์เฟรมลงใน SDK โปรดดูโครงการ https://github.com/dudu502/littlebee_libs นี่คือตัวอย่างเกมการซิงโครไนซ์เฟรมที่ซิงโครไนซ์วัตถุหลายร้อยรายการและสถานะนับพันในเกม พื้นหลังของเกมคือเกมยิงปืนภายใต้ระบบดาวเคราะห์
[ดูการเล่นวิดีโอ (Youtube)]
[ชมการเล่นซ้ำวิดีโอ (Youtube)]
[ดูการเล่นวิดีโอ (bilibili)]
[ชมการเล่นวิดีโอซ้ำ (bilibili)]
การซิงโครไนซ์เฟรม | การซิงโครไนซ์สถานะ | |
---|---|---|
ความสม่ำเสมอ | ระดับการออกแบบจะกำหนดความสอดคล้องที่หลีกเลี่ยงไม่ได้ | สามารถรับประกันความสม่ำเสมอ |
จำนวนผู้เล่น | การสนับสนุนผู้เล่นหลายคนมีจำกัด | ผู้เล่นหลายคนมีข้อได้เปรียบ |
ข้ามแพลตฟอร์ม | จำเป็นต้องพิจารณาความสอดคล้องของการดำเนินการจุดลอยตัว | เนื่องจากการคำนวณหลักเสร็จสิ้นบนเซิร์ฟเวอร์ จึงไม่มีปัญหาข้ามแพลตฟอร์ม |
ต่อต้านการโกง | โกงง่าย แต่สามารถปรับให้เหมาะสมได้ | สามารถป้องกันการโกงได้ดีมาก |
ตัดการเชื่อมต่อและเชื่อมต่อใหม่ | เป็นการยากที่จะนำไปใช้ แต่ก็ไม่ใช่ว่าจะเป็นไปไม่ได้ | คุณจะต้องส่งข้อความอีกครั้งเพียงครั้งเดียว ใช้งานง่าย |
ข้อกำหนดในการเล่น | สามารถรับรู้ได้อย่างสมบูรณ์แบบ | ไม่สามารถบรรลุผลได้ |
หยุดเกมชั่วคราว | ง่ายต่อการปฏิบัติ | ไม่ใช่เรื่องง่ายที่จะปฏิบัติ |
ปริมาณการส่งผ่านเครือข่าย | ค่อนข้างเล็ก | ค่อนข้างใหญ่ |
ความยากลำบากในการพัฒนา | ค่อนข้างซับซ้อน | ค่อนข้างง่าย |
เกมแนวอาร์ทีเอส | เหมาะสม | ไม่เหมาะ |
เกมต่อสู้ | เหมาะสม | ไม่เหมาะ |
เกมโมบา | เหมาะสม | ไม่เหมาะ |
เกมเอ็มเอ็มโอ | ไม่เหมาะ | เหมาะสม |
หลังจากที่เข้าใจถึงความยากลำบากที่ต้องเอาชนะในกระบวนการพัฒนาเฟรมซิงโครไนซ์แล้ว เราจะพิจารณาเลือกวิธีการนำไปใช้งานที่ดีกว่าหรือกรอบงานการพัฒนาต่อไป เนื่องจากการพัฒนาการซิงโครไนซ์เฟรมจำเป็นต้องมีการแยกข้อมูลและประสิทธิภาพ จึงควรแยกออกจากกันในระดับใด ส่วนการคำนวณข้อมูลสามารถวางแยกเธรดได้ ข้อดีของการเขียนตรรกะในลักษณะนี้คือช่วยให้เซิร์ฟเวอร์ทำงานเพื่อให้สามารถเล่นเกมซ้ำได้อย่างรวดเร็ว ฉันคิดว่ามีเพียง ECS เท่านั้นที่สามารถบรรลุการแยกระดับนี้ได้ การซิงโครไนซ์เฟรมและ ECS ถือเป็นคู่หูที่สมบูรณ์แบบอย่างยิ่ง
อันดับแรก เราต้องแนะนำ ECS ก่อน ECS ไม่ใช่เทคโนโลยีใหม่ล่าสุด และไม่ได้ถูกเสนอครั้งแรกโดย Unity คำนี้ปรากฏเร็วมาก และจู่ๆ ก็ได้รับความนิยมในช่วงไม่กี่ปีที่ผ่านมาเนื่องจาก "Overwatch" ของ Blizzard เฟรมเวิร์กเซิร์ฟเวอร์และไคลเอนต์ของ "Overwatch" สร้างขึ้นโดยสมบูรณ์โดยใช้ ECS และมีประสิทธิภาพที่ยอดเยี่ยมในด้านกลไกเกม เครือข่าย และการเรนเดอร์ พูดตามตรง ECS ไม่เหมือนรูปแบบการออกแบบที่เราใช้ก่อนหน้านี้ถูกกล่าวถึงทั้งหมดภายใต้การออกแบบเชิงวัตถุ และ ECS ไม่ใช่เชิงวัตถุ Unity ยังมี ECS อีกด้วย อันที่จริงส่วนประกอบของ Unity เองก็เป็น ECS ประเภทหนึ่งเช่นกัน แต่ก็ไม่บริสุทธิ์เพียงพอ ECS เหมาะอย่างยิ่งสำหรับการเล่นเกม ECS มีหลากหลายรุ่น และนี่คือ ECS ที่มีการปรับเปลี่ยนเล็กน้อย
นี่คือคุณสมบัติของเกมการซิงโครไนซ์เฟรม หากเกมมีระบบการเล่นซ้ำ เกมจะต้องดำเนินการผ่านการซิงโครไนซ์เฟรม การเล่นอาจเรียกว่าการบันทึกวิดีโอ แต่จะแตกต่างอย่างมากจากการบันทึกวิดีโอโดยใช้ไฟล์วิดีโอ เนื่องจากผู้ให้บริการมักจะใช้ไฟล์ขนาดใหญ่ และไม่สามารถสลับหน้าต่างได้ในระหว่างขั้นตอนการเล่น วิดีโอจะถูกขโมยอย่างง่ายดาย ถูกละเมิด หรือเป็นอันตราย ได้รับการแก้ไข บีบอัด และลดคุณภาพ ดังนั้นการเล่นวิดีโอจึงมีข้อเสียอย่างมาก การเล่นแบบซิงโครไนซ์เฟรมอาจทำให้ไฟล์มีขนาดเล็กมากและไม่สามารถแก้ไขได้ ผู้ใช้สามารถสลับหน้าต่างได้ตามต้องการในระหว่างกระบวนการเล่น อาจกล่าวได้ว่าระบบที่จำเป็นสำหรับเกมการซิงโครไนซ์เฟรมคือระบบการเล่นซ้ำ
ขอแนะนำ RevenantX/LiteNetLib ที่นี่ ไลบรารี่นี้มีประสิทธิภาพมากและใช้งานง่าย โดยให้การส่งผ่าน UDP ที่เชื่อถือได้ ซึ่งเป็นสิ่งที่ฉันต้องการจริงๆ มีโปรโตคอลข้อมูลมากมายให้เลือกสำหรับการสื่อสารผ่านเครือข่าย ฉันใช้โปรโตคอลสตรีมแบบไบนารีที่สร้างขึ้นเอง เช่นเดียวกับโครงสร้าง PtRoom นี้:
//Template auto generator:[AutoGenPt] v1.0
//Creation time:2021/1/28 16:43:48
using System ;
using System . Collections ;
using System . Collections . Generic ;
namespace Net . Pt
{
public class PtRoom
{
public byte __tag__ { get ; private set ; }
public uint RoomId { get ; private set ; }
public byte Status { get ; private set ; }
public uint MapId { get ; private set ; }
public string RoomOwnerUserId { get ; private set ; }
public byte MaxPlayerCount { get ; private set ; }
public List < PtRoomPlayer > Players { get ; private set ; }
public PtRoom SetRoomId ( uint value ) { RoomId = value ; __tag__ |= 1 ; return this ; }
public PtRoom SetStatus ( byte value ) { Status = value ; __tag__ |= 2 ; return this ; }
public PtRoom SetMapId ( uint value ) { MapId = value ; __tag__ |= 4 ; return this ; }
public PtRoom SetRoomOwnerUserId ( string value ) { RoomOwnerUserId = value ; __tag__ |= 8 ; return this ; }
public PtRoom SetMaxPlayerCount ( byte value ) { MaxPlayerCount = value ; __tag__ |= 16 ; return this ; }
public PtRoom SetPlayers ( List < PtRoomPlayer > value ) { Players = value ; __tag__ |= 32 ; return this ; }
public bool HasRoomId ( ) { return ( __tag__ & 1 ) == 1 ; }
public bool HasStatus ( ) { return ( __tag__ & 2 ) == 2 ; }
public bool HasMapId ( ) { return ( __tag__ & 4 ) == 4 ; }
public bool HasRoomOwnerUserId ( ) { return ( __tag__ & 8 ) == 8 ; }
public bool HasMaxPlayerCount ( ) { return ( __tag__ & 16 ) == 16 ; }
public bool HasPlayers ( ) { return ( __tag__ & 32 ) == 32 ; }
public static byte [ ] Write ( PtRoom data )
{
using ( ByteBuffer buffer = new ByteBuffer ( ) )
{
buffer . WriteByte ( data . __tag__ ) ;
if ( data . HasRoomId ( ) ) buffer . WriteUInt32 ( data . RoomId ) ;
if ( data . HasStatus ( ) ) buffer . WriteByte ( data . Status ) ;
if ( data . HasMapId ( ) ) buffer . WriteUInt32 ( data . MapId ) ;
if ( data . HasRoomOwnerUserId ( ) ) buffer . WriteString ( data . RoomOwnerUserId ) ;
if ( data . HasMaxPlayerCount ( ) ) buffer . WriteByte ( data . MaxPlayerCount ) ;
if ( data . HasPlayers ( ) ) buffer . WriteCollection ( data . Players , ( element ) => PtRoomPlayer . Write ( element ) ) ;
return buffer . Getbuffer ( ) ;
}
}
public static PtRoom Read ( byte [ ] bytes )
{
using ( ByteBuffer buffer = new ByteBuffer ( bytes ) )
{
PtRoom data = new PtRoom ( ) ;
data . __tag__ = buffer . ReadByte ( ) ;
if ( data . HasRoomId ( ) ) data . RoomId = buffer . ReadUInt32 ( ) ;
if ( data . HasStatus ( ) ) data . Status = buffer . ReadByte ( ) ;
if ( data . HasMapId ( ) ) data . MapId = buffer . ReadUInt32 ( ) ;
if ( data . HasRoomOwnerUserId ( ) ) data . RoomOwnerUserId = buffer . ReadString ( ) ;
if ( data . HasMaxPlayerCount ( ) ) data . MaxPlayerCount = buffer . ReadByte ( ) ;
if ( data . HasPlayers ( ) ) data . Players = buffer . ReadCollection ( ( rBytes ) => PtRoomPlayer . Read ( rBytes ) ) ;
return data ;
}
}
}
}
นี่คือโปรเจ็กต์ Unity ที่อิงจากการซิงโครไนซ์เฟรม
เครื่องมือบางอย่าง: เครื่องมือสร้างโครงสร้าง Pt, เครื่องมือสร้าง Excel2Json, โครงการไลบรารีทั่วไป, โครงการไลบรารี ServerDll
เอกสารการออกแบบ: เอกสารการออกแบบโครงร่าง เอกสารการออกแบบต้นแบบ ตารางการกำหนดค่า
ตัวเลขสามตัวต่อไปนี้อธิบายการใช้ตัวจำลองการซิงโครไนซ์เฟรมในสถานการณ์ที่แตกต่างกันสามแบบ
รูปด้านล่างแสดงพฤติกรรมทั่วไปของไคลเอ็นต์และเซิร์ฟเวอร์ในเวลาเดียวกัน และตรรกะในการเล่นยังสอดคล้องกับพฤติกรรมเดียวกันอีกด้วย
รูปภาพนี้แสดงไคลเอ็นต์และเซิร์ฟเวอร์ที่ดำเนินการตรรกะในแต่ละ TICK แบบลอจิคัล ส่วนบนคือไคลเอนต์ ตรรกะที่ไคลเอนต์จำเป็นต้องดำเนินการนั้นรวมถึงส่วนของ ECSR และส่วนล่างคือส่วนของเซิร์ฟเวอร์
ภาพสุดท้ายอธิบายแต่ละเฟรมลอจิคัลของการเล่น
ด้วยรูปภาพเหล่านี้และประเภทเกมที่เฉพาะเจาะจง เราสามารถตั้งค่าระบบและส่วนประกอบแบบกำหนดเองเพื่อจัดการกับตรรกะที่เกี่ยวข้องได้
นี่คือโครงการรวบรวมบริการ รวมถึง WebServer, GateServer, RoomServer เป็นต้น