UnityのCustom Packageの作り方と自動リリースの方法

#はじめに
UnityのCustom Packageの作り方と自動リリースの方法まとめました。

対象読者

- 自作のカスタムパッケージを作りたい
- Git、GitHubの使用経験がある
- Assembly Definitionの使い方が分かる

#1. 空のプロジェクトを作成
カスタムパッケージ用のプロジェクトを作ります。すでにパッケージの開発用のプロジェクトがある方は飛ばして大丈夫です。

**1. GitHubで新規リポジトリを作成**
ここでREADME.mdとUnity用の.gitignoreとライセンスも追加しておくと楽です。
![リポジトリ作成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405112/67b6ebc5-36f4-1232-5dd0-4bcefc58193e.png)

**2. Unityプロジェクトを作成**
リポジトリをクローンして新規プロジェクトを作ります。

#2. パッケージのひな型を作成
カスタムパッケージのルールに従ってフォルダやファイルを作ります。

**1. パッケージ名を決める**
会社の場合は`com.[会社名].[パッケージ名]`
個人の場合は`jp.[アカウント名].[パッケージ名]`
です。たぶん...
例
`jp.goma_recorder.unity-samplepackage`

**2. フォルダやファイルを作成**
Packagesフォルダ以下にパッケージ名のフォルダを作ります。
作ったフォルダの中にpackage.json、README.md、CHANGELOG.md、LICENSE.mdを作成します。(ファイルの中身は空で大丈夫です。)
Runtimeフォルダ(必要な人はEditor、Testフォルダも)を作成し、それぞれに対応するasmdfファイルを入れます。サンプルを含める場合はSamplesフォルダを作成し、その下に配置します。

例

```:フォルダ構成
Packages
 └ [PackageName]
    ├ package.json
    ├ README.md
    ├ CHANGELOG.md
    ├ LICENSE.md
    ├ Editor
    │  └ [PackageName].Editor.asmdef
    ├ Runtime
    │  └ [PackageName].asmdef
    ├ Samples
    └ Tests
       ├ Editor
       │  └ [PackageName].Editor.Tests.asmdef
       └ Runtime
          └ [PackageName].Tests.asmdef
```
参考 : [Unityマニュアル パッケージレイアウト](https://docs.unity3d.com/ja/2019.4/Manual/cus-layout.html)

#3. package.jsonを記述
先ほど作ったpackage.jsonを編集してください。package.jsonの内容がPackage Managerやnpmjsのサイトに表示されます。

|属性|説明|
|:--|:--|
|name|パッケージの正式名称|
|displayName|パッケージの表示名|
|version|パッケージのバージョン。新規で作った場合は0.0.0にしてください。|
|description|パッケージの説明|
|unity|パッケージが動作するUnityの最低バージョン|
|keywords|Qiitaのタグみたいなものです。とりあえずUnityは入れておきましょう。|
|author|作者名|
|samples|サンプルの設定。displayNameに表示名、descriptionに説明を書きます。pathはSamples~/から始めて下さい。複数個登録できます。|
|homepage|パッケージのホームページ。特になければGitHubのREADMEのリンクを貼っときましょう。|
|repository|typeにgit。urlにGitHubのurlに入れます。|

例

```json:package.json
{
  "name": "jp.goma_recorder.unity-samplepackage",
  "displayName": "Unity Sample Package",
  "description": "サンプルパッケージです。",
  "version": "0.0.0",
  "author": "Tabi Morinaga",
  "unity": "2020.1",
  "keywords": [
    "unity"
  ],
  "license": "MIT",
  "homepage": "https://github.com/goma-recorder/UnitySamplePackage#readme",
  "repository": {
    "type": "git",
    "url": "https://github.com/goma-recorder/UnitySamplePackage"
  },
  "samples": [
    {
      "displayName": "Sample 1",
      "description": "これはサンプルです。",
      "path": "Samples~/Sample1"
    }
  ]
}
```
参考 : [Unityマニュアル パッケージマニフェスト](https://docs.unity3d.com/ja/2019.4/Manual/upm-manifestPkg.html)、[npm Docs](https://docs.npmjs.com/cli/v6/configuring-npm/package-json)

#4. npmjsの設定
npmjsとはパッケージを登録するサイトです。

**1. npmのトークンを生成**
[npmjs](https://www.npmjs.com/)にログインして右上のアイコンをクリックして出てくるメニューからAccess Tokensを選択。Generate New Tokenをクリックし、Automationを選択します。
その後出てくるコードをコピーして下さい。
※このコードは1回しか確認できないので安全な場所にメモしておいて下さい。
![トークン生成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405112/85c6913f-9181-6441-d47c-4bd34b60d450.png)

**2. リポジトリのSecretsにトークンを登録**
GitHubのSettingをクリックしてSecretsを選択。NPM_TOKENという名前で先ほどのコードを登録します。
![トークン登録.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405112/0522a623-8edb-067e-8ca8-26d42bbde416.png)

#5. リリース作業の自動化
GitHub Actionsを使ってmasterブランチにコミットしたときに自動でリリース作業が行われるようにします。

行われるリリース作業の内容

1. バージョン決め(package.jsonのバージョンの書き換えも)
1. CHANGELOG.mdの生成
1. リリースタグの作成
1. パッケージ部分だけupmブランチに切り出し
1. upmブランチのパッケージ内にREADME、CHANGELOG、LICENSEをコピー
1. npmjsにリリース

**1. Release.ymlを作成する**
GitHubのサイト上で.github/workflows/Release.ymlを作成し、下記の内容をコピーして[パッケージ名]を自分のパッケージ名に書き換えてください。.github/workflows以下のファイルはローカルで編集できないので、必ずサイト上で行ってください。

**2. .releaserc.jsonを作成**
こっちはローカルで作成しても大丈夫です。下記の内容をコピーして[パッケージ名]を自分のパッケージ名に書き換えてください。

[パッケージ名]は合計四つあります。

```yml:Release.yml
name: Release

on:
  push:
    branches:
    - master
jobs:
  release:
    name: release
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
      with:
        fetch-depth: 0

    - name: Semantic release
      id: semantic
      uses: cycjimmy/semantic-release-action@v2
      with:
        extra_plugins: |
          @semantic-release/changelog
          @semantic-release/git
        branch: master
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

    - name: Create upm branch
      run: |
        git branch -d upm &> /dev/null || echo upm branch not found
        git subtree split -P "$PKG_ROOT" -b upm
        git checkout upm
        git checkout master README.md || echo README.md not found
        mkdir Documentation~
        git mv README.md Documentation~/[パッケージ名].md
        git checkout master README.md || echo README.md not found
        git checkout master CHANGELOG.md || echo CHANGELOG.md not found
        git checkout master LICENSE || echo LICENSE not found
        mv -f LICENSE LICENSE.md || echo Can not rename LICENSE
        if [[ -d "Samples" ]]; then
          git mv Samples Samples~
          rm -f Samples.meta
          git config --global user.name 'github-bot'
          git config --global user.email 'github-bot@users.noreply.github.com'
          git commit -am "fix: Samples => Samples~"
        fi
        git push -f -u origin upm
      env:
        PKG_ROOT: Packages/[パッケージ名]

    - name: Create upm git tag
      if: steps.semantic.outputs.new_release_published == 'true'
      run: |
        git tag $TAG upm
        git push origin --tags
      env:
        TAG: upm/v${{ steps.semantic.outputs.new_release_version }}
 
    - name: Setup Node.js
      if: steps.semantic.outputs.new_release_published == 'true'
      uses: actions/setup-node@v1
      with:
        node-version: '12.x'
        registry-url: 'https://registry.npmjs.org'

    - name: Publish on npmjs
      if: steps.semantic.outputs.new_release_published == 'true'
      run: |
        npm install
        npm publish
      env:
        NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```

```json:.releaserc.json
{
    "tagFormat": "v${version}",
    "plugins": [
        [
            "@semantic-release/commit-analyzer",
            {
                "preset": "angular"
            }
        ],
        "@semantic-release/release-notes-generator",
        [
            "@semantic-release/changelog",
            {
                "preset": "angular"
            }
        ],
        [
            "@semantic-release/npm",
            {
                "npmPublish": false,
                "pkgRoot": "Packages/[パッケージ名]"
            }
        ],
        [
            "@semantic-release/git",
            {
                "assets": [
                    "Packages/[パッケージ名]/package.json",
                    "CHANGELOG.md"
                ],
                "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
            }
        ],
        "@semantic-release/github"
    ]
}
```

#6. パッケージを実装する
各々心ゆくまでパッケージの実装をしてください。

#7. リリースする
masterブランチでAngular Commit Message Conventionsに沿ったコミットメッセージを書くと自動でバージョンアップされてリリースされます。

|タイプ|上がるバージョンの種類|説明|
|:--|:--|:--|
|BREAKING CHANGE|メジャー|破壊的変更。単体では発動せず2行目に書く必要があります。|
|feat|マイナー|新機能|
|fix|パッチ|バグ修正|
|perf|パッチ|パフォーマンス向上|

例

```:コミットメッセージ
chore: require Node.js >=10.18
BREAKING CHANGE: Require Node.js >= 10.18
```

参考 : [Angular Commit Message Conventions](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#-git-commit-guidelines)

#8. 確認する
ほかのプロジェクトにインポートして確かめてみます。
npmjs経由とGitHub経由の二種類のインポート方法がありますが、npmjsからのほうがおすすめです。理由は後で解説します。

**npmjsからインポート**
Packagesフォルダ下のmanifest.jsonに以下の例のように書き足します。

例

```json:manifest.json
{
  "scopedRegistries": [
    {
      "name": "goma_recorder",
      "url": "https://registry.npmjs.com",
      "scopes": [ "jp.goma_recorder" ]
    }
  ],
  "dependencies": {
    "jp.goma_recorder.midity.playable": "1.1.0",
    ...
```

**GitHubからインポート**
PackageManagerウィンドウを開き、左上の+▼をクリックしてAdd package from git URLに`https://github.com/[ユーザー名]/[リポジトリ名].git#upm`と入れます。

npmjs経由の方が複雑ですが、Unity上でアップデートできるという大きな長所があります。GitHub経由でインポートした場合はアップデートする時URLを入力し直さなければいけません。

- npmjs経由![npmjsからインポート.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405112/704257fe-ca87-c8cd-b3f1-966638fc8e75.png)
- GitHub経由
![GitHubからインポート.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405112/1e8cf2c1-0159-6469-a4e9-4428ef259451.png)
npmjs経由では`Up to date`ボタンがありますが、GitHub経由ではありません。

#おわりに
初Qiita投稿&初Advent Calendar参加でなかなか緊張しましたが、どうにか書ききることが出来ました。ﾖｶｯﾀ
最後に今回のサンプルとして作ったパッケージと、この方法を使っているPlayable Midiのリポジトリのリンクを置いておきます。

- [Sample Package](https://github.com/goma-recorder/UnitySamplePackage)
- [Playable Midi](https://github.com/goma-recorder/PlayableMidi)

#参考
- [Unityマニュアル](https://docs.unity3d.com/ja/2019.4/Manual/CustomPackages.html)
- [OpenUPMの記事](https://openupm.com/docs/managing-upm-project.html)
- [Semantic Releaseのリポジトリ](https://github.com/semantic-release/semantic-release)