HarmonyOS ミニゲーム: Eat Beans---分散データベースと分散タスク スケジューリングに基づく

HarmonyOS ミニゲーム: Eat Beans---分散データベースと分散タスク スケジューリングに基づく

[[417423]]

詳細については、以下をご覧ください。

51CTOとHuaweiが共同で構築したHongmengテクノロジーコミュニティ

https://harmonyos..com

序文

私は深セン大学の学部生です。学校の授業で最も一般的に使用されるプログラミング言語をいくつか学びましたが、それでも本から得た知識は浅く、本当に理解するには実践する必要があると感じています。実践的な練習なしに、ただ単に言語を学ぶだけでは明らかに不十分です。ちょうど Hongmeng がリリースされたので、この機会を利用して、授業で学んだことを Hongmeng で実践してみることにしました。この目的のために、私は深セン大学の木棉鴻蒙グループに参加し、仲間と共に勉強しました。夏休み中に、Kapok グループで Hongmeng 分散データベース、分散タスク スケジューリングなどについて学習したので、この間学んだことを記録するためにこの記事を書きました。

概要

この豆を食べるゲームは、シングルプレイヤーバージョンと2プレイヤーバージョンに分かれています。シングルプレイヤーバージョンでは、2体のモンスターがプレイヤーを追いかけます。追いつかれずに皿の上の豆を全部食べればプレイヤーの勝ちです。 2人用バージョンでは、分散データベースと分散スケジューリングの機能を使用して、2人でオンラインで豆を食べることができます。

ゲームのカバーは次のとおりです。


ゲームモードを選択してください:


2人プレイモードでの装備選択インターフェース:


ゲームインターフェースのスクリーンショットは次のとおりです。

シングルプレイヤーモード


2人プレイモード


文章

シングルプレイヤーゲーム

お絵かきゲーム

マップは 2 次元配列の形式で保存されます。次のように、1 は壁、2 はプレイヤー、3 はモンスター、0 は豆です。

  1. 公共   int [][] グリッド={
  2. {1、1、1、1、1、1、1、1、1、1、1、1、1、1、1}、
  3. {1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1},
  4. {1, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1},
  5. {1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1},
  6. {1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
  7. {1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
  8. {1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
  9. {1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1},
  10. {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1},
  11. {1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1},
  12. {1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
  13. {1, 0, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
  14. {1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1},
  15. {1、2、0、0、0、0、0、0、0、0、0、0、0、0、1}、
  16. {1、1、1、1、1、1、1、1、1、1、1、1、1、1、1}};;

ゲームパネルを描く

  1. パブリックvoid 描画テーブル ( intグリッド [][]){
  2. LayoutConfig を ComponentContainer に追加します。
  3.  
  4. Component.DrawTask タスク = 新しい Component.DrawTask() {
  5. @オーバーライド
  6. public void onDraw(Component コンポーネント、Canvas キャンバス) {
  7. ペイント paint = new Paint();
  8. ペイントの色を黒に設定します。
  9.  
  10. RectFloat rect=new RectFloat(30-20,250-20,長さ*15+間隔*14+30+20,長さ*15+間隔*14+250+20);
  11. キャンバスに矩形を描画します。
  12.  
  13. ( int row = 0; row < height; row++)の場合
  14. {
  15. int  = 0;< 幅;++){
  16. もし
  17. (グリッド[行][]==食べ物 || グリッド[行][]==空){
  18. ペイントの色を黒に設定します。
  19. }
  20. そうでない場合(グリッド[行][]==壁)
  21. ペイントの色を青に設定します。
  22. そうでない場合(グリッド[行][]==プレイヤー1)
  23. ペイントの色をREDに設定します。
  24. そうでない場合(グリッド[行][]==モンスター)
  25. ペイントにMAGENTAを設定します。
  26. RectFloat rectFloat=new RectFloat(30+*(長さ+間隔),250+行*(長さ+間隔),30+長さ+*(長さ+間隔),250+長さ+行*(長さ+間隔));
  27. キャンバスに矩形を描画します。
  28. if(grids[行][]==food){
  29. ペイントの色を黄色に設定します。
  30. 円 円=新しい円(30+*(長さ+間隔)+長さ/2,250+行*(長さ+間隔)+長さ/2,長さ/5);
  31. キャンバスに円を描画します。
  32. }
  33. }
  34. }
  35.  
  36.  
  37. }
  38. };
  39. レイアウトにタスクを追加します。
  40. UIContent を設定します。
  41. }

ボタンを描く

次のコードは、4 つの方向ボタンをそれぞれ描画し、ボタンが押されたときにコールバック メソッドを設定します。下方向のボタンを押すと、プレイヤーの方向は左に設定されます。

  1. パブリックvoid 描画ボタン() {
  2. ShapeElement の背景 = 新しい ShapeElement();
  3. 背景.setRgbColor(新しいRgbColor(174, 158, 143));
  4. 背景.setCornerRadius(100);
  5.  
  6. ボタン button1 = new Button(this);
  7. button1.setText( "←" );
  8. button1.setTextAlignment(TextAlignment.CENTER);
  9. ボタン1にテキストカラーを設定します(Color.WHITE);
  10. ボタン1.テキストサイズを設定します(100);
  11. ボタン1.setMarginTop(1500);
  12. ボタン1.setMarginLeft(180);
  13. ボタン1.setPadding(10,0,10,0);
  14. button1.setBackground(背景);
  15. button1.setClickedListener(新しいComponent.ClickedListener() {
  16. @オーバーライド
  17. パブリックvoid onClick(コンポーネント コンポーネント) {
  18. プレイヤー.left (); //プレイヤーの方向を左に設定する
  19. }
  20. });
  21. レイアウトにコンポーネントを追加します(ボタン1)。
  22.  
  23. ボタン button2 = new Button(this);
  24. button2.setText( "↑" );
  25. button2.setTextAlignment(TextAlignment.CENTER);
  26. ボタン2.setTextColor(Color.WHITE);
  27. ボタン2.テキストサイズを設定します(100);
  28. ボタン2.setMarginLeft(470);
  29. ボタン2.setTopMargin(-330);
  30. ボタン2.setPadding(10,0,10,0);
  31. button2.setBackground(背景);
  32. button2.setClickedListener(新しいComponent.ClickedListener() {
  33. @オーバーライド
  34. パブリックvoid onClick(コンポーネント コンポーネント) {
  35. プレイヤーをアップします。 //プレイヤーの方向を上向きに設定する
  36. }
  37. });
  38. レイアウトにコンポーネントを追加します(ボタン2)。
  39.  
  40. ボタン button3 = new Button(this);
  41. button3.setText( "→" );
  42. button3.setTextAlignment(TextAlignment.CENTER);
  43. ボタン3.setTextColor(Color.WHITE);
  44. ボタン3.テキストサイズを設定します(100);
  45. ボタン3.setMarginLeft(760);
  46. ボタン3.setMarginTop(55);
  47. ボタン3.setPadding(10,0,10,0);
  48. button3.setBackground(背景);
  49. button3.setClickedListener(新しいComponent.ClickedListener() {
  50. @オーバーライド
  51. パブリックvoid onClick(コンポーネント コンポーネント) {
  52. プレイヤーの右() ;プレイヤーの方向を右に設定する
  53. }
  54. });
  55. レイアウトにコンポーネントを追加します(ボタン3)。
  56.  
  57. ボタン button = new Button(this);
  58. ボタン.setText( "↓" );
  59. ボタン.setTextSize(100);
  60. button.setTextAlignment(TextAlignment.CENTER);
  61. ボタン。テキストカラーを設定します(Color.WHITE);
  62. ボタン.setMarginTop(100);
  63. ボタン.setMarginLeft(470);
  64. ボタン.setPadding(10,10,10,10);
  65. ボタンの背景を設定します。
  66. button.setClickedListener(新しいComponent.ClickedListener() {
  67. @オーバーライド
  68. パブリックvoid onClick(コンポーネント コンポーネント) {
  69. プレイヤーを下へ移動します。プレイヤーの方向を下向きに設定する
  70. }
  71. });
  72. レイアウトにコンポーネントを追加します。
  73. }

プレイヤークラス

プレーヤー クラスの属性は、方向、プレーヤーの x 座標、y 座標、プレーヤーのスコアです。プレイヤー クラスのメソッドは、player_run()、left()、right()、up()、down() などです。player_run() は、プレイヤー インスタンスの方向に基づいて、プレイヤー インスタンスの次のステップが壁であるかどうかを判断します。そうでない場合は、次のステップに進みます。方向キーが押されたときに方向を変更するには、left() などのメソッドを使用します。

以下にいくつかのキーコードのみを掲載します。

  1. パブリッククラス Player
  2. {
  3. プライベートint方向;
  4. 公共 プレイヤーxの整数値
  5. 公共 プレイヤーyの整数値
  6. プライベート静的最終int food=0;
  7. プライベート静的最終int wall = 1;
  8. プライベート静的最終intプレーヤー=2;
  9. プライベート静的最終intモンスター=3;
  10. プライベート静的最終int空=4;
  11. プライベート静的最終int  = 1;
  12. プライベート静的最終int up=2;
  13. プライベート静的最終int  =3;
  14. プライベート静的最終int down=4;
  15.  
  16. パブリックプレイヤー( int方向、 intプレイヤー_x、 intプレイヤー_y)
  17. {
  18. this.direction=方向;
  19. プレイヤーxをプレイヤxとします。
  20. プレイヤーyを引数に取ります。
  21. }
  22.  
  23. 公共  int [][] player_run( int [][] グリッド) {
  24. int [][]grid=グリッド;
  25. if (方向 ==) {
  26. (グリッド[プレイヤー_y][プレイヤー_x - 1] != 壁)
  27. {
  28. グリッド[player_y][player_x - 1] = プレイヤー;
  29. グリッド[player_y][player_x] = 空;
  30. プレイヤー_x --;  
  31. }
  32. }それ以外の場合 (方向 == 上)
  33. {
  34. (グリッド[プレイヤー_y - 1][プレイヤー_x] != 壁)
  35. {
  36. グリッド[player_y - 1][player_x] = 食べ物;
  37. グリッド[player_y][player_x] = 空;
  38. プレイヤー_y --;  
  39. }
  40. }
  41. そうでない場合 (方向 ==)
  42. {
  43. (グリッド[プレイヤー_y][プレイヤー_x + 1] != 壁)
  44. {
  45. グリッド[player_y][player_x + 1] = プレイヤー;
  46. グリッド[player_y][player_x] = 空;
  47. プレイヤー_x++;
  48. }
  49. }それ以外の場合 (方向 == 下) {
  50.  
  51. (グリッド[player_y + 1][player_x] != 壁)の場合
  52. {
  53. グリッド[player_y + 1][player_x] = プレイヤー;
  54. グリッド[player_y][player_x] = 空;
  55. プレイヤー_y++;
  56. }
  57. }
  58. グリッドを返します
  59. }
  60.  
  61. パブリックvoid()
  62. {
  63. this.direction =;
  64. }
  65. パブリックvoid up()
  66. {
  67. this.direction=上;
  68. }
  69. 公共の無効()
  70. {
  71. this.direction =;
  72. }
  73.  
  74. パブリックvoidダウン()
  75. {
  76. this.direction=下;
  77. }
  78. }

モンスター

モンスタークラスは主にプレイヤーを追跡することを実現します。ここで使用される方法は、モンスターの現在の座標と追いつくプレイヤーの現在の座標の差を計算することです。追跡はこのゲームで最も複雑な部分です。注意すべき点は次のとおりです:

1. 2体のモンスターが重ならないようにする必要がある

2. モンスターは食べ物があるグリッドを通過すると食べ物を食べることができないため、現在のグリッドが食べ物グリッドであるかどうかを判断する必要があります。この目的のために、ブールフラグ isfood が設定されます。

Monster クラスのプロパティとコンストラクターは次のとおりです。

  1. パブリッククラスモンスター{
  2. プライベートintモンスター_x;
  3. プライベートintモンスター_y;
  4. private int x_difference;//列の差
  5. private int y_difference;//行の差
  6. プライベートint x_distance; //行の距離
  7. プライベートint y_distance; //列の距離
  8. プライベート最終int壁 = 1;
  9. プライベート最終intプレイヤー = 2;
  10. プライベート最終intモンスター = 3;
  11. プライベート最終int food = 0;
  12. プライベート最終int空=4;
  13. プライベートブール値 isfood = true ;
  14.  
  15.  
  16. パブリックモンスター( intモンスターx、 intモンスターy) {
  17. モンスターxをコピーします。
  18. モンスターを生成
  19. }

追跡方法: 現在のモンスターとプレイヤー間の x 方向と y 方向の距離を計算し、距離が大きい方向を選択して最初に追跡します。モンスターがこの方向に移動できない場合(両側に壁がある場合)は、反対方向への追跡に切り替えます。

  1. 公共  int [][] chase1( int [][] grids, Player player) {
  2. int [][] グリッド = グリッド;
  3. this.x_difference = モンスターx - player.getPlayer_x();
  4. this.y_difference = monster_y - player.getPlayer_y();
  5. this.x_distance = 数学。絶対値(x_差);
  6. this.y_distance = 数学。 abs (y_difference);
  7. //現在の位置が食べ物かどうかを判断します。食べ物の場合は、まず現在の位置を食べ物に設定してから次のステップに進みます
  8. //食べ物でない場合は、現在の位置を空に設定して次のステップに進みます
  9. if(isfood == true )
  10. グリッド[モンスター_y][モンスター_x]=食べ物;
  11. そうでない場合 (isfood == false )
  12. グリッド[モンスター_y][モンスター_x]=空;
  13. if (y_distance < x_distance) //y方向の距離が小さい場合は、まずx方向から近づきます
  14. {
  15. if (x_difference > 0 && grid[monster_y][monster_x - 1] != 壁 && grid[monster_y][monster_x - 1] != モンスター)
  16. {
  17. モンスターx --;  
  18. if(grid[monster_y][monster_x]==food) //次のステップが食べ物の場合、フラグを設定する
  19. {
  20. isfood = true ;
  21. グリッド[モンスター_y][モンスター_x] = モンスター;
  22.  
  23. }
  24. そうでない場合(グリッド[モンスター_y][モンスター_x]==4)
  25. {
  26. isfood = false ;
  27. グリッド[モンスター_y][モンスター_x] = モンスター;
  28. }
  29. }
  30. そうでない場合、(x_difference < 0 && grid[monster_y][monster_x +1] != 壁 && grid[monster_y][monster_x - 1] != モンスター) {
  31. モンスター_x++;
  32. if(grid[monster_y][monster_x]==0)//次のグリッドに食べ物があるかどうかを判断します。そうであれば、フラグをtrueに設定しますそれ以外の場合はfalseに設定する 
  33. {
  34. isfood = true ;
  35. グリッド[モンスター_y][モンスター_x] = モンスター;
  36. }
  37. そうでない場合(グリッド[モンスター_y][モンスター_x]==4)
  38. {
  39. isfood = false ;
  40. グリッド[モンスター_y][モンスター_x] = モンスター;
  41. }
  42. }
  43. else // x方向がブロックされている場合は、代わりにy方向に追跡します
  44. {
  45. if (y_difference > 0 && grid[monster_y - 1][monster_x] != 壁 && grid[monster_y - 1][monster_x] != モンスター) {
  46. モンスター_y --;  
  47. グリッド[モンスター_y][モンスター_x]==0の場合
  48. {
  49. isfood = true ;
  50. グリッド[モンスター_y][モンスター_x] = モンスター;
  51. }
  52. そうでない場合(グリッド[モンスター_y][モンスター_x]==4)
  53. {
  54. isfood = false ;
  55. グリッド[モンスター_y][モンスター_x] = モンスター;
  56. }
  57. }
  58. そうでない場合 (y_difference < 0 && grid[monster_y + 1][monster_x] != wall && grid[monster_y + 1][monster_x] != monster)
  59. {
  60. モンスター_y++;
  61. グリッド[モンスター_y][モンスター_x]==0の場合
  62. {
  63. isfood = true ;
  64. グリッド[モンスター_y][モンスター_x] = モンスター;
  65. }
  66. そうでない場合(グリッド[モンスター_y][モンスター_x]==4)
  67. {
  68. isfood = false ;
  69. グリッド[モンスター_y][モンスター_x] = モンスター;
  70. }
  71. }
  72. }
  73. }
  74. else // x 方向の距離が小さいか 2 つが等しい場合は、まず y 方向に追いつきます
  75. {
  76. if (y_difference > 0 && grid[monster_y - 1][monster_x] != 壁 && grid[monster_y - 1][monster_x] != モンスター) {
  77. モンスター_y --;  
  78. グリッド[モンスター_y][モンスター_x]==0の場合
  79. {
  80. isfood = true ;
  81. グリッド[モンスター_y][モンスター_x] = モンスター;
  82. }
  83. そうでない場合(グリッド[モンスター_y][モンスター_x]==4)
  84. {
  85. isfood = false ;
  86. グリッド[モンスター_y][モンスター_x] = モンスター;
  87. }
  88. }
  89. そうでない場合 (y_difference < 0 && grid[monster_y + 1][monster_x] != wall && grid[monster_y + 1][monster_x] != monster) {
  90. モンスター_y++;
  91. グリッド[モンスター_y][モンスター_x]==0の場合
  92. {
  93. isfood = true ;
  94. グリッド[モンスター_y][モンスター_x] = モンスター;
  95. }
  96. そうでない場合(グリッド[モンスター_y][モンスター_x]==4)
  97. {
  98. isfood = false ;
  99. グリッド[モンスター_y][モンスター_x] = モンスター;
  100. }
  101. }
  102.  
  103. それ以外 
  104. {
  105. if (x_difference > 0 && grid[monster_y][x_distance - 1] != 壁 && grid[monster_y][monster_x-1] != モンスター) {
  106. モンスターx --;  
  107. グリッド[モンスター_y][モンスター_x]==0の場合
  108. {
  109. isfood = true ;
  110. グリッド[モンスター_y][モンスター_x] = モンスター;
  111. }
  112. そうでない場合(グリッド[モンスター_y][モンスター_x]==4)
  113. {
  114. isfood = false ;
  115. グリッド[モンスター_y][モンスター_x] = モンスター;
  116. }
  117. }
  118. そうでない場合 (x_difference < 0 && grid[monster_y][monster_x + 1] != 壁 && grid[monster_y][monster_x+1] != モンスター)
  119. {
  120. モンスター_x++;
  121. グリッド[モンスター_y][モンスター_x]==0の場合
  122. {
  123. isfood = true ;
  124. グリッド[モンスター_y][モンスター_x] = モンスター;
  125. }
  126. そうでない場合(グリッド[モンスター_y][モンスター_x]==4)
  127. {
  128. isfood = false ;
  129. グリッド[モンスター_y][モンスター_x] = モンスター;
  130. }
  131. }
  132. }
  133. }
  134. グリッドを返します
  135. }

ゲームオーバーまたはゲーム成功を引きます

まず、ゲームが終了したか成功したかを判断する必要があります。モンスターに捕まることをゲームの終了と定義します。つまり、プレイヤーの x 座標と y 座標が同時にモンスターの 1 つと同じになったときです。ボード上の豆がすべて食べられたらゲーム成功とみなされます。ゲームの終了とゲームの成功を判断するコードは次のとおりです。

  1. プライベートブール型 gamesucess()
  2. {
  3. ( int row = 0; row < height; row++)の場合
  4. {
  5. int  = 0;< 幅;++)
  6. {
  7. if (グリッド[行][] == 0)
  8. 戻る 間違い;
  9. }
  10. }
  11. 戻る 真実;
  12. }
  13.  
  14. private boolean game_over(プレイヤー player,モンスター monster1,モンスター monster2)
  15. {
  16. if((player.getPlayer_x()==monster1.getMonster_x() && player.getPlayer_y()==monster1.getMonster_y()) || (player.getPlayer_x()==monster2.getMonster_x() && player.getPlayer_y()==monster2.getMonster_y()))
  17. 戻る 真実;
  18. それ以外 
  19. 戻る 間違い;
  20. }

ゲーム結果を描画するためのコードは次のとおりです。

  1. プライベート void drawGame_over()
  2. {
  3. タイマーをキャンセルします。
  4. テキスト text=new Text(this);
  5. text.setText( "ゲームオーバー" );
  6. テキストのサイズを100に設定します。
  7. テキストの色を BLUE に設定します。
  8. text.setTextAlignment(TextAlignment.CENTER);
  9. テキストの上下マージンを設定します(-1800,0);
  10. テキストの余白を350,0に設定します。
  11. レイアウトにコンポーネントを追加します。
  12. UIContent を設定します。
  13. }
  14.  
  15. プライベート void drawGamesuccess()
  16. {
  17. タイマーをキャンセルします。
  18. テキスト text=new Text(this);
  19. text.setText( "ゲーム成功" );
  20. テキストのサイズを100に設定します。
  21. テキストの色を BLUE に設定します。
  22. text.setTextAlignment(TextAlignment.CENTER);
  23. テキストの上下マージンを設定します(-1800,0);
  24. テキストの余白を350,0に設定します。
  25. レイアウトにコンポーネントを追加します。
  26. UIContent を設定します。
  27. }

2人用ゲーム

分散タスクスケジューリングとページジャンプ

2人プレイモードでは、デバイス選択ページを設定しました。デバイス A を選択したパーティはプレイヤー 1 を操作し、もう一方のパーティは自動的にパーティ B として設定され、プレイヤー 2 を操作します。さらに重要なのは、2 つのデバイスのうち 1 つが選択を行うと、両方のデバイスが同時にゲーム ページに自動的にジャンプすることを期待しています。そこで、パラメータ付きの分散タスクスケジューリングを使用して別のマシンのタスク起動を制御し、パラメータ付きの同じページジャンプ(つまり、presentステートメント)を使用してこのマシンのジャンプを実現しました。具体的なコードは次のとおりです。

まず、config.json ファイルで必要な権限を宣言し、それを「module」コード ブロックに追加する必要があります。


次に、MainAbility クラスで機密性の高い権限を明示的に宣言します。

  1. パブリッククラス MainAbility は Ability を拡張します
  2. {
  3. @オーバーライド
  4. パブリックvoid onStart(インテント インテント) {
  5. super.onStart(インテント);
  6. 文字列[] 権限 = {
  7. "ohos.permission.READ_USER_STORAGE" 、 // データベースの読み取り権限
  8. "ohos.permission.WRITE_USER_STORAGE" 、 // データベースの書き込み権限
  9. "ohos.permission.DISTRIBUTED_DATASYNC" //分散データ同期権限
  10. };
  11. ユーザーからの権限を要求します(権限、0);
  12. super.setMainRoute(MainAbilitySlice.class.getName());
  13. }
  14. }

次に、分散スケジューリング方式を使用して別のデバイスをプルアップし、ページジャンプを使用してローカルデバイスのジャンプを完了します。これら両方のジャンプはパラメータ付きのジャンプであり、その目的は、デバイス操作オブジェクトの後続のバインディングを容易にすることです。

分散スケジューリングに関して注意すべき点が 2 つあります。

1. BUNDLE_NAMEとABILITY_NAMEの設定

BUNDLE_NAME は、構成ファイルの app ブロックにあります。


ABILITY_NAME は、モジュール コード ブロックの abilities セクションにあります。


2. 分散スケジューリングでは、Ability クラスのみをスケジュールでき、AbilitySlice クラスはスケジュールできません。 AbilitySlice クラスをスケジュールする場合は、まず Ability クラスをスケジュールし、その中の super.setMainRoute() メソッドを介して AbilitySlice クラスにジャンプすることができます。著者は最初にこのミスを犯し、スケジュール作成に失敗しました。

分散スケジューリングとページ スケジューリングのコードは次のとおりです。

  1. パブリッククラス ChooseAB_Ability は AbilitySlice を拡張します {
  2.  
  3. プライベート静的最終文字列 BUNDLE_NAME = "com.example.test" ;
  4. プライベート静的最終文字列 ABILITY_NAME = "com.example.test.Test" ;
  5.  
  6.  
  7. パブリックvoid onStart(インテント インテント) {
  8. super.onStart(インテント);
  9. UIContent をスーパーに設定します。
  10.  
  11. //接続されたデバイスを取得する
  12. リスト<DeviceInfo> deviceInfoList = DeviceManager.getDeviceList(DeviceInfo.FLAG_GET_ONLINE_DEVICE);
  13. if( deviceInfoList.size ()==0)
  14. 戻る;
  15. 文字列デバイスID=deviceInfoList.get(0).getDeviceId();
  16.  
  17. ボタン buttonA = (Button) findComponentById(ResourceTable.Id_A_btn);
  18. buttonA.setClickedListener(コンポーネント -> {
  19.  
  20. // FAをリモートで起動する
  21. インテント remoteIntent = 新しい Intent();
  22. 操作 operation = new Intent.OperationBuilder().withDeviceId(deviceID)
  23. .withBundleName(バンドル名)
  24. .withAbilityName(アビリティ名)
  25. .withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)
  26. 。建てる();
  27.  
  28. リモートインテントの操作を設定します。
  29. remoteIntent.setParam( "デバイス" , "B" ); //ジャンプパラメータを設定します。 「device」はキー、 「B」は値です
  30. 試す {
  31. // ターゲットデバイスに指定されたFAが含まれているかどうか
  32. リスト<AbilityInfo> abilityInfoList = getBundleManager().queryAbilityByIntent(remoteIntent, 0, 0);
  33. (abilityInfoList != null && !abilityInfoList.isEmpty())の場合 {
  34. リモートインテントを開始します。 //別のデバイスをスケジュールする
  35. }
  36. } キャッチ (RemoteException e) {
  37. //エラーを処理する
  38. }
  39.  
  40. //パラメータによるページジャンプ
  41.  
  42. インテントintent1=新しいインテント();
  43. intent1.setParam( "デバイス" , "A" ); //パラメータを設定する
  44. present(new MultiGameAbility_3(),intent1);ページジャンプ
  45.  
  46. });
  47.  
  48. //別のボタンのコードは基本的に上記と同じですが、渡されるパラメータが異なります
  49. ボタン buttonB = (Button) findComponentById(ResourceTable.Id_B_btn);
  50. buttonB.setClickedListener(新しいComponent.ClickedListener() {
  51. @オーバーライド
  52. パブリックvoid onClick(コンポーネント コンポーネント) {
  53. インテント remoteIntent = 新しい Intent();
  54.  
  55. 操作 operation = new Intent.OperationBuilder().withDeviceId(deviceID)
  56. .withBundleName(バンドル名)
  57. .withAbilityName(アビリティ名)
  58. .withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)
  59. 。建てる();
  60.  
  61. リモートインテントの操作を設定します。
  62. remoteIntent.setParam( "デバイス" , "A" );
  63. 試す {
  64. // ターゲットデバイスに指定されたFAが含まれているかどうか
  65. リスト<AbilityInfo> abilityInfoList = getBundleManager().queryAbilityByIntent(remoteIntent, 0, 0);
  66. (abilityInfoList != null && !abilityInfoList.isEmpty())の場合 {
  67. リモートインテントを開始します。
  68. }
  69. } キャッチ (RemoteException e) {
  70. //エラーを処理する
  71. }
  72.  
  73. インテントintent1=新しいインテント();
  74. intent1.setParam( "デバイス" , "B" );
  75. 新しい MultiGameAbility_3() が存在します。intent1 です。
  76. }
  77.  
  78. });
  79.  
  80. }
  81. }

分散データベースの構築

グローバル変数を次のように定義します。

  1. プライベート静的最終文字列 STORE_ID = "distributed_db" ;
  2. プライベート SingleKvStore シングルKvストア;
  3. プライベート KvManager kvManager;

データベースを構築するプロセスは次のとおりです。

  1. プライベート void initDbManager()
  2. {
  3. kvManager = createManager();
  4. 単一のKvStore=createDb(kvManager);
  5. subscribeDb(シングルKvStore);
  6. }
  7.  
  8. プライベート KvManager createManager()
  9. {
  10. KvManagerConfig config = 新しい KvManagerConfig(this);
  11. KvManager マネージャー = KvManagerFactory.getInstance().createKvManager(config);
  12. リターンマネージャー;
  13. }
  14.  
  15.  
  16. //KvManagerを使用してデータベースを作成する
  17. プライベート SingleKvStore createDb(KvManager kvManager)
  18. {
  19. SingleKvStore kvStore = null ;
  20. 試す{
  21. オプション options=新しいオプション();
  22. options.setCreateIfMissing( true ).setEncrypt( false ).setKvStoreType(KvStoreType.SINGLE_VERSION);
  23. kvStore=kvManager.getKvStore(オプション、STORE_ID);
  24. }
  25. キャッチ (KvStoreException 例外)
  26. {
  27. //エラー処理
  28. }
  29. kvStoreを返します
  30.  
  31. }
  32.  
  33. //データベースの変更を購読する
  34. プライベート void subscribeDb(SingleKvStore singleKvStore)
  35. {
  36. クラス KvStoreObserverClient は KvStoreObserver を実装します
  37. {
  38. パブリックvoid onChange(ChangeNotification 通知)
  39. {
  40. if(queryContact_Int( "player1_d" )!=エラー)
  41. direction1=queryContact_Int( "player1_d" );
  42. if(queryContact_Int( "player2_d" )!=エラー)
  43. direction2=queryContact_Int( "player2_d" );
  44. if(queryContact_String( "配列" )!= null )
  45. 配列=queryContact_String( "配列" );
  46. if(queryContact_Int( "A_Score" )!=エラー)
  47. A_スコア=queryContact_Int( "A_スコア" );
  48. if(queryContact_Int( "A_Score" )!=エラー)
  49. B_スコア=queryContact_Int( "A_スコア" );
  50.  
  51. }
  52. }
  53. KvStoreObserver kvStoreObserverClient = 新しい KvStoreObserverClient();
  54. 単一のKvStore.subscribe(SubscribeType.SUBSCRIBE_TYPE_ALL、kvStoreObserverClient);
  55. }

データベースの追加、削除、変更、クエリ

ドキュメントを読むと、分散データベースの挿入操作と変更操作は同じステートメントであることがわかります。キーワードがデータベースに存在しない場合は、挿入操作が実行されます。キーワードがデータベースに存在する場合は、更新操作が実行されます。これはある程度はよりスマートで便利ですが、2 つの機能を分離した方が明確になると思います。学習の過程で、私たちは次のような間違いを犯しました。先入観を持っていて、クエリが成功すると想定し、プログラムがクラッシュするのです。実際、これは Throws クラスであり、スローされるエラーの 1 つはキーワードが存在しないというエラーであるため、すべての Throws クラスに対して try+catch 形式を厳密に使用することが重要です。

文字列の追加、変更、クエリは次のとおりです。

  1. // 文字列を挿入
  2. プライベート void inputData_String(文字列キー、文字列値)
  3. {
  4.  
  5. if(キー== null ||キー.isEmpty()|| 値 == null || 値 .isEmpty())
  6. ;//キーワードが空の場合、何も実行されません
  7. それ以外 
  8. キーワードが空でなく、キーワードに値がない場合、putString操作が実行されます。
  9. if (queryContact_String(キー) == null )
  10. {
  11. singleKvStore.putString(キー、値 );
  12. }
  13. }
  14.  
  15. }
  16. //文字列を変更する
  17. private void update_String(文字列キー、文字列値)
  18. {
  19. if(キー== null || key .isEmpty()|| 値 == null || value.isEmpty()|| queryContact_String(キー)== null )
  20. ; //キーワードが空の場合、何も実行されません
  21. それ以外 
  22. singleKvStore.putString(キー、値 );
  23. }
  24. //文字列クエリ
  25. プライベート文字列 queryContact_String(文字列キー)
  26. {
  27. 試す {
  28. singleKvStore.getString(キー)を返します
  29. }
  30. キャッチ (KvStoreException 例外)
  31. {
  32. 戻る ヌル; //何か問題が発生した場合、以降の操作ではnullを返します
  33. }
  34. }

整数の加算、変更、クエリ

  1. プライベートint queryContact_Int(文字列キー)
  2. {
  3. 試す {
  4. singleKvStore.getInt(キー)を返します
  5. }
  6. キャッチ (KvStoreException 例外)
  7. {
  8. エラーを返します//ここでエラーはプログラムには現れない負の整数(-1)として定義されます
  9. }
  10. }
  11.  
  12. プライベート void update_Int(文字列キー int値)
  13. {
  14.  
  15. if (キー== null ||キー.isEmpty()||queryContact_Int(キー)== error)
  16. ;
  17. それ以外 
  18. singleKvStore.putInt(キー, 値);
  19. }
  20.  
  21. プライベート void inputData_Int(文字列キー int値)
  22. {
  23.  
  24. if(queryContact_Int(キー)==エラー)
  25. {
  26. singleKvStore.putInt(キー, 値);
  27. }
  28. それ以外 
  29. ;
  30.  
  31. }

ネットワークデバイスIDバインディング

ID をバインドするには、データベースを使用してバインドし、デバイス ID と対応する値をデータベースに渡すという方法があります。

(「A」または「B」)。これら 2 つのデバイスは同じプログラムを実行しているため、ミラーリングされていると言えますが、操作がローカルでのみ実行されると「上書き」の状況が発生する可能性があります。そのため、バインド操作を実行するには 3 番目の場所のパブリック領域が必要になり、データベースを選択します。

まず、ローカル デバイス ID と接続されている別のデバイスの ID を取得する必要があります。

  1. プライベート文字列 self; //すべての変数
  2. プライベート文字列その他; //グローバル変数
  3. パブリックvoid getId()
  4. {
  5. KvManagerFactory をインスタンス化します。 //自分のIDを取得する
  6. リスト<DeviceInfo> onlineDevices = DeviceManager.getDeviceList(DeviceInfo.FLAG_GET_ONLINE_DEVICE);
  7. ( onlineDevices.size () == 0)の場合
  8. 戻る;
  9. その他 = onlineDevices.get(0).getDeviceId(); //ネットワーク内の別のデバイスのIDを取得する
  10. }

上記の分散スケジューリングとページジャンプでインテント クラスのパラメータを使用し、分散データベースと連携してデバイスをバインドするコードは次のようになります。

  1. (意図がnullではない場合
  2. //デバイスをバインド
  3. if (intent.getStringParam( "デバイス" ).equals( "A" )) {
  4. 入力データ文字列(自己、 "A" );
  5. 入力データ文字列(その他、 「B」 );
  6. }そうでない場合 (intent.getStringParam( "device" ).equals( "B" )) {
  7. 入力データ文字列(自己、 "B" );
  8. inputData_String(その他、 「A」 );
  9. }
  10. }

この時点でID同期は完了です。

ユーザーインターフェース

シングルプレイヤーモードと比較して、2プレイヤーモードのボタンは操作オブジェクトにバインドされ、ボタンのコールバックメソッドは次のように変更されます(ボタンの1つを例に挙げます)。

  1. button_left.setClickedListener(新しいComponent.ClickedListener() {
  2. @オーバーライド
  3. パブリックvoid onClick(コンポーネント コンポーネント) {
  4. if(queryContact_String(self).equals( "A" )) {
  5. プレイヤー1.左();
  6. update_Int( "player1_d" 、player1.getDirection());
  7. }
  8. そうでない場合、(queryContact_String(self).equals( "B" ))
  9. {
  10. プレイヤー2.左();
  11. update_Int( "player2_d" 、player2.getDirection());
  12. }
  13. }
  14. });

ゲームを同期する方法

ゲームを同期する方法は多数ありますが、基本的には何らかの値をデータベースに渡すことを意味します。考えられる方法は3つあります。

1. 最も直接的なアイデアは、バイト型の配列を渡すことです。渡すことができるのは 1 次元配列のみなので、2 次元配列の行の添え字をキーワードとして使用し、列を 1 次元配列として渡すことができます。しかし、試してみたところ、配列を渡すとプログラムがクラッシュする可能性があることがわかりました。データの読み書きには時間がかかるため、ミニゲームの高いリフレッシュ レートではデバイスが配列転送を完了することが難しいと推測されます。

2. まず 2 次元配列を文字列に変換し、その文字列を分散データベースに転送します。配列と文字列間の変換はローカルで実行できるため、非常に高速になります。この方法では、デバイスはデータベースの読み取りと書き込みを 1 回だけ実行して完了します。

3. プレイヤーの座標と方向を渡します。この方法は、文字列と配列間の変換を必要とせず、データベースの読み取り量も少ないため、デバイスで完了できます。

すると、2 番目の実装コードのみが以下に公開され、3 番目の実装コードは後続の完全なコードで公開されます。

配列と文字列を変換する方法は次のとおりです。

  1. プライベート文字列Array_to_String( int grids[][])
  2. {
  3. StringBuilder ビルダー = 新しい StringBuilder();
  4. ( int i=0;i<15;i++)の場合
  5. {
  6. ( int j=0;j<15;j++)の場合
  7. {
  8. ビルダー.append(グリッド[i][j]);
  9. }
  10. }
  11. builder.toString()を返します
  12. }
  13.  
  14. プライベートint [][] String_to_Array(文字列値)
  15. {
  16. int [][] grid=グリッド;
  17. ( int i=0;i<15;i++)の場合
  18. {
  19. ( int j=0;j<15;j++)の場合
  20. {
  21. グリッド[i][j] =整数.parseInt(値.部分文字列(i*15+j,i*15+j+1));
  22. }
  23. }
  24. グリッドを返します
  25. }

プログラムの初期化: 起動時にデータベースにいくつかの初期値を書き込む

  1. パブリックボイド初期化() {
  2. レイアウト = 新しい DirectionalLayout(this);
  3. inputData_Int( "player1_d" 、方向1); //2人のプレイヤーの初期方向をデータベースにアップロードする
  4. inputData_Int( "player2_d" 、方向2);
  5.  
  6. inputData_Int( "A_スコア" ,0);
  7. inputData_Int( "B_スコア" ,0);
  8.  
  9. 配列=Array_to_String(グリッド); //配列を文字列に変換する
  10. inputData_String( "array" ,array);//文字列をデータベースに書き込みます*/
  11.  
  12. 描画ボタン();
  13. 描画テーブル();
  14. }

視覚的な同期

分散データベース上のデータを視覚的に同期したい場合は、より高速にコールバックする時間オブジェクトを作成し、描画メソッドを継続的に呼び出して視覚的な同期を実現する必要があります。同時に、スタンドアロン バージョンには player_run() をコールバックするための時間オブジェクトもあります。

  1.  パブリックvoid 実行()
  2. {
  3. timer1 = 新しいタイマー();
  4.  
  5. timer1.schedule(new TimerTask() {
  6. @オーバーライド
  7. パブリックボイド実行(){
  8. getUITaskDispatcher().asyncDispatch(()->
  9. {
  10. if(!(game_over(player1,monster1,monster2)||game_over(player2,monster1,monster2)||gamesucess()))
  11. {
  12. if(queryContact_String( "配列" )!= null )
  13. {
  14. player1.setDirection(queryContact_Int( "player1_d" )); player2.setDirection(queryContact_Int( "player2_d" ));
  15. 配列 = queryContact_String( "配列" );
  16. グリッド = String_to_Array(配列);
  17. player1.setScore(queryContact_Int( "A_Score" ));
  18. player2.setScore(queryContact_Int( "B_Score" ));
  19.  
  20.  
  21. }
  22. 描画テーブルテスト();
  23. }
  24. それ以外 
  25. ;
  26. });
  27. }
  28. },0,70);
  29.  
  30. timer2 = 新しいタイマー();
  31. timer2.schedule(新しいTimerTask() {
  32. @オーバーライド
  33. パブリックvoid 実行()
  34. {
  35. getUITaskDispatcher().asyncDispatch(()->{
  36. {
  37. ゲームオーバーの場合(プレイヤー1、モンスター1、モンスター2)||ゲームオーバーの場合(プレイヤー2、モンスター1、モンスター2))
  38. {
  39. ゲームオーバーを描画します。
  40. }
  41. そうでない場合(ゲーム成功())
  42. {
  43. ゲームの成功を描画します。
  44. }
  45. それ以外 
  46. {
  47. player1.setDirection(queryContact_Int( "player1_d" ));
  48. player2.setDirection(queryContact_Int( "player2_d" ));
  49. if(queryContact_String( "配列" )!= null )
  50. {
  51. 配列 = queryContact_String( "配列" );
  52. グリッド = String_to_Array(配列);
  53. }
  54. グリッド = player1.player_run_test(グリッド);
  55. グリッド=player2.player_run_test(グリッド);
  56. 配列=Array_to_String(グリッド);
  57. update_String( "配列" ,配列);
  58. }
  59. }
  60. });
  61. }
  62. },0,2000);
  63. }

まとめ

この学習ノートは、ゲーム「Eating Beans」の実装と、UI 設定、ページ ジャンプ、分散データベース、分散タスク スケジューリングなど、最近学習した内容を記録したものです。私の最初の実践的なプロジェクトであるため、このプロジェクトには多少の小さな問題と未解決の問題があります。例えば、2人プレイモードではモンスターが存在すると「モンスタークローン問題」が頻繁に発生するため、モンスター設定は解除されます。解決しようとしましたが、うまくいきませんでした。これはデータベースの読み取りと書き込みに関連していると思います。完全なコードは、いくつかの変更を加えた後に後でリリースされる予定です。その時までに、皆さんがその中の誤りを指摘してくれることを願っています。もちろん、私はこのプロジェクトで未解決の問題を解決するために努力し続けます。

詳細については、以下をご覧ください。

51CTOとHuaweiが共同で構築したHongmengテクノロジーコミュニティ

https://harmonyos..com

<<:  保険業界における Kafka の応用事例

>>:  エッジコンピューティング、エッジネットワーキング、エッジデータ管理がどのように連携するか

推薦する

Kafkaがメッセージを失わないようにするにはどうしたらよいかと質問されるたびに、私は泣きそうになります。

1. 背景の紹介この記事では、オンライン本番環境でメッセージ ミドルウェア テクノロジを使用する際...

データ分析: Baidu は K-station を通じてユーザー エクスペリエンスを向上できるか?

Baidu は、ユーザー エクスペリエンスを向上させるためだと主張して、大規模なサイト禁止を実施して...

Kubernetesはまだ歴史が浅く、ローカルでの導入がパブリッククラウドでの導入を上回っている

最近、VMware は Kubernetes に関する調査を実施し、5 年間の開発歴を持つ Kube...

ウェブサイト運営を理解するための4つの視点

ウェブサイト運営者は、プラットフォーム上の毎日のUVとIPに主に責任があり、ウェブサイトプラットフォ...

Rancher がエッジ コンピューティング エコシステムをリードする Kubernetes オペレーティング システム、k3OS をリリース

業界トップのコンテナソフトウェアプロバイダーであるRancher Labs(以下、Rancher)は...

ドメインとサイトのスナップショット間の不一致の詳細な分析と処理

前回、Baiduがアルゴリズムを大幅に変更して以来、Baiduのアルゴリズムはますます高度化し、手動...

予算があればチャンネルプロモーションをうまくできると思いますか?必要なのは以下の4点です

プロモーターは皆、予算が決して十分ではないと感じているはずです。お金を賢く使いたいなら、自分自身と敵...

金群宝:核分裂成長の究極の秘密、あなたが知らない交通の背後にあるロジックを明らかにする

月収10万元の起業の夢を実現するミニプログラム起業支援プランWeChatインターネットはトラフィック...

cloudcone: 月額 69 ドル、米国の高防御サーバー、e3-1270v2/32g メモリ/512gSSD/100M 無制限、1Tbps 高防御の場合は +2 ドル

Cloudcone は、ロサンゼルス MC データセンターの専用サーバーを特別価格で提供しています。...

蘇寧によるレッドベイビーの買収は、中小規模の垂直型電子商取引企業の課題を解決する方法のサンプルである。

蘇寧のような業界大手に買収されたことは、すでにRedbabyにとって最高の行き先だ。同社は、中国で問...

企業のウェブサイトの最適化には、「非公式」CMSは避けてください

ウェブサイトの最適化は、一方では検索エンジンに対する最適化であり、他方ではウェブサイトのユーザーに対...

百度の新アルゴリズム開始 - ハイパーリンク不正サイトを厳しく取り締まる

百度は現在「大乱」期にあります。ウェブサイトのランキングは極めて不安定です。基本的に、1時間ごとにラ...

racknerd: 安価な米国クラスター サーバー、32C セグメント (511 IP)、月額 130 ドルから、Alipay/PayPal 決済

Racknerd は VPS 業界で非常に人気があるだけでなく、同社が提供する米国のクラスター サー...

外部リンク構築戦略の5つの側面

外部リンク構築の戦略原則は比較的マクロです。いわゆる戦略的方向性とは、外部リンク構築のプロセスにおい...