20200126のUnityに関する記事は5件です。

作業3日

 ゆっきーの作業3日目  

今日のやりたいこと。
・ボタンを追加したい。
    ↓
・ので今日は先生が作ってくれたプログラミングを読み解く努力をする。

● [SerializeField]

・インスペクターウィンドウで編集できるようにすること

・publicより定義を変えにくいので
 複数の人が関わる分業の時に適している

(つまり、publicの場合はフィールドの定義を変えやすいので
 誰かが知らずに一人で定義を変更すると、動かなくなってしまう場合がある)

small_girl_3.png (ほう)

● Transform

Transformは、オブジェクトの「位置」「回転」「縮尺」を設定するコンポーネント。
大きさや配置場所が決まる。

small_girl_3.png (これ、Unityアセットもこれで変更できるのかな・・・)

●List

Listは配列ととても良く似たものです。
配列を宣言するときは、初めから配列の個数は決められています。
しかし、Listの場合は長さを決めずに宣言することができます。その時によって要素数を増減させることができます。

small_girl_3.png (配列って何?・・・とりあずわからないけど次!)

●リストの定義方法はこれらしい。
private List myList = new List();

small_girl_3.png (intって・・・?)

●int

int a;
と宣言し
a=10;
となると

int型の変数aに値10を入れる

・・・

・・・

cairo_girl_yoko.png
(ゲームオーバー。ライフ1つ失う。)

というか
ボタンを作るつもりが別なスクリプトを見ていたこと事にきづいた為
一旦終了。

ボロボロ過ぎやな。

あまり私を信じずブログをお読み下さい。

次に続く。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】国産揺れものアセット『Magica Cloth』を試してみた Bone Cloth編

Magica Clothとは

●特徴(公式サイトより引用)
・Unity Job system + Burstコンパイラによる高速なクロスシミュレーション
・WebGLを除くすべてのプラットフォームで動作可能
・ボーンで駆動するBone Clothとメッシュで駆動するMesh Clothを装備
・Mesh Clothはスキニングメッシュ上でも動作可能
・簡単で直感的なインターフェースによりすぐにセットアップ可能
・スローなどの時間操作が可能
・フルソースコード付き

詳細は公式サイトを見て下さい。国産アセットなので日本語です。

使い方

とりあえず触ってみたい方には、以下の記事がオススメです
【unity】スカートはこれで決まり!「Magica Cloth」導入編(MeshCloth)【アセット】

詳しい使い方は、マニュアルが日本語なのでそちらを読みましょう。

パフォーマンス比較

私的には「Unity Job system + Burstコンパイラによる高速なクロスシミュレーション」ってところが気になるので、Bone Clothと有名な揺れ物アセットの『DynamicBone』とでFPSに差が出るか検証してみました。
自作のモデルに同じような見た目になるようにセットアップ、VSyncをオフにしてBuildし、6万フレームの平均を計測しました。
MagicaClothTest.gif

結果

FPS
揺れ物なし 604
DynamicBone 590
MagicaColoth 570

使い方によるのかもしれませんが、DynamicBoneの方が速かったです。

まとめ

個人的にはDynamicBoneよりちょっと使いやすいと思ったんですが、DynamicBoneの速いという結果になってしまいましたね。

Unity標準のClothコンポーネントとMesh Clothの比較もいずれやってみたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity(C#)】Extenject(Zenject)使って入力機能を切り離したものを管理してみた

はじめに

Extenject(Zenject)、設計、インターフェース等々、理解が浅い部分が多いので
誤りがあれば遠慮なくご指摘ください。

Extenject(Zenject)って?

依存性の注入のためのフレームワークです。

【参考リンク】:Zenject入門その1 疎結合とDI Container

疎結合な設計をする際に発生する問題に対して、
いろいろと手助けをしてくれる最強ライブラリです。

ExtenjectはZenjectのメンテナンス用として作られたそうですが、
なんやかんやあって今はExtenjectらしいです。

Extenject is the continuation of the Zenject Project!

【引用元】:Extenject Dependency Injection IOC

密結合

疎結合の反対は密結合ですが、なぜダメなのでしょう。
私が陥った状況に沿って説明していきます。


下記のGIFのようなゲームを趣味で作ってました。

CubeAdventure3.gif

Virtual JoyStickと呼ばれる、
スマホゲームでよく見かける入力インターフェースを実装してあります。

ただ、このGIFはスマホ用で、最終的にはWebGLでも遊べるようにしたかったです。
WebGLで遊ぶならキー入力の方が操作しやすいので
実装を変更するつもりでした。

もちろん、プラットフォーム判定とコピペを駆使すれば、
後から付け加えることも不可能ではありませんが、
1つのUpdate内に大量の分岐処理を書き込んだり、
他の入力系との参照関係が生まれることは予期せぬ挙動を生み出す可能性があり、
あまり好ましくありません。

オープンクローズド原則1にも反するので、
もし今後、ステージのギミックで"入力が反転する"などを
実装したくなった際にスパゲッティコードになります。

疎結合

先述の問題を解消するためにExtenjectを使います。
そのために入力機能を疎結合にするのですが、
めちゃくちゃ簡単に言うと、
入力機能をごっそり差し替えても問題なく動くような仕組みにする
ってことです。

図にするとこんな感じです。
本当はクラス図書くときは矢印の種類やら
いろいろとルールがあるんですが、
この図の矢印は単純に知ってるか知らないかの方向を
表しているものとします。
Interface.PNG

要するに中央のIInputProviderというインターフェースを使って、
MoveCubeというクラスが、一番下のInput(入力機能)を知らない状態を作り出せば、
入力機能を差し替えても、MoveCubeに変更を与える必要はない
ということです。

コード

まずはIInputProviderを作ります。

 public interface IInputProvider
{
    bool InputLeft(bool isSpaceDirection);
    bool InputRight(bool isSpaceDirection);
    bool InputUp(bool isSpaceDirection);
    bool InputDown(bool isSpaceDirection);
}

次にIInputProviderを実装したKeyInputProviderを作成します。

ステージのギミックで"入力が反転する"という実装をあらかじめ仕込んでます。

using UnityEngine;

public class KeyInputProvider : IInputProvider
{
    public bool InputUp(bool isSpaceDirection)
    {
        if (isSpaceDirection)
        {
            return Input.GetKey(KeyCode.UpArrow);
        }
        else
        {
            return Input.GetKey(KeyCode.DownArrow);
        }
    }

    public bool InputDown(bool isSpaceDirection)
    {
        if (isSpaceDirection)
        {
            return Input.GetKey(KeyCode.DownArrow);
        }
        else
        {
            return Input.GetKey(KeyCode.UpArrow);
        }
    }

    public bool InputLeft(bool isSpaceDirection)
    {
        if (isSpaceDirection)
        {
            return Input.GetKey(KeyCode.LeftArrow);
        }
        else
        {
            return Input.GetKey(KeyCode.RightArrow);
        }
    }

    public bool InputRight(bool isSpaceDirection)
    {
        if (isSpaceDirection)
        {
            return Input.GetKey(KeyCode.RightArrow);
        }
        else
        {
            return Input.GetKey(KeyCode.LeftArrow);
        }
    }
}

同様に差し替え機能の一つとしてJoyStickInputProviderを用意します。

やっていることはKeyInputProviderと同じで、
ジョイスティックの入力具合に応じてbool値を返すだけです。

public class JoyStickInputProvider : IInputProvider
{
    float joyStickSensitivity = 0.7f;

    public bool InputUp(bool isSpaceDirection)
    {
        if (isSpaceDirection)
        {
            return SimpleTouchController.GetTouchPosition.y > joyStickSensitivity;
        }
        else
        {
            return SimpleTouchController.GetTouchPosition.y < -joyStickSensitivity;
        }
    }

    public bool InputDown(bool isSpaceDirection)
    {
        if (isSpaceDirection)
        {
            return SimpleTouchController.GetTouchPosition.y < -joyStickSensitivity;
        }
        else
        {
            return SimpleTouchController.GetTouchPosition.y > joyStickSensitivity;
        }

    }

    public bool InputLeft(bool isSpaceDirection)
    {
        if (isSpaceDirection)
        {
            return SimpleTouchController.GetTouchPosition.x < -joyStickSensitivity;
        }
        else
        {
            return SimpleTouchController.GetTouchPosition.x > joyStickSensitivity;
        }
    }

    public bool InputRight(bool isSpaceDirection)
    {
        if (isSpaceDirection)
        {
            return SimpleTouchController.GetTouchPosition.x > joyStickSensitivity;
        }
        else
        {
            return SimpleTouchController.GetTouchPosition.x < -joyStickSensitivity;
        }
    }
}

Extenject

そして、ここからExtenjectの力を借ります。

導入までは下記参考リンクで完璧です。
導入以降もめちゃくちゃわかりやすいので100回ぐらい見た方がいいです。

【参考リンク】:Zenject入門その1 疎結合とDI Container

※AssetStoreではExtenjectと検索すれば出てきます


MoveCubeIInputProviderを使うわけですが、
このままだとnullになってしまいます。

IInputProviderってどこ?どれ?って状態です。

MoveCube
    //MoveCubeはどこのどのIInputProviderを使えばいいかわからない
    IInputProvider inputProvider;

    [SerializeField]
    bool tmpFlag = true;

    void Update()
    {
        //Right 
        if (inputProvider.InputRight(tmpFlag))
        {
            //適当な処理
        }
        //Left 
        if (inputProvider.InputLeft(tmpFlag))
        {
           //適当な処理
        }
        //Up 
        if (inputProvider.InputUp(tmpFlag))
        {
            //適当な処理
        }
        //Down 
        if (inputProvider.InputDown(tmpFlag))
        {
            //適当な処理
        }
    }

そこで、[Inject]を使います。

インターフェースに[Inject]というアトリビュートを与えることで、
MoveCubeが「IInputProviderを使いたい!」となった際に、
Extenjectが「お前が使いたいのはこれかい?」
といった感じに手助けしてくれます。

 [Inject]
 IInputProvider inputProvider;

Extenjectに"これ"がなんなのかあらかじめ設定しておく

先ほど

Extenjectが「お前が使いたいのはこれかい?」
といった感じに手助けしてくれます。

と書きましたが、Extenjectに助けてもらうには
まずはこちら側で"これ"が一体何のことを示しているのか
あらかじめ教えておくことが必要です。

Installer

Extenjectに助けてもらうためにInstallerを作ります。

CreateZenjectInstaller で作れます。

ZenjectInstaller.png

中身はこんな感じにします。
それぞれ、IInputProviderが呼ばれた際に、
To以下のジェネリクスで指定したProvider"これ"」になるよ~
って設定してます。

using Zenject;

public class KeyInputInstaller : Installer<KeyInputInstaller>
{
    public override void InstallBindings()
    {
        Container
            .Bind<IInputProvider>()
            .To<KeyInputProvider>()
            .AsCached();
    }
}
using Zenject;

public class JoyStickInputInstaller : Installer<JoyStickInputInstaller>
{
    public override void InstallBindings()
    {
        Container
            .Bind<IInputProvider>()
            .To<JoyStickInputProvider>()
            .AsCached();
    }
}

さらに、今回はプラットフォーム判定を利用して
より簡単に差し変わるようにしてみました。

using Zenject;

public class InputInstaller : MonoInstaller
{
    public override void InstallBindings()
    {
        //スマホ用
#if UNITY_ANDROID || UNITY_IOS 
        JoyStickInputInstaller.Install(Container);
#endif

        //WebGL
#if UNITY_WEBGL 
        KeyInputInstaller.Install(Container);
#endif
    }
}

Installerをもう一つ用意して、
先ほど作成した2つのInstallerをプラットフォーム別判定の中で呼び出しています。

後ほどInspectorで登録するのでMonoInstallerを継承させておく必要があります。

Context

次に、Installerの影響範囲決めとInstallerの登録を行います。

下記リンクに全部載ってますが、一応メモします。

【参考リンク】:Zenject入門その1 疎結合とDI Container


Contextというものを作成します。
今回使うのはSceneContextと呼ばれるものです。

SceneContext.png

影響範囲や設定方法については下記リンクが参考になります。

【参考リンク】:【Unity】【Zenject】DIの影響範囲を指定するContextの使い方まとめ

あとはInstallerの登録を行えば完了です。
SceneContextSettings.png

おわりに

インターフェースってなんのためにあるんだ?
使ったことないけど困ったことないぞ?
って感じだったので触ってみました。

まだまだ小規模なテストも書いてない個人製作の範囲なので、
その強力さにいまいちピンときてませんが、
できるだけ疎結合を意識した設計ができたらいいなと思ってます。


追記

記事を書くにあたって、Unityゲーム開発者ギルドでいろいろと
疑問点にお答えいただいた方、ありがとうございました。

まだ入ってない人は早急に入った方がいいと思います。
損することは何もないです。(強いて言えばみんな強すぎてちと凹む。。。)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【PART1】もしあなたがUnityのGit管理をしようとしてこられてきてたとしたら、絶対にさせてあげられてたと思うか?

UnityをGit管理したい!

タイトルをかまいたち風にしたところで、どうもBuzzです。今日はUnityでGit管理をする方法をアウトプットしていこうと思います。

前半はただGit管理の方法を書いて、後半はチームで開発するための知識を書いていきます。

なぜ書くのか

今回この記事を書いているのにはこのような経緯がありました。

プログラミングを今までやってこなかった完全な初心者の友達と「一緒にゲーム開発したくね?」

僕も一緒に技術書を買って勉強し初める

技術書を読み終えてひと段落したので、その初心者の友人にむけてGit管理の方法をしらべてテキストファイルにしようと思いつく

どうせ書くならQiitaや!←いまここ

そんなこんなでかなり初歩的なことも書いてありますし、僕自身Gitに詳しい訳でもないので大目に見てください。間違い・ご意見ありましたらコメントしていただけると今後に役立ちます。

Gitとは?

Wikiから引用

gitは、プログラムのソースコードなどの変更履歴を記録・追跡するための分散型バージョン管理システムである。Linuxカーネルのソースコード管理に用いるためにリーナス・トーバルズによって開発され、それ以降ほかの多くのプロジェクトで採用されている。
gitでは、各ユーザのワーキングディレクトリに、全履歴を含んだリポジトリの完全な複製が作られる。したがって、ネットワークにアクセスできないなどの理由で中心リポジトリにアクセスできない環境でも、履歴の調査や変更の記録といったほとんどの作業を行うことができる。

このことから、要は開発中の煩わしいところ省いていこうぜ!ってことです。

個人開発の場合

例えばいままで一人で何かを開発するにしても、「急に動かなくなった」とか「前の状態に戻したいけど保存しちゃった!」とかそういった場合に気を配って開発していく必要がありました。

ですが便利な時代になり、「ソースのバックアップ」をとることができるようになりました。

携帯も「やべ、全部データけしちゃった!!」って状態になった時、バックアップを取っていたら、パソコンに繋ぐだけでそのデータたちが戻ってきますよね?それと一緒で、コードも「やべ、動かなくなった」に対応できるわけです。

チーム開発の場合

チーム開発の場合を考えると、もちろんコードのバックアップもそうですが、もっとメリットがあります。

Gitが主流になる前のチーム開発では一つのフォルダを複数人で共有する必要がありました。

例えば3人でチーム開発をしようとしていえて、共有フォルダにはAファイル、Bファイル、Cファイルがあった場合。

AさんはAファイルを編集して共有フォルダに戻す!
BさんはBファイルを編集して共有フォルダに戻す!
CさんはCファイルを編集して共有フォルダに戻す!
背景.png

このようにそれぞれが別々のファイルを編集していれば対して問題ではなく、ただ単にソースのバックアップが面倒くさいというのという状況だけで済みます。

ですが例えばこのような状況はどうでしょうか。

家電量販店のシステムを開発しているとして、Aファイルにはすでに価格設定処理が追加されていると想定してください。

A,B,CさんはAファイルを自分の環境に持ってくる
AさんはAファイルに商品登録処理を追加して共有フォルダに戻す!
BさんはAファイルに在庫管理処理を追加して共有フォルダに戻す!
CさんはAファイルに発注処理を追加して共有フォルダに戻す!

共にAファイル.png

見ての通り地獄の始まりですよね。
この時Aさんが1番最初にタスクを終わらせて、Bさん、Cさんの順にタスクが終わったとしましょう。

その場合Aさんはいままで通り、Aファイルを編集して共有フォルダに戻すだけでいいです。
ですが、BさんCさんは違います。

この3人はもともと共有フォルダに置いてあった価格設定処理のみが書かれたAファイルをコピーしてそれぞれ処理を追加しているのですから、最初に終わったAさんはいいですが、BさんCさんにはAさんが追加した処理が反映されていない状況になります。

以上により、この時3人は反映時に以下のことに気を配る必要があります。

・Aファイルに他の二人の処理が反映されていないか
・反映されていた場合、まずそのコードの該当箇所を自分のファイルに反映
・反映したファイルで共有フォルダのAファイルを上書き

面倒くさいですよね。
まあ上書きせずに共有フォルダのAファイルに追加した処理をコピペすればいいんですが、追加処理が多かったり反映ファイルが多かったりすると「追加し忘れ」などが生じる可能性があります。

気配り軽減!Gitの登場

そこで登場するのがGitです。
Gitにはリポジトリというものがあります。
さっきの例で言うと「共有フォルダ」がこのリポジトリに当たります。
実際のソースコードはファイル単位ではなくディレクトリ(フォルダ)単位である場合がほとんどなので、先ほどを例に「Buzz電気」というお店のシステム開発をする想定で見てみましょう。

Buzz電気システムリポジトリという空間にAフォルダがあり、その中にA、B、Cと言う名のファイルがあるとします。
GitではまずそのリポジトリをClone(クローン)してきます。(本来はこの作業の前にforkをした方がいいのですが必ず行わなければならない訳ではない為、割愛しています)

クローンというのは要は「コピー」です。Aフォルダをそのまま自分のところに持ってきます。

先ほどは以下の手順が必要でした。

①A,B,CさんはAファイルを自分の環境に持ってくる

②AさんはAファイルが更新されていないか目視で確認した後、商品登録処理を追加したAファイルを共有フォルダに戻す!

③BさんはAファイルが更新されていないか目視で確認した後、Aさんが追加した箇所を見つけ出し、在庫管理処理を追加したAファイルに反映させた後、そのAファイルを共有フォルダに戻す!

④CさんはAファイルが更新されていないか目視で確認した後、AさんBさんが追加した箇所を見つけ出し発注処理を追加したAファイルに反映させた後、そのAファイルを共有フォルダに戻す!

言葉で表してもややこしい作業です。
これをGitの機能を使って再現してみます。わからない言葉が出てきても一旦スルーしてください。

①A,B,CさんはBuzz電気システムリポジトリ(リモートリポジトリ)を自分の環境にクローン(複製)する

②Aさんは商品登録処理を追加した後、リモートリポジトリをPull(プル)してからCommit(コミット)しPush(プッシュ)します。

②Bさんは在庫管理処理を追加した後、リモートリポジトリをPull(プル)してからCommit(コミット)しPush(プッシュ)します。

②Cさんは商品登録処理を追加した後、リモートリポジトリをPull(プル)してからCommit(コミット)しPush(プッシュ)します。

なんとなく楽な感じが伝わりますか?(伝んねぇよ)

まず、なんども出てきたリモートリポジトリというのが、みんなの共有フォルダみたいなものです。そこに向かって変更を加えていく訳ですが、手元にはそのリモートリポジトリの複製がある状態です。
ここからがGitの幸せなところで、手元にある複製になんらかの機能を追加したとします、Aさんは商品登録処理ですし、Bさんは在庫管理処理を追加するのでしたね。

追記後は、以前のままだと共有フォルダにあるAファイルに他の人がなんらかの処理を追加していないか、目で確認する必要がありました。

ですがGitでは共有フォルダと手元にある複製を見比べ、両者になんらかの差異があれば、手元にある複製にその差分だけ反映してくれるのです。
これが先ほど手順で出てきた「Pull」というものです。英語で見るとわかりやすいですね。Pullは「引く」ですから、リモートリポジトリ(わかりやすく共有フォルダ)から差分を引っ張ってきてくれるわけです。

これで今まで目視で確認していた面倒くさい手順から解放されるわけです。この時点「いいもの」ということはわかりますね。

Pullすることで手元のフォルダと共有フォルダ(リモートリポジトリ)の違いはなくなりました。そのあとはどうしたらいいのでしょうか?手元には自分が追加した処理と共有フォルダ(リモートリポジトリ)に反映されていた処理が記述されたAファイルがあるのですから、もちろんそのAファイルを共有フォルダに戻しますよね。そうすると正式に共有フォルダのAファイルに自分の処理が追加されるわけです。

ではそれだけ目視でコピペしていくのかと言うとそれは違います。
Gitにはコミットという行為があり、コミットとは自分が変更した箇所の集まりだと思ってください。例えば、商品登録処理であれば、商品を登録するための入力フォームを作った時点で、一旦コミットしてその変更を保存しておき、その後その入力フォームから受け取ったデータでなんやかんやする処理を追加した後にまたコミットしたり。
いつでも後戻りできるように保険を作っていくわけです。

最後にそのコミットの塊をPushという行為で共有フォルダに反映させます。これで煩わしい作業から解放されるわけですね。

実はGitにも気を配るべき事項がいくつもあるのですが、それは実際にチーム開発などするときに気をつければいいので今回は割愛しました。かなり長くなりましたがGit管理という考え方をわかって欲しかっただけなので、「もっと詳しくしりたい!」という方は、さらに詳しく書いてあるブログやQiitaなどありますので、こちらをご覧ください。
ただ百聞は一見に如かずです。すこしでも理解したら実際に触ってみることをお勧めします。新卒でIT業界に就職した方は、くれぐれも会社のGitでチャレンジしないでくださいね!!w

こちらのサイトはわかりやすく解説されています。
ここは体型的にGitを学んでいけます。

UnityでのGit管理方法

前置き長くなりましたが本題に入っていきましょうか。
ここからは簡潔にUnityでGit管理していく方法を記述していきます。

準備

僕はWindowsを持ってないのでMacでの説明になりますが、ほとんど変わらないと思うのでその都度補足説明を入れていきます。

①何はともあれ、Unityでプロジェクトを作ろう!

スクリーンショット 2020-01-26 0.39.26.png

いつも通りなんらかのプロジェクトを作りましょう!今回は/User/kohei/UnityGamesというフォルダに「Unity_de_Git」というプロジェクトを作りました。
ここはWindowsも変わらないとおもいます。

②Finder(フォルダ)で確認しよう!

スクリーンショット 2020-01-26 0.42.04.png

今回はGitについてなのでプロジェクト作成後に開くUnityの画面はとりあえず閉じて大丈夫です。
先ほど僕は/Users/kohei/UnityGamesフォルダにプロジェクトを作りました。ご自身のプロジェクト保存先をみて、プロジェクトが作成されていることを確認しましょう。
Windowsの場合もプロジェクトを作成する際、「保存先」として使用したフォルダに行けばプロジェクトがあるはずです。
ちなみに「Users/kohei/UnityGames」という保存先は「Unity/kohei/UnityGames」というフォルダがあるわけではなく、「Usersフォルダの中のkoheiフォルダの中のUnityGamesフォルダ」という意味です。

③できていましたか?中身を確認しましょう

スクリーンショット 2020-01-26 0.44.37.png
では作成したプロジェクトをダブルクリックして開いてみましょう。
バージョンによって多少違うかもしれませんが、WindowsもMacもおおよそこのような内容になっているはずです。

⑤Gitのご登場、、、の前に!

これからGit、、、といきたいところですがその前に、実はGit管理にはコマンドを使う方法というのがあり、プログラマとして勤務する人はわりかし目にする機会があるかもしれませんが、全く無知な方からすると黒い画面に呪文のような文字を打っていくことになり、今自分が何をしているのかわからない状態でGit管理をしても感覚がつかみにくいと思いますので、呪文を使わないやり方でご紹介していきます!慣れたらぜひコマンドにも挑戦してみてくださいね!

ではまずこちらにいって「SourceTree」をダウンロードしましょう!僕はMacなので「Download for Mac OS X」となっていますが、Windowsのパソコンからこのサイトにアクセスすると「Download for Windows」になっているとおもいます。もしなっていない場合は、「Download for Max OS X」ボタンの下に「Also available for Windows」と書いてあるところをクリックしてください。
スクリーンショット 2020-01-26 0.58.59.png
これはGitを直感的に管理できるソフトで、プログラマの方以外でも比較的導入しやすいGUIのGit管理ツールです。
スクリーンショット 2020-01-26 1.01.17.png
僕がダウンロードしようとした時は(2020/01/26現在)このような確認画面がでました。
「あんた、このソフトダウンロードするんやろ?ほなライセンスとポリシーに同意するんやな?」的な文です。
これは下のチェックボックスにチェックを入れましょう。すると「Download」ボタンが押せるようになるはずです。これはWindowsも同じです。
スクリーンショット 2020-01-26 1.04.08.png
スクリーンショット 2020-01-26 9.08.20.png
するとSoucetree_4.0.xxx.zipがダウンロードされました。この数値の部分はバージョンなのでそこまで重要ではありません。
これをクリックして開きましょう。Macなら自動でFinderに展開してくれると思います。Windowsはパソコンによりまちまちなので、任意のフォルダに展開してください。よく使うソフトなのでデスクトップにショートカットアイコンを作ってもいいと思います。
スクリーンショット 2020-01-26 9.13.12.png
Macの場合はいきなり「Sourcetree.app」ができるはずです。アプリケーションなので「アプリケーション」フォルダに移動させておいてもいいかもしれません。
スクリーンショット 2020-01-26 9.15.48.png

⑥実際に作業していこう!

これでダウンロードは終了しました、Sourcetreeを入れればすぐにGit管理できるのかというとそうではないのでこれからはGit管理するための設定なんだりを説明していきます。
ここからはアプリケーションでの作業になるためMacとWindowsで違うことはあまりないと思いますので、最小限に省きます。
まずはSousetreeを開きましょう!
スクリーンショット 2020-01-26 9.40.28.png
.png">

初期画面はこのような感じだと思います。まずはGithubというサービスと接続しましょう!ちなみにこのGithubもGitを使いやすくするサービスなのでGitとは別物です。
右上の歯車のマークをクリックしましょう
スクリーンショット 2020-01-26 9.43.57.png
このように設定一覧が表示されるとおもうので、「アカウント」をクリックしてください。
するとこのような画面が表示されるはずです。左下の追加をクリックしてください。
スクリーンショット 2020-01-26 9.45.10.png
するとこのような画面になると思いますのでホストを「Github」に変更してください。その他画像と違う箇所があれば画像の通りに設定しましょう。
スクリーンショット 2020-01-26 9.49.29.png
画像の通りに設定できたらアカウントの連携です。「接続アカウント」ボタンをクリックしてください。
するとWindows、Macに関わらずブラウザが開きGithubにログインを求められるとおもいます。
スクリーンショット 2020-01-26 9.58.10.png
すでにアカウントを持っている場合はログインしてください。アカウントを持っていない場合は画像下の「Create an account」をクリックして、必要事項を入力してアカウントを作成しましょう。ちなみにアカウント作成時にプランの洗濯を求められるのですが、「Freeプラン」で大丈夫です!
スクリーンショット 2020-01-26 10.01.36.png
するとブラウザからSourcetree開くか聞かれるので開いてください。
開くとこのようにアカウントが接続されていることが確認できると思います。あとは「保存」をクリックしてください。
スクリーンショット 2020-01-26 10.02.42.png
このようにアカウントが表示されたら成功です!
スクリーンショット 2020-01-26 10.05.51.png
ここまできたら一旦設定画面は閉じてメイン画面に移動しましょう。
メイン画面で①の「ローカル」が選択されている状態で②の「新規」をクリックしてください。
スクリーンショット 2020-01-26 10.08.35.png
するとこのような選択肢が表示されるので「ローカルリポジトリを作成」をクリックしてください。
スクリーンショット 2020-01-26 10.10.38.png
ローカルリポジトリというのはリモートリポジトリ(共有フォルダ)と逆で、個人個人のリポジトリです。先ほどはリモートリポジトリにあったAファイルを自分のところに持ってきました。この持ってきたものがGitでは「ローカルリポジトリ」という扱いになります。今回はローカルリポジトリから作成しているので、リモートリポジトリはまだ存在しない状態です。
スクリーンショット 2020-01-26 10.17.18.png
開くとこのように表示されるので
①にUnityのプロジェクトを作成したフォルダを選択してください。
②にはリポジトリの名前をつけることができます。任意で名前をつけてください。特に理由がなければそのままで大丈夫です。
ここで注意してほしいのは①で選択するフォルダは僕で言えば「Users/kohei/UnityGames」ではなく、今回作成したプロジェクトなので画像のように「Users/kohei/UnityGames/Unity_de_Git」になります。あくまでプロジェクトを選択してください。
設定できたら作成をクリックしましょう。「リモートリポジトリも作成する」はとりあえずチェックなしで大丈夫です。
スクリーンショット 2020-01-26 10.24.43.png
するとこのように何も表示されていなかった一覧に今作成したローカルリポジトリが表示されます。

つづく

お疲れ様でした!まだ作業は続くのですが、かなり長くなりそうなので数回に分けます。

次回はこちら←準備中です

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【香川県】App Storeのガイドラインに抵触しない「ゲーム依存症対策条例案」対応を実装してみた【Unity】

タイトルなし3.gif

はじめに

香川県議会による「ネット・ゲーム依存症対策条例案」が話題になっています。

これは18歳以下の子どもがネット・ゲーム依存状態になることを防ぐ目的で、ゲームのプレイ時間を平日は60分(休日は90分)までに制限する項目が盛り込まれています。

問題は、ゲームソフトを提供する事業者等に対しても上記の条例に対して協力する義務を課せられる可能性があります。可決されたら令和2年4月1日より施行されるため、開発者としては早急に今後必要な対策が気になるところです。

スクリーンショット 2020-01-24 1.02.50.png
出典: 香川県の「ネット・ゲーム依存症対策条例案」【全文】 - ITmedia NEWS

この記事ではiOS向けのApp Storeに配布されるゲームアプリにおいてどういった対応ができるか検討し、Unityを使って実装してみました。

※ 専門的な法律の知見に基づくものではないので、情報の取り扱いは自己責任でお願いします。

条例により課せられる制限と対応

第18条2項より下記のような基準になります。(第2条(4)より、「子ども」とは18歳未満の者を指すようです)

  • 総利用時間の制限
    • 18歳未満
      • 通常: 1日60分まで
      • 休日: 1日90分まで (学校等の休業日)
  • 利用時間帯の制限
    • 15歳以下: 21:00まで (義務教育修了前)
    • 18歳未満: 22:00まで

ただし、これらはあくまで基準で「子どもの年齢や各家庭の実情等を考慮し、話し合いの上で使用に関するルールづくりをする」という旨の記載があります。正確に準拠するのなら各家庭の状況に応じて規制の強化・緩和を行えるべきかもしれませんが、今回は考慮していません。

『あなたは香川県民ですか?』は実現可能か

法律的には「属地主義の原則」という考え方があるそうです。これは、法律は定められた領土内に対して適用されるというもので、例えば国内の法律は原則として海外にいる時に適用されません。今回の条例をこの考えに照らし合わせて考えると、適用される人は「香川県民」ではなく「今現在香川県内にいる人」となりそうです1

この場合、実装としてはGPSなどを用いたリアルタイムの位置情報を利用することが好ましいかと思います。

年齢に関する情報の取得

条例案の対象になるのは18歳未満の子どもですが、その中でも段階的な利用時間帯の制限があるため、3段階の区別が必要になります。「義務教育修了前の子供については」という記載があるため、年齢よりも中学生以下かどうかで区別する方が好ましそうです。

質問形式による取得の場合、虚偽の情報を入力できてしまう可能性が考えられます。一部マッチングアプリやペイメント系サービスのように本人確認書類等を用いた年齢認証を行う方法もありますが、こちらの記事によると香川県議会の「自主的な対策を行って欲しい」という旨の説明をしているため、そこまで厳格な対応は必要ないように思います。

余談ですが、韓国のシャットダウン制では住民登録番号を用いた識別が義務付けられており、違反したメーカーは2年以下の懲役、又は1千万ウォン(約75万円)以下の罰金の処罰が下されるそうです。

参考: 韓国最新オンラインゲームレポート 「青少年夜間ゲームシャットダウン制」に揺れる韓国オンラインゲーム業界 - GAME Watch

プレイ時間の制限

単一のゲームでプレイ時間の制限を行うことは難しくないです。アプリの起動時間を計測し、規程の60分を経過した時点でプレイに制限をするような実装になるかと思います。時間帯についても、端末もしくはネットワークを介した現在時刻を取得すれば実現できます。

問題は「複数のゲームのプレイ時間を合算した制限をかける必要があるのか」という点です。例えば、一つのゲームを60分間遊んだ後に制限されてしまっても、別のゲームでは制限がかかっていない状態で遊べてしまうような状況が考えられます。

この対策には各事業者毎のゲームにおいてプレイ時間を記録し、ネットワークを介して共有するような仕組み作りが必要なので非常に困難です。少なくとも単一の事業者で対応できるようなことではないため今回は考慮しないものとします。

App Store Reviewガイドラインに抵触しそうなポイント

App Store Reviewガイドライン (日本語)

個人情報の収集に関する項目で抵触する可能性があり、最悪の場合アプリのリリース審査に通らない場合が考えられます。特に近年のAppleは個人情報の保護に非常に力を入れており、例えば2018年10月からは全てのアプリにプライバシーポリシーが必須となりました。

参考: 予定されているプライバシーポリシー要件の変更について。 - ニュース - App Store Connect - Apple Developer

抵触しないためには「何のために収集するのか」をユーザーにきちんと示すこと、不必要な情報を収集しないこと、また上記のプライバシーポリシーの提出が必要だと思います。

参考: 個人情報を送信するアプリには必然性がいる 〜 17.2対応 - Qiita

「子ども向け」カテゴリの場合

さらに、App Storeの「子ども向け」カテゴリでは、子ども達が安心して遊べるようより厳格な個人情報の保護が求められています (ここでいう「子ども」は11歳以下を指す(参考)))

「子ども向け」カテゴリのAppでは、個人を特定できる情報またはデバイス情報を第三者に送信することはできません。また、「子ども向け」カテゴリのAppには、他社製の分析機能や広告を組み込むことはできません。

参考: 1.3 「子ども向け」カテゴリ

これを見ると一切の個人情報の収集が認められていないように見えますが、法的事項/プレイバシーに関する章には下記のようにも記載されています。

多くの理由から、子どもの個人データを扱う場合は厳重な注意が求められます。児童オンラインプライバシー保護法(COPPA)やEU一般Data protection規則(GDPR)のような法律、およびその他の適用される規制または法律をすべて慎重に確認してください。
Appでは、これらの法律に準拠する目的でのみ生年月日や保護者の連絡先を要求することができます。

参考: 5.1.4 「子ども向け」

今回の条例案は「その他の適用される規制または法律」に該当するように思うので、準拠する目的に限定していれば認められそうです。

余談: ペアレンタルコントロール

iOS 12以降のペアレンタルコントロールという機能では、保護者がアプリのカテゴリ毎に起動時間の制限をかけることができます。下記の画像のように、ゲームカテゴリに対して制限時間を設定すると、上限時間を超えた場合に該当アプリがグレーアウトする仕様になっています。

1579951896.jpg

各ゲーム事業者の立場では対応に限界があるので、香川県にお住まいのお子さんにiPhoneを貸与している保護者の方は是非ご活用ください。

参考: お子様の iPhone、iPad、iPod touch でペアレンタルコントロールを使う - Apple サポート

条例に準拠したUnity製サンプル【Udon】

上記を踏まえ、以下のようなダイアログを実装してみました2

GitHub: nkjzm/Udon
タイトルなし3.gif

(Gif画像にはリポジトリには含まれていないフォント(URW MARU GOTHIC)と画像アセット(Simple UI)を使用しています)

サンプル(Udon)の使い方

リポジトリReleasesから最新の*.unitypackageをダウンロードし、Unityプロジェクトにインポートしてください。Google Geocoding APIを使用しているため、別途API Keyの取得が必要です。

var popup = Instantiate(Prefab, Canvas.transform);
popup.Open(onComplete: flg =>
{
    Debug.Log(flg ? "設定完了" : "未完了");
});

表示の際にはInstantiateメソッドで生成し、Open()メソッドを呼んでください。

ポイント①: 個人情報収集の目的を明記する

ダイアログ内では収集する目的や背景を説明した上で、これ以外の目的にしようしないことを明記しています。また、画面下部にはプライバシーポリシーのリンクも設置しています。

ポイント②: 年齢の確認

今回の用途では正確な生年月日は必要ないため、どの区分に該当するかどうかのみを質問する形式にしました。入力が容易であるというメリットもあります。

ただし時間の経過で区分が変わった場合に対応できないデメリットがあり、後から登録した区分を変更する機能は子どもが制限を解除するため悪用されるリスクが伴います。ゲームアプリにおいては課金上限額の設定のため年齢確認をすることがありますが、毎回未成年かどうかを質問する例(『どうぶつの森 ポケットキャンプ』)や、生まれた月までを登録して後から変更できない例(下記画像)、変更の際には運営への問い合わせが必要な例(『荒野行動』)などがあるようです。

622346685927b307757b70011-1495773978.jpg

出典: ブシロードとKLab、『ラブライブ!スクフェス』で年齢別課金上限を設定…未成年者保護のため | Social Game Info

ハイブリットな手法として、として、18歳未満の選択肢を答えた場合のみ「生まれた年と月」を入力してもらうような方法も良いかもしれません。

スクリーンショット 2020-01-26 7.13.34.png

ポイント③: 二つの方法で位置情報を設定する

GPSを用いた位置情報取得では、iOSのシステムダイアログ上でユーザーに権限の許可をしてもらう必要があります。システムダイアログを表示する前に、きちんとユーザーに目的を伝えることが大切です。

スクリーンショット 2020-01-26 7.17.17.png
↑ボタン内に確認画面が出る旨を表記しました

ボタン押下後の画面は下記のようになっています。

1579991647.jpg

↑左が取得が成功した場合、右が取得に失敗した場合

位置情報の取得が成功した場合、GPSの緯度経度からどの県にいるかを取得し表示しています。

取得に失敗した場合ですが、二通りの状況が考えられます。一つは端末自体で位置情報の使用が許可されていない場合で、「設定」アプリから位置情報を有効にしてもらう必要があります。もう一つはシステムダイアログで許可されなかった場合です。iOSの仕様ではシステムダイアログが表示されるのは「初めてそのアプリで位置情報を使おうとした時」のみで、一度許可されなかった場合は再度ダイアログを表示することはできません。許可してもらうためには先ほどと同様に「設定」アプリからの再設定が必要です。

sfasdfas.png

GPSでの位置情報取得に加えて、「手動で位置情報を追加する」ボタンも設置しています。

スクリーンショット 2020-01-26 7.18.48.png

これはガイドラインにある以下の記載に準拠するためのものです。

可能であれば、アクセスに同意しないユーザー向けに別の方法を用意してください。たとえば、位置情報の共有に同意しないユーザーには、住所を手動で入力できる機能を用意することができます。

出典: App Store Reviewガイドライン

位置情報の共有を必須にしてしまうとリジェクトされる可能性があるため、こういった対応をしておくとより安心かと思います。

ポイント④: プレイ制限の情報をユーザーに明示する

入力状況に応じてどんなプレイ制限が適用されるかを、リアルタイムに表示しています。18歳以上、もしくは香川県外にいる場合は、制限がかからない旨が表示されます。

タイトルなし4.gif

こうした表示をすることで、制限が適用されるユーザーがきちんと内容を知ることができます。

また「ポイント①: 個人情報収集の目的を明記」を強化する意図もあり、入力された情報がどのように利用されているか示すことでユーザーより安心して使ってもらえるように思いました。

実装の解説

香川県かどうかの判定でいくつかのハマりポイントがあったので、簡単に解説したいと思います。

位置情報(座標)の取得

UnityでiOSの位置情報を取得する場合はInput.locationを使います。

まず端末自体の位置情報が有効かどうかを調べるためInput.location.isEnabledByUserにアクセスします。ユーザーに位置情報取得の権限を許可してもらうシステムダイアログはInput.locationのいずれかの機能にアクセスした時点で表示されるため、このタイミングが該当します。

    // 端末自体の位置情報が有効か
    if (!Input.location.isEnabledByUser)
    {
        LocationSettingWarning.text = $"「設定」アプリから位置情報を有効にしてください";
        LocationSettingWarning.color = Color.red;
        yield break;
    }

ちなみにシステムダイアログが表示されるUnityアプリケーション自体はスリープ扱いになるため、入力待ちの非同期処理を書く必要なありません。

次に、Input.location.statusの値に応じた処理を行います。メソッド全体を示します。

IEnumerator GetLocation()
{
    // 端末自体の位置情報が有効か
    if (!Input.location.isEnabledByUser)
    {
        LocationSettingWarning.text = $"「設定」アプリから位置情報を有効にしてください";
        LocationSettingWarning.color = Color.red;
        yield break;
    }

    while (true)
    {
        var status = Input.location.status;
        switch (status)
        {
            case LocationServiceStatus.Stopped:
                Input.location.Start();
                break;
            // 位置情報が有効になった場合
            case LocationServiceStatus.Running:
                // Reverse Geocoding APIから件名を取得
                var data = Input.location.lastData;
                StartCoroutine(GetAreaName(data.latitude, data.longitude));
                yield break;
            // ユーザーが位置情報を許可しなかった場合
            case LocationServiceStatus.Failed:
                LocationSettingWarning.text = $"「設定」アプリから位置情報を有効にしてください";
                LocationSettingWarning.color = Color.red;
                yield break;
            default:
                break;
        }
        // 1秒毎に状態を再取得
        yield return new WaitForSeconds(1f);
    }
}

はじめはLocationServiceStatus.Stoppedが返ってくるためInput.location.Start()で開始を要求します。成功する場合はLocationServiceStatus.Initializingを経てLocationServiceStatus.Runningが返ってくるため、Input.location.lastDataから緯度経度の情報を取り出すことができます。ユーザーがパーミッションの許可をしなかったなどの理由で取得に失敗した場合はLocationServiceStatus.Failedが返ってくるため、再設定の旨を表示しています。

難しいのが「位置情報の使用が許可されていないこと」を確認するためには、一度上記の処理を試みる必要がある点です。Input.locationにアクセスした時点でシステムダイアログが表示されてしまうため、状態を確認する前にボタン押下のアクションを挟んでいます。二回目以降で許可されているかどうかを確認するためには初回のシステムダイアログが表示済みかどうかを判断する必要があるため、別途アプリ側で状態を保持する必要がありそうです。

緯度経度から「県名」を取得

GoogleのGeocoding APIを使用しています。これは住所の文字列などから位置座標(緯度経度)を取得するためのAPIですが、Reverse geocodingと呼ばれる「緯度経度→住所」の機能も提供されています。

取得するためのURLはこのような形にになります。認証されたAPI Keyを発行する必要がある点に注意してください(無料枠の範囲内で利用可能)。

var url = $"https://maps.googleapis.com/maps/api/geocode/json?" +
$"latlng={lat},{lng}&result_type=administrative_area_level_1&key={API_KEY}&language=ja";

APIを叩くとこんなJSONが返ってきます。

{
   "plus_code" : {
      "compound_code" : "82RV+28 日本、香川県高松市",
      "global_code" : "8Q6P82RV+28"
   },
   "results" : [
      {
         "address_components" : [
            {
               "long_name" : "香川県",
               "short_name" : "香川県",
               "types" : [ "administrative_area_level_1", "political" ]
            },
            {
               "long_name" : "日本",
               "short_name" : "JP",
               "types" : [ "country", "political" ]
            }
         ],
         "formatted_address" : "日本、香川県",
         "geometry" : {
            "bounds" : {
               "northeast" : {
                  "lat" : 34.5646136,
                  "lng" : 134.4474078
               },
               "southwest" : {
                  "lat" : 34.0123081,
                  "lng" : 133.4465942
               }
            },
            "location" : {
               "lat" : 34.2225915,
               "lng" : 134.0199152
            },
            "location_type" : "APPROXIMATE",
            "viewport" : {
               "northeast" : {
                  "lat" : 34.5646136,
                  "lng" : 134.4474078
               },
               "southwest" : {
                  "lat" : 34.0123081,
                  "lng" : 133.4465942
               }
            }
         },
         "place_id" : "ChIJr363rNTcUzURsdqbibLWgS0",
         "types" : [ "administrative_area_level_1", "political" ]
      }
   ],
   "status" : "OK"
}

今回の実装では、JsonUtilityを使って下記のように県名のみを取得しています。

// 一部処理を抜き出して掲載
IEnumerator GetAreaName(float lat, float lng)
{
    var url = $"https://maps.googleapis.com/maps/api/geocode/json?" +
    $"latlng={lat},{lng}&result_type=administrative_area_level_1&key={API_KEY}&language=ja";
    var request = UnityWebRequest.Get(url);

    yield return request.SendWebRequest();

    var response = JsonUtility.FromJson<Response>(request.downloadHandler.text);
    var prefecture = response.results?[0].address_components?[0].long_name;

    LocationProgress.text = $"あなたの現在地は<color=red>{prefecture}</color>です";
}

[System.Serializable] class Response { public Result[] results; }
[System.Serializable] class Result { public Adress[] address_components; }
[System.Serializable] class Adress { public string long_name; }

ちなみにテスト用にInspectorからTestDummyLocationにチェックを入れると香川県庁の座標で試せるようになっています。

if (TestDummyLocation)
{
    // 香川県庁の座標に置き換える
    var lat = 34.340117f;
    var lng = 134.043312f;
    StartCoroutine(GetAreaName(lat, lng));
    yield break;
}

参考: Get Started  |  Geocoding API  |  Google Developers

最後に

現在香川県では「ネット・ゲーム依存症対策条例」に対するパブリックコメントを募集しています。対象は香川県民もしくはゲーム事業者のみですが、意見のある方は是非公募してみてください。

香川県|香川県ネット・ゲーム依存症対策条例(仮称)素案についてパブリック・コメント(意見公募)を実施します

参考


  1. 「属地主義の原則」を適用すると香川県外のゲーム事業者に対応の義務はないのではないかという議論もあるようです。 

  2. ガイドラインを考慮して実装したものですが、リジェクトされないことを保証するものではありません。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む