プログラマブル深海魚

目立たないけど華やかに

アバター改変でやってしまったアニメータ関連のミス事例

VRC向けのアバター改変をしている際に、アニメータ関連でミスしてしまった事例を備忘録としてまとめます。

目次

事例

アニメーションレイヤーのWeightが0になっている

初歩的なミスにして、結構やりがち(だと思う)なミス。Weightが0になっているので、当然アニメーションが適用されない。
これをやりがちなのには理由があって、アニメーションレイヤーを新規作成したときのWeightのデフォルト値が0だからです。(デフォルトは1でもいい気がするんだけどね……)

新規作成したレイヤーはWeightが0となっている

VRC Expression Parametersにアニメーションパラメータを追加していない

VRC特有の問題です。アバターを他人から見たときに、アニメーションが同期されない問題。
アニメーションパラメータはVRC Expression Parametersに設定して"Synced"にチェックを入れると他人にパラメータ値が同期されますが、設定していなかったためにパラメータが同期されず、他人視点ではアニメーションが発動しませんでした。
もともと私は少し勘違いをしていて、VRC Expression ParametersはVRC Expression Menuでパラメータを変更できるようにするための設定だと思っていたのですが、それが理解として正しくありませんでした。

アニメーションパラメータを新規追加した (New Bool)

VRC Expression Parametersにもパラメータを追加しないと他人に同期されない

なお、ContactReceiver等に"LocalOnly"というパラメータがありますが、こちらのチェックを外してもパラメータが同期されるわけではありません。詳細は割愛します。

Interruption Sourceの設定が適切でない

この問題は、おそらく私が使用しているアバターの「びしょぬれのしずくさん」の表情アニメータコントローラがやや特殊な作りであるため発生したものだと思います。(他のアバターについてはあまり知識がないので、もし一般的な作りだったらすみません)
しずくさんの表情アニメータコントローラは、以下のように表情が切り替わるときにデフォルト表情を経由するようになっています。本来、ステートのTransision設定でInterruption SourceがNext Stateになっているため、途中のステート(デフォルト表情)がスキップされて変更先の表情に直接変わるような動きになっています。それが、Interruption SourceがNoneになっていたことで、表情の切替時に一瞬だけデフォルト表情が表示されるようになってしまっていました。

しずくさんの表情アニメータコントローラを簡略化して、Face 1からFace 2の切替を図示したもの

おわり

以上、アニメータ関連のミス実例3つでした。ただの備忘録ですが、誰かのお役に立てば幸いです。

参考文献

知識0だけどExメニューでアクセとか武器とかをオンオフしたい!!(初心者向けExpression解説1)[VRChat]|Ran_kotonoha
https://note.com/ran_kotonoha/n/nd1fc7f1d84ea#9fe187ed-1547-45ba-b9d3-f3b695817597

【VRChat】Contactsで物を出し入れする【Avatar Dynamics】|風庭ゆい
https://note.com/yui0471/n/n54beffc6df97

VRChat上のアバター間データ同期の概念|おにく/Oniku@VRC
https://note.com/onikuvrc/n/n4fc95cf23dab

【初心者Unity】アニメーションの遷移を検証③【遷移の割り込み】 | TECH PROjin
https://tech.pjin.jp/blog/2021/08/31/unity-transition_3/

ScalaTestのウォークスルー

ScalaTestでどのようにテストを記述するのかを学習したため、メモ的に記事に内容を残します。

目次

テストの実行(sbt)

testタスク

プロジェクトに含まれるすべてのテストを実施するには、sbtのtestタスクを実行します。

testOnlyタスク

特定のテストクラスのみテストを実施するには、testOnlyタスクを実行します。
"testOnly {クラス名}"の形式で実行すると、指定されたクラスのテストのみが実行されます。また、クラス名にはワイルドカードも使用できます。

テストの記法

ScalaTestでは、いくつかの記法でテストを記述することができます。
記法はテストの宣言の見た目にのみ影響を与え、テストの内容には影響を与えません。そのため、その場で最適な記法を選ぶことができます。

自分で記法を選択するのが面倒な場合、ScalaTestの公式DocではFlatSpecが推奨されています。

以下では一部の記法を紹介し、それ以降ではWordSpecスタイルを採用します。

FunSuiteスタイル

FunSuiteスタイルは非常にシンプルな記法で、xUnit(例えばJUnit)に近い記法です。

import org.scalatest.funsuite.AnyFunSuite

class FunSuiteTest extends AnyFunSuite {
  test("A negative number should be less than 0") {
    assert(-1 < 0)
  }
}

FlatSpecスタイル

FlatSpecスタイルはFunSuiteに近い記法ですが、"X should Y"や"X must Y"といった形式で記述します。
FunSuiteスタイルを、やや自然言語のように記述できるようにしたといった感じでしょうか。

import org.scalatest.flatspec.AnyFlatSpec

class FlatSpecTest extends AnyFlatSpec {
  "A negative number" should "be less than 0" in {
    assert(-1 < 0)
  }
}

FunSpecスタイル

FunSuiteスタイルを入れ子構造にできるようにしたような記法です。
RubyRSpecと似た記法のようで、describeとitによりテストを入れ子で記述します。

import org.scalatest.funspec.AnyFunSpec

class FunSpecTest extends AnyFunSpec {
  describe("A number") {
    describe("when negative") {
      it("should be less than 0") {
        assert(-1 < 0)
      }
    }
  }
}

WordSpecスタイル

こちらはFlatSpecスタイルを入れ子構造にしたような記法です。
Scalaの別のテストフレームワークであるspecs/specs2と似た記法です。

import org.scalatest.wordspec.AnyWordSpec

class WordSpecTest extends AnyWordSpec {
  "A number" when {
    "negative" should {
      "be less than 0" in {
        assert(-1 < 0)
      }
    }
  }
}

アサーション(Assertions)

アサーションを使用してテストを記述することができます。
以下で、一部のアサーションを紹介します。

assert

assertは記述した条件がtrueの場合に成功し、falseの場合に失敗します。

"A Set" when {
  "empty" should {
    "have size 0" in {
      assert(Set.empty.size == 0)
    }
  }
}

assertResult

あるコードの結果が特定の値になることを確認したい場合、assertResultを利用できます。

"A Set" when {
  "singleton" should {
    "have size 1" in {
      assertResult(1) {
        Set("test").size
      }
    }
  }
}

assertThrows

あるコードが特定の例外をスローすることを確認したい場合、assertThrowsを利用できます。

"A Set" when {
  "empty" should {
    "produce NoSuchElementException when head is invoked" in {
      assertThrows[NoSuchElementException] {
        Set.empty.head
      }
    }
  }
}

intercept

assertThrowsと同様に例外をスローすることを確認できますが、テストを中断せず例外の内容をさらにテストできます。

"A Set" when {
  "empty" should {
    "produce NoSuchElementException including 'empty' in message when head is invoked" in {
      val caught = intercept[NoSuchElementException] {
        Set.empty.head
      }
      assert(caught.getMessage.contains("empty"))
    }
  }
}

succeed/fail

テストを強制的に成功/失敗させます。

"A test" should {
  "succeed" in {
    succeed
  }

  "fail" in {
    fail("Always fails.")
  }
}

マッチャー(Matchers)

ScalaTestには、アサーションを"should"や"must"といった単語を使用して記述するためのDSLドメイン固有言語)が定義されています。 これをマッチャー(Matchers)と言います。
これらは、should.Matchersまたはmust.Matchersをミックスインすることで使用できます。

以下では、一部のマッチャーを紹介します(Shouldマッチャーを使用します)

等価性

equal, not equalを用いると、等価性をチェックできます。
また、be, not beも用いることができますが、こちらは等価性のカスタマイズができません(デフォルトの等価性チェック)

"A Set" when {
  "empty" should {
    "have size 0" in {
      Set.empty.size should equal (0)
    }
    "have not size 1" in {
      Set.empty.size should not equal (1)
    }
  }
}

より大きい・小さい

<, > , <=, >= を利用すると、より大きい・小さいや以上・以下の比較ができます。

"A number" when {
  "negative number" should {
    "less than 0" in {
      -1 should be < 0
    }
  }
  "positive number" should {
    "greater than 0" in {
      1 should be > 0
    }
  }
  "0" should {
    "0 or less" in {
      0 should be <= 0
    }
  }
}

ある数から一定の範囲内

ある数nから一定の範囲dの中に数値が入っているかを、"+-"を利用してチェックできます。小数も可能。

"A number" when {
  "5" should {
    "in range 6 +- 2" in {
      5 should equal (6 +- 2)
    }
  }
}

サイズ・長さ

have size, have lengthを用いると、サイズや長さのチェックができます。

"A Set" when {
  "empty" should {
    "have size 0" in {
      Set.empty[Number] should have size 0
    }
  }
}
"'ABC'" should {
  "have length 3" in {
    "ABC" should have length 3
  }
}

Booleanプロパティ

shouldBeのあとにプロパティ名のシンボルを記述すると、Booleanプロパティの真偽値をチェックできます。

"A Set" when {
  "empty" should {
    "be empty" in {
      Set.empty[Number] shouldBe Symbol("isEmpty")
    }
  }
}

参考文献

ScalaTest User Guide
https://www.scalatest.org/user_guide

ScalaTest入門 - Qiita
https://qiita.com/verdoyant/items/e8d81e80268b714fdfbc

Scalaユニットテスト入門 - seratch's weblog in Japanese
https://seratch.hatenablog.jp/entry/20110807/1312726957

【STEP4】テスト : Scalaでコードを書く - Qiita
https://qiita.com/yukinagae/items/038f75e9a1bf17978886

Scalatest: 特定のテストケースだけ実行したい - Qiita
https://qiita.com/suin/items/0294a53d6babd69f29a9

Google Mapをページに埋め込む

WebページにGoogle Mapを表示してみたかったので、実際にやってみました。

Google Mapを表示するには、Maps JavaScript APIを利用します。
Maps JavaScript APIGoogle Cloud Platformを構成するAPIの1つなので、まずはGoogle Cloud Platformで必要な準備をしてから実際にコードを書いていきます。

目次

プロジェクトを作成する

APIを使用するために、まずGoogle Cloud Platformにプロジェクトを作成する必要があります。

まず、Google Cloudを開きます。
https://cloud.google.com/

Google Cloud

[コンソールへ移動]を押して、Google Cloud Consoleに移動します。

Google Cloud Console

[プロジェクトの選択]を押して、プロジェクト選択画面の[新しいプロジェクト]からプロジェクトを作成します。

プロジェクト選択画面
プロジェクト名に「Test Project」と入力している
プロジェクトの作成

プロジェクト名に「Test Project」と入力し(実際には何でもいい)、[作成]を押します。
これでプロジェクトが作成されます。

新規プロジェクト

請求先アカウントの作成

課金をしない場合でも、プロジェクトには請求先の設定が必要になります。
請求先を設定するためには、請求先アカウントを作成してプロジェクトとリンクします。

まず、請求先アカウントを作成します。左のメニューから[お支払い]を押します。

プロジェクトの画面

[請求先アカウントを管理]を押します。

お支払い

[アカウントを作成]を押します。

請求先アカウントの管理画面

名前を入力して(ここでは「Test Project 請求先」)、[続行]を押します。

名前に「Test Project 請求先」と入力している
請求先アカウント作成

お支払い方法を入力して、[送信して課金を有効にする]を押します。これで請求先アカウントが作成されます。

お支払い方法を入力する
お支払い方法入力

次に、プロジェクトに請求先アカウントをリンクするために、
プロジェクトのアカウントに「プロジェクト請求管理者」のロールを追加します。

左のメニューから[IAM]を押して、IAMの管理画面を開きます。 アカウントの右にある鉛筆アイコンを押します。

IAMの管理画面

[別のロールを追加]を押して、増えた枠の「ロール」に[Billing]>[プロジェクト請求管理者]を指定します。
指定したら、[保存]を押します。

[[Billing]>[プロジェクト請求管理者]を追加する]
ロールの割り当て

これで、「プロジェクト請求管理者」のロールが追加されました。

IAMの管理画面(ロール追加後)

最後に、請求先アカウントをプロジェクトにリンクします。

再度、左のメニューから[お支払い]を押して、表示された画面で[請求先アカウントをリンク]を押します。

お支払い

作成した請求先アカウントを選択し、[アカウントを設定]を押します。

請求先アカウントを指定する

これで、請求先の設定が終わりました。

お支払い(請求先アカウント設定後)

APIキーを作成する

Maps JavaScript APIを利用するために、まずはGoogle Maps Platform APIAPIキーを作成します。

左のメニューから[Google Maps Platform]を押します。

プロジェクトの画面

そのまま、[GOOGLE MAPS PLATFORM に移動]を押します。

APIキー作成

APIキーを、使用するリファラーに制限します。
今回、私はローカル環境で、Play Frameworkで作成したアプリケーション上を動かす想定なので、HTTPリファラーを選択してリファラーを"localhost:9000/*"としました。
(9000はAkka HTTPのデフォルトのポート番号)

今回は「HTTPリファラー(ウェブサイト)」、リファラーに「localhost:9000/*」を指定
APIキーの制限

以上で、APIキーの作成が完了しました。
[認証情報]の画面で「鍵を表示します」を押すと、作成したAPIキーを表示できます。

実際に埋め込んでみる

実際に、以下を参考にGoogle Mapを表示してみます。
https://developers.google.com/maps/documentation/javascript/overview

まず、HTMLを作成します。
Google Mapを表示するdiv要素を用意し、Google Maps APIスクリプトを読み込みます。("YOUR_API_KEY"に発行したAPIキーを入れます)

<!DOCTYPE html>
<html>
<head>
  <title>Google Maps Test</title>
</head>
<body>
  <div id="map"></div>
  <script async src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap"></script>
</body>
</html>

次に、スタイルシートスクリプトを書きます。今回はSCSSとTypeScriptで書きます。
スタイルは、マップ表示領域のサイズを指定します。スクリプトでは、Google Maps APIスクリプト読み込みでcallbackに指定しているinitMap関数を定義します。
TypeScriptからGoogle Maps APIを使用する場合、先に@types/google.mapsパッケージをインストールする必要があります。

body {
  margin: auto;
  width: 60vw;
}
#map {
  width: 100%;
  height: 600px;
}
let map: google.maps.Map;
function initMap(): void {
  map = new google.maps.Map(document.getElementById("map") as HTMLElement, {
    center: { lat: 35.625, lng: 139.429 },
    zoom: 18,
  });
}

HTMLのhead内にCSSJavaScriptの読み込みを追加します。
(それぞれ/assets/styles/test.cssと/assets/scripts/test.jsに配置されると仮定)

<link href="/assets/styles/test.css" rel="stylesheet">
<script defer src="/assets/scripts/test.js"></script>

これで、画面にGoogle Mapが表示されます。

Google Mapの表示

初期の中心点・倍率を指定する

google.maps.Mapのコンストラクタの第2引数に与えたオプションによって、中心地と倍率を変更することができます。

  • center: 中心点(緯度・軽度)
  • zoom: 倍率

上記の例ではサンリオピューロランドを表示しましたが、例えば以下とするとよみうりランドを表示することができます。

map = new google.maps.Map(document.getElementById("map") as HTMLElement, {
  center: { lat: 35.628, lng: 139.516 },
  zoom: 16,
});

中心点をよみうりランドに変更

マーカーを表示する

以下のステップ2にある例のように、google.maps.Markerでマップのマーカーを定義します。
https://developers.google.com/maps/documentation/javascript/adding-a-google-map

スクリプトにマーカーの定義を追加します。また、オプションでdraggable=trueとすると、マーカーを画面上でドラッグして移動できるようになります。
(デフォルトではドラッグ不可)

const marker = new google.maps.Marker({
  position: { lat: 35.625, lng: 139.429 },
  draggable: true,
  map: map,
});

マーカーの表示

マーカーの位置を取得する

マーカーの位置を取得するには、マーカーに対してgetPosition()を使用します。
getPosition()により位置を取得して、さらにlat()とlng()でそれぞれ緯度と経度を取得できます。

「Get Position」ボタンを押すと、ダイアログで現在の緯度・経度を表示するように修正してみました。

<!DOCTYPE html>
<html>
<head>
  <title>Google Maps Test</title>
  <link href="/assets/styles/test.css" rel="stylesheet">
  <script defer src="/assets/scripts/test.js"></script>
</head>
<body>
  <div id="map"></div>
  <input id="getPosition" type="button" value="Get Position">
  <script async src="https://maps.googleapis.com/maps/api/js?key=AIzaSyC7BUBhU-Mf2GRr8ZRXF5IXF8oW3nnmQ20&callback=initMap"></script>
</body>
</html>
let map: google.maps.Map;
let marker: google.maps.Marker;

function initMap(): void {
  map = new google.maps.Map(document.getElementById("map") as HTMLElement, {
    center: { lat: 35.625, lng: 139.429 },
    zoom: 18,
  });

  marker = new google.maps.Marker({
    position: { lat: 35.625, lng: 139.429 },
    map: map,
    draggable: true,
  });
}

document.getElementById("getPosition").addEventListener('click', (e: Event) => {
  alert(`Lat: ${marker.getPosition().lat()}, Lng: ${marker.getPosition().lng()}`);
});

緯度・経度の取得と表示

おわり

以上の内容で、位置を表示したり逆に位置を指定させたりといったことが簡単にできるようになります。
今回はこちらには載せていませんが、Web APIのGeolocation APIを使って現在位置をマーカーの位置にするといったこともできたので、
いろいろと応用できそうです。

参考文献

Google Maps Platform のドキュメント | Maps JavaScript API | Google Developers
https://developers.google.com/maps/documentation/javascript?hl=ja

Google Maps API を使ってみた - Qiita
https://qiita.com/Haruka-Ogawa/items/997401a2edcd20e61037

Google Maps PlatformのAPI 使用の上限設定 – @dream-PLUS
https://ring-and-link.co.jp/dream2000/user/notice/web/2975

Google Cloud Platformの課金アカウント作成手順
https://webservicies.net/gcp-accreate/

Play FrameworkでTypeScript, SCSSを動かす

Play FrameworkでTypeScript, SCSSを動かしてみたので、
備忘録も兼ねて記録を残します。

目次

sbtプラグインを追加する

sbtには、sbt-webというWeb開発用のプラグイン向けライブラリがあり、
プロジェクトでSbtWebを有効にしてsbt-web用プラグインを追加すれば、対応するファイルがコンパイルされるようになります。
https://github.com/sbt/sbt-web

TypeScript, SCSS(Sass)にも、それぞれプラグインが作られています。
https://github.com/platypii/sbt-typescript
https://github.com/irundaia/sbt-sassify

以下のようにplugins.sbtにプラグインの記述を追加して、

addSbtPlugin("com.github.platypii" % "sbt-typescript" % "4.6.4")
addSbtPlugin("io.github.irundaia" % "sbt-sassify" % "1.5.2")

SbtWebをプロジェクトで有効にします。

lazy val proj = (project in file("."))
  .settings(
    name := "SampleProject"
  )
  .enablePlugins(PlayScala, SbtWeb)

これで、assetsタスクでTypeScriptとSCSS(Sass)のコンパイルが有効になります。
(runで実行中のときも、ファイルの変更を検知して再コンパイルしてくれる)

プラグインの設定

sbt-typescriptは、プロジェクトのルートディレクトリにtsconfig.jsonを作成し、
"compilerOptions"の下にコンパイラオプションを記述することができます。
コンパイラオプションの一覧はこちら(https://www.typescriptlang.org/docs/handbook/compiler-options.html
今回は、SourceMapを生成するようにしたかったので、以下の設定を入れました。

{
  "compilerOptions": {
    "sourceMap": true,
    "mapRoot": "/assets",
    "sourceRoot": "/assets"
  }
}

sbt-sassifyは、build.sbtからSassKeysを通して設定することができます。
sbt-sassifyを使う上で実は1点うまくいかなかった点があって、assetsのルート直下ではなくサブディレクトリ(たとえば/assets/styles)にSCSSを配置すると、
SourceMapの対応付けがおかしくなってしまいます("sources"が"styles/<ファイル名>"となってほしいところが"<ファイル名>"となる)
そこで、今回は"SassKeys.assetRootURL"をサブディレクトリに設定しました(ただし、この方法はSCSSをディレクトリ分けすると使えない……)

SassKeys.assetRootURL := "/assets/styles"

テンプレートからの参照

作成したTypeScriptやSCSSは、テンプレートから参照する必要があります。
TypeScriptやSCSSをコンパイルして生成されたJavaScript/CSSを参照するには、
アセット用のルーティングを作成し、controller.routes.Assetsに作成されるアセット用のリバースコントローラを利用します。

routesに以下の記述でルーティングを追加します。

GET /assets/*file controllers.Assets.at(path="/public", file)

これによりcontroller.routes.Assetsにリバースコントローラが生成され、以下のように@Assets.at("<生成したファイルの相対パス>")により参照できるようになります。

@import controllers.routes.Assets

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Test</title>
  <link href="@Assets.at("styles/test.css")" rel="stylesheet">
  <script type="module" src="@Assets.at("scripts/test.js")"></script>
</head>
<body>
  ......
</body>
</html>

実際に動かす

実際に簡単なページを作って動かしてみます。

まずは、テンプレートとControllerを作成します。

@import controllers.routes.Assets

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Test</title>
  <link href="@Assets.at("styles/test.css")" rel="stylesheet">
  <script type="module" src="@Assets.at("scripts/test.js")"></script>
</head>
<body>
<main>
  <input id="testBtn" type="button" value="TEST">
</main>
</body>
</html>
import play.api.i18n.I18nSupport
import play.api.mvc.{AbstractController, Action, AnyContent, ControllerComponents}

import javax.inject.Inject

class TestController @Inject()(cc: ControllerComponents)() extends AbstractController(cc) with I18nSupport {
  def test(): Action[AnyContent] = Action { implicit request =>
    Ok(views.html.Test())
  }
}

次に、/assets/scriptsと/assets/stylesにそれぞれTypeScriptとSCSSを作成します。

document.querySelector('#testBtn').addEventListener('click', (event: Event) => {
  alert('TEST!');
});
body {
  margin: auto;
  width: 60vw;
}
input[type="button"] {
  background-color: #efefef;
  border: 1px solid #efefef;
  border-radius: unset;
  width: 100%;
  height: 40px;
  line-height: 1.2;
  &:focus {
    outline: none;
  }
  &:hover {
    color: #fdfdfd;
    background-color: #777777;
    border-color: #777777;
    cursor: pointer;
  }
}

最後に、routesにルーティングを定義します。

GET /test TestController.test()

これで動くようになるので、実際に動かしてみます。

TESTボタンが表示されている
実際の画面

ボタンを押すと、こちらのようにポップアップが表示されます。

TEST!と表示される
ポップアップ

クライアント側のライブラリ依存関係を管理する

JavaScriptCSSを書く上で、サードパーティのライブラリ(例えばnode.jsやjQueryなど)を使用する場合があると思います。
そうした場合、WebJarsというクライアント側のライブラリをMaven等で管理できるようにしたサービスを利用することができます。

build.sbtに以下のようにwebjarsのライブラリの依存関係を追加することで、クライアント側のライブラリについても依存関係を管理できます。
TypeScriptのコンパイル時に必要な情報も、これによって追加することができます。

libraryDependencies += "org.webjars.npm" % "jquery" % "3.6.4"
libraryDependencies += "org.webjars.npm" % "types__jquery" % "3.5.16"
libraryDependencies += "org.webjars.npm" % "types__sizzle" % "2.3.3"

注: @types/jqueryを追加すると@types/sizzleを解決できなくてエラーになりました。[0,)というversionの表記を解釈できていない模様。
範囲指定のバージョンには対応しているみたいですが、[0,)という記述には対応できていない?
https://github.com/sbt/sbt/issues/2647

また、@typesなど特殊な文字を含むライブラリを追加する場合、npmパッケージとWebJarsでの名前が若干異なるので、
以下の設定も追加する必要があるようです。

resolveFromWebjarsNodeModulesDir := true

WebJarsで追加したライブラリをPlayで扱うために、webjars-playを依存関係に追加します。

libraryDependencies += "org.webjars" %% "webjars-play" % "2.8.18"

webjars-playにWebJars用のルーティングが用意されているため、routesに以下を追加してインクルードします。

-> /webjars webjars.Routes

これで、リバースルーティングにより、@org.webjars.play.routes.WebJarAssets.at("<ライブラリ名>/<ライブラリのバージョン>/<パッケージ内のパス>")で参照できるようになります。

@import org.webjars.play.routes.WebJarAssets
<script src="@WebJarAssets.at("jquery/3.6.4/dist/jquery.min.js")"></script>

jQueryを使えるようになりました。

$('#testBtn').on('click', (event: JQuery.Event) => {
  alert('TEST!');
});

おわり

今回はTypeScriptとSCSS(Sass)を使いましたが、CoffeeScriptやLESS等でもほぼ同様の手順で利用可能なはずです。
想像していたよりは手軽に導入ができました。素のJavaScriptCSSを書くよりは絶対書きやすいと思うので、使っていきます。

参考文献

Assets - 2.8.x
https://www.playframework.com/documentation/2.8.x/Assets

Scala Routing - 2.8.x
https://www.playframework.com/documentation/2.8.x/ScalaRouting

webjars/webjars-play
https://github.com/webjars/webjars-play

platypii/sbt-typescript: An sbt plugin for compiling typescript
https://github.com/platypii/sbt-typescript

irundaia/sbt-sassify: sbt-web plugin for Sass files
https://github.com/irundaia/sbt-sassify

WebJars - Documentation
https://www.webjars.org/documentation

Play Framework with WebJars で管理画面をサクッと作ってみる | | AI tech studio
https://cyberagent.ai/blog/tech/scala/3334/

java - Play Framework: Arrow ("->") in routing - Stack Overflow
https://stackoverflow.com/questions/31009785/play-framework-arrow-in-routing

UAC管理下にあるフォルダを変更可能にする

UAC管理下の場所にプログラムをインストールすると設定ファイル等が作れないので、 一般ユーザにも変更権限を与える。

プログラムをインストールした特定のフォルダのみに権限を与えるのでセキュリティ上問題にはならなさそうだが、 もしかするとVirtualStore機能を使ったほうがいいかもしれない。

対象のフォルダを 右クリック→[プロパティ] でプロパティ画面を開く。
[セキュリティ]タブを開き、[編集]をクリックする。[追加]から「Authenticated Users」を追加する。
(Usersでもいいが、Authenticated UsersだとGuestユーザは除外される)
「変更」の「許可」にチェックを入れて、[OK]をクリックする。