Unity3dでのクラむアントサヌバヌ通信

みなさんこんにちは 私は垞に、他の人の実際の経隓に関する蚘事を読んで、プレヌサヌレヌキたたはレヌキを通過するこずに非垞に興味がありたす。 したがっお、この蚘事を始めお、ナニットでのゲヌムの䞖界でのささやかな経隓を共有し、ナニットでの䜜業の他の人の経隓に぀いおさらに孊びたいず思いたす。

そこで、昚幎の11月に、私たちのチヌムはクラむアントセッションセッションmnogoshochkaを䜜り始めたした-車に乗っお、敵を撃ちたす。 私は、チヌムがナニットで成功したプロゞェクトの経隓をすでに持っおいたず蚀わなければなりたせん、それは接觊のための3Dレヌスでした。 そのため、ナニット内の車のテヌマはすでによく知られおおり、これを節玄するこずが蚈画されおいたした。 最初に決められた最初のこずは、抂念実蚌を可胜な限り迅速に䜜成するこずでした-ゲヌムのデモを可胜な限り正確に瀺すこずです。 このむベントの目的は理解できる-ゲヌムに収たらないすべおのものをできるだけ早く遮断するこずです。 さらに、サヌバヌ゚ンゞンも遞択したした。 クラむアントですぐにすべおが明らかになりたした。Unity3dがすべおですが、サヌバヌ゚ンゞンずしお䜕を遞ぶべきでしょうか。 それが問題です。 これに぀いおさらに詳しく説明したす。


そのため、次の応募者が芋぀かりたした。
1. フォトン
2. Smartfox
3. 組み蟌みのUnityネットワヌク
4. ElektroServer
5. uLink
6. CrystalEngine

Unityの組み蟌みネットワヌクはすぐに拒吊したした。 この゜リュヌションはサヌバヌ䞊の物理孊をサポヌトしおいたすが、これは完党に暩嚁あるサヌバヌを意味したすが、この堎合、新しいマッチルヌムを開始するには、サヌバヌ䞊で新しいUnityむンスタンスを䜜成する必芁がありたす。これは非垞に高䟡です。
ElektroServerは、遞択の時点では、他の応募者ほどペヌスが速くないように思えたので、心の底から良いかもしれたせん。 さらに、Webサむトを芋るず、倧倚数の顧客はElektroServerを䜜成したスタゞオのサヌビスを泚文しおおり、サヌバヌ゜リュヌションをリモヌトで泚文した顧客は少数です。 したがっお、ElektroServerも拒吊したした。
uLinkは興味深いものですが、珟時点では非垞に新しいものです。 さらに、私が芚えおいる限りでは、uLinkはナニット内に存圚し、クラむアント、サヌバヌ、したがっおサヌバヌパフォヌマンスの問題が発生したす。 原則ずしお、たずえばカりンタヌのように、プレヌダヌ自身が詊合のためにサヌバヌを䞊げるこずができるようにした堎合、゜リュヌションは良いはずです。 倖芳では、ナニットに組み蟌たれたネットワヌクよりもuLinkの方がはるかに倚くの利点がありたす。
CrystalEngineは若い、進化する゚ンゞンです。 利点-運甚前のデモを䜜成する時点で、私たちのチヌムはこの゚ンゞンの䞻な開発者でした。 マむナス面のうち、゚ンゞンは若すぎお、ドキュメントの欠劂ず倧きなコミュニティの欠劂に盎面しおいたす。 したがっお、この゚ンゞンを䜿甚しないこずにしたした。

合蚈で、2人のファむナリストが残っおいたす。
Photon vs Smartfox

残念ながら、運甚前に䞡方の゜リュヌションをテストする時間はありたせんでした。 䞡方の゚ンゞンが尊敬を呌び起こし、ゲヌム、ドキュメント、掻気のあるコミュニティをリリヌスしたした。 そのため、゚ンゞンを遞択する䞊で重芁な圹割を果たしたのは、私がすでにスマヌトフォンに粟通しおいお、それを良い面でしか知っおいなかったずいう事実です。 思慮深く、理解しやすいIPA、膚倧な数の立ち䞊げプロゞェクトがLinuxで動䜜したす。 䞀般的に、Smartfoxが勝ちたした。 サヌバヌをテストするためのデモも䜜成したしたが、オフィスから700クラむアントたでしか起動したせんでしたが、その埌、オフィスネットワヌクはすでにゆっくりず死に始め、サヌバヌはほずんど負荷を感じたせんでした。 これに関しお、私は質問がありたす-誰かがゲヌムサヌバヌの負荷テストをしたしたか もしそうなら、どのようにテストされたしたか

それでは先に進みたしょう。 プログラマヌのデモの䜜成の最初に、チヌムには3぀のメむンサヌバヌがありたした。メむンサヌバヌであり、狭いサヌクルで広く知られおいるNeodropUnity3dおよびサむトunity3d.ruのロシアのむンタヌネットコミュニティの創蚭者。 サヌバヌ゚ンゞンずしおSmartfoxが遞択された埌、私たちのサヌバヌの人は去りたした。 そこで、サヌバヌプログラマのふりをし始めたした。
プリプロダクションの1か月半の間、次のデモを開始するこずが刀明したした。
1.共有チャットルヌムのあるガレヌゞがありたす。
2. 2チヌムの戊いを開始できたす。
3.各プレむダヌは4台のうち1台目に乗りたす。
スカりト、重装甲車、ミサむル発射機、たたぱンゞニアなど、すべおが元気に運転し、向きを倉え、射殺し、殺し合いたした。 ああ、World of Tanksに䌌た芖界システムがただありたしたが、もちろんそれほどクヌルではありたせんでした。

将来を芋据えお、次の事実に泚目する䟡倀がありたす。このようなデモの䜜成には、1か月もかかりたせんでした。 その埌、生産のためにコヌドをよりきれいにするためにすべおをれロから曞き盎すこずにし、䞀般にモゞュヌルや機噚の倉曎などの必芁な機胜がすべお可胜になったずき、そのようなデモのプレむアビリティのレベルに到達できるのは2〜3か月埌です。 さらに、2人ではなく6-7人がプログラムに参加したした。 これは、デモでは実行時にモゞュヌルや機噚を倉曎するこずが䞍可胜だったために起こりたした。この可胜性は最終補品にあるはずです。 そのため、1぀の機胜で人件費が増加する堎合があるこずがわかりたした。 もちろん、それは1぀だけではありたせん。最終補品では、管理パネルで管理できるものずすべきでないものはすべお、それも楜しいです。

䜕かが歌詞を傷぀けるこずが刀明したので、もう少し技術的な詳现を远加しおみたす。 プロゞェクト䞭、かなりの数の問題や問題が衚面化したした。これらは小さなプロゞェクトにはほずんど関係がないか、ほずんど無関係ですが、倧芏暡なプロゞェクトでは䜜業が遅くならないように解決する必芁がありたす。

最初に遭遇した問題は、クラむアントずサヌバヌ間の通信プロトコルです。 スマヌトフォンでは、すべおが次のように配眮されたす。
1.クラむアントコヌドは、次のタむプのむベントにサブスクラむブしたす。

smartFox.AddEventListener(SFSEvent.EXTENSION_RESPONSE, OnExtensionResponse); 


2.サヌバヌからコマンドを呌び出すこずにより、クラむアント䞊のすべおのサブスクラむバヌは、サヌバヌからパラメヌタヌを送信するメ゜ッドを持ちたす。 サヌバヌからのパラメヌタヌは、コマンド名文字列ず、ハッシュテヌブルに類䌌した特定のSFSObjectの圢匏で送信され、サヌバヌからクラむアントに送信されたすべおのデヌタが含たれたす。

したがっお、サヌバヌからコマンドを実行するには、適切な堎所でサヌバヌメッセヌゞをサブスクラむブする必芁があり、メ゜ッド内でこれが必芁なコマンドであるかどうかを確認し、これが正しいコマンドである堎合、 SFSObjectから必芁なすべおのデヌタをSFSObject 、最埌にコマンドを実行したす。 圓然、私は垞にペンでこれをしたくありたせんでした。 すぐに私に起こった最も簡単なこずは、このむベントに察しお1぀のゲヌムコントロヌラに眲名し、今埌は明瀺的に個別のコマンド、個別のパラメヌタヌを含むむベントを䜜成するこずでした。 次のようになりたした

 public class SFSExtensionsController : MonoBehaviour { public delegate void ExtensionResponceDelegate(string cmd, SFSObject parameters); public static event ExtensionResponceDelegate onExtensionResponce = null; void Start() { smartFox.AddEventListener(SFSEvent.EXTENSION_RESPONSE, OnExtensionResponse); } static void OnExtensionResponse(BaseEvent evt) { string cmd = (string)evt.Params["cmd"]; SFSObject sfsParameters = (SFSObject)evt.Params["params"]; if (onExtensionResponce != null) { onExtensionResponce(cmd, sfsParameters); } } } 


そもそも悪くはありたせんが、各受信者の解析パラメヌタヌの問題は䟝然ずしお残っおいたす。 したがっお、次に行われたのは、 onExtensionResponceむベントにサブスクラむブするコントロヌラヌの導入でした。コントロヌラヌ内では、論理的に䞍可欠なチヌムの䞀郚をむンタヌセプトしたす。たずえば、移動のみ、たたは射撃のみを担圓するチヌムをむンタヌセプトし、ゲヌムロゞックの準備が敎ったデヌタでむベントを提䟛したす。
このような゜リュヌションには利点がありたす-ゲヌムロゞック内では、必芁なフィヌルドが存圚するかどうSFSObject毎回確認する必芁はありたせん。 しかし、十分なマむナスがありたす-むベントが狭く専門化されおいお、そのサブスクラむバヌが1人だけである堎合-これのために、私はたったく倧隒ぎしたくありたせん。 さらに、そのようなコントロヌラヌが倚数ある堎合-目的のむベントが配眮されおいるコントロヌラヌを芋぀ける方法は たた、 SFSObjectには自動SFSObjectがないこずも忘れないでください。これたでのずころ、クラむアントずサヌバヌの䞡方で目的のコヌドを蚘述するこずにより、各クラスを手動でシリアル化する必芁がありたす。 はい、 SFSObjectにはSFSObject任意のクラスをSFSObject化するメ゜ッドがありたすが、この機胜のようなものはSFSObjectたせんでした。敎理する時間はありたせんでした。クラむアントおよびサヌバヌ䞊のコマンドの文字列名のIDをペンで維持するこずも必芁です。 神は犁じられおいたす、誰かがどこかに封印されおいたす。 そのため、SmartFoxのパワヌず思慮深さにもかかわらず、それをそのたた䜿甚しお、耇数の人が行うこずのできないプロゞェクトの楜しみに䜿甚するこずはできたせん。

しかし... ...すべおの欠点に぀いお、䞊蚘のシステムはタスクを完了し、デモは完党に機胜したした。 しかし、この堎所は生産段階で最初に手盎しされたした。 解決すべきいく぀かのタスクがありたした。

1.デヌタ亀換プロトコルのコマンドのID、およびクラむアントずサヌバヌ間でSFSObjectれるSFSObjectの構造。
2.ゲヌムロゞックデヌタの自動シリアル化/逆シリアル化。
3.ゲヌムロゞックを提䟛するクラスからの䟿利なメッセヌゞの送受信。

順調に行きたしょう。

ポむント2自動シリアル化/逆シリアル化。 些现なこずのように思えたす-リフレクションを䜿甚しお、クラスの名前などの必芁な远加情報をすべおSFSObjectすれば、幞犏が埗られたす。 しかし、おもちゃはリアルタむムであり、機械の䜍眮、速床、タレットの回転など、絶えず倉化するデヌタの倧芏暡なストリヌムであるため、この堎合の幞犏は完党にはほど遠いでしょう。 など このデヌタの倚くは、サヌバヌずクラむアントの間で毎秒数回送信されたす。各郚屋には32人のプレヌダヌが蚈画されおいたす。これらの郚屋は、プロゞェクトが砎産しないように少なくずも50のサヌバヌに必芁です。したがっお、゜リュヌションは額に収たりたせん。 そしお、次のアむデアが私たちの助けになりたした。 SFSObjectゲヌムデヌタをパック/アンパックするためのコヌドは簡単で、プログラムでさえこのコヌドを䜜成できたす。 合蚈で、入力で指定された圢匏のiksmファむルを受け取り、出力で゜ヌスコヌド付きの2぀のファむルを出力するナヌティリティが䜜成されたした。1぀はUnityのCで、もう1぀はSmartfoxのJavaです。 結果のファむルには、 SFSObject ToSFSObject()ずvoid FromSFSObject(SFSObject sfsObject) 2぀のメ゜ッドを持぀クラスが含たれたす。

C甚にプログラムで生成されたクラスの䟋

 public class StartMultipleArtilleryShootProxy : ISFSSerializable { public int userId { get; set; } public long barrelId { get; set; } public long projectile { get; set; } public Vec3fProxy position { get; set; } public List<Vec3fProxy> direction { get; set; } public float startSpeed { get; set; } //---------------------------------------------------------------- public StartMultipleArtilleryShootProxy() {} public StartMultipleArtilleryShootProxy(ISFSObject obj) { FromSFSObject(obj); } public StartMultipleArtilleryShootProxy(int userId, long barrelId, long projectile, Vec3fProxy position, List<Vec3fProxy> direction, float startSpeed) { this.userId = userId; this.barrelId = barrelId; this.projectile = projectile; this.position = position; this.direction = direction; this.startSpeed = startSpeed; } //---------------------------------------------------------------- public void FromSFSObject(ISFSObject obj) { this.userId = obj.GetInt("ui"); this.barrelId = obj.GetLong("bi"); this.projectile = obj.GetLong("pr"); this.position = new Vec3fProxy(obj.GetByteArray("p").Bytes); this.direction = new List<Vec3fProxy>(); ISFSArray direction_array = obj.GetSFSArray("d"); for (int i = 0; i < direction_array.Size(); i++) this.direction.Add( new Vec3fProxy(direction_array.GetByteArray(i).Bytes)); this.startSpeed = obj.GetFloat("s"); } //---------------------------------------------------------------- public SFSObject ToSFSObject() { SFSObject obj = new SFSObject(); obj.PutInt("ui", userId); obj.PutLong("bi", barrelId); obj.PutLong("pr", projectile); obj.PutByteArray("p", new ByteArray(position.ToBytes())); if (this.direction == null) Debug.LogError("direction == null in ToSFSObject()"); ISFSArray direction_array = new SFSArray(); for (int i = 0; i < this.direction.Count; i++) direction_array.AddByteArray(new ByteArray(direction[i].ToBytes())); obj.PutSFSArray("d", direction_array); obj.PutFloat("s", startSpeed); return obj; } } 


iksmlファむル内のこのクラスに関するレコヌド

 <StartMultipleArtilleryShootProxy userId-ui="int" barrelId-bi="long" projectile-pr="long" position-p="Vec3fProxy" direction-d="Vec3fProxy array" startSpeed-s="float"/> 


プロトコルを倉曎する堎合、xmlファむルを倉曎し、ナヌティリティを実行し、 SFSObjectを必芁なクラスにすばやくシリアル化/逆シリアル化できるコヌドを取埗したす。 クラむアントずサヌバヌのファむルを眮き換えるだけです

xファむルの圢匏を開発し、クラむアントずサヌバヌ間で送信されるデヌタだけでなく、コマンドの名前もそこに保存するこずにより、最初のポむントを解決するこずは論理的です。 しかし...タむムラむンは垞にオンになっおおり、珟時点ではそのようなこずをする時間はありたせん。 したがっお、チヌム名のタむプミスの問題は、パラグラフ3を解決するプロセスで郚分的に解決されたした。

サヌバヌからメッセヌゞを受信するための䞭倮コントロヌラヌのアむデアはそれほど悪くなかったので、このコントロヌラヌを残すこずにしたした。 さらに、サヌバヌからのむベントごずに、凊理するコマンドず出力するデヌタを正確に把握する独自の小さなクラスを䜜成するこずが決定されたした。 そのようなクラスをすべお1぀の堎所にプッシュし、それらに適切な名前を付けお、アプリケヌションの開始時にそれらを初期化できたす。 したがっお、クラむアントロゞックは、あらゆる皮類のスマヌトフォンに぀いお知る必芁はありたせん。コマンドストアにアクセスするだけで十分です。

 SFSProtocol.Protocol.BATTLE.HitRivalRequest.onGetResponce += OnGetHitRivalResponce; 


むベントハンドラヌは、ゲヌムロゞックに必芁な既に厳密に型指定されたデヌタを取埗したす。

 void OnGetHitRivalResponce(HitResultProxy hitResultProxy) { //    } 


そしお今、少し魔法がありたす。これは、Unityがそれ自䜓の内郚でシングルスレッドコヌドをプロモヌトし、サヌバヌからのむベントも1぀のスレッドで呌び出されるずいう事実に基づいおいたす。 このシングルスレッド操䜜のおかげで、スマヌトフォンず盎接通信する唯䞀のコントロヌラヌで、誰かがサヌバヌからのコマンドを凊理したかどうかを確認し、誰も凊理しおいない堎合は、すぐにそれをログに叫ぶこずができたす。 簡単に芋える

 cmdWasCatched = false; if (onExtensionResponce != null) { onExtensionResponce(cmd, sfsParameters); } if (!cmdWasCatched) { Debug.LogError(string.Format("SFSExtensionsController.OnExtensionResponse cmd={0} was not catched!", cmd)); } 


さらに、このようなアヌキテクチャにより、クラむアントずサヌバヌの通信に絶察に必芁なこずを行うこずができたしたサヌバヌからの応答のタむムアりトをチェックし、ゲヌムロゞックデヌタに加えお远加のサヌビスフィヌルドを远加したすたずえば、クラむアントからの芁求が正垞に凊理されたかどうか、倱敗した堎合-説明サヌバヌ゚ラヌ。

蚘事の最埌で、螏み蟌んだもう1぀のレヌキに蚀及する䟡倀がありたす。 プロトコルでの新しいコマンドの䜜成を簡玠化するために、パラメヌタヌ化可胜なクラスが䜜成されたした。このクラスから、サヌバヌに送信するデヌタのタむプ、サヌバヌから受信するデヌタのタむプを指定し、コンストラクタヌをカットできたす。 このクラス内のオブゞェクトを逆シリアル化するコヌドは次のようになりたした。

 public class SFSRequest<TReqData, TRespData> : DataTransporter where TReqData : ISFSSerializable, new() where TRespData : ISFSSerializable, new() { 
.......... protected override void Execute(Sfs2X.Entities.Data.SFSObject sfsParameters) { responceData = new TRespData(); responceData.FromSFSObject(sfsParameters); OnGetResponce(); } } 


そしお、すべおがうたくいくように芋えたすが、1぀の倧きなこずがありたす。この堎合、 new TRespData()コンストラクタヌはリフレクションを䜿甚したす。 その結果、圌らは戊っおいたものに遭遇したした反射がただ䜿甚されおおり、すでに戊闘で8人のプレむダヌが枛速しおいるこずが顕著です。 状況を修正するために、チヌムのクラスにデリゲヌト甚の2぀のフィヌルドが远加されたした。これらのフィヌルドはTReqDataずTReqData䜜成TRespDataをTReqData 。 そのため、基本クラスハンドラは次のようになり始めたした。

 protected override void Execute(Sfs2X.Entities.Data.SFSObject sfsParameters) { responceData = respDataConstructor(); responceData.FromSFSObject(sfsParameters); onGetResponce(); } 


そしお、特定のコマンドの最終クラスは次のずおりです。

 public class MultipleArtilleryShootStartRequest : SFSRequest<StartMultipleArtilleryShootProxy, StartMultipleArtilleryShootProxy> { public MultipleArtilleryShootStartRequest(string sendCommand, string receiveCommand, Func<StartMultipleArtilleryShootProxy> reqDataConstructor, Func<StartMultipleArtilleryShootProxy> respDataConstructor) : base(sendCommand, receiveCommand, reqDataConstructor, respDataConstructor) { } } 


ちなみに、Habrからの蚘事は、残念ながら私が倱ったリンクを芋぀けるのに圹立ちたした。

これらの方法はあたり掗緎されおいたせんが、非垞に䟿利なクラむアントサヌバヌ通信であるこずがわかりたした。 質問やコメントがある堎合は、お気軜にコメントしおください。 この蚘事を最埌たで読んだ人にずっお、小さなパンずは、むンタヌネットで掘り出されたゲヌム゚ンゞンのパラメヌタヌの抂芁衚です 。

Source: https://habr.com/ru/post/J149244/


All Articles