多次元ListをInspectorに表示するclassの拡張

多次元クラスを使うときってないですか?? ちょっと使うところがあったので,以下のサイトを参考にちょっと拡張してみました

kan-kikuchi.hatenablog.com

拡張した機能

  • 指定したindexの値の取得
  • リストの削除(中身の削除)
  • リストの長さの取得

Inspector上での見た目

Odinが入っているため,デフォルトと少し違います f:id:ayousanz:20201004205706p:plain

class Code

//Inspectorに複数データを表示するためのクラス
[System.SerializableAttribute]
public class ValueList{
    public List<string>  list = new List<string>();
    
    public string GetValue(int index)
    {
        return list[index];
    }

    public ValueList(List<string>  list){
        this.list = list;
    }

    public int GetLength()
    {
        return list.Count;
    }

    public void Clear()
    {
        list.Clear();
    }
}

GoogleSpreadSheetから定期予定をtrelloに追加する【GoogleAppsScript】【TrelloAPI】

IFTTTが有料になったことで,月単位や週単位での予定をIFTTTで管理,追加していたものが数の制限で厳しなくなりました

そこで,SpreadSheetに予定の詳細や追加予定日,周期などの書き,GASで自動追加してくれるようにしてみました!

完成画像

カード追加後は締め切りと次回登録日は周期に基づいて,変更されます

スプレットシートでの管理画面画像

trelloのカード

実装機能

  • カードの名前,締め切り,追加するリストの指定
  • 周期より次回登録日を追加後に自動更新
  • 周期のプルダウン選択
  • (注) 周期は現在のところ[毎日,2日,週,月,年]での固定になります

準備

Google Sheet

  1. プルダウンや項目の順番がスクリプトに関係してくるため,こちらからテンプレートをダウンロードしてご使用ください

  2. trelloのkey,tokenの設定をします プルダウンは現在のところ,自分で設定する必要があります. リスト名の列を右クリックし,データの入力規則から「リストを直接指定」で自身のリスト名を追加してください

GoogleAppsScript

Script fileの作成

Goole Driveの中に適当な名前でScriptを作成します (以下のコードをスクリプトに貼り付けます,ファイル名は適当で大丈夫です)

Main.gs

function main() {
  let registrationList = getCanRegistrationItems();
  for(let index=0;index<registrationList.length;index++){
    addCardToTrello(registrationList[index]);
    changeGSheetNextRegistrationDate(registrationList[index]["infoLine"]);
  }
}

//登録する情報をGSheetからfilter->return
function getCanRegistrationItems(){
  let registraionData = [];
  let sheet = SpreadsheetApp.openById(spreadSheetId).getActiveSheet();
  for(let index = 2;index<=sheet.getLastRow();index++){
    let range = sheet.getRange(index,1,1,5).getValues();
    if(isCanRegistration(range[0][4])){
      registraionData.push({
        taskName:range[0][0],
        listName:range[0][2],
        deadline:range[0][3],
        infoLine:index //google sheetのどの行にあったのかを保持
      });
    }
   }
  return registraionData;
}

//google sheetの次回登録日及び締め切りを変更する
function changeGSheetNextRegistrationDate(infoLine){
  let sheet = SpreadsheetApp.openById(spreadSheetId).getActiveSheet();
//  締め切りと次回登録日の取得 = [締め切り,次回登録日]
  let valueTemp = sheet.getRange(infoLine, 2,1,4).getValues();
  let period = valueTemp[0][0];
  console.log("追加周期:"+period);
//  現在の日付を取得
  console.log(valueTemp[0]);
  let beforeRegistrationDate = Moment.moment(valueTemp[0][2]); 
  let beforeDeadlineDate = Moment.moment(valueTemp[0][3]);
  let temp1 = "";
  let temp2 = "";
//  周期の応じて日付を更新後,GSheetの値の更新
  if(period == "毎日"){
    temp1 = beforeRegistrationDate.add(1,'days');
    temp2 = beforeDeadlineDate.add(1,'days');
  }else if(period == "2日"){
    temp1 = beforeRegistrationDate.add(2,'days');
    temp2 = beforeDeadlineDate.add(2,'days');
  }else if(period == "週"){
    temp1 = beforeRegistrationDate.add(1,'weeks');
    temp2 = beforeDeadlineDate.add(1,'weeks');
  }else if(period == "月"){
    temp1 = beforeRegistrationDate.add(1,'months');
    temp2 = beforeDeadlineDate.add(1,'months');
  }else if(period == "年"){
    temp1 = beforeRegistrationDate.add(1,'years');
    temp2 = beforeDeadlineDate.add(1,'years');
  }
  
  console.log("日付の変更")
  let temp1F = temp1.format("YYYY-MM-DD");
  let temp2F = temp2.format("YYYY-MM-DD");
  console.log(temp1F);
  console.log(temp2F);
  let setValues = [[temp1F,temp2F]];
  let nextRegistrationDate = sheet.getRange(infoLine,4,1,2).setValues(setValues);
}

//日付が現在時刻よりも古い場合trueを返す
function isCanRegistration(date){
  let today = new Date();
  let diffDate = new Date(date);
  return Moment.moment(diffDate).isBefore(today);
}

TrelloAPI.gs

const idList = getTrelloListId();

function addCardToTrello(cardInfo){
  let cardName = cardInfo['taskName'];
  let listId = isListId(cardInfo['listName']);
  let dueDate = new Date(cardInfo['deadline']);
  const options =
   {
     "method" : "post",
   };
  let urlCard ="https://trello.com/1/cards?key="+key+"&token="+token+"&idList="+listId+"&name="+cardName+"&due="+dueDate;
  let responseCard = UrlFetchApp.fetch(urlCard,options);
  console.log(cardName+"を"+listId+"のリストに期限を"+dueDate+"として登録しました");
}

function isListId(listName){
  for(let index = 0;index<idList.length;index++){
    if(idList[index]['name'] == listName) {
      return idList[index]['id'];
    }
  }
}

function getTrelloListId(){
  let urlList =  "https://trello.com/1/boards/"+privateBoardId+"/lists?key="+key+"&token="+token+"&fields=name";
  let json = JSON.parse(UrlFetchApp.fetch(urlList).getContentText())
  return json;
}

config.gs

key = "your key";
token = "your token";
privateBoardId = 'your board id';
spreadSheetId = 'spread sheee id'

GASにMoment.jsを追加します

スクリプトエディタから「リソース」→「ライブラリ」から MHMchiX6c1bwSqGM1PZiW_PxhMjh3Sh48 を入れてライブラリを追加してください Versionは最新のもので大丈夫です

(2021-3-15 追記) 2021-3-15現在 Moment.jsのライブラリIDが 15hgNOjKHUG4UtyZl9clqBbl23sDvWMS8pfDJOyIapZk5RBqwL3i-rlCo になっているそうです.

GASを定期実行できるようにする

以下のボタンを教えて定期実行の設定をする

設定画面は以下の通り

時刻の設定は任意の時間で大丈夫です

使用方法

ここまで来れば,spreadsheetの方で追加するカードの名前,日付などを追加してください 日付はダブルクリックすることで,カレンダー記入することができます 一番上と同じ画像ですが..

参考文献

沢山あるので,覚えている範囲で.... qiita.com

tonari-it.com

UnityからiOSアプリをビルドした時にエラー解消メモ【iOS,Unity】

エラーの内容

Could not launch “yousan”
Domain: IDEDebugSessionErrorDomain
Code: 3
Failure Reason: The operation couldn’t be completed. Unable to launch com.yousan because it has an invalid code signature, inadequate entitlements or its profile has not been explicitly trusted by the user.
User Info: {
    DVTRadarComponentKey = 855031;
    RawLLDBErrorMessage = "The operation couldn\U2019t be completed. Unable to launch com.yousan because it has an invalid code signature, inadequate entitlements or its profile has not been explicitly trusted by the user.";
}
--

解消方法

実機からのAppを信頼

iPhone実機で「設定」→「一般」→「プロファイルとデバイス管理」→当該アプリのプロファイルを選択し「Appを信頼」して、もう一度XCodeでBuild->Runすれば起動する。

www.meiseid.co.jp

iOS Developer PortalのIdentifiresで設定するApp ID及びBundle IDと一致させる

slackのchannelの履歴を取得する【GoogleAppsScript】

今回はchannelの投稿履歴をちょっと月ごとにまとめたいと思うことがあったので何番煎じになるかわかりませんがやってみます

slackのtokenを取得とscopeの設定

https://www.dkrk-blog.net/slack/slack_api01www.dkrk-blog.net

slackのapiconversations.historyを使用します

dev.to

チェンネルの履歴の取得

www.pre-practice.net

channel id の取得

  1. チェンネルを右クリック
  2. リンクを取得
  3. URLの最後の文字列

GASのレスポンス情報の確認

qiita.com

コード

ほかのいろいろしたいのでdoPostになっていますが気にせずに

function doPost(e) {
  const base_url = 'https://slack.com/api/conversations.history';
  const api = 'your token key';
  const channelId = 'channel id';
  
  let now = new Date();
  console.log(now.getMonth());
  let url = base_url+'?token='+api+'&channel='+ channelId;
  let respons = UrlFetchApp.fetch(url);
  let json = JSON.parse(respons);
  console.log(json);
  
  var response = { text: 'テストメッセージ' };
  return ContentService.createTextOutput(JSON.stringify(response)).setMimeType(ContentService.MimeType.JSON);
}

Maps for Unityを使ってみる【Unity】

CEDECgoogle が提供するUnityで使えるMaps for Unityというものがあることを知ったので触ってみました (いろんな方がやっているので,ちょっとやったよーとのメモです)

API keyの取得

bibinbaleo.hatenablog.com

実際の感じ

ここら辺を見てみます f:id:ayousanz:20200910005327p:plain

f:id:ayousanz:20200910005358g:plain

TextMeshProとDOTween Proをつかってゲームの起動時のタイトルをいい感じにする【Unity,TextMeshPro,DOTween】

デモ

使用アセット

TextMeshProの日本語化

hi-network.sakura.ne.jp

blog.naichilab.com

TextMeshProのoutlineについて

tsubakit1.hateblo.jp

kazupon.org

実装

タイトルのアニメーション

using System;
using Cysharp.Threading.Tasks;
using DG.Tweening;
using TMPro;
using UnityEngine;

public class TitleAnimation : MonoBehaviour
{
    [SerializeField] private TextMeshProUGUI title;
    // Start is called before the first frame update
    async void Start()
    {
        await UniTask.Delay(TimeSpan.FromSeconds(1f));
        title = GetComponent<TextMeshProUGUI>();
        DOTweenTMPAnimator tmproAnimator = new DOTweenTMPAnimator(title);
        for (int i = 0; i < tmproAnimator.textInfo.characterCount; ++i)
        {
            tmproAnimator.DOScaleChar(i, 0.7f, 0);
            Vector3 currCharOffset = tmproAnimator.GetCharOffset(i);
            DOTween.Sequence()
                .Append(tmproAnimator.DOOffsetChar(i, currCharOffset + new Vector3(0, 30, 0), 0.4f).SetEase(Ease.OutFlash, 2))
                .Join(tmproAnimator.DOFadeChar(i, 1, 0.4f))
                .Join(tmproAnimator.DOScaleChar(i, 1, 0.4f).SetEase(Ease.OutBack))
                .SetDelay(0.07f * i);
        }
    }
}

メニュー選択

using System;
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using TMPro;
using UniRx;
using UniRx.Triggers;
using UnityEngine;

public class SelectButton : MonoBehaviour
{
    
    [SerializeField] private List<TextMeshProUGUI> menuList;
    [Header("game mode")]
    [SerializeField] private Canvas settingsPanel;
    [SerializeField] private Canvas gameContinuePanel;
    private TextMeshProUGUI selectedMenu;

    private int menuIndex = 0;
    private bool isOpenSettings = false;
    private bool isOpenContinue = false;

    private const float OutlineWidth = 0.3f;
    // Start is called before the first frame update
    void Start()
    {
        selectedMenu = menuList[menuIndex];
        selectedMenu.outlineWidth = OutlineWidth;
        Debug.Log(selectedMenu.name);
        this.UpdateAsObservable().Subscribe(
                _ =>
                {
                    if (menuIndex < menuList.Count-1 && Input.GetKeyDown(KeyCode.DownArrow))
                    {
                        selectedMenu.outlineWidth = 0f;
                        menuIndex++;
                        selectedMenu = menuList[menuIndex];
                        selectedMenu.outlineWidth = OutlineWidth;
                        Debug.Log(selectedMenu.name);
                    }else if (0 < menuIndex && Input.GetKeyDown(KeyCode.UpArrow))
                    {
                        selectedMenu.outlineWidth = 0f;
                        menuIndex--;
                        selectedMenu = menuList[menuIndex];
                        selectedMenu.outlineWidth = OutlineWidth;
                        Debug.Log(selectedMenu.name);
                    }
                    if(Input.GetKeyDown(KeyCode.Z))OpenMenu();
                });
    }

    async void OpenMenu()
    {
        if (selectedMenu.name == "NewGame")
        {
            Debug.Log("new game");
        }else if (selectedMenu.name == "Continue")
        {
            isOpenContinue = true;
            gameContinuePanel.gameObject.SetActive(true);
            await UniTask.Delay(TimeSpan.FromSeconds(3f));
            gameContinuePanel.gameObject.SetActive(false);
        }else if (selectedMenu.name == "Settings")
        {
            isOpenSettings = true;
            settingsPanel.gameObject.SetActive(true);
            await UniTask.Delay(TimeSpan.FromSeconds(3f));
            settingsPanel.gameObject.SetActive(false);
        }else if (selectedMenu.name == "Exit")
        {
            #if UNITY_EDITOR
                  UnityEditor.EditorApplication.isPlaying = false;
            #elif UNITY_STANDALONE
                  UnityEngine.Application.Quit();
            #endif
        }
    }

    void SettingOption()
    {
        
    }
}

参考サイト

game-ui.net

EasySave3で自作のclassを使って保存する【Unity,EasySave3】

はじめに

ゲームのテンプレートを作成してる中でセーブ&ロードは必須だろうと思い実装をしていたのですが,基本EasySaveではint,sting,Vectorしか保存できないみたいなので自作のclassどうやってやるんだろうと思い,思ったよりも時間がかかったので記事にしておきます

サンプルアプリ

実際に今回は以下のようなものを作って自作のclassの情報をセーブ&ロードしてみました

  • 現在時間を取得・表示
  • 画面のクリックの取得・表示
  • 画面をクリックしたときにランダムの数字を発行・表示
  • ドラッグアンドドロップできるUIを配置(この位置を保存)

こんな感じで動きます 途中で保存,最後のほうでロードしています

使用アセット

実装

難しいことではなく,単にclass -> jsonにしてからstringで保存すればよかったみたいです

ドラッグアンドドロップ

using System;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

// Imageコンポーネントを必要とする
[RequireComponent ( typeof ( Image ) )]

// ドラッグとドロップに関するインターフェースを実装する
public class DragAndDrop : MonoBehaviour, IDragHandler, IBeginDragHandler, IEndDragHandler
{
    [SerializeField] private Image circle;
    private Color _beforeColor;
    private Vector3 _circlePosition;

    private void Start()
    {
        _circlePosition = transform.position;
    }

    public void OnBeginDrag ( PointerEventData eventData )
    {
        _beforeColor = circle.color;
        circle.color = Color.red;
    }

    public void OnDrag ( PointerEventData eventData )
    {
        // ドラッグ中は位置を更新する
        transform.position = eventData.position;
    }

    public void OnEndDrag ( PointerEventData eventData )
    {
        circle.color = _beforeColor;
        transform.position = eventData.position;
        _circlePosition = eventData.position;
    }

    public Vector3 GetCirclePosition()
    {
        return _circlePosition;
    }

    public void SetCirclePosition(Vector3 position)
    {
        gameObject.transform.position = position;
    }
}

セーブ&ロード

using UniRx;
using UniRx.Triggers;
using UnityEngine;

public class GameManager : MonoBehaviour
{
    [SerializeField] private ClickManager clickManager;

    [SerializeField] private DragAndDrop dragAndDrop;
    private SaveData _saveData;

    private void Start()
    {
        #region save data
        this.UpdateAsObservable().Where(_ => Input.GetKeyDown(KeyCode.S)).Subscribe(_ =>
        {
            _saveData = new SaveData();
            _saveData.Time = clickManager.GetTime();
            _saveData.RandomNumber = clickManager.GetRandomNumber();
            _saveData.ClickNumber = clickManager.GetClickCount();
            _saveData.CirclePosition = dragAndDrop.GetCirclePosition();
            SaveES3(_saveData);
        });
        #endregion

        #region load data

        this.UpdateAsObservable().Where(_ => Input.GetKeyDown(KeyCode.L)).Subscribe(_ =>
        {
            SaveData saveData = LoadES3("1");
            dragAndDrop.SetCirclePosition(saveData.CirclePosition);
            clickManager.SetText(saveData.Time,saveData.ClickNumber,saveData.RandomNumber);
        });

        #endregion
    }

    void SaveES3(SaveData saveData)
    {
        string json = JsonUtility.ToJson(saveData);
        Debug.Log(json);
        ES3.Save("1",json);
    }
    
    SaveData LoadES3(string key){
        if (ES3.KeyExists(key))
        {
            var json = ES3.Load<string>(key);
            Debug.Log(json);
            SaveData saveData = JsonUtility.FromJson<SaveData>(json);
            return saveData;
        }
        else
        {
            return null;
        }
    }
}

class SaveData
{
    public string Time;
    public string RandomNumber;
    public string ClickNumber;
    public Vector3 CirclePosition;
}

クリックしたときの処置

using System;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using Random = UnityEngine.Random;

public class ClickManager : MonoBehaviour,IPointerClickHandler
{
    [SerializeField] private Text timeText;
    [SerializeField] private Text clickCountText;
    [SerializeField] private Text randomNumberText;
    private int _clickCount = 0;
    

    public void OnPointerClick(PointerEventData eventData)
    {
        int temp = Random.Range(0, 100);
        randomNumberText.text = "randomNumber:"+temp;

        _clickCount++;
        clickCountText.text = "clickCount:"+_clickCount;
        timeText.text = "now time:" + DateTime.Now.ToLongTimeString();
    }

    public string GetTime()
    {
        return timeText.text;
    }

    public string GetClickCount()
    {
        return clickCountText.text;
    }

    public string GetRandomNumber()
    {
        return randomNumberText.text;
    }

    public void SetText(string time, string clickCount, string randomNumber)
    {
        timeText.text = time;
        clickCountText.text = clickCount;
        randomNumberText.text = randomNumber;
    }
}

参考サイト

kan-kikuchi.hatenablog.com

kazupon.org

unity-shoshinsha.biz

negi-lab.blog.jp

http://magcat.php.xdomain.jp/brog/?p=195magcat.php.xdomain.jp