対戦カードゲーム開発ブログ 【2019年秋リリース(ios,android)】

【UniRx】瀕死状態のユニティちゃんをUniRxでリファクタリングしてみる

こんにちは、たかせです。カードゲーム作ってます。

twitter.com

前回の記事では、驚異的な速度で歩くユニティちゃんを使ってTaskを0から説明してみました。

今回はこのユニティちゃんに機能追加を行いつつ、UniRxについて紹介したいと思います。

UniRxはUnityにおけるリアクティブプログラミングをサポートしてくれるライブラリです。UniRxに関する説明は素晴らしい記事がすでにたくさんあるので、そちらを参考にしてください。記事の最後にいくつかリンクを貼っておきます。

今回はUniRxについての0からの説明はせず、UniRxを使う前と後でどう変わるのか?を見てもらいたいと思っています。

利用例は引き続きユニティちゃん(スクリプト)です。ユニティちゃんには申し訳ないですが、説明のため一度見るも無残な姿(スクリプト)になってもらい、UniRxによる改造を経て新生ユニティちゃん(スクリプト)となってもらう予定です。

 

 

※UniRxの利用には次の2つが必要です。

  1. UniRx Assetのインポート
  2. Unity 2018.3以上、もしくは Unity 2018.1~2 + Incremental Compiler

目次

  • 前回までのユニティちゃん
  • 急な修正依頼に雑な設計で対応してみる
  • リファクタリングの方針について
  • RxRxしていく前に、UniRxについて少しだけ
  • UniRxでユニティちゃんを新生せてみる

前回までのユニティちゃん

1フレームごとに一歩進むユニティちゃんを作成しました。また、Taskを使って、100歩ごとに進捗を報告するようにしました。

using UniRx.Async;
using UnityEngine;
public class ユニティちゃんだよ : MonoBehaviour {
  private int 歩数;

  private async void Update() {
    一歩進む();
    歩数++;
    
    if (歩数 % 100 == 0) {
      var result = await 歩数を報告する();
      //resultがtrueなら報告成功
    }
  }

  private UniTask<bool> 歩数を報告する() {
    var task = UniTask.Run(arg => {
      var 報告用歩数 = (int) arg;
      return 歩数をサーバに送信して成功したらtrueをもらう(報告用歩数);
    }, 歩数);

    return task;
  }
  ...

(UniTaskに移行していたり引数を渡していたり、前回とは比べるとしれっと変わっている部分がありますがお許しを🙇‍♂️)

UniTaskについて気になる方は、この辺りの記事がおすすめです。

neue cc - UniTask - Unity + async/awaitの完全でハイパフォーマンスな統合

UniRx.Async(UniTask)機能紹介 - Qiita

次からはUniRxのお話です。下準備として、各種方面から飛んでくる修正依頼にくそ修正で対応し、ユニティちゃんを瀕死に追い込みます。

急な修正依頼に雑な設計で対応してみる

一人目のPMさん「サボってるのかバグってるのか分からないからさ、100歩ごとじゃなくて10秒ごとに報告するよう修正してよ」

僕「ちょっと雑だけどこんな感じでいいかな?」

Carbon

二人目のPMさん「こっちから指示したら走れるようにしといて。そんなの必要ないだろうって? すまんな上からの要望なんだ」

僕「もっと早く言ってよ...。通常の5倍歩けば走ったことになるしょ」

Carbon

サーバエンジニアさん「ユーザ行動を把握したいのでFirebaseにも報告よろしく。いやぁサーバから送るののは大変でさ。あ、こっちには"前回の報告から何歩歩いたか"でよろしくね」

僕「変数をもう一つ増やして...Taskを連結させて...」

Carbon

外野さん1「ランダムに立ち止まるようにしてみようよ! そんな時間ないって? 前のエンジニアさんは楽しそうに対応してくれたのになぁ...」

外野さん2「ムーンウォークできるようにします!!!これは決定事項です!!!」

外野さん3「まだリリースしないの?」

 

 

僕「」

ユニティちゃん「」

Carbon

リファクタリングの方針について

各種方面からの依頼を請け負い、合計5つの機能追加が追加されました。 もともとあった歩くという機能も含めると、今のユニティちゃんは次の6つの機能があります。

  • 歩く
  • 歩数をサーバに送信する
  • 指示があったら走る
    • 走る = 通常の5倍の速度で歩くこととします
  • 前回の報告から何歩歩いたかをFirebaseに報告する
  • ランダムに立ち止まる
  • ボタンクリックでムーンウォークに切り替える

それでは、改めてユニティちゃんを見てみましょう。

using System;
using UniRx.Async;
using UnityEngine;
using UnityEngine.UI;
using Random = System.Random;
public class 汚れてしまったユニティちゃん : MonoBehaviour {
  private Random _random;
  private int 歩数;
  private int 最後に報告した歩数;
  private DateTime 最後に報告した時刻;
  private bool 走れの指示がある;
  private bool ムーンウォークの指示がある;
  [SerializeField]
  public Button ムーンウォークボタン;

  private void Awake() {
    _random = new Random();
    ムーンウォークボタン.onClick.AddListener(() => {
    ムーンウォークの指示がある = !ムーンウォークの指示がある;
    });
  }

  private async void Update() {
    var 止まれ = _random.Next(2) < 1;
    var 走れ = 走れの指示がある;
    var ムーンウォークせよ = ムーンウォークの指示がある;

    if (止まれ) {
      Debug.Log($"立ち止まっているよ");
    }
    else if (ムーンウォークせよ) {
      一歩戻る();
    }
    else {
      var count = 走れ ? 5 : 1;
      for (var i = 0; i < count; i++) {
        一歩進む();
        歩数++;
      }
    }

    var now = DateTime.Now;
    if ((now - 最後に報告した時刻).Seconds > 10) {
      最後に報告した時刻 = now;
      var previous = 最後に報告した歩数;
      await 歩数をサーバに送信して成功したらtrueをもらう(歩数);
      最後に報告した歩数 = 歩数;
      if (previous != default) {
        await 歩数をFirebaseに送信して成功したらtrueをもらう(歩数 - previous);
      }
    }
  }
  ...

さっきの画像と違うじゃないかって?さすがにひどかったので少しだけリファクタリングしました。

それでもまだ直したいところがあります。例えば、

  private Random _random;
  private int 歩数;
  private int 最後に報告した歩数;
  private DateTime 最後に報告した時刻;
  private bool 走れの指示がある;
  private bool ムーンウォークの指示がある;

というフィールドの多さとか。利用箇所が限定的なフィールドは、可能であればローカル変数に留めたいものです。

他にも、

  最後に報告した時刻 = now;
  var previous = 最後に報告した歩数;
 await 歩数をサーバに送信して成功したらtrueをもらう(歩数);
  最後に報告した歩数 = 歩数;
  if (previous != default) {
    await 歩数をFirebaseに送信して成功したらtrueをもらう(歩数 - previous);
  }

このへんとか。ひとえにややこしい。

後、Updateごとに乱数を生成して止まるかどうか判定するのはいろいろとやばそうなのでどうにかしたい。

さて、具体的な修正方法入る前に、結果だけ先に共有しておきます。

ででん!

 

行数増えちゃいましたね。てへ。

いやでもそこはいいんです。Rx化にあたって避けては通れぬ道です。それよりも見てほしいのは、色の散らばり具合です。同じ目的を持つコードを(だいたい)一箇所を集めることができています。

あちらを立てればコチラが立たずのようなコードは、読むにも消すにも直すにも厄介です。今回のユニティちゃんは、そんな観点からの新生を目指します。

RxRxしていく前に、UniRxについて少しだけ

新生に取り掛かる前に、UniRxを一度も触ったことのない人向けに、少しだけ説明させてください。

UniRxではこんなふうにSubscribeというメソッドを使って変数にアクセスします。

変数という表現はあまり的確ではない(ストリームとか、オブザーバブルとかよく呼ばれます)のですが、「値の入っている箱」という意味では同じという点を伝えたいのであえてこの表現で。

変数が増えても一緒ですが、その分長くなります。

Zipとかいう謎のメソッドも増えてますね。

こんな冗長な書き方が真価を発揮するのは、次のようなケースです。

通常の例ではUniTaskを使って5秒待機してからチェックしていますが、Rxの例ではそれをしていません。代わりに、「5秒後にfalseに変化する変数」を使っています。

この「5秒後に」のような点がRxの特徴で、Rxの世界では「変数」という存在に時間軸という概念が増えています。

例えば1から10まで2秒おきに変化する変数とか。

var hensu = Observable.Interval(TimeSpan.FromSeconds(2))
  .Zip(Observable.Range(1, 10), (interval, range) => range)
  ;

ZipCombineLatestSelectもいみわかんねーぞちくしょーっって方は、別に今覚える必要はないので安心してください。UniRxを使うとこんなことができるんだ...!と知った後に、UniRx逆引きから探すのが良いです。

最後に一つだけ、どのスクリプトにもおまじないとかいうクソワードともにAddTo(this)というメソッドが呼ばれていますが、これを消すとwhile(true) {}と同じような目に会います。UniRxに不慣れなうちは絶対につけておきましょう。AddToの実態については次の記事なんかが分かりやすかったです。

【UniRx】AddTo とは何かまとめてみた - Qiita

UniRx の AddTo と TakeUntilDestroy - Qiita

UniRxでユニティちゃんを新生させてみる

次の方針でユニティちゃんを新生させてみます。

  • 変数はなるべくローカル化する
  • Updateメソッドを使わない
  • ムーンウォークの指示」「立ち止まる指示」「走る指示」の3つを「歩くための各種パラメータ」としてまとめる
  • 「立ち止まる」「後ろ向きに歩く」「前に歩く」の3つを「進む方向を決める」「進む」の2つに抽象化する

まずは「歩くための各種パラメータ」の作成です。

パラメータの1つ目、ランダムに立ち止まる機能のため、「3秒ごとにランダムなboolフラグを返す」変数を作ります。  

//3秒ごとにランダムなboolフラグを返す変数
var requestStop = Observable.Interval(TimeSpan.FromSeconds(3))
  .WithLatestFrom(Observable.Return(new Random()), (interval, random) => random.Next(2) < 1)
  .StartWith(false);

0秒目は常にfalseとしたいので、StartWithオペレータを使って初期値を設定しています。

次に、パラメータの2つ目、ムーンウォーク機能です。「ボタンが押されるたびにフラグを反転させる」変数を作ります。

//ボタンが押されるたびにフラグを反転させる
var requestMoonWalk = moonWalkButton
 .OnClickAsObservable()
 .Scan(false, (doRequest, unit) => !doRequest)
 .StartWith(false);

Scanメソッドを使って前回のフラグ状態を覚えておき、ボタンが押されるたびにその値を反転させます。また、ボタンが一度も押されていない時はfalseとしたいので、こちらもStartWithを使って初期値を設定しています。

最後のパラメータは、指示があったら走る機能です。これはReactivePropertyを定義しているだけです。

[SerializeField]
public BoolReactiveProperty requestRun = new BoolReactiveProperty();

BoolReactivePropertyを始め、いくつかのReactivePropertyはInspector上から変更することができるようになっています。今回はひとまず、「Inspector上から値が変更された」=「走ることが指示された」とみなしました。

requestStoprequestRunrequestMoonWalkという3つのパラメータができたので、これらをもとに「進む方向」の変数を作りましょう。

//歩くときのパラメータをまとめる
var walkParams = Observable.CombineLatest(
    requestRun
    , requestMoonWalk
    , requestStop
    , (run, moon, stop) => (run, moon, stop));

//歩く方向を決める
var walkDirection = walkParams.Select(x => {
    var (run, moonWalk, stop) = x;
    
    if (stop) {
      //立ち止まるが指示されているので0方向へ進む
      return Observable.Return(Vector3.zero);
    }
    
    if (moonWalk) {
      //ムーンウォークが指示されている場合は走る命令を無視して後方へ進む
      //Note: ユニティちゃんは後ろ向きには知れない
      return Observable.Return(Vector3.back);
    }
    
    //走る(n歩連続で進む)命令をn個の歩く(1歩進む)命令に変換する
    return Observable.Repeat(Vector3.forward, run ? 5 : 1);
  })
 .Switch();

requestMoonWalkrequestStopの値に応じて方向を変更します。また、requestRunの値に応じて送信数を増幅させます。

パラメータが完成したので、毎フレームごとにwalkDirectionを確認し、その方向に歩きましょう。AddToを忘れずに。

this.UpdateAsObservable()
 .WithLatestFrom(walkDirection, (count, direction) => direction)
 .Subscribe(x => {
    if (x == Vector3.zero) {
      Debug.Log("立ち止まっているよ");
    }
    else if (x == Vector3.forward) {
      一歩進む();
      歩数++;
    }
    else if (x == Vector3.back) {
      一歩戻る();
    }
    else {
      Debug.Log("そんな方向いけないよ...");
    }
  })
 .AddTo(this);

サーバへの進捗報告はこんな感じです。

//サーバへの報告が完了したら報告した歩数を返す
var reportToServer = Observable.Interval(TimeSpan.FromSeconds(10))
   .SelectMany(x => {
      var count = 歩数;
      return 歩数をサーバに送信して成功したらtrueをもらう(count)
       .ToObservable()
       .Zip(Observable.Return(count), (a, b) => b);
    })
   .Share()
  ;
  
 //サーバに報告する
reportToServer.Subscribe()
 .AddTo(this);

サーバに報告するUniTaskをToObservableオペレータでRx化し、Zipオペレータを使って「成功したかどうか」のフラグを「成功したときの歩数」に変化させています。

SelectではなくZipを使っているのは、報告に成功したときのみ変化させたかったからです。Firebaseへの報告は今の所「報告に成功した時点の歩数の差分」なので、サーバへの報告に失敗したらFirebaseへの報告も行わないようにしなければいけません。

最後に、Firebaseへの報告処理はこのようになっています。

//Firebaseに報告する
reportToServer
  //直近の2つの報告を一つにまとめる
 .Pairwise()
 .Subscribe(async x => {
    var countDelta = x.Current - x.Previous;
    await 歩数をFirebaseに送信して成功したらtrueをもらう(countDelta);
  })
 .AddTo(this);

今回と前回のサーバへの報告に成功した時点の歩数をPairwiseオペレータによりまとめ、その差分を算出します。

1つ前のコードでShareというオペレータを付けていましたが、これは、サーバへの2重報告を防ぐためです。ShareをつけないままFirebaseへの送信処理をSubscribeすると、ColdObservableの性質により2重報告が発生してしまいます。詳しくはコチラの記事が参考になります。

【Reactive Extensions】 Hot変換はどういう時に必要なのか? - Qiita

これまでの修正をまとめると、新生ユニティちゃんはこのようになります。

using System;
using UniRx;
using UniRx.Async;
using UniRx.Triggers;
using UnityEngine;
using UnityEngine.UI;
using Random = System.Random;
using Vector3 = UnityEngine.Vector3;
public class SampleScript6 : MonoBehaviour {
  private int 歩数;
  [SerializeField]
  public BoolReactiveProperty requestRun = new BoolReactiveProperty();
  [SerializeField]
  public Button moonWalkButton;

  private void Awake() {
    //3秒ごとにランダムなboolフラグを返す
    var requestStop = Observable.Interval(TimeSpan.FromSeconds(3))
     .WithLatestFrom(Observable.Return(new Random()), (interval, random) => random.Next(2) < 1)
     .StartWith(false);

    //ボタンが押されるたびにフラグを反転させる
    var requestMoonWalk = moonWalkButton
     .OnClickAsObservable()
     .Scan(false, (doRequest, unit) => !doRequest)
     .StartWith(false);

    //歩くときのパラメータをまとめる
    var walkParams = Observable.CombineLatest(
        requestRun
        , requestMoonWalk
        , requestStop
        , (run, moon, stop) => (run, moon, stop));

    //歩く方向を決める
    var walkDirection = walkParams.Select(x => {
        var (run, moonWalk, stop) = x;
        if (stop) {
          //立ち止まるが支持されているので0方向へ進む
          return Observable.Return(Vector3.zero);
        }
        if (moonWalk) {
          //ムーンウォークが指示されている場合は走る命令を無視して後方へ進む
          //Note: ユニティちゃんは後ろ向きには知れない
          return Observable.Return(Vector3.back);
        }
        var multiplier = run ? 5 : 1;
        //走る(n歩連続で進む)命令をn個の歩く(1歩進む)命令に変換する
        return Observable.Repeat(Vector3.forward, run ? 5 : 1);
      })
     .Switch();

    //歩く
    this.UpdateAsObservable()
     .WithLatestFrom(walkDirection, (count, direction) => direction)
     .Subscribe(x => {
        if (x == Vector3.zero) {
          Debug.Log("立ち止まっているよ");
        }
        else if (x == Vector3.forward) {
          一歩進む();
          歩数++;
        }
        else if (x == Vector3.back) {
          一歩戻る();
        }
        else {
          Debug.Log("そんな方向いけないよ...");
        }
      })
     .AddTo(this);

    //サーバへの報告が完了したら報告した歩数を返す
    var reportToServer = Observable.Interval(TimeSpan.FromSeconds(10))
       .SelectMany(x => {
          var count = 歩数;
          return 歩数をサーバに送信して成功したらtrueをもらう(count)
           .ToObservable()
           .Zip(Observable.Return(count), (a, b) => b);
        })
       .Share()
      ;

    //サーバに報告する
    reportToServer.Subscribe()
     .AddTo(this);

    //Firebaseに報告する
    reportToServer
      //直近の2つの報告を一つにまとめる
     .Pairwise()
     .Subscribe(async x => {
        var countDelta = x.Current - x.Previous;
        await 歩数をFirebaseに送信して成功したらtrueをもらう(countDelta);
      })
     .AddTo(this);
  }
  ...

 

 

うわなっげえ!!!

いやまってください結構コメント書いたんです。あとほら、こうやってみるとすごく見通しが良いと思いませんか?Updateメソッドを使ってないし。

終わりに

UniRxを使えばソースコードをこんなふうにキレイに書けるよ(行数は増えるけどね)、っていう記事でした。

 

UniRxには今作っているBeyondTheFieldもめちゃくちゃお世話になっています(Github Sponsorsで寄付可能になってほしい...!)。現段階で17000行ほどのコードですが、まだ一度もUpdateメソッドを使っていません。

慣れるまで大変だったり、メモリに気を使わなければいけなかったり、デメリットも多々あるUniRxですが、慣れた後の開発スピードはなかなかなものです。みなさんもぜひ使ってみてくださいね!何かわからないことがあれば聞いてもらえると嬉しいです!

twitter.com

参考

UniRxを体系的に知りたい時はコチラがおすすめです。

UniRx入門シリーズ 目次 - Qiita

また、リアクティブプログラミングそのものについて興味がある方はコチラの記事がおすすめです。

【翻訳】あなたが求めていたリアクティブプログラミング入門 - ninjinkun's diary

一日1回はお世話になってる逆引きです。ZipLatestとCombineLatest何が違うんだっけとか、UniRxのError周りの操作って何があったっけとか、ある程度慣れたあとの参考文献としては情報量が最適で助かってます。

UniRx オペレータ逆引き - Qiita

「こうこうこういうことを実現したいんだけどうまい検索ワードが見つからなくて...」という方は、UniRxのgithubページを熟読するか、人に聞くのが一番です。指摘も質問もどうぞTwitterまでー!

GitHub - neuecc/UniRx: Reactive Extensions for Unity

Rx全般へのneueccさんの見解なんかも面白かったです。

各言語に広まったRx(Reactive Extensions、ReactiveX)の現状・これから - Build Insider

Beyond the fieldの開発スケジュールについて

プロジェクトリーダーの松本です。


Twitterのフォロワー数が1000人になりました!
嬉しいことに、最近では質問箱やダイレクトメッセージなどで、質問や応援のコメントをいただけるようになってきました!ありがとうございます!


Beyond the fieldは、2019年秋のリリースに向けて、着々と開発が進んでおります。
今回は、「いつリリースするんだ!」という方のために、開発スケジュールについて詳しく説明します。


現状では、ゲームの対戦部分の実装は終わっており、カードの効果の実装を行なっております。6月中には、全てのカードの効果を実装できる予定です。 その際には、カードリストを公開したいと思います!

 

イラストも着々と進行しております!最近では、リリース時点では4体しかいない最高レアリティのカードイラストが完成いたしました!あと3体いるので、是非お楽しみください!(天使と魔物と巨像...)

 

f:id:beyond-the-field-tcg:20190525231646p:plain


8月には、ゲームの大まかな部分が完成いたしますので、ゲームのルール説明動画と対戦動画を計画しています!非常に残念ですが、αやβテストなどを行う予定はありません…。


「Beyond the field」を開発して、6ヶ月が経ちましたが、正直この開発スピードに驚かされています!協力してくれるメンバーには本当に感謝しています。


リリースまでには、もう少しかかりそうですが、必ず良いものを作りますので、もうしばらく様子を見届けていただけると幸いです。

 

twitter.com


uGUIのデフォルトシェーダーを編集して表裏があるカードを作ろう - デザイン開発誌 #4 -

f:id:beyond-the-field-tcg:20190518175337j:plain こんにちは、下原です。

Beyond the filed では、画面上の各要素をuGUIで作成しています。
プレイヤーが操作するカードはイコールでUIでもあるので、uGUIでの作成になるのですが、そこで問題となるのがカードの裏面です。    

カードゲームを作る上でuGUIの問題

TCGのカードは当然裏面は固定のデザイン、もしくはスリーブになっていますよね。
ですが、これをuGUIでやろうとすると↓のように表裏同じ表示になってしまいます。
f:id:beyond-the-field-tcg:20190514012018g:plain
これはuGUIのデフォルトシェーダーが両面を描画する設定になっているからです。uGUIのImageを作成するとマテリアルは空になっているのですが、実はデフォルトのビルドインシェーダーが割り当てられています。
このuGUIのデフォルトシェーダーが両面を描画する設定になっているため、表裏同じ表示がされるようになっています。

これをシェーダーを編集して、裏面は表示しない設定にします。

ビルドインシェーダーをDL

まずはUnityのダウンロードアーカイブから、使用しているバージョンのビルドインシェーダーをDLします。

Unity - Download Archive

f:id:beyond-the-field-tcg:20190514000411p:plain   

UI-Defaultシェーダーを編集する

  この中にある「UI-Default」シェーダーを編集します。このファイルをVisual Studioなどで開きます。
書き換えるのは実はたったの一行。

Cull off

ここを以下のように書き換えます。

Cull Back

Cullといのはカリングのことを指しており、「間引く」という意味があります。
Unityにおけるカリングというのは、「負荷を下げるために描画しない」設定になります。
シェーダーの中のCullはフェースの描画設定で、設定は以下の3つ。

  • Cull Back(裏面非表示)
  • Cull Front(表面非表示)
  • Cull off(非表示にしない。両面表示)

フェースというのは3DCGの頂点・エッジ・フェースのフェースです。Unity上ではuGUIも3Dの描画と同じルールでできています。uGUIの画像は4つの頂点の板ポリでできています。

書き換えたらシェーダーを保存するのですが、そのままだとデフォルトシェーダーと同じ名前になってしまうため、シェーダーの一番最初に書かれている以下の行を、

Shader "UI/Default"

以下のようにリネームしておきます。

Shader "UI/Cull-Back"

カードに割り当てる

できたシェーダーをUnityプロジェクトに入れます。マテリアルを作成し、編集したシェーダーを割り当てます。このマテリアルを裏面表示したくないuGUIに割り当てます。
f:id:beyond-the-field-tcg:20190514012059g:plain
これで表裏のあるカードができました。
ちなみにTextMeshProのシェーダーもCull Backに変更しています。

twitter.com

Twitterのプロフィール内容を改善してみた結果....驚きの真実が....!!!【マーケ戦略 #2】

 

f:id:beyond-the-field-tcg:20190515111719p:plain

どうも!
Beyond the field マーケティング担当のふみです!

 

今回は、
Twitterアカウントを運用している方向け!!
・特に個人開発者の皆様に読んでもらいたいです!!

Twitterのプロフィール欄は
Twitterでは、その人の「顔」の役割に当たる部分です!
印象次第でフォローするか決まる大事な要素ですね!

 

その改善過程を
実際の例をもとに解説していくので
少しでも、多くの方に参考になると幸いです!

 

===========================================================


Beyond the fieldBTF)では
Twitterを利用して多くの方に開発情報を提供しようと考えております!

twitter.com

Twitter運用の目的は
【リリース時に多くのユーザーにBTFを遊んでもらうこと】
です!!!!!!!

そのために開発時点でBTFに興味を持ってもらい
フォローしてもらうための方法を日々研究しております。


まずTwitterでフォロワー獲得する流れを考えると

1.ツイート主を認知する
 フォローされたとき
 ファボされるとき
 リツイートが流れてきたとき
 
2.ツイート主に興味を持つ
 リツイートの内容を見たとき
 プロフィールを見たとき
 固定ツイートを見たとき
 その他のツイート内容を見たとき
 
3.フォローするか判断する
 2の内容が自分にとって有用な情報かどうか
 2の内容が自分にとって面白い情報かどうか
 フォロワー/フォローの比率が適正かどうか
 フォロワーの属性/質はどうか

他にも細かい要素はあるかと思いますが
ざっくりユーザーの動きを洗い出すとこんな感じですかね!

その中でも
手っ取り早く改善でき、効果対象が広そうな
プロフィール内容を、まずは改善することを決めました!!


そこで先人の知恵を借りるべく
いろいろググってみた結果、
このサイトがわかりやすかったです!

Twitterプロフィールの書き方│誰でもできる!5000フォロワー達成した実例

 

サイトの内容を引用させていただくと


ツイッタープロフ項目の5要素
・自分が何者なのか?(肩書き・得意なもの・好きなこと等)
・誰に向かって発信してるのか?(ターゲットを具体的に)
・自分の経歴(過去→現在→未来の時系列がベスト)
・プライベート要素(親近感・人格を出そう!)
・このアカウントは何を発信しているのか?(メリットを出しつつ書く)

どうやらこれらの要素が大事のようです!

これに合わせてBTFプロフィールの改善を行いました!

 

まずは改善前の文章を見てください!

Before
【カードゲーム開発中】
個人開発やカードゲームが好きな人をフォローしています!
UnityやPhotoshopの記事とか書いているので、是非見てください
たくさんの人からの応援(フォロー)が開発する力になっています!
よろしくお願い致します

至って普通な感じですね...!
個性はあまり感じられないです・・・

 

では早速改善していきましょう!!!

 


まずは1つ目
・自分が何者なのか?(肩書き・得意なもの・好きなこと等)

いちばん大事な自己紹介ですね!


Beforeでは
>【カードゲーム開発中】
にあたる部分...

なんて情報が少ないんだ......
これではプロフィールを見た人が想像力を働かせないと
よくわからないですね....

 

まず僕らは個人ではなく、団体で活動しているので
それも皆さんに知ってもらいたい!

しかもそのメンバーは素人ではなく、
ゲーム会社の人間たちで構成されているといことを知ってもらいたい!

そして僕らのプロジェクトが個人制作の域を超えているっていう
本気のプロジェクトだと知ってもらいたい!

 

そんな思いを踏まえた、

Afterはこちら
>【対戦カードゲーム開発中】ソシャゲ会社メンバーを中心に10人体制

 

上の熱量まで伝わるかは謎ですが(笑)
それなりに情報量を詰め込んだ1文ができたかなと思います!


つぎに2つ目!
・誰に向かって発信してるのか?(ターゲットを具体的に)

Beforeでは
>個人開発やカードゲームが好きな人をフォローしています!
>UnityやPhotoshopの記事とか書いているので、是非見てください
ですね

具体的に誰向けか書いてはいたのですが、
本来のアカウントの目的とはズレてますね。。。

 

僕らがツイッターを運用する目的は
初めに話したとおり
【リリース時に多くのユーザーにBTFを遊んでもらうこと】です!


そのための広報窓口がTwitterなので
開発ノウハウを求める人ばかり集めても
本来の目的は達成できないですね...

 

ゲームのファンになってくれる層
TCGトレーディングカードゲーム)好き層も

合わせて獲得していかないとダメだと改めて気づきました!

 

そんな思いを踏まえた、Afterはこちら
>TCG好き&ゲーム開発者向け

 

簡潔ではありますが、
僕らが繋がりたい層が明確になりました!

 

そして3つ目!
・自分の経歴(過去→現在→未来の時系列がベスト)

僕らでいうと、
プロジェクトがスタートするまでの歴史と将来の予定ですね!

Beforeでは....
特に記載していなかった内容ですね。

 

Afterはこんな感じです!
>TCGをこよなく愛する代表松本が2012年に企画構想
>→熱意に惹かれ10名のメンバーが2018年12月に集結
>→2019年秋のリリースを目指す

 

ここでは、
TCGを大学時代に研究し、
TCGを大好きで、
TCGをもっと多くの人に遊んでもらいたい想いを秘める
代表松本を紹介したかったということ!

 

そんな松本が練りに練った企画が
現在進行中なのだということ!

 

そして松本という人物に
メンバー達はそれぞれ惹かれ
プロジェクトに無償で参加していること!

さらに、そんなプロジェクトが今年には完成するということ!!!!!
これらを伝えたいと思いました!!!!!

※松本がどんな思いで企画を作ったかは
こちらの記事をご確認ください^^! 

Beyond the field-終わらない楽しみを提供する - 【TCGゲームアプリ】Beyond-the-field 開発ブログ

 

そして、4つ目と5つ目の項目!!
・プライベート要素(親近感・人格を出そう!)
・このアカウントは何を発信しているのか?(メリットを出しつつ書く)

Beforeはこんな感じ!
>たくさんの人からの応援(フォロー)が開発する力になっています!
>よろしくお願い致します

 

Afterはコチラ!
>開発ノウハウをブログにて公開中
>応援お願いします


僕らはチームでやってるのでプライベート要素は
ちょっと混ぜにくいなと思い、特に言葉は入れず^^;

 

そして、
このTwitterのメリットとしては
開発ノウハウを発信していること!

 

ここまでプロフを読んで頂いて、
興味を持ってもらった人に
情報を提供させてもらうこと

 

それを求める人に
是非フォローしてもらい
プロジェクトのファンになってもらいた!!

 

そんな思いで最後締めくくりました!

 

最後に完成した全文を比較してみましょう!

Before

f:id:beyond-the-field-tcg:20190515111724j:plain

 

After

f:id:beyond-the-field-tcg:20190515111727j:plain

そして最後に
親しみやすさと、読みやすさの調整として
適宜、絵文字を入れてみました!

 

さあ、どうでしょうか?
良くなりましたかね??笑

 

僕らがどんな活動をしているのか
少しでも想いが伝わるようになっていれば幸いです!

 

================================================


さーーーー!!!
渾身のプロフィールができたし
この修正の効果を数値分析するぞーーーー!!!

そう思っていろいろ分析ツールを触ってみましたが


なんと.....................

 


プロフィール総閲覧数のデータが取れない!!!!!!!!!!!!!!!!

(タイトル釣ってすみません。。。これが驚きの事実でした(笑))

(追記 5/15 16:50)
Twitterアナリティクスから普通に計測できるとご指摘ありました

※完全に見落としていました。。。恥ずかしい。。。。w

 

数値の分析結果に関しては、別記事でまとめようと思いますので

お楽しみに!!!!!!!!!!!!!!!!!!

 

 

本来は
フォロー率=フォロー数/プロフィール総閲覧数
こんな感じのデータが取れれば良いなー!と思ってたのですが
そんな甘くは分析できないですね.....
(分析したい数値は最初に取れることを確認しようという教訓を改めてGET!)

 

※※どなたかプロフィール内容の効果測定方法を知っている方
ご享受いただけると幸いです※※

 

まあでも
そんなこんなでプロフィールの改善は終了しました!

 

定量的な効果はよくわからなかったですが、
定性的(チーム内)では、評判も良く改善してよかったなと思ってます!

 

この記事が少しでも個人開発者のTwitter広報に役立つと幸いです!
ご精読ありがとうございました!


まだTwitterフォローしていない方は
チームメンバーの励みになりますので
フォローよろしくお願い致します!

twitter.com

【Unity】カードゲームっぽい手札を作りたかった話

こんにちは、たかせです。カードゲーム作ってます。

twitter.com

先日、手札のカードをこんな風に表示していたら、デザイナさんに突っ込まれました。もっと シャドバ カードゲームっぽくしたいとのこと。

UIを円状に配置できるScriptを見つけたので試してみたものの、いまいちカードゲームっぽさが出ない。

やっぱこうじゃないと...!!!

 

ということで、SectorLayoutGroupを作成しました。

github.com

デモ

3D対応!アニメーション可能!

D&Dでラクラク設定!Gizmosによる補助機能付き!

使い方

スクリプトとプレファブをインポートすると使えます。UniRxというAssetに依存しているので、利用していない方はコチラからインポートしてください(無料です)。

  1. SectorLayoutGroup.csをインポート
  2. SectorTemplate.prefabをインポート
  3. 並べたいGameObjectをSectorTemplateの子供に子供として追加
  4. 完了!

パラメータの説明

  • Center
    • 必須。扇形の中心点となるGameObjectを指定します(図中の赤点)
  • Start
    • 必須。扇形の始点となるGameObjectを指定します(図中の青点)
  • End
    • 必須。扇形の終点となるGameObjectを指定します(図中の緑点)
  • ChildrenRotateOffset
    • 子供の向きを調整します。デフォルトではCenterを向きます
  • FrameCount
    • アニメーションの実行時間を調整します。最終的な時間はFrameCount * FrameIntervalにより決まります
  • FrameInterval
    • アニメーションの実行間隔を調整します。最終的な時間はFrameCount * FrameIntervalにより決まります
  • useSlerp
  • enable
    • Gizmosを使ってデバッグ用の扇形と点を描画します(予めGizmosをONにしておく必要があります)
  • freeze
    • 要素を並べる時、回転情報をリセットします

とりあえず動作確認してみたいという方へ

お試し用のプロジェクトを作成してあります。コチラからZipファイルをダウンロードして、UnityでSampleSolutionフォルダを開くと動作確認が可能です。

git cloneでもOKです

 

動作の解説(と苦労話)

要素を扇状に並べる手順をざっくり言うと、次のようになります。

1. 3点(赤点、青点、緑点)をもとに扇型を定義する
2. 扇型を曲座標変換して半径、xy平面上の角度、xz平面上の角度を取得する
3. 2つの角度をn等分し、その等分線を直交座標系に戻す
4. 等分線の終点にGameObjectを配置する
5. 中心点(赤点)を向くようにGameObjectを回転させる

文字数にして149文字の短い説明ですが、ベクトルの足し算ってどうやるんだっけ...?な状態だった僕にはなかなかしんどい実装でした。

 

いやほんとに。高校の数学、こういう動機を持って勉強したかったなぁ。

 

Unityの座標系はxyzではなくxzyであることに気づかず数時間悩んだり、気まぐれにWordScopeからLocalScopeに切り替えてみたらxyz座標系に戻って悩んだ時間が吹っ飛んだり、Quaternion.LookRotationの説明にあるspecified forward directionsがさっぱりイメージできなくて場当たり的に引数を設定してみたり...。もうさんざんでした。初めてUnityエンジニアらしい実装をした気がします。

 

わりと困ったのが「扇形の形が変化したら子供を並べ直す」実装で、3点(赤点、青点、緑点)のpositionの変化を検知したかったのですが、その時点で唯一知っていたOnValidateでは検知できませんでした。困った挙げ句、UniRxのObserveEveryValueChangedを使ってtransform.positionの変化を購読することで対応しました。動作上問題は出ていないものの、誰がもっと良い実装があれば教えてください...

 

また、どうせUniRxを使うなら...と思いたった結果、要素数の変化もObserveEveryValueChangedで行いました。これにより、LayoutGroupの継承をせずに済むようになり、uGUIではないLayoutGroupが完成しました。作った手前あれなんですが、非uGUIなLayoutGroupって嬉しいことあるんでしょうか...。継承を外せたのは嬉しかったんですが、使う側としても何か嬉しいことがあればぜひ教えてもらえると🙇‍♂️

 

2箇所もUniRxに依存してしまった現状、Vector3.LerpによるアニメーションにUniRxを使わない手はありませんでした。

 

こんなデータをクラスを作り、

こんなストリームを作れば、

あっと言う間にUpdateメソッドを使わないLerpアニメーションの完成です 。

 

Corotuineとか使ったほうがスマートかもしれません

おわりに

SectorLayoutGroupにより、こんな素敵な手札が完成しました。

 

カードゲームを作っているみなさんが少しでも楽になりますように!

 

こんな風に使えるともっと便利なんだけど...みたいなご提案、お待ちしております!!!    

こうして私はゲームプランナーになった。

Beyond the fieldのプロジェクトリーダーの松本です。

今回は私が「ゲームプランナー」になるまでの話をしたいと思います。

 

・どうやってゲームプランナーになったの?
・どんな能力や知識を持っていたの?

 

という問いに少しでも答えることができればいいなと思っています。
結論から言いますと、カードゲームのおかげです。

 

【はじめに】
私はゲーム会社に入社するまで、ちゃんとしたゲーム開発を経験したことはありません。なので、「Unity?」というレベルで、ゲームの中身がどうなっているか全く想像もできませんでした。

 

【ゲームを遊ぶ側から作る側になる】

小、中、高、大学とずっとカードゲームを遊んで生きてきました。はじめは、カードゲームをただ楽しく遊ぶ側だったのですが、大学生の時に起こったある出来事がきっかけでカードゲームを作る側になりました。

 

大学生の頃です。仲良くなった友人がいて、私はどうしてもこの人とカードゲームが遊びたい!と思いました。彼は元々カードゲームを遊んでいた人で、カードゲームの話をして仲良くなったからです。

 

私はきっと彼ならカードゲームを一緒に遊んでくれるだろうと思っていましたが、なんと「カードゲームはお金がかかるから遊びたくない」と言われてしまいました。

 

しかし、どうしても一緒にカードゲームを遊びたかった私はあることを思いつきます。「そうだ!だったら自分がカードゲームを作ればいいじゃん!そしたらタダだし一緒に遊べるじゃん!」という安易な発想を。

 

が、結果は大成功です!
なんと私の考えたカードゲームを遊んでもらうことができ、楽しいカードゲームライフを送りました。そして、カードゲームを楽しそうに遊んでいる友人の姿を見て私はこう感じました。

「やっぱりカードゲーム好きなんだな!」

 

【カードゲームと真剣に向き合う】
あの出来事から、カードゲームを遊んでいた人にカードゲームを嫌いな人はいないと思うようになりました。そして、きっとカードゲームを遊びたいけど何かしらの理由で遊べない人がいるんだろうなと考えるようになります。

 

「よし!カードゲームを遊んでいた人達がもう一度遊びたくなるカードゲームを考えよう!それで、その人たちにずっと遊んでもらって、カードゲームをずっと楽しんでもらおう!みんなでまた楽しくカードゲームを遊ぼう!」

 

そして、ここからはカードゲームを調べる毎日です。どんなカードゲームがあって、どんな人が楽しんでいて、どんな理由でやめてしまったのか。カードショップに行って色々な人から話を聞いたり、色々なカードゲームを遊んで人と繋がったり、大学の教授に会いに行き、消費者の考え方などを学んだり。

 

そして、「Beyond the field」というカードゲームのアイディアが生まれました。

 

【ゲームプランナーになる】

「Beyond the field」の構想がほぼ出来上がった頃、たまたま応募していた企業のインターンに参加することになりました。そこで私は「カードゲームを作る!絶対出す!」と言いまくり、その時に「面白いね!カードゲームを作っていいよ!」と言ってくれた企業に魅力を感じて面接に行くことを決めました。

 

面接の内容ですが、実は今までの話をほとんどそのまま伝えただけなんですよね…!
そしたら、合格。なんとゲームプランナーになりました!正直この時点でも「ゲームプランナー」が何をするのかよく分かってなかったです…。

 

【ゲームプランナーに必要なこと】
少し成長した自分が見返してみて思ったのが、当時の私はゲームプランナーとして必要な考え方が少しできていたなと思います。

それは、「何のために作るのか、誰のために作るのかを徹底して考え抜くこと」です。

 

この考え方は、ゲームに関する全てのことに役に立ちます。

例えば)

・このアプリのこの機能は何のためにあるのか?
スマホゲームは何のためにイベントを開催するのか?
・どんなイベントでどんな人が楽しんでいるのか?
などです。

 

もしゲームプランナーに興味がある人は、今自分が遊んでいるゲームを一つ起動して、一つ一つの機能を深く考えてみると良いと思います。何のための機能で、誰が何で嬉しいのか、徹底的に考え抜いてほしいです。

自分が面白くないからこの機能はいらないのではなく、誰が喜んでいるのかを想像することが大事です。

 

あとは自分が一番好きなんだ!という絶対に負けたくないものを作ることです。
それは自信に繋がり、ずっと支えてくれる存在になります。

 

【最後に】

私がこんなにもカードゲームを好きな理由は、カードゲームのおかげで人生を楽しく過ごせたからです。私はカードゲーム文化を大切にしたいと思っています。

現在開発しているBeyond the fieldがカードゲームを好きになるきっかけになっていただければ嬉しいです。

 

今後とも、Beyond the fieldをよろしくお願いいたします。

Photoshopで魔法陣やエムブレムをデザインする際に使える小技集 - デザイン開発誌 #3 -

f:id:beyond-the-field-tcg:20190502120006p:plain

ゲーム開発していると度々魔法陣やトライバル、エムブレムのような「かっこいい模様デザイン」が必要になることがあります。素材を使うのも良いのですが、マッチするものが無かったり、オリジナルデザインを作りたくなったりします。

そういった模様デザインの際に使える(先輩デザイナーから盗んだ)小技を紹介します。

 

不透明度100%ブラシと消しゴムで描いて削る

不透明度100%のブラシと消しゴムで適当に描いては削るを繰り返して形を整えます。模様は基本的にシルエットなので、微妙な濃淡は必要ないのでこうした手法が適してたりします。

 

f:id:beyond-the-field-tcg:20190502112548p:plain

 

対称機能を使って描く

f:id:beyond-the-field-tcg:20190502112611p:plain

PhotoshopCCの対称機能を使って描くと、魔法陣などのデザインに便利です。シンプルな左右対称から、放射状や曼荼羅など対称のさせ方も種類があり、適当に線を引いているだけでもおもしろい模様になります。

 

f:id:beyond-the-field-tcg:20190502112619p:plain

パスで清書してCCライブラリに登録する

f:id:beyond-the-field-tcg:20190502112504p:plain

ある程度デザインができたらパスをひいて清書します。それぞれ使えそうなデザインを小分けにしてパーツ化し、CCライブラリに登録しておきます。

f:id:beyond-the-field-tcg:20190502112531p:plain

その組み合わせで新しい模様をデザインしたり、付け足してちょっと豪華にしたり。ちょっとしたUIパーツなどにも使用できて便利です。

 

 

 実践 カードスリーブデザイン

こういった盗んだ小技を駆使して、Beyond the fieldのカードスリーブデザインをしてみました。

f:id:beyond-the-field-tcg:20190502120546p:plain

 

  • 魔道書モチーフ
  • 目のような模様

 


世界観設定をベースに、以上の2点を盛り込んでデザインしてみました。たびたびこの模様はゲーム中で使用しています。

 

まとめ

  • 模様描くのは楽しい
  • CCライブラリ便利 

twitter.com