simplestarの技術ブログ

目的を書いて、思想と試行、結果と考察、そして具体的な手段を記録します。

React(SemanticUIかな)覚書き

なんか下書きでずっと残ってたのですが
消すのももったいないので後で自分が引っ張れるように公開しておきます。

初の React カテゴリ記事

f:id:simplestar_tech:20200307141943p:plain
こんなのの見た目を作るのに

import React, { FC } from 'react';
import { Link } from 'react-router-dom';
import { List } from 'semantic-ui-react';

import './Home.css';

const companies = ['unity-technologies', 'UnitySamples'];

const Home: FC = () => (
  <>
    <List celled relaxed>
      {companies.map(companyName => (
        <List.Item className="list-item" key={companyName}>
          <List.Icon name="users" size="large" verticalAlign="middle" />
          <List.Content>
            <Link to={`/${companyName}/members`}>{companyName}</Link>
          </List.Content>
        </List.Item>
      ))}
    </List>
  </>
);

export default Home;

f:id:simplestar_tech:20200307142911p:plain
こういう見た目は

import React, { FC } from 'react';
import { Redirect, Route, Switch } from 'react-router';
import { Grid, Menu, Container } from 'semantic-ui-react';
import { Link } from 'react-router-dom';

import './App.css';
import './styles/semantic.min.css';

interface MenuItem {
  name: string;
  linkto: string;
}

const title = 'SimplestarGame';
const menuItems = [
  { name: 'SimplestarGame', linkto: '/UnitySamples/members' },
  { name: 'Home', linkto: '' },
  { name: 'About', linkto: '/UnitySamples/members' },
  { name: 'Contact', linkto: '/unity-technologies/members' },
];

const App: FC = () => (
  <>
    <Grid padded className="tablet computer only">
      <Menu borderless fluid inverted size="huge">
        <Container>
          {menuItems.map((item: MenuItem) => (
            <Menu.Item header as={Link} to={item.linkto}>
              {item.name}
            </Menu.Item>
          ))}
        </Container>
      </Menu>
    </Grid>
  </>
);

export default App;
const useTimer = (limitSec: number): [number, () => void] => {
  const [timeLeft, setTimeLeft] = useState(limitSec);

  const reset = () => {
    setTimeLeft(limitSec);
  };

  useEffect(() => {
    const tick = () => {
      setTimeLeft(prevTime => (prevTime === 0 ? limitSec : prevTime - 1));
    };
    const timerId = setInterval(tick, 1000);

    return () => clearInterval(timerId);
  }, [limitSec]);

  return [timeLeft, reset];
};

React x Typescript で index.html を独自ドメインで世界公開

まえがき

Unity Asset Store に例の

f:id:simplestar_tech:20200209162015j:plain
例のアセット
を提出したんだけど、リジェクトされてしまった。

どうも初回投稿なら、技術力を見せるサイトを持ってないとダメな様子
仕方なく、作ることにします。
とりあえず index.html にアセットの画像がごろごろあればいいんかな

Node 環境を整える

Web サイトか…

今なら React x Typescript かな?

ということで node パッケージマネージャーを使えるようにしましょう。
Windows 環境はインストーラから入れるなど
nodejs.org

React x Typescript のサンプルをローカルで作る

こちらの技術書を読んで、サンプルを動かしながらコードを理解してみる
oukayuka.booth.pm

typescript で、見ただけでデータの動きがアニメで見えることが大事
以下のような単語で絵が動けばいい

分割代入
スプレッド演算子

読み切れば Redux + Saga の概念の理解と関数コンポーネントによる React の具体的実装がわかる

独自ドメインで世界に公開する

https://unityassets.elastprism.com/

具体的な手順はこちら
qiita.com

1.yarn build でできた build フォルダの中身を S3 にアップロード
2.Cloudfront でディストリビューションを作って、データオリジンに S3 を選ぶ
参考記事
simplestar-tech.hatenablog.com
3.バージニア北部にドメイン指定で証明書を作る
4.Route53で独自ドメインからCloudfrontへのAlias作る

以上の手順で世界からの独自ドメインリクエストするとCloudfrontへルートができて、各エッジサーバーからReact の SPA のアクセスが有効になる

肝となるのが、上記の Qiita の紹介にあるとおり Cloudfront の Custom Error Response で /index.html を 403 や 404 のときに返すようにする
これで、どの Route 先ページでも F5 でリロードしても S3 アクセス拒否という 403 エラーは返ってこない

そういえば昔ホームページの作り方を書いたことあったけど5年もすると Cloudfront で SPA を配信できるようになる(腕を上げたな)
simplestar-tech.hatenablog.com

ところでアセット紹介ページがまだぜんぜん形になってないのです。

追記:

単純に yarn build の成果物を公開すると .map ファイルが同梱されるためソース内容(ts, tsx)実装が同時に公開されてしまいます。
.env ファイルを直下に置いて
GENERATE_SOURCEMAP=false
を書き込んでから yarn build することで without sourcemap で build でき、それを公開した場合は実装がばれずに公開することができました。

情報元はこちら
qiita.com

CubeWalk: go言語cacheサーバーのリージョン選定

前書き

先月、こちらに登壇して PlayFab の CloudScript で同時実行を回避しつつ、不正させないゲームのためのアイディアを語ってみたのですが
jpfug.connpass.com
質問者から CloudScript が実行されている場所って US West オレゴンだから、日本にキャッシュサーバー置くと
結果的に情報路が長くなって、片道 1.3万キロメートルだから、理論上通信に最小 0.1 秒ほどの不要な遅れが足されて
あと日本でサーバー立てる方がランニングコストも通信費も高いですよね

とご指摘いただいたのです。

なるほど、go言語キャッシュサーバーを US West オレゴンに置いて、そこにアクセスする CloudScript も用意して
実際の通信速度に差が 0.1 秒ほど出るのか試してみますね!

やったこと

以前行った時のログをたよりにドキュメントを読み直し 東京リージョンと同じ構成でオレゴンリージョンにも同じ golang キャッシュサーバーを構築します。
simplestar-tech.hatenablog.com

まず、自宅(日本、東京)の pc からリクエストをそれぞれの golang サーバーに送ってヘルスチェックレスポンスが返ってくるまでの時間を計測

東京リージョンは 110~180 ms を要してました。
オレゴンリージョンは 610~680 ms を要してました。

この、それぞれのサーバーにヘルスチェックリクエストをする PlayFab CloudScript を javascript で実装し
自宅(日本、東京)の pc 上で実行する Unity クライアントから、CloudScript を呼び出してヘルスチェックレスポンスが返ってくるまでの時間を計測しました。

東京リージョンは 650~700 ms を要してました。
オレゴンリージョンは 220~300 ms を要してました。

結果

日本・東京の Unity クライアントから、PlayFab CloudScript を呼び出して golang のキャッシュサーバーのレスポンスを確認して Unity に戻ってくるまでの時間を計測しました。
サーバーの場所:
東京リージョンは 650~700 ms を要してました。
オレゴンリージョンは 220~300 ms を要してました。

PlayFab の PlayStream には、CloudScript 呼び出しの詳細が json で記録されますが
東京リージョンに置いた golang キャッシュサーバーにヘルスチェックを返してもらうのに "ExecutionTimeSeconds": 0.5307192
オレゴンリージョンに置いた golang キャッシュサーバーにヘルスチェックを返してもらうのに "ExecutionTimeSeconds": 0.0244125

PlayFab のスタジオのリージョンを日本にできればいいんだけどね…オレゴンにあるのは間違いない
今は東京リージョンにキャッシュサーバーを置くのは間違い確定なので、しばらくオレゴンリージョンに置いて作業を進めます。

Unity: SteamVR 2.0 の入力処理の入門記事

前書き

こちらの記事の和訳だと思ってください
medium.com

VR コントローラの入力について、入門記事
アクションという概念を取り入れ、アクションを定義するところから始まります。

アクション単体では作れない

アクションを束ねた、アクションセットを作成するところから始まります。
最初はいくつものアクションが定義された default アクションセットが選択されている

default のほかに platformer, buggy, mixedreality のアクションセットが確認できる

アクションセットを作る

Window > Steam VR Input でダイアログを表示し
既存の default, platformer, buggy, misedreality のボタンの右側に + ボタンあるので押す

NewSet で作成、何か一つ In Actions に NewAction を追加 boolean Type とする

Save and generate ボタンを押し、完了を待つ

BooleanAction を参照してイベントハンドラを登録

using UnityEngine;
using Valve.VR;
public class MyActionScript : MonoBehaviour
{
    // a reference to the action
    public SteamVR_Action_Boolean sphereOnOff;
    // a reference to the hand
    public SteamVR_Input_Sources handType;
    //reference to the sphere
    public GameObject sphere;

    void Start()
    {
        this.sphereOnOff.AddOnStateDownListener(this.TriggerDown, handType);
        this.sphereOnOff.AddOnStateUpListener(this.TriggerUp, handType);
    }

    private void TriggerDown(SteamVR_Action_Boolean fromAction, SteamVR_Input_Sources fromSource)
    {
        Debug.Log("Trigger is down");
        this.sphere.GetComponent<MeshRenderer>().enabled = true;
    }

    private void TriggerUp(SteamVR_Action_Boolean fromAction, SteamVR_Input_Sources fromSource)
    {
        Debug.Log("Trigger is up");
        this.sphere.GetComponent<MeshRenderer>().enabled = false;
    }
}

ということ

f:id:simplestar_tech:20200217230014p:plain
エディタから参照!

Activate Action Set しないといけない

自分で作ったアクションセットはゲーム開始時にアクティブではない
アクティブになるように明示

f:id:simplestar_tech:20200217230122p:plain
なんでだよ

先ほどの MyActionScript より前に実行される位置にシーンに配置しないといけない制約もある

Open Binding UI

Window > Steam VR Input でダイアログを表示し、Open Binding UI ボタンを押す
newset という、さっき作ったアクションセットのタブが見える。
アクションが割り当たっていないことを示す表示かな

f:id:simplestar_tech:20200217230217p:plain
アクションが割り当たってないぞ

タブを選択して

f:id:simplestar_tech:20200217230554p:plain
トリガーボタンに boolean アクションを割り当て

はじめて動作することになる

まとめると、以下の一つが欠けると、何も反応ない結果になる

  • アクションセットを作る
  • アクションを作る
  • アクションハンドラを作る
  • アクションハンドラを登録するコードを書く
  • アクションを参照
  • 順番を守ったアクションアクティベート
  • 入力管理でアクションセットのアクションをコントローラのいずれかの操作にバインディング

Unity:VRでも使えるMToon互換のURPシェーダを30分で作ろう

前書き

Unity でゲーム作っている人なら多くの方が知っている VRM - VR向け3Dアバターファイルフォーマット -
その VRM の標準シェーダーとして採用されている MToon を URP でも表示できるようにしたい!

作りましょう!
そのための情報集めから作業をこの記事で公開します。

そのあと、公開しました。
github.com

MToon のパラメータ一覧

新しく ShaderGraph - Unlit を作成して公開パラメータとして次のものを作成します。

f:id:simplestar_tech:20200216160442p:plain
MToon の入力

光源情報を Custom Function ノードで取得

github.com
ここにあるサブグラフ一式と .hlsl シェーダー実装をコピーしておきます。いつでも使えるように
内容はほんと、単に光源情報取ってきているだけです。

f:id:simplestar_tech:20200216160717p:plain
サブグラフ一式

Toon シェーディング for ShaderGraph

次の動画の説明欄にあるリンクから Toon 表現の肝となる Node を見つけてコピーします。

ほしいのはこの Node です。

f:id:simplestar_tech:20200216161217p:plain
Toon 表現

アウトライン Shader

そして MToon で外せないのが Outline です。
Shader Graph で実現する方法が紹介されている記事がこちら

Outline Shader

Normal が Alpha テストできない問題に最終的に当たるので、もろもろ削除して、結局 Depth を書き出すプロジェクトで次の CustomFunction を呼ぶだけにします。

最終的に Outline サブシェーダーを作りますが、そこに登場する Custom Function は三つ

下図のとおり、左上のオフセットが

Out = unity_StereoEyeIndex;

左下の係数が

Out = 1.0;
#if UNITY_SINGLE_PASS_STEREO
Out = 0.5;
#endif

右下の Custom Function がこちら

TEXTURE2D(_CameraDepthTexture);
SAMPLER(sampler_CameraDepthTexture);
float4 _CameraDepthTexture_TexelSize;


void OutlineObject_float(float2 UV, float OutlineThickness, float DepthSensitivity, float NormalsSensitivity, out float Out)
{
    float halfScaleFloor = floor(OutlineThickness * 0.5);
    float halfScaleCeil = ceil(OutlineThickness * 0.5);
    
    float2 uvSamples[4];
    float depthSamples[4];

    uvSamples[0] = UV - float2(_CameraDepthTexture_TexelSize.x, _CameraDepthTexture_TexelSize.y) * halfScaleFloor;
    uvSamples[1] = UV + float2(_CameraDepthTexture_TexelSize.x, _CameraDepthTexture_TexelSize.y) * halfScaleCeil;
    uvSamples[2] = UV + float2(_CameraDepthTexture_TexelSize.x * halfScaleCeil, -_CameraDepthTexture_TexelSize.y * halfScaleFloor);
    uvSamples[3] = UV + float2(-_CameraDepthTexture_TexelSize.x * halfScaleFloor, _CameraDepthTexture_TexelSize.y * halfScaleCeil);

    for(int i = 0; i < 4 ; i++)
    {
        depthSamples[i] = SAMPLE_TEXTURE2D(_CameraDepthTexture, sampler_CameraDepthTexture, uvSamples[i]).r;
    }

    // Depth
    float depthFiniteDifference0 = depthSamples[1] - depthSamples[0];
    float depthFiniteDifference1 = depthSamples[3] - depthSamples[2];
    float edgeDepth = sqrt(pow(depthFiniteDifference0, 2) + pow(depthFiniteDifference1, 2)) * 100;
    float depthThreshold = (1/DepthSensitivity) * depthSamples[0];
    Out = edgeDepth > depthThreshold ? 1 : 0;
}

f:id:simplestar_tech:20200216162116p:plain
Outline for URP VR

あとはつなぐだけ

Toon 表現と Outline がそろっているので、接続して MToon の説明をたよりに編みます
MToon の心臓となる処理は Main と Shade のテクスチャを陰影で利用すること。
なので、Toon 表現で白黒を出力して、それを Lerp する処理が書けたらほぼ完成。自分はさらにライト色をもらうように Multiplyしてます。

f:id:simplestar_tech:20200216162748p:plain
心臓部 Lerp

MToon の Add を少々

最後に塩コショウのように Sphere Add と Emission を足すと

f:id:simplestar_tech:20200216163046p:plain
Add するだけ

完成!

f:id:simplestar_tech:20200216163358p:plain
MToon互換 URP VR 結果

VR の Single Pass Stereo で覗いてもアウトラインが引けていました。
そこがポイント

色々と動的読み込みしても、今のところ困った絵にはならない様子
みんなも試してみてね!

f:id:simplestar_tech:20200216163948p:plain
オニャンコポンなつのすがた
f:id:simplestar_tech:20200216164103p:plain
童田明治ファンアートモデル