- 投稿日:2021-03-21T23:56:24+09:00
Unity初心者がゲームを作ってみる②
前回①の続きから。
ランダムな位置にネコ影を配置できるようになりました
コメントくださった方+有識者兼友人のE氏により、無限増殖しないようにすることができました。
【原因】
ShadowGeneratorをネコ影オブジェクトにそのままアタッチしていたことにより、生成し終わった後にもう一度生成処理が走ってしまっていたため。
【解決方法】
空のオブジェクトを用意してそちらにShadowGeneratorをアタッチすることでゲーム開始時に1度だけ生成することが可能となる。
ネコ影にウキを近づけたら「突っつくネコ影スプライト」へ変更する
ウキが近づいてきたらネコ影がウキを突っついているようなスプライトに変更させていきます。
ウキとネコ影の距離を判定する必要があるので、DistanceManagerを作成しました。DistanceManager.csusing System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; /* * ネコ影とウキの距離判定をするクラス */ public class DistanceManager : MonoBehaviour { GameObject shadow_1; GameObject uki_1; [SerializeField] private GameObject shadow_2; /* * ネコ影とウキのオブジェクトをみつける */ public void FindNekoUkiObject() { this.shadow_1 = GameObject.Find("neko_1(Clone)"); this.uki_1 = GameObject.Find("uki_1"); } /* * オブジェクト間の距離を判定する */ public void JudgeDistance() { Vector3 shadowPosition = shadow_1.transform.position; Vector3 ukiPosition = uki_1.transform.position; float distance = Vector3.Distance(shadowPosition, ukiPosition); if (distance <= 1.0f) { // つつくネコ影(shadow_2)へスプライト変更 shadow_1.GetComponent<SpriteRenderer>().sprite = shadow_2.GetComponent<SpriteRenderer>().sprite; } else { // なにもしない } } }JudgeDistance()はウキとネコ影の距離を常に測っていたいのでUkiController.csのUpdate()で呼ぶように追加しました。
UkiController.csusing System.Collections; using System.Collections.Generic; using UnityEngine; /* * ウキの動きを制御するクラス */ public class UkiController : MonoBehaviour { GameObject distanceManager; void Start() { // DistanceManagerを設定する distanceManager = GameObject.Find("DistanceManager"); distanceManager.GetComponent<DistanceManager>().FindNekoUkiObject(); } void Update() { // 左クリックされた場合 if (Input.GetMouseButton(0)) { // ウキ→カーソル方向のベクトル取得 Vector3 vector = Camera.main.ScreenToWorldPoint(Input.mousePosition) - this.transform.position; // 正規化(ベクトルの大きさが1になる) Vector3 vectorUki = vector.normalized; vectorUki = new Vector3(vectorUki.x, vectorUki.y, 0); // ベクトルの向きにウキを移動 this.transform.position += vectorUki; } // DistanceManagerの距離判定を呼び出す distanceManager.GetComponent<DistanceManager>().JudgeDistance(); } }これでウキにネコ影が近づくと突っつくネコ影スプライトが表示されるようになりました。
ランダムな秒数待機して「沈むウキスプライト」へ変更する
次に、ランダムな秒数待機しウキが沈むようなスプライトへ変更させます。
待機してから処理を行う方法については下記サイトを参考に作成しました。
http://tsujitaku50.hatenablog.com/entry/2017/01/11/204150DistanceManager.csusing System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; /* * ネコ影とウキの距離判定をするクラス */ public class DistanceManager : MonoBehaviour { GameObject shadow_1; GameObject uki_1; [SerializeField] private GameObject shadow_2; [SerializeField] private GameObject uki_2; /* * ネコ影とウキのオブジェクトをみつける */ public void FindNekoUkiObject() { this.shadow_1 = GameObject.Find("neko_1(Clone)"); this.uki_1 = GameObject.Find("uki_1"); } /* * オブジェクト間の距離を判定する */ public void JudgeDistance() { Vector3 shadowPosition = shadow_1.transform.position; Vector3 ukiPosition = uki_1.transform.position; float distance = Vector3.Distance(shadowPosition, ukiPosition); Sprite shadow1Sprite = shadow_1.GetComponent<SpriteRenderer>().sprite; if (distance <= 1.0f) { // shadow_1のスプライトが変更されていなかった場合 if (shadow1Sprite.Equals(shadow_1.GetComponent<SpriteRenderer>().sprite)) { // スプライト変更処理呼び出し StartCoroutine("changeSprite"); } } else { // なにもしない } } /* * ネコ影とウキのスプライト変更を行う */ IEnumerator changeSprite() { // つつくネコ影(shadow_2)へスプライト変更 shadow_1.GetComponent<SpriteRenderer>().sprite = shadow_2.GetComponent<SpriteRenderer>().sprite; // ランダムな秒数待機する yield return new WaitForSeconds(Random.Range(4.5f, 7.5f)); // 沈むウキ(uki_2)へスプライト変更 uki_1.GetComponent<SpriteRenderer>().sprite = uki_2.GetComponent<SpriteRenderer>().sprite; } }JudgeDistance()を呼び出しているのがUkiControllerのUpdate()なので、changeSprite()が何度も呼ばれないように「shadow_1のスプライトが変更されていなかった場合」で処理の呼び出し判定を行っています。
以上の処理で、
ネコ影にウキを近づける→ウキを突っつくネコ影スプライトが表示
→何秒か待つ→沈むウキのスプライトが表示
ができあがりました。
次はユーザーの釣り上げ処理を作っていきます。
コードの改善点などありましたら是非お気軽にコメントください!
- 投稿日:2021-03-21T20:59:14+09:00
PlayFabのGoogle Play Game連携でAddOauthScope("profile")が必要な件について
概要
Unity で Google Play ゲームを使用して PlayFab 認証を設定する(マイクロソフト公式, 2018/06/11)(以下、本家)
「Unity ゲームへの Google サインインの追加」のコードでAddOauthScope("profile")
が必要になっていることの調査。環境
- Unity 2019.4.17f1
- PlayFab SDK 2.104.210208
- Google Play Games plugin for Unity v0.10.12
本家ドキュメントなどを参考にひととおりセットアップが終わっているものとします。
Google サインインのコード
本家ドキュメントからコピペ
PlayGamesClientConfiguration config = new PlayGamesClientConfiguration.Builder() .AddOauthScope("profile") .RequestServerAuthCode(false) .Build(); PlayGamesPlatform.InitializeInstance(config);サインインのダイアログ
「Googleで公開されているお客様の個人情報とお客様を関連付ける」とかいう、ちょっとものものしく感じる許可を求められます。
言っても公開されている個人情報ってデフォルトは氏名ぐらいではあるんですが、ゲーム程度で要求されたらサインインを躊躇orプレイ自体離脱するユーザーもいそうな気配。
PlayFabマスタープレイヤーアカウント
サインイン→PlayFabClientAPI.LoginWithGoogleAccountしたら、作成されたPlayFabのマスタープレイヤーアカウントにもガッツリ氏名が出てきます(下側の黒塗り部分)
アカウント連携の目的って、端末変更した時のユーザー紐付けだけできたら良くて、氏名はゲームのアカウントに必要な情報ではないと思うんですけどねー
AddOauthScope("profile")
を削除してみるPlayGamesClientConfiguration config = new PlayGamesClientConfiguration.Builder() .RequestServerAuthCode(false) .Build(); PlayGamesPlatform.InitializeInstance(config);必要なのはPlayFabClientAPI.LoginWithGoogleAccountのrequestに含めるServerAuthCodeだけで、profileとか要らないのでは? と思ったんですが、
AddOauthScope("profile")
無しだと、PlayFabClientAPI.LoginWithGoogleAccountで以下のエラーが返ってきて、PlayFabログインに失敗します。
(PlayFabError.GenerateErrorReportをDebug.Logしたものをlogcatで取得)I Unity : /Client/LoginWithGoogleAccount: GoogleOAuthNoIdTokenIncludedInResponseサインインのダイアログ
「Googleで公開されているお客様の個人情報とお客様を関連付ける」がなくて、健全っぽい。
(でもPlayFabログインでエラーになるから、ダメ)
Firebaseなど他のWebサービスとの連携のサンプルコードをWebで見ていると、この
AddOauthScope("profile")
無しパターンなので、PlayFabもそれじゃダメなのかなーと思うわけですが、うーむ![]()
RequestIdToken()
してみる
GoogleOAuthNoIdTokenIncludedInResponse
というエラーコードをふまえて、IdTokenが要るのかな? と思ってAddOauthScope("profile")
の代わりにRequestIdToken()
を追加してみました。PlayGamesClientConfiguration config = new PlayGamesClientConfiguration.Builder() .RequestServerAuthCode(false) .RequestIdToken() .Build(); PlayGamesPlatform.InitializeInstance(config);これだとサインイン→PlayFabClientAPI.LoginWithGoogleAccountでPlayFabアカウント作成できました。
サインインのダイアログ
「Googleで公開されているお客様の個人情報とお客様を関連付ける」有り
![]()
APIのスコープ説明を見てると、OAuthでopenid取り扱うこと自体がこれにあたるから、この個人情報〜の許可確認はあって然るべきということなのかな?
PlayFabマスタープレイヤーアカウント
ここはユーザーに見える画面ではないので、ユーザー目線では何も変わっていないけど、現状はこれが落とし所なのかなーという感じです。
宣伝
そんなこんなでGoogle Play ゲームとPlayFabを連携させたゲームがこちらです。
PlayFabでは、先に作ったWebGL版(unityroomで公開)と共有になるランキング(リーダーボード)機能程度しか使っていないのに、個人情報の関連付け要求とか表示されるのしのびないのですが…
Google Play ゲームのサインインを拒否した場合も、Android端末に紐づけてユーザー作るので、遊べないことはないです。
あとGoogle Play ゲーム初めて使ったついでに、実績(クエスト、アチーブメント)もつけてみました。参考文献
- 投稿日:2021-03-21T18:54:27+09:00
【M1 BigSur】JenkinsからGit(LFS)のチェックアウトからUnityへのビルドまで(Github)
概要
Mac Book Air (M1 2020)
macOS Big Sur 11.2.2【M1 BigSur】Jenkins導入編
こちらで書かせたいただいた記事の続きです。
この記事を残しておきたかったのでついでにJenkins導入を書いた次第です。
JenkinsからGitでチェックアウトができればいいので別にUnityでなくてもいいですが今回は確認のために利用しています。一連の流れを記事にしましたが割と困るのが
BigSurでのgit-lfs filter-process: git-lfs: command not found
エラーの回避方法だと思います。準備
Github
Githubで作成したリポジトリーを使っているのでアカウントは作成しておきましょう。
unityのインストール
・とりあえずUnityはインストールしておいてください。
今回はUnity2020.3.0f1
で試しています。GitのSSHキー(作っていない場合)
$ ssh-keygenいろいろ聞かれるみたいですがすべて空白ままエンターして続けました。
生成場所は
Users/[ユーザー名]/.ssh/
に作られるのでこちらを使用してGithubからSSHを登録します。
Githubのアカウントから
Settings>SSH and GPC keys
を選択してNew SSH Key
を選択する。
設定 内容 Title これは好きに入力してください。 Key ここは先ほど作成した Users/[ユーザー名]/.ssh/id_rsa.pub
の中をコピーして貼り付けてください。このように作られます。
JenkinsのUnityプラグインを導入
インストール後に再起動してもらうために
Download now and install after restart
を選択しました。
インストールできました。
設定したら保存しましょう。
インストールディレクトリにはjenkinsをインストールしたUnityのパスを設定しますが
FinderでUnityからoptionを押しながらするとパスをコピーできます。
ジョブを作成
最初なので
ジョブ名:
unity_test
お好きな名前
フリースタイル・プロジェクトのビルド
で作成します。
認証情報を追加します。
追加した認証情報を選択して保存します。
これでプロジェクトの設定はできました。
Unityのビルドをしてみる
リポジトリーの準備ができたのでunityに対してビルドしてみます。
適当にビルドするものを用意して試してみました。
BuildTest.cs(ビルドの確認)
BuildTest.csusing UnityEngine; namespace Build { /// <summary> /// ビルドのサンプル /// </summary> public class BuildTest { public static void HelloWorld() { Debug.Log("Hello World!"); } } }
Invoke Unity3d Editor
を選択してビルドの確認のために作ったビルドをjenkinsから呼び出してみます。
-quit -batchmode -projectPath /Users/[ユーザー名]/.jenkins/workspace/[githubのプロジェクトパス] -executeMethod Build.BuildTest.HelloWorld
/Users/[ユーザー名]/.jenkins/workspace/[githubのプロジェクトパス]
を設定して
ビルド実行
してみましょう。ビルド時のエラー
git-lfs filter-process: git-lfs: command not found
GitLFSをインストールしているか確認してなければインストール
ターミナル:-zsh% git lfs versionインストールなしgit: 'lfs' is not a git command. See 'git --help'.インストールありgit-lfs/2.13.2 (GitHub; darwin amd64; go 1.15.6)git-lfsをインストール
ターミナル:-zsh% brew install git-lfsBigSurではCatalinaと違って読み取り専用な
/usr/bin/
などを一時的に無効にしてシンボリックリンクを作成できないみたいなので、Appleの公式の書かれている書き込み可能なマウントパスに設定してgit-lfsのシンボリックリンクを作成します。・リカバリモードで再起動して実行します(M1の場合は電源ボタンを長押ししてリカバリーモードに入ります。)
セキュリティの確認をします。
ターミナル:-zsh% csrutil statusSystem Integrity Protection status: enabled.enabledの場合は無効に
ターミナル:-zsh% csrutil disableターミナル:-zsh% csrutil authenticated-root disableMacOSに再起動します。
ここからgit-lfsのシンボリックリンクを作成するためにマウントフォルダーを作成してディスクをマウントします。
ターミナル:-zsh% mkdir ~/mountターミナル:-zsh% sudo mount -o nobrowse -t apfs /dev/[デバイス] ~/mount
[デバイス]
はディスクユーティリティの値を見ます。あなたのルートを検索-あなたのルートは/dev/disk1s2s3であればmount実行し、最後のを切り落とす、例えば、あなたは/dev/disk1s2をマウントします
Appleの公式に書かれているの通りに
disk3s1s1
の場合はdisk3s1
でマウントします。ターミナル:-zsh% sudo ln -s /usr/local/bin/git-lfs ~/mount/usr/bin/ターミナル:-zsh% sudo bless --folder ~/mount/System/Library/CoreServices --bootefi --create-snapshotそのまま再起動
ターミナル:-zsh% sudo reboot再度jenkinsの
ビルド実行
してみてください。参考リンク
【Jenkins】GitのSSH接続をするための認証情報を設定する【GitHub】
JenkinsでUnityプロジェクトをビルドする
Unityコマンドライン引数
「macOS Big Sur 」でgit-lfs filter-process: git-lfs: command not foundに対応する
- 投稿日:2021-03-21T18:06:49+09:00
GitHub for Unityでコミットできない時
起こった現象
プロジェクトの途中でGitHub for Unityを導入したところ、Commitボタンを押しても「Staging Changes...」のまま動かず、しばらく経つとコミット自体が自動キャンセルされる。
(GitHub for Unityの導入方法は以下の記事を参考にさせていただきました。)
【超初心者向け】Unityのプロジェクトを、GitHub for Unityを使って超簡単にバックアップする方法 - Qiita
解決策
結局このプロジェクト内では何をやっても直らず、新しいプロジェクトを作って移植したらうまくいきました。
プロジェクトの最初にGitHub for Unityを導入するのがおすすめです。
途中から入れた場合でもコミットできるようになる方法があれば、コメント頂けると幸いです。
- 投稿日:2021-03-21T16:58:10+09:00
github actionsでunityビルドしてiOSとAndroidのストアに自動的に提出する
こんにちは。virapture株式会社のもぐめっとです。
この記事を書いてるときは冬なんですけど、もう自分の写真がなさすぎて夏までさかのぼってしまいました。
本日はCI/CD with Unity, GitHub Actions, and Fastlaneという記事を参考にUnityでの自動ビルドとストア提出を作ってみたのでそのメモ書きを残しておきます。
概要
UnityにはUnity Cloud Buildという公式のCIがあるのですが、いかんせんfastlaneとの連携ができないので、証明書を引っ張ってきたり、ストアにアップロードというのができません。
そこで探してみたところ、GameCIが出しているunity-builderというgithub actionsを使ってビルドを行い、デプロイにはfastlaneを使用することでうまくできました。
このunity-builderが結構優秀で、ライセンスの認証とかもやってくれる。そして、ビルドが終わった後はライセンスをリターンしてくれるという優れもの。
もはやUnity Cloud Build使わなくてもCIができちゃいます。構築手順
github actions workflowの設置
記事の人のリポジトリのファイルをベースにしながら作りました。
ios,android以外にもmac, windowsなどもあったのですが、邪魔だったのでそのへんは抹消しました。
こんなワークフローになってます。
iOSのビルド長すぎぃぃ。。
お待ちかね実体ファイルはこんな感じになってます。name: Test, Build, and Release CGS on: # push: { branches: [master] } #masterにpushされたときにやりたい人はこちらをお使いください release: { types: [published] } env: UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} BUILD_NUMBER: ${{ github.run_number }} jobs: tests: name: Test Code Quality runs-on: ubuntu-latest timeout-minutes: 60 steps: - name: Checkout Repository uses: actions/checkout@v2 with: fetch-depth: 0 - name: Cache Library uses: actions/cache@v2 with: path: Library key: Library - name: Run EditMode and PlayMode Tests uses: game-ci/unity-test-runner@main - name: Publish Test Results if: ${{ always() }} # Avoid skipping on failed tests uses: davidmfinol/unity-test-publisher@main with: githubToken: ${{ secrets.GITHUB_TOKEN }} buildWithLinux: name: Build for ${{ matrix.targetPlatform }} by Unity runs-on: ubuntu-latest timeout-minutes: 90 needs: tests strategy: fail-fast: false matrix: targetPlatform: - Android - iOS steps: - name: Checkout Repository uses: actions/checkout@v2 with: fetch-depth: 0 lfs: true - name: Cache Library uses: actions/cache@v2 with: path: | Library build/${{ matrix.targetPlatform }} key: Library-${{ matrix.targetPlatform }}- restore-keys: Library- - name: Free Disk Space for Android if: matrix.targetPlatform == 'Android' run: | sudo swapoff -a sudo rm -f /swapfile sudo apt clean docker rmi $(docker image ls -aq) df -h - name: Build Unity Project uses: game-ci/unity-builder@main with: customParameters: -buildNumber ${{ github.run_number }} targetPlatform: ${{ matrix.targetPlatform }} buildMethod: Cgs.Editor.BuildCgs.BuildOptions androidAppBundle: true androidKeystoreName: keystore.keystore androidKeystoreBase64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} androidKeystorePass: ${{ secrets.ANDROID_KEYSTORE_PASS }} androidKeyaliasName: ${{ secrets.ANDROID_KEYALIAS_NAME }} androidKeyaliasPass: ${{ secrets.ANDROID_KEYALIAS_PASS }} - name: Upload Build uses: actions/upload-artifact@v2 if: github.event.ref != 'refs/heads/develop' with: name: cgs-${{ matrix.targetPlatform }} path: build/${{ matrix.targetPlatform }} releaseToGooglePlay: name: Release to the Google Play Store runs-on: ubuntu-latest timeout-minutes: 60 needs: buildWithLinux if: github.event.action == 'published' env: GOOGLE_PLAY_KEY_FILE: ${{ secrets.GOOGLE_PLAY_KEY_FILE }} GOOGLE_PLAY_KEY_FILE_PATH: ${{ format('{0}/fastlane/api-finoldigital.json', github.workspace) }} ANDROID_BUILD_FILE_PATH: ${{ format('{0}/build/Android/Android.aab', github.workspace) }} ANDROID_PACKAGE_NAME: com.sample.app # 自分のアプリのパッケージ名に合わせよう! RELEASE_NOTES: ${{ github.event.release.body }} steps: - name: Checkout Repository uses: actions/checkout@v2 - name: Download Android Artifact uses: actions/download-artifact@v2 with: name: cgs-Android path: build/Android - name: Prepare for Upload run: | echo "$GOOGLE_PLAY_KEY_FILE" > $GOOGLE_PLAY_KEY_FILE_PATH echo "$RELEASE_NOTES" > fastlane/metadata/android/en-US/changelogs/default.txt - name: Upload to Google Play uses: maierj/fastlane-action@v1.4.0 with: lane: 'android playstore' buildIOS: name: Build Archive for iOS runs-on: macos-latest timeout-minutes: 60 needs: buildWithLinux if: github.event.action == 'published' env: APPLE_CONNECT_EMAIL: ${{ secrets.APPLE_CONNECT_EMAIL }} APPLE_DEVELOPER_EMAIL: ${{ secrets.APPLE_DEVELOPER_EMAIL }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} APPLE_TEAM_NAME: ${{ secrets.APPLE_TEAM_NAME }} APPLE_ITC_TEAM_ID: ${{ secrets.APPLE_ITC_TEAM_ID }} MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} MATCH_PERSONAL_ACCESS_TOKEN: ${{ secrets.MATCH_PERSONAL_ACCESS_TOKEN }} IOS_APP_ID: com.sample.app # bundle idを指定します IOS_BUILD_PATH: ${{ format('{0}/build/iOS', github.workspace) }} PROJECT_NAME: onenitejinro RELEASE_NOTES: ${{ github.event.release.body }} MATCH_REPOSITORY_ACCOUNT: ${{ secrets.MATCH_REPOSITORY_ACCOUNT }} USYM_UPLOAD_AUTH_TOKEN: 'fake' # ビルドが途中でこけるのでfake用に環境変数を追加。 steps: - name: Checkout Repository uses: actions/checkout@v2 - name: Cache restore for debug uses: actions/cache@v2 with: path: | Library build/iOS key: Library-iOS- restore-keys: Library- - name: Download iOS Artifact uses: actions/download-artifact@v2 with: name: cgs-iOS path: build/iOS - name: Cache restore cocoapods # firebase使ってるとcocoapodsを使うのですが、cocoapodsのキャッシュとらないと毎回時間かかるのでキャッシュしておきます。 uses: actions/cache@v2 if: ${{ always() }} with: path: | build/iOS/iOS/Pods ~/.cocoapods/repos key: Pods-${{ hashFiles('**/Podfile') }} restore-keys: Pods- - uses: actions/setup-ruby@v1 with: ruby-version: '2.7.2' - name: Prepare for fastlane # GateKeeper対策 run: | sudo spctl --master-disable - name: Archive iOS uses: maierj/fastlane-action@v2.0.1 with: lane: 'ios build' - name: run if fail_step failed # ビルドがコケた原因がわかるようにcatしておきます if: failure() run: cat /Users/runner/Library/Logs/gym/*Unity-iPhone.log - name: Upload Build uses: actions/upload-artifact@v2 if: github.event.ref != 'refs/heads/develop' with: name: ipa path: | ${{ github.workspace }}/*.ipa ${{ github.workspace }}/*.dSYM.zip releaseToAppStore: name: Release to the App Store runs-on: macos-latest timeout-minutes: 60 needs: buildIOS if: github.event.action == 'published' env: APPLE_CONNECT_EMAIL: ${{ secrets.APPLE_CONNECT_EMAIL }} APPLE_DEVELOPER_EMAIL: ${{ secrets.APPLE_DEVELOPER_EMAIL }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} APPLE_TEAM_NAME: ${{ secrets.APPLE_TEAM_NAME }} APPLE_ITC_TEAM_ID: ${{ secrets.APPLE_ITC_TEAM_ID }} FASTLANE_PASSWORD: ${{ secrets.FASTLANE_PASSWORD }} FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: ${{ secrets.FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD }} MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} MATCH_PERSONAL_ACCESS_TOKEN: ${{ secrets.MATCH_PERSONAL_ACCESS_TOKEN }} ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }} ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }} ASC_KEY_CONTENT: ${{ secrets.ASC_KEY_CONTENT }} IOS_APP_ID: com.sample.app # bundle idを指定します IOS_BUILD_PATH: ${{ format('{0}/build/iOS', github.workspace) }} PROJECT_NAME: onenitejinro RELEASE_NOTES: ${{ github.event.release.body }} MATCH_REPOSITORY_ACCOUNT: ${{ secrets.MATCH_REPOSITORY_ACCOUNT }} steps: - name: Checkout Repository uses: actions/checkout@v2 - name: Download iOS Artifact uses: actions/download-artifact@v2 with: name: ipa path: | ${{ github.workspace }}/*.ipa ${{ github.workspace }}/*.dSYM.zip - name: Upload to the App Store uses: maierj/fastlane-action@v1.4.0 with: lane: 'ios release'このファイルのポイントを話しておくと、linuxでビルドが終わった後、fastlaneを使ってandroid/iosともにビルドもしくはアップロードを行っています。
iosは結構曲者で、UnityでCLIビルドが結構大変なので随所随所にビルドできるような仕組みを置いてます。
fastlaneの設置
fastlaneで使うファイルを参考ファイルをベースにカスタマイズした下記を設置しています。
keychain_name = "temporary_keychain" keychain_password = SecureRandom.base64 platform :android do desc "Upload a new Android version to the Google Play Store" lane :playstore do upload_to_play_store( aab: "#{ENV['ANDROID_BUILD_FILE_PATH']}", track: 'internal', skip_upload_screenshots: true, skip_upload_images: true ) end end platform :ios do desc "Push a new release build to the App Store" lane :release do api_key = app_store_connect_api_key( key_id: ENV['ASC_KEY_ID'], # your key id issuer_id: ENV['ASC_ISSUER_ID'], # your issuer id key_content: ENV['ASC_KEY_CONTENT'], # your secret key body # ex) key_content: '-----BEGIN PRIVATE KEY-----\nfoobar\n-----END PRIVATE KEY-----' ) upload_to_app_store( api_key: api_key, # pass api_key force: true, skip_screenshots: true, skip_metadata: true ) end desc "Submit a new Beta Build to Apple TestFlight" lane :beta do api_key = app_store_connect_api_key( key_id: ENV['ASC_KEY_ID'], # your key id issuer_id: ENV['ASC_ISSUER_ID'], # your issuer id key_content: ENV['ASC_KEY_CONTENT'], # your secret key body # ex) key_content: '-----BEGIN PRIVATE KEY-----\nfoobar\n-----END PRIVATE KEY-----' ) upload_to_testflight( api_key: api_key, # pass api_key skip_waiting_for_build_processing: true ) end desc "Create .ipa" lane :build do cocoapods(podfile: "#{ENV['IOS_BUILD_PATH']}/iOS/Podfile") disable_automatic_code_signing(path: "#{ENV['IOS_BUILD_PATH']}/iOS/Unity-iPhone.xcodeproj") certificates update_project_provisioning( xcodeproj: "#{ENV['IOS_BUILD_PATH']}/iOS/Unity-iPhone.xcodeproj", target_filter: "Unity-iPhone", profile: ENV["sigh_#{ENV['IOS_APP_ID']}_appstore_profile-path"], # より動的にみるようにしています code_signing_identity: "Apple Distribution: #{ENV['APPLE_TEAM_NAME']} (#{ENV['APPLE_TEAM_ID']})" ) gym( workspace: "#{ENV['IOS_BUILD_PATH']}/iOS/Unity-iPhone.xcworkspace", scheme: "Unity-iPhone", clean: true, #clean: false, skip_profile_detection: true, codesigning_identity: "Apple Distribution: #{ENV['APPLE_TEAM_NAME']} (#{ENV['APPLE_TEAM_ID']})", export_method: "app-store", export_options: { method: "app-store", provisioningProfiles: { ENV["IOS_APP_ID"] => "match AppStore #{ENV['IOS_APP_ID']}" } } ) end desc "Synchronize certificates" lane :certificates do cleanup_keychain create_keychain( name: keychain_name, password: keychain_password, default_keychain: true, lock_when_sleeps: true, timeout: 3600, unlock: true ) match( type: "appstore", readonly: true, keychain_name: keychain_name, keychain_password: keychain_password ) end lane :cleanup_keychain do if File.exist?(File.expand_path("~/Library/Keychains/#{keychain_name}-db")) delete_keychain(name: keychain_name) end end after_all do if File.exist?(File.expand_path("~/Library/Keychains/#{keychain_name}-db")) delete_keychain(name: keychain_name) end end end元ファイルからcocoapodsのインストールや、api keyまわりの設定、provisioning profileの指定方法などが若干違います。
また、問題を切り分けやすくするためにビルドとapp storeにリリースする処理はわけたりしています。
参考元ファイルでは現在、apikey周りは証明書をおいていい感じにやるようにしているみたいです。
他にfastlaneに付随するファイルも置いていきます
- Appfile
for_platform :android do package_name(ENV["ANDROID_PACKAGE_NAME"]) json_key_file(ENV["GOOGLE_PLAY_KEY_FILE_PATH"]) end for_platform :ios do app_identifier(ENV["IOS_APP_ID"]) apple_dev_portal_id(ENV["APPLE_DEVELOPER_EMAIL"]) # Apple Developer Account itunes_connect_id(ENV["APPLE_CONNECT_EMAIL"]) # App Store Connect Account team_id(ENV["APPLE_TEAM_ID"]) # Developer Portal Team ID itc_team_id(ENV["APPLE_ITC_TEAM_ID"]) # App Store Connect Team ID end
- Deliverfile
submit_for_review false automatic_release true force true skip_screenshots true release_notes({ 'default' => ENV["RELEASE_NOTES"], 'en-US' => ENV["RELEASE_NOTES"] }) run_precheck_before_submit false submission_information({ add_id_info_uses_idfa: false, export_compliance_compliance_required: false, export_compliance_encryption_updated: false, export_compliance_app_type: nil, export_compliance_uses_encryption: false, export_compliance_is_exempt: false, export_compliance_contains_third_party_cryptography: false, export_compliance_contains_proprietary_cryptography: false, export_compliance_available_on_french_store: false });
- Matchfile
git_url("https://github.com/iosのcertificateおいてるgitリポジトリ") # sshのurlではなく、httpsで指定してます。 git_basic_authorization(Base64.strict_encode64("#{ENV['MATCH_REPOSITORY_ACCOUNT']}:#{ENV['MATCH_PERSONAL_ACCESS_TOKEN']}")) storage_mode("git") type("appstore") # The default type, can be: appstore, adhoc, enterprise or development app_identifier(["com.sample.app"]) # bundleIDを指定しよう username("apple@example.com") # Your Apple Developer Portal usernameprojectの準備
BuildCgs.csをAssets/Scripts/Cgs/Editorディレクトリに置いておく。
このファイルがビルド番号の設定などをしてくれている。もぐめっとの場合、CIのビルド番号とアプリのビルド番号を紐付けしたかったので冒頭部分だけbuildNumberを参照するように少し修正しました。
修正後BuildCgs.csPlayerSettings.macOS.buildNumber = options["buildNumber"]; PlayerSettings.iOS.buildNumber = options["buildNumber"]; PlayerSettings.Android.bundleVersionCode = int.Parse(options["buildNumber"]); PlayerSettings.WSA.packageVersion = new Version(options["buildVersion"]);metadataの準備
metadataがないとfastlane途中でこけるため準備しておく。
androidの準備
fastlane run download_from_play_store json_key:<json path>jsonファイルはストアにアクセスするために必要なものになるが、こちらの記事に解説は委ねます。
iosの準備
fastlane deliver download_metadata上記で生成されたファイルをコミットしておく
githubのsecrets設定
先術したymlやfastfileなどを見てもらったとおり、環境変数をふんだんに使うので、その準備をしていきます。
game-ci/unity-builder周りで使う環境変数
細かいことはGame CIのドキュメントをご参照ください
Unityのアカウント周りの情報は下記の変数になります
- UNITY_EMAIL
- UNITY_PASSWORD
- UNITY_SERIAL
unityのserialについてはunityのwebサイトにログインして確認します。
Androidの公開設定周りはこちらの変数になります
- ANDROID_KEYALIAS_NAME
- ANDROID_KEYALIAS_PASS
- ANDROID_KEYSTORE_BASE64
- ANDROID_KEYSTORE_PASS
上記四点はandroidの公開設定でも使うこの辺の情報ですね。
keystoreの作り方は公式に委ねます
fastlaneのアップロード周りで使ってる環境変数
アップロードするときなどに使われるfastlaneのDeliverで使われるAppfileで定義して使ってる一覧です。
- APPLE_CONNECT_EMAIL
App Store Connect Accountにアクセスできるアカウントを指定します
- APPLE_DEVELOPER_EMAIL
Apple Developer Accountにアクセスできるアカウントを指定します。
- APPLE_TEAM_ID
- APPLE_TEAM_NAME
上記2点はDeveloper Portalのサイトでアクセスできる項目を確認します
- APPLE_ITC_TEAM_ID
APPLE_TEAM_IDではなくて、app store connectで使うアカウントのほうのIDになるらしい。とりあえずfastlane使えるならspaceship使えば取得できます。
- ASC_ISSUER_ID
- ASC_KEY_CONTENT
- ASC_KEY_ID
Appleの2段階認証を突破してアップロードできるようにする用の変数です。解説は下記記事に委ねます。
- FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD
- FASTLANE_PASSWORD
先程のAppStoreConnect API Keyを使った方法でできると思うので、おそらくこの2点は設定しなくてもいいと思いますが、念の為設定しておきます。
設定方法はこちらに。
- GOOGLE_PLAY_KEY_FILE
metadataをダウンロードする時お話した、jsonファイルの中身をはっつけます。
再掲)
fastlane match周りの環境変数
fastlane matchについてはこちらをご参照ください。証明書を管理するやつです。
- MATCH_PASSWORD
matchに設定してるパスワードを指定します。
- MATCH_PERSONAL_ACCESS_TOKEN
- MATCH_REPOSITORY_ACCOUNT
上記2つはMatchfileで指定したgit_basic_authorizationに使うものなのですが、repositoryのcloneがgithub action上でできるようにするために使っています。
MATCH_PERSONAL_ACCESS_TOKENはgithubのaccess tokenを指定します。
MATCH_REPOSITORY_ACCOUNTはMatchのリポジトリにアクセスできるアカウントを指定します。(access tokenを発行したアカウント名ですね)
使い方
ようやく設定がおわりました。
このworkflowはreleaseを切ったときに初めて発火されます。
Draft a new releaseボタンからリリースを作りましょう!
成功すればreleaseで書いた内容でappstoreとplay storeにリリースされるはずです!
まとめ
というわけでgame ciさんのunity-builderを使うことでgithub actionsでunityのCIを回すことができるようになりました!
unityはビルドに時間かかるので結構めんどくさかったのですがこの辺が省略できるようになるのはとても楽になりました。みなさんの開発にあてる時間がこれで増えたら幸いです!
最後に、ワンナイト人狼オンラインというゲームを作ってます!よかったら遊んでね!
他にもcameconやoffchaといったサービスも作ってるのでよかったら使ってね!
- 投稿日:2021-03-21T14:15:05+09:00
Unityを始める - オブジェクトの物理処理 -
はじめに
今までの学習で、Unityを触ったことがないという状態から、Unityで開発を始められるようになりました。
今回は、Unityでの開発で、「ピンボールゲーム」や「コイン落としゲーム」のような、物理演算を使うシンプルなゲームが作成できるようになる方法をまとめます。Unityではrigidbodyを使えば、非常に簡単に物理演算を行うことができます。
過去の記事
Rigidbodyとは
Rigidbody を使うと、ゲームオブジェクトを物理特性によって制御する事ができるようになります。リジッドボディがフォースやトルクを受けると、オブジェクトはより現実的な動きをします。重力をうけたり、スクリプトを使ってフォースを与えたり、 NVIDIA PhysX 物理エンジンを通して他のオブジェクトの影響を受けるようにするためには、ゲームオブジェクトに Rigidbody を追加する必要があります。
参考サイト : https://docs.unity3d.com/ja/2020.2/Manual/class-Rigidbody.html
事前準備
- 新規でプロジェクト作成の作成を行います。
- Hierarchy に Cube を追加します。
- Hierarchy に Sphere を追加します。
実装
Sphere に Add Component で Rigidbody を追加します。
実行すると Sphere が Cube へ落下します。
まとめ
Rigidbody を使う事で、ゲームオブジェクトに物理特性を加える事ができました。
Sphere が Cube へ落下後、跳ね返りには Physic Material を使用しています。Physics の内容を把握する事で、よりリアルな表現が出来るようになります。
- 投稿日:2021-03-21T10:30:55+09:00
VContainer入門(1) - IContainerBuilderとIObjectResolver
この記事について
Unity用のDIコンテナであるVCointanerの使い方を解説します。DIコンテナを使うと複雑な依存関係を楽に管理できるようになります。
DI(DependencyInjection)そのものについて詳しい説明はここではしません。
環境
記事中のソースコードは以下の環境で試しています。
- Unity 2020.3.0f1
- VContainer 1.6.0
VContainerの導入
VContainerはUPM(UnityPackageManager)を通してプロジェクトにインポートできます。
まずはUnityで新しいプロジェクトを作成し、プロジェクトのPackagesフォルダにある
manifest.json
を開きます。
そしてdependencies内に以下の設定を追加します。manifest.json"jp.hadashikick.vcontainer": "https://github.com/hadashiA/VContainer.git?path=VContainer/Assets/VContainer#1.6.0"これでVContainerが使えるようになりました。
VCointanerを試してみる
動作確認用のクラスを作成
まずはサンプルとして使うための適当な依存関係があるクラスを作っておきます。
Mock.csusing UnityEngine; using VContainer; // 先頭に[Logger]を付けてログ出力するクラス public sealed class Logger { public void Log(string message) => Debug.Log("[Logger] " + message); } // 足し算するだけのクラス public sealed class Calculator { public int Add(int a, int b) => a + b; } // LoggerとCalculatorに依存するクラス public sealed class HogeClass { private readonly Logger logger; private readonly Calculator calculator; [Inject] public HogeClass(Logger logger, Calculator calculator) { this.logger = logger; this.calculator = calculator; } public void LoggerTest() { logger.Log("LoggerTest"); } public void CalculatorTest(int a, int b) { int result = calculator.Add(a, b); logger.Log($"{a} + {b} = {result}"); } }
HogeClass
はコンストラクタでLogger
とCalculator
を受け取ります。[Inject]
属性が付いているのはVCointanerがこのコンストラクタを使えるようにするためです。1VContainerを利用しない場合、
HogeClass
は以下のように使えます。var logger = new Logger(); var calculator = new Calculator(); var hoge = new HogeClass(logger, calculator); hoge.LoggerTest(); // [Logger] LoggerTest と出力される hoge.CalculatorTest(3, 5); // [Logger] 3 + 5 = 8 と出力されるVContainerはHogeClassにLoggerとCalculatorを渡して生成する部分を肩代わりしてくれます。次の項から実際に使ってみます。
VContinerを使う流れ
VCointanerは以下の流れで使えます。
IContainerBuilder
を生成する。IContainerBuilder
に使いたいクラスを登録する。IContainerBuilder
からIObjectResolver
を生成する。IObjectResolver
を通して使いたいクラスを生成する。実際にVContainerを使う際は
LifetimeScope
を使うことが多いです。LifetimeScope
はIContainerBuilder
とIObjectResolver
をいい感じに管理してくれるクラスです。この
LifetimeScope
を通して使う場合でも内部の動作を知っていた方がわかりやすいので、まずはIContainerBuilder
とIObjectResolver
を直接使ってみます。実際のコードは以下になります。このコンポーネントを適当なGameObjectにアタッチして実行すると動作確認できます。
TestMonoBehaviour.csusing UnityEngine; using VContainer; public sealed class TestMonoBehaviour : MonoBehaviour { private void Start() { // 1. IContainerBuilderを生成 IContainerBuilder containerBuilder = new ContainerBuilder(); // 2. IContainerBuilderに使いたいクラスを登録 containerBuilder.Register<Logger>(Lifetime.Singleton); containerBuilder.Register<Calculator>(Lifetime.Singleton); containerBuilder.Register<HogeClass>(Lifetime.Singleton); // 3. IContainerBuilderからIObjectResolverを生成 using (IObjectResolver objectResolver = containerBuilder.Build()) { // 4. IObjectResolverで使いたいクラスを生成 HogeClass hoge = objectResolver.Resolve<HogeClass>(); hoge.LoggerTest(); // [Logger] LoggerTest と出力される hoge.CalculatorTest(3, 5); // [Logger] 3 + 5 = 8 と出力される } } }コード中の1、2、3、4をそれぞれ説明していきます。
1. IContainerBuilderを生成
IContainerBuilder
の実体としてContainerBuilder
クラスを使います。普通にnewで生成します。2. IContainerBuilderに使いたいクラスを登録 (Register)
IContainerBuilder.Register
を使ってクラスを登録できます。型引数に登録したいクラス、引数にLifetimeを渡します。Lifetimeは生成されたオブジェクトの生存期間を指定するものです。あとで説明するのでとりあえずLifetime.Singletonを渡しておいてください。
3. IContainerBuilderからIObjectResolverを生成 (Build)
IContainerBuilder.Build
でIObjectResolver
を生成できます。IObjectResolver
はDisposeが必要なのでusingで囲っています。4. IObjectResolverで使いたいクラスを生成 (Resolve)
IObjectResolver.Resolve
でBuildする前に登録しておいたクラスが生成できます。ここではHogeClassを生成しています。型引数に生成したいクラスを指定すればLoggerクラスやCalculatorクラスも生成できます。Register/Build/Resolveの内部動作
VContainerの内部動作も知っていた方が便利なので簡単に説明しておきます。
Register
Registerした時点では登録されたクラスを
ContainerBuilder
の内部に保存しているだけです。Build
ContainerBuilder
をBuildするとIObjectResolver
が生成されます。
この段階で、Registerされたクラス全てをリフレクションで解析して生成に必要な情報を集めます。2HogeClassの場合はInject属性がついたコンストラクタを発見し、引数にLoggerとCalculatorが必要なことが記録されます。
LoggerとCalculatorはInject属性がついたメンバがないのでただ生成すればいいことが記録されます。Resolve
Build時に記録した情報に基づいて指定されたクラスのインスタンスを返します。
ここでResolveという単語は「依存関係を解決して生成されたオブジェクトを取り出すこと」を表します。
HogeClassの場合はLoggerとCalculatorが必要なので、まずはLoggerとCalculatorをResolveします。それからこのLoggerとCalculatorを使ってHogeClassのコンストラクタを呼び出してHogeClass自体を生成します。
要するにobjectResolver.Resolve<HogeClass>()
の中ではnew HogeClass(new Logger(), new Calculator());
と同様のことが実行されています。依存先のクラス(ここではLoggerやCalculator)も再帰的にResolveされていくのが重要です。
例えば、LoggerがコンストラクタでFileWriterというクラスを受け取らなければならなくなったとします。この場合でもFileWriterがRegisterされていれば自動的にFileWriterがResolveされてLoggerのコンストラクタに渡され、そのLoggerがまたHogeClassに渡されます。依存関係が何段階になっても同様にResolveされていきます。3Lifetimeについて
あとで説明すると言っていたLifetimeについて説明します。Lifetimeを変えるとResolve時の動作が少し変わります。
Lifetimeには
Lifetime.Singleton
・Lifetime.Transient
・Lifetime.Scoped
の3つがあります。Lifetime.Singleton
Lifetime.Singleton
ではResolveで生成されたインスタンスがIObjectResolver内でキャッシュされます。つまり複数回Resolveを呼んでも同じインスタンスが返ってきます。using (IObjectResolver objectResolver = containerBuilder.Build()) { // hogeとhoge2は同じインスタンスになる HogeClass hoge = objectResolver.Resolve<HogeClass>(); HogeClass hoge2 = objectResolver.Resolve<HogeClass>(); }IObjectResolverごとにキャッシュされるためIObjectResolverが異なると別のインスタンスになります。
次の例では2回BuildしてIObjectResolverを2つ生成しています。using (IObjectResolver objectResolver = containerBuilder.Build()) using (IObjectResolver objectResolver2 = containerBuilder.Build()) { // hogeとhoge2は違うインスタンスになる HogeClass hoge = objectResolver.Resolve<HogeClass>(); HogeClass hoge2 = objectResolver2.Resolve<HogeClass>(); }Lifetime.Transient
Lifetime.Transient
ではResolveするたびに別のインスタンスが生成されます。private void Start() { IContainerBuilder containerBuilder = new ContainerBuilder(); containerBuilder.Register<Logger>(Lifetime.Singleton); containerBuilder.Register<Calculator>(Lifetime.Singleton); containerBuilder.Register<HogeClass>(Lifetime.Transient); // Lifetime.Transientを使ってみる using (IObjectResolver objectResolver = containerBuilder.Build()) { // hogeとhoge2は違うインスタンスになる HogeClass hoge = objectResolver.Resolve<HogeClass>(); HogeClass hoge2 = objectResolver.Resolve<HogeClass>(); } }HogeClassのRegisterで
Lifetime.Transient
を渡すように変えたのでhogeとhoge2は別のインスタンスになります。LoggerとCalculatorは
Lifetime.Singleton
のままなので、hogeとhoge2のコンストラクタに渡されるLoggerとCalculatorはそれぞれ同じインスタンスになります。これもLifetime.Transient
にすれば別のインスタンスが渡されることになります。Lifetime.Scoped
Lifetime.Scoped
はLifetime.Singleton
に似ていますがIObjectResolverが親子関係を持ったときの動作が異なります。親子関係と一緒に説明するのでここでは省略します。IDisposableの自動Dispose
IDisposableを実装したクラスをResolveするとLifetimeによっては自動的にDisposeされます。
Lifetime.Singleton
かLifetime.Scoped
の場合、IObjectResolverがDisposeされるとResolveで生成されたインスタンスも一緒にDisposeされます。Lifetime.Transient
の場合はDisposeされません。IObjectResolverは作りっぱなしなのでDisposeする責任はインスタンスを渡された側になります。まとめ
Register -> Build -> Resolveの順に使うイメージを持っておいてください。
次回はVContainerの本来の使い方であるLifetimeScopeを通した方法を説明します。
- 投稿日:2021-03-21T00:22:19+09:00
ゲームアクセシビリティの取り組みについて
こんにちは。
個人でゲーム開発をしておりますToya Shiwasuと申します。
この記事では、拙作のオンラインアクションRPGでのアクセシビリティの取り組みについて紹介させていただきます。アクセシビリティとは
アクセシビリティとは、障害の有無に関わらず、誰でも使える、誰でも使いやすいように工夫することです。
例えば、字幕があれば耳の不自由な方でも台詞の内容がわかりますし、フルボイスなら目の不自由な方でもゲームをプレイできるかもしれません。
最近のゲームとしては、PS4の『The Last of Us Part II』というゲームが、ストーリーに賛否両論はあるものの、高いアクセシビリティで評価されています。アクセシビリティ対応の経緯
拙作のオンラインアクションRPGは、2018年10月17日にVersion 1.0を公開し、当時は特にアクセシビリティに配慮したゲームではありませんでした。
ほそぼそとアップデートを続けて、2019年12月08日のVersion 2.0で最終エリアを実装し、エンディングまで作り終えました。
さて次に何をしようかとなって、ゲームの3DCG化を検討したりしばし迷走した後、オーディオゲームというものを知りました。
オーディオゲームとは主に視覚障害者向けの音声だけでプレイするゲームで、比較的シンプルなミニゲームから重厚長大な長編のRPGまでいろいろあります。
2020年02月頃からゲームの読み上げ及び3Dサウンドの対応を始めました。
当初は1ヶ月位で対応できるだろうと根拠のない見積もりをしていたのですが、実際にやってみるとUIはすべて作り直しになったり、なかなか大変でした。
そして、8ヶ月後の2020年10月09日のVersion 2.1で「アクセシビリティアップデート」として、公開できました。
その後も、主にアクセシビリティ機能を中心にアップデートを続けています。視覚障害者向けの機能
視覚障害者向けの機能としては、テキストの読み上げ機能、3D効果音機能、全てのUIの再設計、配色が挙げられます。私自身は晴眼者(目の見える人)ですが、実際に画面を見ずにテストプレイを行い、ソロでの討伐を想定していた中ボスを音だけを頼りにソロで撃破しています。
テキストの読み上げ機能
テキストの読み上げ機能とは、ゲーム中の全てのテキストをOSの読み上げ機能を用いて読み上げる機能です。
残念ながら、Unityの標準機能には音声合成がなかったので、OSの読み上げ機能のラッパーライブラリを自作しました。
初期の読み上げ対応の動画です。
https://twitter.com/ToyaShiwasu/status/1224681290508591104
Version 2.1の時点では、WindowsはC++でSAPIを、AndroidはJavaでTextToSpeechをそれぞれ呼び出していました。
ちなみに、私は実装しなかったのですが、MacはObjective CでNSSpeechSynthesizerを、iOSはObjective CでAVSpeechSynthesizerを使うといいらしいです。
UnityはC#ですので、異なるプログラミング言語で機能を呼び出す、マーシャリングという作業がとてもつらかったのを覚えています。
また、Version 2.2では、要望の多かったスクリーンリーダーのNVDAと呼ばれるソフトへの出力に対応しました。
苦労しつつも、開発者の環境では問題なく動作するものが作れましたが、公開後海外のユーザーからプログラムがクラッシュしたり正常に読み上げないといったバグ報告がちらほらと寄せられます。
調査はしたもののよく分からなかったため、2021年02月15日のVersion 2.3でUnityの有料のアセットであるRT-Voice Proに切り替えました。
こちらもいくつかバグはありましたが、自作した経験が生きて修正はできました。
無事起動できたという報告を受けた一方、使えたボイスが使えなくなったという報告もあり、なかなかままならないものです。3D効果音機能
キャラクターが縦横に動き回るアクションゲームにおいて、キャラクターの位置を音声だけで伝えるのはなかなか難しい課題です。
Cキーで相対的な方向と距離を読み上げる機能も用意しましたが、戦闘ではリアルタイム性にかけます。
3D効果音機能とは、プレイヤーから見て左で発生した効果音はヘッドホンの左から、右で発生した効果音はヘッドホンの右から、上で発生した効果音は高い音で、下で発生した効果音は低い音で、それぞれ再生される機能です。
左右は特に問題ないですが、上下を音の高低で区別するのは少々直感的ではありません。
しかし、オーディオゲームではよく使われる手法であり、私は慣れれば割と区別できるようになりました。
初期の3D効果音対応の動画です。
https://twitter.com/ToyaShiwasu/status/1225416962827665413
UnityにはAudio SourceにPitchとSpatial Blendという項目があったので、それを使いました。
音圧(音の大きさ)は距離によって減衰するのですが、減衰の仕方は試行錯誤で調整しました。
3D効果音の減衰の仕方の調整の開発記録です。
https://twitter.com/ToyaShiwasu/status/1225795880038486016
開発記録はTwitterに残しているのですが、具体的にどう調整したのかよくわからないですね。
もっと詳しく過程を残しておくべきでした。全てのUIの再設計
テキストの読み上げ機能とともに全てのUIの再設計を行いました。
レイアウト調整中の動画です。
https://twitter.com/ToyaShiwasu/status/1228610119639789568
特に全盲の方はUIのコンポーネントがどのように配置しているか見えないため、フォーカスの移動方法を分かりやすくするのは重要です。
最終的にはほぼすべてのコンポーネントは上下に配置し、キャラ削除のトグルなどだけ例外的に左右に配置しています。
インベントリスロットは、もともと縦横のグリッド表示だったのですが、上下のリスト表示を追加し、そちらをデフォルトにして、オプションで切り替えられるようにしています。
アイテムウィンドウの所持金などのリスト項目以外の付加的な情報は、Cキーを押すと読み上げるようにしています。
取扱説明書にはCキーでいろいろ読み上げられることを明記していたのですが、ユーザーから所持金はどうやって知るのだという質問が何回か寄せられました。分かりづらかったかもしれません。
オプションにUI倍率を変更する項目もありますが、デフォルトで最大倍率です。配色
色覚多様性(色覚特性、色覚異常、色盲などとも呼ばれる)の方にとって、ゲームの配色も重要となります。
人の色を感じる錐体細胞には3つの種類があり、障害のある錐体細胞によって色の見え方は異なります。
なので、こうすれば大丈夫といった明確な配色はないのですが、次のサイトに無難な配色の例が掲載されています。
https://jfly.uni-koeln.de/colorset/このサイトを参考にゲーム内の一部アイコンを修正しました。
https://twitter.com/ToyaShiwasu/status/1290416279258456065
回復アイテムのポーションの種類が色でしか判別できないのは問題だと考え、種類によって突起を付けました。
魔法書アイテムの配色は、無難な配色に修正しています。
転移アイテムの種類が色でしか判別できなかったので、数字を入れました。
オーブという種類のアイテムは、形状を全て変更しました。
ただし、実在する物体の色は変更せずそのままにしています。赤いリンゴは赤く描いていいはずです。オプションでウィンドウやテキストの配色を変更する機能も実装しています。
https://twitter.com/ToyaShiwasu/status/1288883453132382209
ウィンドウの色は以前は半透明だったのですが、完全に不透明に変更しました。
デフォルトの配色は白黒で、私も若干ダサいとは感じているのですが、一番見やすいはずなので変更する予定はありません。聴覚障害者向けの機能
聴覚障害者向けの機能としては攻撃マークを表示、オーディオスペクトラムを表示が挙げられます。私自身あまり調査していないこともあって、まだまだ対応は不十分と感じています。また、聴覚障害者は視覚障害者と比べて遊べる市販のゲームも多いこともあって、わざわざ個人開発のゲームが遊ばれるかという問題もあります。まあ、後者はゲームのクオリティの問題なのでこれ以上言及はしません。
攻撃マークを表示
キャラクターが攻撃する瞬間頭上に攻撃マークを表示する機能です。
https://twitter.com/ToyaShiwasu/status/1345646571124211713オーディオスペクトラムを表示
オーディオスペクトラムを折れ線グラフで表示する機能です。
https://twitter.com/ToyaShiwasu/status/1345696416543383552その他の機能
その他の機能として、ロックオン機能、多言語対応、ゲーム速度変更が挙げられます。
ロックオン機能
このゲームはもともとヒットアンドアウェイが重要なアクション制の高いゲームとして作成しました。
なので、Version 2.1のアクセシビリティアップデートを公開した時点でも、ゲーム性を損なうのではと考え、ロックオン機能は実装していませんでした。
作者によるテストプレイでも、実際にロックオン機能なしの音のみのプレイで中ボスの撃破まで可能なことを確認しています。
アップデートのたびにちょくちょくテストプレイはしているのですが、そのときにマップの移動や武器の射程内への移動がストレスに感じられました。
なので、特にユーザーからの要望は特になかったのですが、Version 2.3でShiftキーによるロックオン機能を実装しました。
https://twitter.com/ToyaShiwasu/status/1347499546042007554
カーソルキーとの同時押しでマップ移動を行うこともできます。
それと同時に能動防御のアクションを実装し、それまでのヒットアンドアウェイの他に、その場で耐えるという別のゲーム性を模索してみました。多言語対応
多言語対応は、あまりアクセシビリティの文脈で語られることは少ないのですが、オーディオゲームはまだまだ少ないので、多言語対応を行ってより多くの方に遊んでもらえるようにするのは、意味があると思います。
Version 2.4で12の言語に対応しています。日本語と英語は自分で、ポルトガル語とロシア語はゲームファンの有志の方に、それ以外の言語は機械翻訳を使って翻訳しました。
Unityはアラビア語に対応していないので、とりあえず右から左に表示するようスクリプトを作成しました。ただし、アラビア語は本当は双方向テキストで、非アラビア文字は左から右に表示するのが正しいです。今後の課題です。ゲーム速度を変更する機能
Version 2.4でゲーム速度を変更する機能を実装しました。
https://twitter.com/ToyaShiwasu/status/1367726769361408002
オンラインプレイの場合、クライアントはサーバーのゲーム速度と同じになります。
オフラインプレイの場合、プレイ中にゲーム速度を変更できます。
Unityの場合、Time.timeScaleを変更することで、簡単にゲーム速度を変更できます。対応できていない障害と今後の展望
現時点で対応できていない障害もあります。
目が見えず耳も聞こえない盲ろう者が遊べる日本語に対応したオンラインアクションRPGを私は見つけることができませんでした。
点字ディスプレイを使えば技術的には可能なように思われますが、非常に高価ですし解像度も高くありません。
精神障害者とゲームについて話題になることはあまりないように思われます。
意識はあるが体が完全に麻痺している閉じ込め症候群の方が遊べるコンピュータゲームは今の日本にはないようです。
対応にはブレイン・マシン・インターフェースの技術が必要でしょうか。ゲームアクセシビリティについて、ゲーム開発者が個別に対応する以外の方法も模索されています。
スマホのOCRソフトを利用してゲームのテキストを読み上げる方法があるようです。
https://whiteblackspace.hatenablog.com/entry/2019/10/18/200500
また、ゲーム機のエミュレータに読み上げ機能を統合した例もあります。
https://www.gamespark.jp/article/2019/12/28/95758.htmlゲームを巡るアクセシビリティはまだまだ発展途上と言えるでしょう。