simplestarの技術ブログ

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

CubeWalkGame:AIが使う脳内コンテキスト

前書き

自作ゲームの開発ネタです。
考えていることを書くと、頭が整理されてコードに落とし込める気がして

最近の進捗絵はこんな感じ

f:id:simplestar_tech:20200616212308p:plain
木のぼり

動画はこちら

作りたいのはワールドロジックとその共用

世界がどうなるかの先行きを小さなロジックを使って取り扱いたくて
キューブのデータをある場所を中心に切り取ってコピーを受け取れる方法がこちら

        /// <summary>
        /// 対象のチャンク、チャンク内のキューブ位置インデックスと格納先配列を渡すと、データを埋める
        /// </summary>
        /// <param name="chunkInt3">ベースとなるチャンクの位置インデックス</param>
        /// <param name="chunkCubeInt3">ベースチャンク内での中心となるキューブの位置インデックス</param>
        /// <param name="radius">ppCubeデータの半径キューブ数</param>
        /// <param name="ppCubeData">埋めてもらうキューブデータ配列</param>
        static unsafe void FillAroundCubeData(Vector3Int chunkInt3, Vector3Int chunkCubeInt3, int radius, byte** ppCubeData)

radius * 2 + 1 の 3乗要素数の byte x 4 配列を確保して渡すと、指定した位置を中心にローカル最新データを埋めてくれるもの

うまくいきまして、AIが周囲のキューブの状態から未来予測を行い、木が育つことをサーバーに報告すると
サーバーが検算して合格したら、本当に木が育つようになりました!

最近の様子を記録したので載せておきますね

Python:コマンドの実行と標準出力文字列の取得+aws cli 呼び出し例

aws cli を呼べる環境なら、次の pthon で動作中の task arn を for 文で処理できるよ

import subprocess
import json
from typing import Union, List


def callCommand(commands: List[str]) -> str:
    """
    コマンド呼び出しと標準出力文字列を返す
    """
    cmd = []
    for command in commands:
        cmd.append(command)
    subprocess.call(cmd)
    output = subprocess.Popen(cmd, stdout=subprocess.PIPE)
    return output.communicate()[0]


def main():
    cmd = []
    cmd.append("aws")
    cmd.append("ecs")
    cmd.append("list-tasks")
    cmd.append("--region=ap-northeast-1")
    cmd.append("--cluster")
    cmd.append("clustername")
    cmd.append("--service-name")
    cmd.append("servicename")
    jsonS = callCommand(cmd)
    for taskArn in json.loads(jsonS)['taskArns']:
        print(taskArn)


if __name__ == "__main__":
    main()

CubeWalk:自分で遊んでダメ出し

ずっと未公開だったメモ記事
公開して完成時に当時を振り返ります。

前書き

最近のチート対策のサーバー側の複雑な CustomData 計算は本当はやりたくなかったんですけど、こまめに記録しながらなんとか動き始めました。
たとえるなら、まだごみが詰まった歯車が回りだした状態で、ちょっとならし回転させて、不純物を取り除いていこうと思います。

今回は機能追加ではなく、動かして気になるところをつぶしていく回です。
未来の自分に仕事をまかせて、とりあえず何でも書いてみようと思います。

ゲーム開始までのながれがない

いきなり機能追加となりましたが、確かに起動に時間を要するうえ、変に操作されるとすぐに壊れてしまうので
今一度ゲーム開始までのながれを確認してみようと思います。

アイテムの増減は一瞬で行われてほしい

これについては前々から気づいていて、サーバーの結果をもらってからアイテム数の増減には問題がありますよね。
そこで、ローカルでは一瞬で操作結果を反映しつつ、サーバー側にはインターバルを組んでアクションを送信してもらいたいものです。
ローカルではアクションをキューに詰め込んで、順にサーバーにリクエストするようにしてみます。

これなんですが、レスポンスが来るまで破壊アニメーションとしようとしてます。(きっとうまくいく)

そろそろ音が鳴ってほしい

UI操作をしているときに、ピンポンと音が鳴ってくれると嬉しいのですけど

わかる

番号のテクスチャつまらなくなってきた

キューブのマテリアルをそろそろ作りこんでほしいですね

こちらは
simplestar-tech.hatenablog.com
こちらのシェーダを作って対処しました。

オンライン通信でキューブアクションを同期

互いにアクションを交換し合い、世界の共有を頑張ってほしいです。

サーバー側の関数見直しで、一緒に考えてみます。

CubeWalk:ゲーム開始までの流れをどう実装するか案

これまた下書きのままずっと放置されていた記事
Unity のコンポーネントが Start や Awake で好き放題やるのは、引き継いだり後から見直したときに
全体で何しているかわからないので、例えばオンラインのログイン完了後に処理してほしいとかそういうの指示するとき困るよね
これをどうするか考えた記事

開始フロー

調べてみると、各コンポーネントがスタート時に自由に開始していました。

やりたいことはゲームのタイトル情報、プレイヤーの現在の状況を読み込んでから

タイトルメニューで情報表示
プレイヤーの現在の状況から再スタート

バージョンとIDの表示だが

まずプレイヤーIDとは?

なるほどGenericIDとして、これを使ってサーバーAPIでPlayFabIDを問い合わせることができる

GenericIDの実態はサービス名とIDのペア

これを見てひらめいたのは、ゲーム内のキューブにGenericIDを刻み込み
サービス名はワールド名とし、IDにはワールド内の位置インデックス0~1700万くらいFFFFFFとする
ぎりぎり
その位置とキャラクターを結ぶようにGenericIDを加える

そうすれば、キューブ内のデータに描画用の情報を詰めてもよいことになる。
これがプレイヤー復活のためのメモリーキューブで、開始時にプレイヤーはしゃがんだ状態でキューブと交換するようにして現れることになる

開始フローを決める前に、まだまだ、そういうことしたいと思っていて要素がそろいきっていないことがわかった。

結論

最終的に GameManager が各種イベント接続をするコードを担当
そこを見ればゲームの処理の流れが追えるようになりました。

あとからどういうゲーム実装だっけ?と思って実装を読んだときに GameManger だけ読めば漏れなくわかる
というのを目指します。

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