状態の同期は、オンライン ゲームで習得する必要があるサーバー/クライアントの同期テクノロジです。状態同期とは何か、具体的にゲーム内でどのように実装されているのか、これらの疑問を念頭に置いて、この記事では次の 3 つの側面から状態同期を詳細に分析します。
右!ここにはゲームを学ぶのが大好きなゼロベースの初心者が集まるゲーム開発交流会があり、ゲーム開発に携わる技術者もおり、交流や学びができます。
どのゲームが状態同期の使用を選択するか
オンライン ゲームであれば、サーバーとクライアントの同期を伴うゲームで状態同期を使用できます。状態同期の例としては、「World of Warcraft」、Tencent の「Chicken」、「Auto Chess」などがあります。その他のゲーム マルチプレイヤー オンライン ゲームはすべて状態同期で作られており、つまり、数人で対戦する Moba や FPS ゲーム (5 対 5、10 対 10) を除いて、残りのオンライン ゲームは状態同期で作られています。大規模なマルチプレイヤー同時オンライン ゲームのようなゲームは、状態の同期を使用してのみ実行できます。現在主流のネットワーク同期方式はステート同期とフレーム同期です。ステート同期はあらゆるタイプのゲームに使用できますが、フレーム同期は数人のプレイヤーが同時に対戦する短期間のオンライン ゲームにのみ使用できます。 。ゲームで状態同期の使用を検討する必要があるかどうかの理由は、ゲームが
状態同期と比較すると、フレーム同期はユーザー操作のみを同期し、クライアントはすべてのゲーム ロジックを繰り返し計算します。これは、一部のプレイヤーの入力がサーバーから取得される点を除けば、スタンドアロン ゲームに似ています。操作感やプレイヤーの応答時間の点では、ステート同期よりもフレーム同期の方が優れています。しかし、不正行為防止、切断と再接続は最初からフレームを計算し、高速に繰り返す必要があるなどの問題もあります。これにより、フレーム同期で実行できるゲームの種類が制限され、多数の人が同時にオンラインになったり、長時間オンラインになったりするゲームは実行できません。たとえば、MMORPG では、1 台のサーバー上で 5,000 人が同時にオンラインになります。フレーム同期を行う場合、クライアントは 5,000 人のプレイヤー オブジェクトの各フレームの計算を反復すると同時に、長時間オンラインになる必要があります。たとえば、MMORPG ゲームのサービスが開始されて 1 か月が経過し、新しいユーザーが入ってきたとします。
サーバー上でゲームロジックを実行する方法
状態同期の中核は、ゲーム全体のすべてのデータとロジックをサーバーに対して実行することです。一見難しそうに見えますが、実際には、更新フレーム頻度を実行して反復するクライアントを作成するようなものです (通常、1 秒あたり 15 ~ 20 回の反復、または動的に調整されます)。クライアントと同じように、各ゲームの
public class PlayerEntity {
@Protobuf(order = 1, required = true)
public StatusComponent statusInfo; // 玩家的状态;
@Protobuf(order = 2, required = true)
public AccountComponent accountInfo;
@Protobuf(order = 3, required = true)
public GhostPlayerInfoComponent ghostInfo;
@Protobuf(order = 4, required = true)
public EquipmentComponent equipmentInfo;
@Protobuf(order = 5, required = true)
public TransformComponent transformInfo;
// 玩家私密的数据是不必要的;
@Protobuf(order = 6, required = false)
public PrivatePlayerInfoComponent playerInfo;
public AOIComponent aoiComponent;
public SkillBuffComponent skillBuffComponent;
public AttackComponent attackComponent;
public ShapeComponent shapeInfo;
public AStarComponent astarComponent;
public JoystickComponent joystickComponent;
public PlayerEntity() {
this.accountInfo = null;
this.playerInfo = null;
this.ghostInfo = null;
this.equipmentInfo = null;
this.shapeInfo = null;
this.transformInfo = null;
this.astarComponent = null;
this.joystickComponent = null;
this.attackComponent = null;
this.skillBuffComponent = null;
this.statusInfo = null;
}
public static PlayerEntity create(Player p) {
PlayerEntity entity = new PlayerEntity();
entity.statusInfo = new StatusComponent();
entity.statusInfo.state = RoleState.Idle;
entity.accountInfo = new AccountComponent();
entity.playerInfo = new PrivatePlayerInfoComponent();
entity.ghostInfo = new GhostPlayerInfoComponent();
entity.equipmentInfo = new EquipmentComponent();
entity.shapeInfo = new ShapeComponent();
entity.transformInfo = new TransformComponent();
entity.aoiComponent = new AOIComponent();
SkillBuffSystem.InitSkilBuffComponent(entity);
entity.astarComponent = new AStarComponent();
entity.joystickComponent = new JoystickComponent();
entity.attackComponent = new AttackComponent();
entity.statusInfo.entityId = p.getId();
LoginMgr.getInstance().loadAccountComponentFromAccountId(entity.accountInfo, p.getAccountId());
PlayerMgr.getInstance().loadPlayerEntityFromPlayer(entity, p);
PlayerMgr.getInstance().loadEquipMentComponentDataFromPlayer(entity.equipmentInfo, p);
entity.shapeInfo.height = 2.0f;
entity.shapeInfo.R = 0.5f;
return entity;
}
}
たとえば、上記の TransformComponent は、ワールド内のゲーム オブジェクトの位置と回転を記述します。
public class TransformComponent {
@Protobuf(order = 1, required = true)
public Vector3Component position;
@Protobuf(order = 2, required = true)
public float eulerAngleY = 0;
public TransformComponent clone() {
TransformComponent t = new TransformComponent();
t.position = this.position.clone();
t.eulerAngleY = this.eulerAngleY;
return t;
}
public void LookAt(Vector3Component point) {
Vector3Component src = this.position;
Vector3Component dir = Vector3Component.sub(point, src);
float degree = (float)(Math.atan2(dir.z, dir.x) * 180 / Math.PI);
degree = 360 - degree;
this.eulerAngleY = degree + 90;
}
}
ロジック サーバーにログインするプレーヤーを作成すると、サーバー内の 3D ワールドによってそのようなデータ オブジェクトが作成されます。次のステップは、このオブジェクトを実行してゲームの世界で対話できるようにすることです。サーバー側でマップを作成するにはどうすればよいでしょうか? 実際、地図データは、地形高さマップ (x、y、z) + 道路接続データ (どの道路が歩けてどの道路が歩けないのか) としてエクスポートできます。これにはチームの技術的な蓄積が少し必要です。さまざまな種類のゲームに応じて、最適なテクノロジーを使用してマップ エディターを作成します。同時に、クライアントとサーバーはこのセットを使用する必要があります。クライアントには、マップの地形とベイクされたマップ接続データを編集するためのマップ エディター ツールがあり、これらのデータを対応する形式でサーバーにエクスポートできます。サーバーはこれらのデータを使用します上記の Update を反復計算に使用します (クライアント開発の Update 反復と同じ)。
サーバーとクライアントを同期する方法
ゲームマップはサーバー上にセットアップされ、サーバーアップデートの繰り返しの下で、プレイヤーデータとNPCデータがサーバー上で1つずつ「実行」され、次のステップはクライアントとの同期方法です。ステータスの同期とは、サーバー上の位置が変更され、クライアントの位置を一度同期する必要があることを意味しますか? ゲーム内の重要な変化を「状態変化」と表現します。状態の同期 非常に重要な点を覚えておいてください。 サーバーはサーバー上で実行され、クライアントはクライアント上で実行されます。状態が変化するたびに、処理前に同期する必要があります。
次にこの文を具体例を挙げて説明していきます。クライアントを使用してマップ上の点 p をクリックし、点 p に移動する方法を見つけます。このプロセスでは状態の同期について説明します。
Step1: クライアントプレイヤーAがマップ上をマウスやタッチでクリックすると、対象のマップ座標と操作イベント(種類識別)がサーバーに送信されます。
ステップ4: サーバーはサーバーを使用し、パスファインディング + ナビゲーションのアクションの下で毎回プレーヤー A のデータ オブジェクトを反復して A の位置を更新します。サーバーがサーバーを経由する場合、このプロセスはクライアントと同期されないことに注意してください。
ステップ 5: クライアントは、プレイヤー A が地点 P まで歩きたいというイベントを受信します。A の場合、状態が変化しました。最初に同期し、データ パケット内のサーバー上のプレイヤー A の最新の状態情報を同期します。同期後、ローカルで実行します。ポイント p まで歩くための経路探索とナビゲーションを行うには、 クライアントがクライアントを実行します。
Step6: サーバー上のプレーヤー A が目的地に移動すると、プレーヤー A のステータスが変化し、サーバーはこのステータス イベントをプレーヤー A に興味のあるすべてのプレーヤー (A 自身も含む) に送信します。
ステップ 7: クライアントは、プレーヤー A が目的地に到着したという状態イベントを受信した後、データ パケット内のサーバー上のプレーヤー A の状態情報を同期します。
上記から得られた結論は、歩行プロセス中に位置変化をクライアントに同期させる必要はない、サーバーがサーバーを実行し、クライアントがクライアントを実行する、重要な状態変化があった場合にのみ、サーバーが最新の情報を送信する、というものです。クライアントへのクライアントの同期。上記の例では、プレイヤーが歩いていて、プレイヤーが他の攻撃操作を行った後、再び状態が変化し、サーバーは攻撃イベントの処理時に最新の状態情報を取得し、クライアントはその状態情報を同期してから攻撃します。 (攻撃アニメーション、モンスターの流血アニメーションを再生)、サーバーも攻撃中、攻撃完了後は最新のステータスと血液量を同期することができます。状態同期の核心は、各状態とイベントを処理する前にまず同期し、次にサーバーがサーバーを計算し、クライアントがクライアントを計算することです。新しい状態が出るまで、まず同期してから処理します。
さて、今日のステータス同期はここで共有されます。オンライン ゲームの戦闘シリーズのチュートリアルをさらに学ぶためにフォローしてください。