🏰

メルクストーリアのギルド機能を大改修しました

2021/12/16に公開

こんにちは、メルクストーリア(以下、メルスト)チームのエンジニアのY.Kです。
2021年にメルストでは、ギルド機能の大改修を行いました。
リリースから7年経った今、ギルド機能を改修するにあたり気を付けた点やこだわった点などを紹介できればと思います。

改修内容について

まずは、改修前後の画面を見ていただければと思います。

旧画面

新画面

2画面に分割しました。
【ギルドマップ画面】

【ギルドバトル画面】

画面を新旧で見比べると、がらりと変わったことが伝わると思います。

旧画面は、ギルド情報やギルド設定を行う機能(画面右側)とギルドバトルを行う機能(画面左側)が1つの画面で実装されていました。
メルストのメインコンテンツの1つであるギルドバトルがかなり小さな範囲だけで行われており、操作性や視認性が低いというお声もいただいていたので、この2つの機能を別画面に分割して利便性を高めるため改修を行いました。

新画面では、ギルドの繋がりを感じてもらえるように、ギルド機能を拡張してギルドマップを作成しました。
ギルドマップには、ギルドマップ及びギルドバトルに参加しているメンバーのユニットが表示されるようになっています。ギルドマップに表示されているメンバー同士はログ(チャット機能)かスタンプでコミュニケーションを取ることができます。
ギルドイベントの前後では、ギルドマップを使って交流を楽しんでもらっているユーザー様の様子を見ることもでき大変うれしく思っています。

旧画面の左側半分で実施していたギルドバトルは、ギルド機能から切り出して1画面全てを使うように変更しました。かなり大掛かりな改修でしたが、操作性や表現を向上させることができたのではないかと思っています。
ギルドバトルのモチベーションを上げることに繋がっていると嬉しいです。

また、この改修の中で、従来のサーバーとは別にリアルタイム通信用のサーバーを構築しギルドメンバーとラグの少ないスムーズなコミュニケーションが取れるように対応を行いました。
アドベントカレンダーの記事の中で、リアルタイムサーバーに関する記事もありますので、詳しくはそちらを御覧ください。
[長谷川さんと岸本さんの記事へのリンクを貼りたい]
[岸本さんは翌日なのでどうしましょうか?]

ギルドマップ

それぞれの画面で実装時に気を付けた点を紹介します。
まずは、ギルドマップから紹介します。

  • 小物の配置

背景は一枚絵なのですが、その上に建物を置き、次に花や木などといったマップ上の小物をそれぞれ1つ1つ座標を指定してオブジェクトを配置しました。
これはルール決めの問題なのですが、画面の下に行くほど手前・上に行くほど奥になるようにオブジェクトの前後関係を指定してあります。

https://youtu.be/PgLOybUscf8

これによりマップ上を歩くユニットが自然にオブジェクトを回り込むようになりました。
■ 中央のギルドメニューの後ろ

https://youtu.be/ARMhj4LWJYA

■ 手前のオブジェクトの間

https://youtu.be/Bt5vAssfm9E

  • 背景の表現

マップを左右に移動した際に、背景の空(雲)や山が遠くのものをゆっくり、近くのものは速く動くように対応しました。
電車とかに乗っている時の外の景色の見え方のあれですね。
※ 山に注目して見てください。少しだけ地面とずれて動くようになっています。

https://youtu.be/oBJsPXIjZA0

  • ユニットの移動処理

ユニットの移動処理は NavMesh を使って実装しようと思ったのですが、メルストの実装との相性が良くなかったため NavMesh の利用は断念し移動処理は自前で実装しました。
マップ上に細かい仮想的なマスを敷き、そのマスを利用して経路探索を行うことで歩行可能エリアの中で最短のルートを移動するように実装しました。

  • ズーム処理

メルストで初めてピンチ操作を導入しました。
拡大して見たい物に向けて自然に拡大・縮小されるように、2つの指で挟んだ中心点に目掛けて拡大・縮小されるようにこだわって対応しました。
(波紋のようなエフェクトがタップした場所で、その2点の間に向けて拡大しているのが分かると思います。)

https://youtu.be/jdfjxgDJZUU
https://youtu.be/vwHUKQEefrM

ソースコード

async UniTask PinchZoom()
{
  float beganZoomRate = 0.0f;
  float beganDist = 0.0f;
  var tapCenterPos = Vector3.zero;
  var localPosition = Vector3.zero;
  var beganMapPosition = Vector3.zero;

  // 2点タッチ中はズーム処理継続
  while (Input.touchCount >= 2) {
    // タッチしている2点を取得
    var touch1 = Input.GetTouch(0);
    var touch2 = Input.GetTouch(1);

    //2点タッチ開始時の距離を記憶
    if (touch2.phase == TouchPhase.Began) {
      beganZoomRate = state.zoomRate;
      beganDist = Vector2.Distance(touch1.position, touch2.position);
      tapCenterPos = Vector3.Lerp(touch1.position, touch2.position, 0.5f);
      var worldPosition = AppCamera.instance.camera.ScreenToWorldPoint(tapCenterPos);
      localPosition = -draggablePanel.transform.InverseTransformPoint(worldPosition);
      beganMapPosition = draggablePanel.transform.localPosition;
    }

    if (touch1.phase == TouchPhase.Moved && touch2.phase == TouchPhase.Moved) {
      // タッチ位置の移動後、長さを再測し、開始時の距離からの相対値を取る。
      float newDist = Vector2.Distance(touch1.position, touch2.position);
      float variableRate = Mathf.Min(Mathf.Max((newDist - beganDist) * state.constantData.pinchZoomRate, -0.5f), 0.5f);

      // 開始時の指の幅と比較して拡大率を変更する
      state.SetZoomRate(beganZoomRate + variableRate);
      UpdateMapView();

      var centerPosition = beganMapPosition;
      float diffZoomRate = state.zoomRate - beganZoomRate;
      if (diffZoomRate < 0.0f) {
        // 拡大
        centerPosition = Vector3.Lerp(beganMapPosition, Vector3.zero, Math.Abs(diffZoomRate) * 2);
      } else if (diffZoomRate > 0.0f) {
        // 縮小
        centerPosition = Vector3.Lerp(beganMapPosition, localPosition, Math.Abs(diffZoomRate) * 2);
      }

      // 中心点を計算してなるべく自然に拡縮するように
      if (tapCenterPos != Vector3.zero) {
        draggablePanel.transform.localPosition = CalculateMapPositionForPinch(centerPosition);
        UpdatePanelClipRange();
      }
    }
    await UniTask.Yield();
  }
}

※ ピンチ処理の動作確認をするためには、端末に書き出す必要があるので、細かい調整に結構時間が掛かりました。
他にもちょっとした遊びも仕組んであったりするので、ぜひ探して見てください。

ギルドバトル

次はギルドバトル画面です。
まず絶対条件として、既存のギルドバトルの操作性を悪化させることがないようにと言うことを念頭に置いて開発を行いました。操作性に変更を加える場合は、運営チーム内で慎重に協議を重ねた上で実装しました。
当初画面半分で実装されていた機能を1画面全てを使って実装することになったので、ゆとりを持ったUIの配置ができるようになるだろうと思っていたのですが、そんなことはなくUIを配置する余裕が全然ありませんでした。
※ タブレット端末の場合、左右が狭くなるのでかなり圧迫感が増します。

  • UI配置
    異なる画面サイズのスマートフォンやタブレットに対応できるUI配置にするためにはどうすれば良いか、デザイナーと連携を取りながら細かく調整を行いました。
    特効薬などはなく、ご操作が起こりにくくなるように泥臭く調整を重ねて対応しました。

  • ログ表示

    右サイドにログ表示エリアを設け、ログページを開かずにギルドメンバーとコミュニケーションを取ることができるようにしました。
    リアルタイムサーバーを導入したことによってコメントのやり取りをリアルタイムで行うことができるようになったこともあり、ギルドバトルで連携を取りやすくなったと思います。

  • ユニットの攻撃アニメーション表現
    背景との融和性を意識して他ギルドへのユニットの攻撃アニメーションを調整しました。
    攻撃するユニットがギルド間の橋から落ちないようにギルドの配置や移動する導線を調整したのですが、メルストには2000体ほどのユニットがいるため、多くのユニットが自然に見えるように調整するのはかなり大変でした。
    テンションが上がる格好いい背景をイラストレーターに描いてもらったので、違和感がないように気を付けて調整しました。

https://youtu.be/YM8dvQDcOp0

  • 長押し機能の実装
    技術的には大したことではないのですが、ボタン長押しによる連続祈りと連続応援の機能を実装しました。
    ギルドバトルで祈りや応援のボタンを連打する機会が多いためユーザー負荷軽減を狙って実装しました。

まとめ

ユーザーの皆様に支えられてメルストは2月でリリースから8周年を迎えることができます。
長く続いているタイトルですが、守りに入るのではなく、新しい表現にも挑戦してユーザーの皆様に喜びや驚きを届けられるように頑張っています。
ギルド機能の改修についても、慣れ親しんだ画面や機能を改修するのは、一定の不安がありましたが、残した方が良い箇所と改善した方が良い箇所を見極めた上で、新画面ならではの付加価値を追加し良い改修ができたのではないかなと感じています。

スマートフォンの端末性能も上がってきており、リリース時より表現の幅が広がっていることもあり、メルストでは古臭さを撤廃しようという取り組みを継続して行っております。
今後も新しい体験を届けられるように頑張っていきます!

GitHubで編集を提案
Happy Elements

Discussion