個人開発でGithub ActionsでUnityのCI/CDを使ってみた 【Unity,Github Actions(game-ci)】

TechTrainさんのほうでゲームの課題?をやらせていただいたときにGithub Actionsを使ったUnityのCI/CDを取り組みました(特に課題内容とかではないです)

Github Actionsを使ったUnityのCI/CDはいろいろ詰まるところがあったので,今後使う方/未来の自分に向けてまとめています

成果

  • UnityTestからのUnityビルドの実行(1枚目画像) : テストが成功しないとビルドが実行されない
  • テスト結果とBuildファイルをアーティストに保存
  • Unity TestのPlayModeTestの実行と結果の表示(2枚目画像)

f:id:ayousanz:20210526174305p:plain f:id:ayousanz:20210526174333p:plain

リポジトリは公開していますので,詳細はリポジトリをご確認ください また,テスト用にシンプルな実装でも同じことを行っています.こちらのリポジトリもご参考ください.

環境

CI/CDの実行の流れ

  1. テストを実行するマシーンの作成
  2. リポジトリをcheckout
  3. テストの実行
  4. テスト結果をアーティストをしてアップロード
  5. ビルドを実行するマシーンの作成
  6. リポジトリをcheckout
  7. cacheの取得(これがないと初回と同じ時間毎回かかるらしい)
  8. ビルドの実行
  9. ビルドファイルをアーティストにアップロード

Unityのシリアルキーを取得する

詳しい説明はこちらの記事を参考にさせていただきました

注意ポイント 公式のライセンスを取得できるサイトからダウンロードできるファイルの中身は全部そのまま Github secretのvalueに入れましょう!

license.unity3d.com

TestをActions上で実行する

Unityのテストを作成する

テストフレームのライブラリを導入

今回はUnityでUIテストを簡単に作成するために,Unity UI Test Automation Frameworkを導入しました 詳細は,こちらで詳しく説明されています

baba-s.hatenablog.com

unityUITestライブラリから,DependencyInjector.csUITest.cs を導入します

f:id:ayousanz:20210527172314p:plain

テストの作成

テストファイルと asmdefファイルを作成します

f:id:ayousanz:20210527174213p:plain

実行されるようにymlファイルを設定する

test:
    name: Run EditMode and PlayMode Test
    runs-on: ubuntu-latest
    steps:
      - name: Check out my unity project.
        uses: actions/checkout@v2
      - name: Run EditMode and PlayMode Test
        uses: game-ci/unity-test-runner@v2
        with:
          projectPath: .
          githubToken: ${{ secrets.GITHUB_TOKEN }}
          unityVersion: 2020.3.9f1
      # テストの実行結果をアーティファクトにアップロードして後から参照可能にする
      - uses: actions/upload-artifact@v2
        if: always()
        with:
          name: Test results
          path: artifacts

テストの結果を保存/簡単に確認できるようにする

テスト結果の表示

githubToken: ${{ secrets.GITHUB_TOKEN }} を設定すると,以下のようにテストの結果が簡単に見れます. 詳細はこちらに記載されています f:id:ayousanz:20210527175815p:plain

テスト結果の保存

- uses: actions/upload-artifact@v2
        if: always()
        with:
          name: Test results
          path: artifacts

を入れることで,テスト結果をアーティストとして保存することができます f:id:ayousanz:20210527175609p:plain

BuildをActions上で実行する

ビルドのプラットフォームは,以下のものが設定できるみたいです(公式サイトより)

targetPlatform:
          - StandaloneOSX # Build a macOS standalone (Intel 64-bit).
          - StandaloneWindows # Build a Windows standalone.
          - StandaloneWindows64 # Build a Windows 64-bit standalone.
          - StandaloneLinux64 # Build a Linux 64-bit standalone.
          - iOS # Build an iOS player.
          - Android # Build an Android .apk standalone app.
          - WebGL # WebGL.

ビルドの設定は以下になります.

build:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        projectPath:
          - .
        unityVersion:
          - 2020.3.9f1
        targetPlatform:
         - Android # Build an Android player.
    needs: test
    steps:
    - name: Checkout
      uses: actions/checkout@v2
      with:
        lfs: false
        clean: false
        
    # Cache
    - uses: actions/cache@v2
      with:
        path: Library
        key: Library

    # Build
    - name: Build project
      uses: game-ci/unity-builder@v2.0-alpha-6
      with:
        unityVersion: ${{ matrix.unityVersion }}
        targetPlatform: ${{ matrix.targetPlatform }}

結論

game-ciのライブラリを使うことですごく楽にUnityのCI/CDを構築できることができました~ 他にもいろいろ機能があるみたいなので,使っていきたいです

ymlファイル全体の構成

# This is a basic workflow to help you get started with Actions

name: Test and Build,Release APK

env:
  UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}

# Controls when the action will run. 
on: [push]
  # Triggers the workflow on push or pull request events but only for the main branch

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:  
  test:
    name: Run EditMode and PlayMode Test
    runs-on: ubuntu-latest
    steps:
      - name: Check out my unity project.
        uses: actions/checkout@v2
      - name: Run EditMode and PlayMode Test
        uses: game-ci/unity-test-runner@v2
        with:
          projectPath: .
          githubToken: ${{ secrets.GITHUB_TOKEN }}
          unityVersion: 2020.3.9f1
      # テストの実行結果をアーティファクトにアップロードして後から参照可能にする
      - uses: actions/upload-artifact@v2
        if: always()
        with:
          name: Test results
          path: artifacts
  build:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        projectPath:
          - .
        unityVersion:
          - 2020.3.9f1
        targetPlatform:
         - Android # Build an Android player.
    needs: test
    steps:
    - name: Checkout
      uses: actions/checkout@v2
      with:
        lfs: false
        clean: false
        
    # Cache
    - uses: actions/cache@v2
      with:
        path: Library
        key: Library

    # Build
    - name: Build project
      uses: game-ci/unity-builder@v2.0-alpha-6
      with:
        unityVersion: ${{ matrix.unityVersion }}
        targetPlatform: ${{ matrix.targetPlatform }}

    # Output
    - uses: actions/upload-artifact@v2
      with:
        name: Build-${{ matrix.targetPlatform }}
        path: build/${{ matrix.targetPlatform }}

Unity・ゲームの開発でのおすすめ/よく使用するアセット・参考サイト(その他あり)【Unity,UnityAssets,素材リンク】

自分用またほかの学習者の方が何を使っていいのかがわからなくなったときにご活用ください

アセットストア経由

(OpenUPMでも入れれるものはあります)

1. DoozyUI: Complete UI Management System

DoozyUI: Complete UI Management System | GUI Tools | Unity Asset Store

使い方などが紹介されているサイト

Doozy UI 使おうぜ! #unity_lt

DoozyUI使ってみる【Unity】 - トマシープが学ぶ

2. Odin - Inspector and Serializer

assetstore.unity.com

使い方などが紹介されているサイト

kan-kikuchi.hatenablog.com

www.midnightunity.net

3. UniRx - Reactive Extensions for Unity

公式より

Provides an efficient allocation free async/await integration for Unity.

assetstore.unity.com

使い方などが紹介されているサイト

qiita.com

こちらはUniTaskも合わせてこちらの本がおすすめです

3. DOTween Pro

assetstore.unity.com

無料版もあります(正直無料版で十分だと思います)

assetstore.unity.com

使い方などが紹介されているサイト

qiita.com

amagamina.jp

Easy Save - The Complete Save & Load Tool for Unity

assetstore.unity.com

使い方などが紹介されているサイト

ayousanz.hatenadiary.jp

kan-kikuchi.hatenablog.com

Open UPM経由

unity-reference-viewer

📦 unity-reference-viewer - jp.amagamina.reference-viewer | OpenUPM

UniTask

github.com

VContainer

公式より

The extra fast DI (Dependency Injection) library running on Unity Game Engine.

Zenjectよりも速度が速く,機能も欲しいところだけになっています

github.com

使い方などが紹介されているサイト light11.hatenadiary.com

qiita.com

UnityScreenNavigator

UnityのuGUIで画面遷移、画面遷移アニメーション、遷移履歴のスタック、画面のライフサイクルマネジメントを行うためのライブラリです。

(ReadMeより)

github.com

フリー素材(Unity使用OK)

ドット素材

damagedgold.wp.xdomain.jp

itch.io

フリー3Dモデル

3dyasan.com ]

www.kenney.nl

お寿司3Dモデル(商用利用OK)

ddd.pink

quaternius.com

パワポとか会議資料風

kage-design.com

アイソメトリックの画像・マップの作成サイト

その他

www.shigureni.com

soco-st.com

woobro.design

www.openpeeps.com

mixkit.co

pixelbuddha.net

www.manypixels.co

www.manypixels.co

フォント

dotcolon.net

Sound

splice.com

UI/UX

商用でも完全無料で利用できる、SVG完備のアイコン素材 -iconsax

coliss.com

参考サイト

www.gameuidatabase.com

interfaceingame.com

www.designnotes.co

Unity uGUI アドバンスド・リファレンス

github.com

参考記事・サイト

無料で使えるツールをまとめているサイト

freestuff.dev

PlantUMLで高画質な画像を出力する【Rider,PlatUML】

昨日Unity1Weekでゲームを作った際にクラスの依存関係のクラス図を作成しました.しかし,画質がわるくほかの人が見るには絶えないものでした..

公式に質問したところ解決方法がわかったので,ほかの方のお役に立てればと思います

変化

画像を新しいウインドウで確認すると高画質にしたほうはそれぞれのクラスの名前が確認できると思います!!(うれしい)

追加する画像情報

skinparam dpi 600

を入れることでdpiを変えることができます

(画像のサイズも変更できるみたいです.詳しくは参考サイトのほうを見てください)

変更前

f:id:ayousanz:20210503185723p:plain

変更後

f:id:ayousanz:20210504192149p:plain

参考情報

forum.plantuml.net

MV(R)Pでゲームを作ってみた【Unity,Unity1Week】

今回はアーキテクチャを学ぶ目的でUnity1Weekに参加しました.

制作物

プレイ動画

f:id:ayousanz:20210503024527g:plain

公開サイト

unityroom.com

宣伝ツイート

目的と目的

目的

目標

  • MV(R)Pを使ったゲームを作成する

開発環境

  • Unity 2020.3.5f1
  • Rider
  • WinPC

使用したアセット

Unity拡張系

素材系

期間が1週間ということもあり,途中で大きな企画,仕様変更が怖かったのでざっくりと企画と仕様を作ってからゲームの作成を行いました

企画(1-1.5日)

  • メインキャラの動きだけ決まっている.

  • キャラクターの配置をして,当たらないようにする

  • 配置するキャラクターは事前に数と種類が決まっている.

  • すべて使わないとPlayMoveできない

  • 最後まで当たらずに動くとステージクリア

  • ステージが上がるごとにキャラクターの種類が増える

(注)

実装時に以下は実装していません

  • チュートリアル画面 → 音声と文字でゲームの操作方法及び流れを説明するように変更しました
  • ステージ選択 → ステージの選択はシステム側でランダムで選択されるようにしました

仕様(1-2日)

画面遷移

f:id:ayousanz:20210503192030p:plain

スタート画面

  • ゲーム開始でスタート
  • ゲーム終了でUnityRoomにトップページに飛ぶ

f:id:ayousanz:20210503020102p:plain

ステージ選択

f:id:ayousanz:20210503020244p:plain

チュートリアル画面

  • 実際に使用する画面にテキストをいれた画像を表示する

こんな感じのところに各箇所の説明を入れる

3枚くらい入れて,ボタンで移動? 

一枚でもいいかも

f:id:ayousanz:20210503020312p:plain

キャラクターの動きをプレイヤーが確認

  • 中心にいるキャラクターが動く(ロードまいにランダムで動きを決める)

プレイヤーによるキャラクターの配置

  • 右のキャラクターをクリックすることで,配置するキャラクターを選択することができる.
  • 選択した状態で任意の水色の部分を選択することで配置することができる

配置ユニットの動き確認

  • 配置したユニットが実際に動く
  • スタート画面を押してから行動開始(この間は基本ほかの操作はうけつけない)

Result画面

  • 成功だったかどうか f:id:ayousanz:20210503020339p:plain

実装(4-5日)

大まかな機能の実装とクラス図は以下になります. なおシーンは一つのみで,画面の切り替えはDoozyUIを使っています

クラス図

意識してViewとModelの部分を分けて実装しました UniRxとUniTaskのおかけでコードがきれいになっているのではないかと思います. (もっときれいになりそうですが,初めということもあり今回はここまでで..)

f:id:ayousanz:20210503185723p:plain

Presenterのコード

以下はGamePresenter.csのコードのなります

using System;
using System.Linq;
using AcquireChan.Scripts;
using Cysharp.Threading.Tasks;
using Doozy.Engine;
using Project.Scripts;
using Project.Scripts.Model;
using Project.Scripts.Model.Character;
using Project.Scripts.View;
using Sirenix.OdinInspector;
using UniRx;
using UnityEngine;

public class GamePresenter : MonoBehaviour
{
    [SerializeField,FoldoutGroup("ViewClass")] private SelectUnitView selectUnitView;
    [SerializeField,FoldoutGroup("ViewClass")] private GamePlayView gamePlayView;
    [SerializeField,FoldoutGroup("ViewClass")] private SelectUnitPlacementView selectUnitPlacementView;
    [SerializeField,FoldoutGroup("ViewClass")] private LoadView loadView;
    [SerializeField,FoldoutGroup("ViewClass")] private TitleView titleView;
    [SerializeField,FoldoutGroup("ViewClass")] private ResultView resultView;
    
    [SerializeField,FoldoutGroup("ModelClass")] private CharacterMotion characterMotion;
    [SerializeField,FoldoutGroup("ModelClass")] private CharacterMove characterMove;
    [SerializeField,FoldoutGroup("ModelClass")] private CharacterCollisionDetection characterCollisionDetection;
    [SerializeField,FoldoutGroup("ModelClass")] private SoundModel soundModel;
    [SerializeField,FoldoutGroup("ModelClass")] private StageModel stageModel;
    private UnitModel _unitModel;
    private ICharacterMotion _characterMotion;
    private ICharacterMove _characterMove;
    private IDisposable _disposable;

    private bool _isPlaying;
    private bool _isPlayMode = false; //アニメションモードではない,ゲームモード
    private readonly BoolReactiveProperty _isLoading = new BoolReactiveProperty();
    private const float MAXTime = 20f;
    private readonly IntReactiveProperty _placementUnitCount = new IntReactiveProperty();//設置したユニットの数

    [SerializeField] private LoadStageInfo loadStageInfo;

    private StageInfo _stageInfo;
    // Start is called before the first frame update
    private void Start()
    {
        _unitModel = GetComponent<UnitModel>();
        _characterMotion = characterMotion;
        _characterMove = characterMove;

        selectUnitPlacementView.PlacementEnum
            .SkipLatestValueOnSubscribe()
            .Where(value =>!_isPlaying)
            .Subscribe(value =>
            {
                _unitModel.PlacementUnit(selectUnitView.SelectUnitIndex.Value, value);
                selectUnitView.SelectUnitButtonNoActive();
                selectUnitView.IsImageAlpha(selectUnitView.SelectUnitIndex.Value,false);
                _placementUnitCount.Value++;
            }).AddTo(this);

        _placementUnitCount
            .SkipLatestValueOnSubscribe()
            .Where(value => value == _stageInfo.UnitInfos.Count())
            .Subscribe(_ => gamePlayView.IsUnitPlayInteractable(true)).AddTo(this);
        
        //ユニットの詳細を更新
        selectUnitView.SelectUnitIndex
            .SkipLatestValueOnSubscribe()
            .Where(value => 0 < value)
            .Subscribe(value =>
        {
            gamePlayView.SetHowToPlayText(false);
            gamePlayView.SetUnitDetailText(true);
            gamePlayView.UpdateUnitDetail(_stageInfo.UnitInfos[value]);
        }).AddTo(this);
        
        

        #region ボタンのイベント


        //Unitの位置のリセット
        gamePlayView.OnClickResetUnit()
            .Where(_=> !_isPlaying)
            .Subscribe(_ =>
            {
                _unitModel.ResetUnitPlacement();
                selectUnitView.SetAllFrameZeroAlpha();
                selectUnitView.AllButtonActive(true);
            }).AddTo(this);

        //ゲームモード開始
        gamePlayView.OnClickPlayUnit()
            .Where(_=>!_isPlaying)
            .Subscribe(_ =>
            {
                _isPlaying = true;
                _isPlayMode = true;
                AllButtonActive(false);
                selectUnitView.SetAllFrameZeroAlpha();
                _characterMotion.WalkAnimation();
                _characterMove.Move();
                _unitModel.PlayUnit();
            }).AddTo(this);

        //キャラクターの動き確認
        gamePlayView.OnClickPlayCharacterMove()
            .Where(_ => !_isPlaying)
            .Subscribe(async _ =>
            {
                _isPlayMode = false;
                _unitModel.SetAllUnitCollider(false);
                CharacterPlayMAnimation();
                _characterMove.ResetPosition(_stageInfo.StartXPosition,_stageInfo.StartZPosition);
                
                //ボタンを押せないように
                AllButtonActive(false);
                await UniTask.Delay(TimeSpan.FromSeconds(_characterMove.AnimationTime));
                AllButtonActive(true);
                _unitModel.SetAllUnitCollider(true);
            }).AddTo(this);

        //ゲームのロード
        titleView.OnClickGameStart()
            .Subscribe(_ =>
            {
                LoadStart();
                _stageInfo = loadStageInfo.GetStageData();
                stageModel.CreateStage(_stageInfo.stageObject);

                // ユニットの初期設定
                _unitModel.RegistrationUnits(_stageInfo.UnitInfos);
                selectUnitView.SetUnitIcons(_stageInfo.UnitInfos);
                
                //キャラクターの初期設定
                _characterMove.ResetPosition(_stageInfo.StartXPosition,_stageInfo.StartZPosition);
                _characterMove.SetAnimation(_stageInfo.CharacterDirectionList);
            }).AddTo(this);

        //ゲーム終了
        titleView.OnClickGameEnd()
            .Subscribe(_ =>
            {
                soundModel.GameEndVoice();
            }).AddTo(this);


        //同じゲームを実行
        resultView.OnClickReGameButton()
            .Subscribe(_ =>
            {
                GameEventMessage.SendEvent("ToGamePlay");
                LoadStart();
                ResetData();
            }).AddTo(this);

        //新しいゲームを実行
        resultView.OnClickNewGameButton()
            .Subscribe(_ =>
            {
                ResetData();
                GameEventMessage.SendEvent("ToGamePlay");
                LoadStart();
                _stageInfo = loadStageInfo.GetStageData();
                stageModel.ClearStage();
                stageModel.CreateStage(_stageInfo.stageObject);

                // ユニットの初期設定
                _unitModel.RegistrationUnits(_stageInfo.UnitInfos);
                selectUnitView.SetUnitIcons(_stageInfo.UnitInfos);
                
                //キャラクターの初期設定
                _characterMove.ResetPosition(_stageInfo.StartXPosition,_stageInfo.StartZPosition);
                _characterMove.SetAnimation(_stageInfo.CharacterDirectionList);
                
            }).AddTo(this);
        

        #endregion

        _isLoading
            .SkipLatestValueOnSubscribe()
            .Where(value => !value)
            .Subscribe(async _ =>
        {
            soundModel.StartBgm();
            soundModel.StopVoice();
            soundModel.ConfirmVoiceForCharacterMove();
            GameEventMessage.SendEvent("GameStart");
            await UniTask.Delay(TimeSpan.FromSeconds(1f));
            LoadStop();
            CharacterPlayMAnimation();
            await UniTask.Delay(TimeSpan.FromSeconds(9f));
            _characterMove.ResetPosition(_stageInfo.StartXPosition,_stageInfo.StartZPosition);
            soundModel.StopVoice();
            soundModel.PlaceUnitVoice();
                    
            //すべてのボタンをアクティブ
            AllButtonActive(true);
            gamePlayView.IsUnitPlayInteractable(false);
        }).AddTo(this);


        #region Result処理

        _characterMove.IsMove.Where(value => !value)
            .Subscribe(_ =>
            {
                _characterMotion.IdleAnimation();
                if (_isPlayMode)
                {
                    ToResult("ゲームクリアおめでとう!");
                }
            })
            .AddTo(this);

        characterCollisionDetection.IsHitAnimal.Where(value => value)
            .Subscribe(_ =>
            {
                ToResult("もう一回やってみよう!");
            }).AddTo(this);
        

        #endregion
    }

    private void ToResult(string resultContent)
    {
        soundModel.StopBgm();
        _characterMotion.IdleAnimation();
        _characterMove.Stop();
        _characterMove.ResetPosition(_stageInfo.StartXPosition,_stageInfo.StartZPosition);
                
        _unitModel.StopUnit();
        _unitModel.ResetUnitPlacement();
        resultView.SetResultText(resultContent);
        GameEventMessage.SendEvent("ToResult");

        _isPlaying = false;
        gamePlayView.IsUnitPlayInteractable(true);
        selectUnitView.ResetSelectUnit();
        selectUnitPlacementView.ResetSelectPlacement();
    }

    private void CharacterPlayMAnimation()
    {
        _characterMotion.WalkAnimation();
        _characterMove.Move();
    }

    private void LoadStart()
    {
        _isLoading.Value = true;
        soundModel.LoadVoice();
        _disposable = Observable.Interval(TimeSpan.FromSeconds(0.1f))
            .Subscribe(value =>
            {
                loadView.ChangeLoad(value/MAXTime);
                if (MAXTime <= value)
                {
                    _isLoading.Value = false;
                }
            });
    }

    private void LoadStop()
    {
        loadView.ResetValue();
        _disposable.Dispose();
    }

    private void AllButtonActive(bool active)
    {
        gamePlayView.AllButtonActive(active);
        selectUnitPlacementView.AllButtonActive(active);
        selectUnitView.AllButtonActive(active);
    }

    private void ResetData()
    {
        _isPlaying = false;
        _isPlayMode = false;
        _placementUnitCount.Value = 0;
        characterCollisionDetection.ResetData();
        gamePlayView.IsUnitPlayInteractable(false);
    }
}

Viewクラスのコード

多数あるので,一部だけになります.

ゲームプレイ画面を更新するGamePlayView.csになります

using System;
using Sirenix.OdinInspector;
using TMPro;
using UniRx;
using UnityEngine;
using UnityEngine.UI;

namespace Project.Scripts.View
{
    public class GamePlayView : MonoBehaviour
    {
        [SerializeField] private Button resetUnit;
        [SerializeField] private Button playUnit;
        [SerializeField] private Button playCharacterMove;
        [SerializeField] private TextMeshProUGUI howToPlayText;
        
        [SerializeField,BoxGroup("ユニット詳細")] private TextMeshProUGUI unitNameText;
        [SerializeField,BoxGroup("ユニット詳細")] private TextMeshProUGUI unitSpeedText;
        [SerializeField,BoxGroup("ユニット詳細")] private TextMeshProUGUI unitSpecialMoveText;
        [SerializeField,BoxGroup("ユニット詳細")] private Image unitImage;
        [SerializeField, BoxGroup("ユニット詳細")] private GameObject imageFrame;
        
        /// <summary>
        /// ユニットの配置をリセット
        /// </summary>
        /// <returns></returns>
        public IObservable<Unit> OnClickResetUnit()
        {
            return resetUnit.OnClickAsObservable();
        }

        /// <summary>
        /// ユニットの動きを確認
        /// </summary>
        /// <returns></returns>
        public IObservable<Unit> OnClickPlayUnit()
        {
            return playUnit.OnClickAsObservable();
        }

        /// <summary>
        /// キャラクターの移動アニメーション開始
        /// </summary>
        /// <returns></returns>
        public IObservable<Unit> OnClickPlayCharacterMove()
        {
            return playCharacterMove.OnClickAsObservable();
        }

        public void IsUnitPlayInteractable(bool active)
        {
            playUnit.interactable = active;
        }

        /// <summary>
        /// すべてのボタンのInteractableの切り替え
        /// </summary>
        /// <param name="active"></param>
        public void AllButtonActive(bool active)
        {
            playUnit.interactable = active;
            resetUnit.interactable = active;
            playCharacterMove.interactable = active;
        }

        /// <summary>
        /// ユニットの詳細情報の更新
        /// </summary>
        /// <param name="unitInfo"></param>
        public void UpdateUnitDetail(UnitInfo unitInfo)
        {
            unitNameText.text = unitInfo.Name;
            unitSpeedText.text = $"移動速度:{unitInfo.Speed}";
            unitImage.sprite = unitInfo.UnitIcon;
            unitSpecialMoveText.text = unitInfo.SpecialMoveContent;
        }

        /// <summary>
        /// ゲーム操作テキストの表示の切り替え
        /// </summary>
        /// <param name="active"></param>
        public void SetHowToPlayText(bool active)
        {
            howToPlayText.gameObject.SetActive(active);
        }

        /// <summary>
        /// ユニットの詳細の表示の切り替え
        /// </summary>
        /// <param name="active"></param>
        public void SetUnitDetailText(bool active)
        {
            unitImage.gameObject.SetActive(active);
            unitNameText.gameObject.SetActive(active);
            unitSpeedText.gameObject.SetActive(active);
            unitSpecialMoveText.gameObject.SetActive(active);
            imageFrame.SetActive(active);
        }
    }
}

画面の切り替え

以下がDoozyUIのGraphになります. GameEventの発行とボタンでの遷移を使い分けています.

f:id:ayousanz:20210503020645p:plain

画面サイズ・アスペクト比の対応

light11.hatenadiary.com

上記を参考にCanvasCanvas Scalerを以下のように設定しました

f:id:ayousanz:20210503185748p:plain

設計で参考にサイト

qiita.com

qiita.com

xrdnk.hateblo.jp

note.com

qiita.com

Oculus Quest2でのOnApplicationFocusとOnApplicationPauseの挙動まとめ【Oculus,Quest2,Unity】

いろいろ詰まったためメモしておきます. Questでのアプリ開発でアプリを終了したときに何かの処理をしたいと思うことが多々あるかと思います. (間違えている可能性もがあるため,コメントまたはtwitterのDMにてご指摘等お待ちしています)

環境

  • Unity 2019.4.5f1
  • Oculus XR Plugin 1.7.0
  • XR Plugin Management 3.2.16
  • Oculus Integration 25.0

挙動まとめ表

関数名 UI表示[1] 時間経過[2] アプリ終了
OnApplicationFocus(bool hasFocus) 変化なし true → false true → false
OnApplicationPause(bool pauseStatus) false → true 変化なし false → true

[1] Oculusボタンを押してメニュー画面を出した状態
[2] HMDを外してHMDが非アクティブになった状態

またそれぞれの関数のEventStatusの初期値は以下の通りです.

  • OnApplicationFocus(bool hasFocus) : true
  • OnApplicationPause(bool pauseStatus) : false

その他の関数の挙動

  • OnDestroy() 呼ばれなかった
  • OnApplicationQuit() 呼び出されなかった

追記 以下のサイトより,Oculus Goの時代から残る問題として上記の二つが呼ばれない問題が残っているそうです. forum.unity.com

関連サイト

developer.oculus.com

stackoverflow.com

qiita.com

greenkour.hateblo.jp

pafu-of-duck.hatenablog.com

developer.android.com

docs.unity3d.com

NotionにTrelloのアーカイブを毎日記録する【Trello,Notion,Python,GCE】

自分のやったタスクを記録しておきたいとおもい,前にEvernoteにTrelloのアーカイブを保存する記事を書きました. その後Notionを知りこちらのほうが便利だと思い乗り換えたのですが,アーカイブの保存のスクリプトの切り替えがやっと終わったのでメモです GCEのcronで毎日0:00に実行してNotionに保存するようにしています

ayousanz.hatenadiary.jp

環境

使用ライブラリ

準備

pageIDとtokenの取得

DBとなるページを公開します. PAGE_IDとTOKENが必要だと言うことで,こちらのサイトを参考にして取得します.

trello側のkey,tokenの取得

こちらのページにアクセスをして,key,tokenを取得します

ライブラリのインストール

使用するライブラリをインストールします

pip install py-trello

pip install notion

Trelloからアーカイブしたカードを取得する

from trello import TrelloClient
import datetime

client = TrelloClient(
    api_key='your trello api key',
    api_secret='your trello api secret',
    token='your trello token'
)


def get_cards():

    cards = []

    board = client.get_board("board id")
    for card in board.all_cards():
        jp_time = card.dateLastActivity.astimezone(datetime.timezone(datetime.timedelta(hours=9)))
        if card.closed:
            card.dateLastActivity = jp_time
            cards.append(card)
    return cards

Notionのtableに書き込む

from notion.client import NotionClient

import get_trello_data

# login
token_v2 = 'token'
client = NotionClient(token_v2=token_v2)

url = "url"
cv = client.get_collection_view(url)

for card in get_trello_data.get_cards():
    row = cv.collection.add_row()
    row.name = card.name
    row.listName = card.get_list().name
    row.task_completion_date = card.dateLastActivity
    row.description = card.description

print("終了")

GCEにサーバーを立てて,cronで毎日実行する

GCEのサーバーの立て方等はいかのサイトを参考にしました.

qiita.com

Anacondaのインストール&ライブラリのインストール

  1. wget http://repo.continuum.io/archive/Anaconda3-2020.11-Linux-x86_64.sh でダウンロードして,ダウンロードしたファイルを bash Anaconda3-2020.11-Linux-x86_64.sh で実行してインストールします.

  2. pip install py-trello ,pip install notion この二つもインストールします.

NotionのAPI制限を回避するためにNotinoスクリプトの書き換え

2021-3-8現在こちらの問題によりNotionライブラリを自分でいけないみたいです. t.co

AnacondaでGUIを簡単に使う方法がすぐにわからなかったので,あきらめてviで書き換えていきましょう(数行なのでなんとかなりました)

上のissueによると store.pyclient.py を書き換えないといけないみたいなので,この二つのファイルパスを調べます.

find . -name "store.py"こちらのコマンドを使って指定したファイルを探します.

./anaconda3/lib/python3.8/site-packages/_pytest/store.py
./anaconda3/lib/python3.8/site-packages/notion/store.py
./anaconda3/pkgs/pytest-6.1.1-py38_0/lib/python3.8/site-packages/_pytest/store.py
./anaconda3/pkgs/pytest-6.2.2-py38h06a4308_2/lib/python3.8/site-packages/_pytest/store.py

という結果が返ってきたので,二番目のパスのファイルを書き換えます.

viなどの使い方は調べたらすぐに出てくると思うので割愛します.

cronの設定

crontab -e を使ってcronの設定をします

日付が変わると記事実行をしてほしいので,00:01に実行するように設定しましょう. また,エラーが出て時にもわかるようにログも残すようにします.

1 0 * * * /home/your name/anaconda3/bin/python3.8 /home/your name/save_trello_task/post_notion.py > /home/your name/save_trello_task/post_notion.log 2 > &1

参考サイト

note.com

一覧からScriptableObjectのカスタム作成と検索ができるScriptableObject拡張【Odin,Unity】

ゲームで敵を複数作るときに ScriptableObject を使っています.少ない数なら自分で画像や名前を追加して生成するというのはいいかもしれません. しかし,これを List<ScriptableObject>で管理する場合作成したものをリストに追加するというのは割と手間です そこで,リストから ScriptableObjectを自分の作りたいようにカスタマイズしながら作成でき,これらを検索できるようにエディタ拡張をOdinを使って作成していきます.

Demo

敵データの作成

f:id:ayousanz:20201226134947g:plain

各データからも再度ステータスを調節することができます

敵の個別データからステータスを再生成

f:id:ayousanz:20201226134902g:plain

作成したデータの検索

f:id:ayousanz:20201226134759g:plain

実装内容

  • リストから敵を作成(名前と画像の指定)
  • 敵の特徴を指定 →特徴に応じてステータスを生成時に決定できます
  • 重複した名前の敵が作られない,空の名前の敵などのミス作成の防止機能(ボタンにヴァリエーション機能)
  • リストの中に敵の検索機能(特徴を指定)
  • 各データからステータスの再生成

Odinについて

既存のワークフローに完璧に展開できる簡単な統合で、Odin を使ってすべてをシリアル変換することができ、80種類以上あるインスペクター属性やその他の機能を備えた、定型コードのない Unity を利用できます。

assetstore.unity.com

実装について

リストから敵を作成

実装方法

  1. CreateInstance<EnemyInfo>() を使って,ScriptableObject を作成します.
  2. enemyInfoの変数を設定します
  3. AssetDatabase.CreateAsset(enemy,createPath) を使って特定のフォルダに .assetを作成します

Code

private const string DATA_PATH = "Assets/TurnGame/Scripts/ScriptableObject/EnemyInfo/EnemyInfos/";

    [BoxGroup("敵生成"), LabelText("名前")] public new string name;
    [BoxGroup("敵生成"), LabelText("敵Sprite"),PreviewField(64)] public List<Sprite> sprites;
    [BoxGroup("敵生成"), LabelText("高HP")] public bool isHighHp;
    [BoxGroup("敵生成"), LabelText("高ATK")] public bool isHighAtk;
    [BoxGroup("敵生成"), LabelText("高攻撃速度")] public bool isHighAtkInterval;

    [Button("敵を追加",ButtonSizes.Large),BoxGroup("敵生成")][DisableIf("IsValidation")]
    public void CreateEnemy()
    {
        var enemy = CreateInstance<EnemyInfo>();
        enemy.name = name;
        enemy.enemySprites = sprites;
        if (isHighHp)
        {
            enemy.hp = Random.Range(3, 5);
            enemy.isHighHp = true;
        }
        else
        {
            enemy.hp = Random.Range(1, 3);
            enemy.isHighHp = false;
        }

        if (isHighAtk)
        {
            enemy.atk = Random.Range(3, 5);
            enemy.isHighAtk = true;
        }
        else
        {
            enemy.atk = Random.Range(1, 3);
            enemy.isHighAtk = false;
        }

        if (isHighAtkInterval)
        {
            enemy.atkInterval = Random.Range(3f, 6f);
            enemy.isHighAtkInterval = true;
        }
        else
        {
            enemy.atkInterval = Random.Range(6f, 10f);
            enemy.isHighAtkInterval = false;
        }
        var createPath = DATA_PATH + name + ".asset";
        AssetDatabase.CreateAsset(enemy,createPath);
        enemies.Add(enemy);
    }

ミス作成の防止機能

実装方法

  1. ListのExistsを使ってリスト内に同じものがあるかどうかを検索します.
  2. attributeの部分に [DisableIf("IsValidation")] を追加して,以下の関数を作成しました.

Code

private bool IsValidation()
    {   
        //名前が空鶴またはスペースの時,リストの中に同じ名前がある場合はボタンを非アクティブにする
        return name.IsNullOrWhitespace() || enemies.Exists(lName => lName.name == name);
    }

各データからステータスの再生成

実装方法

リストから作成する方法と同じ方法で,ステータスを再生成します.

参考サイト

baba-s.hatenablog.com

kan-kikuchi.hatenablog.com

her-hibari-blog.ssl-lolipop.jp

bocchi-games.hatenablog.com