Gaming Life

一日24時間、ゲームは10時間

AsciiDoc + VSCodeでかっこいい文書作る

某方に布教されてAsciiDocにめっちゃハマった。

AsciiDocって

markdownのような軽量マークアップ言語の一種。

markdownより優れている点

  • markdownより高機能。
    • 困ったらHTMLタグ直書きとかしなくていい。
    • 表セルを結合出来る。
    • ソースコード、PlantUMLなどをincludeできる。
    • 目次が自動で作られる。
    • 使用可能なアイコンが大量にある。
    • PDF書き出しした時の見た目が安定&綺麗。

(plantUMLについては過去記事参照)

ai-gaminglife.hatenablog.com

markdownに劣る点

  • 環境構築がダルい
  • 普及してない
  • はてなブログがasciidocに対応していない

環境構築

windowsを想定。macでも大体変わらない(と思う)。

  • ruby installerをDLし、実行環境を整える
  • asciidoctorとエクステンションを追加する
    • 以下をコマンドプロンプトで実行
    • asciidoctor-diagramはplantUML等をasciidocで利用するためのエクステンション
    • asciidoctor-pdfはPDF変換に必要。
gem install asciidoctor
gem install asciidoctor-diagram
gem install --pre asciidoctor-pdf
  • VSCodeにエクステンションを追加する

  • VSCodeのsetting.jsonを変更する

    • ctrl + ,で基本設定ウインドウを表示。
    • setting.jsonに以下を追加。
"AsciiDoc.use_asciidoctor_js": false,
 "AsciiDoc.asciidoctor_command": "asciidoctor -a outdir=tmp -a imagesdir=tmp -a imagesoutdir=tmp -r asciidoctor-diagram -o-",

これで環境構築完了。.adocフォルダを作って編集していく。

ctrl + K + V もしくは Ctrl + Shift + P → AsciiDoc: Open preview to the side でサイドパネルにリアルタイムプレビューが表示される。

f:id:ai_gaminglife:20181113232211p:plain

HTML&PDF書き出し

  • HTMLに書き出す時
asciidoctor [ファイル名].adoc
  • PDFに書き出す時
asciidoctor-pdf [ファイル名].adoc
  • asciidoctor-pdf等を使う時
asciidoctor -r asciidoctor-diagram [ファイル名].adoc
asciidoctor-pdf -r asciidoctor-diagram [ファイル名].adoc

文法

ここで説明するより既にあるリファレンスを見てもらうほうが確実。

  • 超基本的な機能に絞って紹介されている記事

qiita.com

  • 公式リファレンス

takumon.github.io

iconについて

  • 冒頭に :icons: font を追加すればアイコンが使用できる。
NOTE: NOTE

TIP: TIP

IMPORTANT: IMPORTANT

WARNING: WARNING

CAUTION: CAUTION

レンダリング結果

f:id:ai_gaminglife:20181113232225p:plain

  • 他にも色々なアイコンが使用できる。有名企業のアイコンもある。
icon:font[]
icon:fire[] 
icon:hand-stop-o[] 
icon:amazon[]
[aqua]#icon:twitter[]#

レンダリング結果

f:id:ai_gaminglife:20181113232234p:plain

  • 使えるフォントはFont-Awesomeに依存。以下のサイトで調べることが出来る。

Icons from Font Awesome, Bootstrap and Google

HTML/PDF書き出しのテーマの変更

  • 書き出し時、ヘッダーフッターを設定したり、文字のフォーマットを指定したり、色々な事ができる。 f:id:ai_gaminglife:20181113232319p:plain
    • PDF書き出しのフォーマットを変更したければ、デフォルト設定なら以下のテーマを書き換えることで実現。 C:\Ruby25-x64\lib\ruby\gems\2.5.0\gems\asciidoctor-pdf-1.5.0.alpha.16\data\themes
    • 参考サイト

asciidocをPDFに変換してみた(asciidoctor-pdf) – Aimless

OpenSiv3D C++ 反射ベクトルを計算して玉の反射を実現する1

OpenSiv3Dのリファレンスにはブロック崩しのサンプルがある。

https://scrapbox.io/Siv3D/%E3%83%96%E3%83%AD%E3%83%83%E3%82%AF%E3%81%8F%E3%81%9A%E3%81%97

このサンプルでは玉が壁 or ブロックに衝突した時、玉の速度ベクトルのX成分もしくはY成分を反転させることで反射を実現している。

しかし、この方法では斜め壁に対応できていない。

というわけで壁に衝突した時に反射ベクトルを計算して玉を反射させるプログラムを書いた。

f:id:ai_gaminglife:20181110013644g:plain

コード

# include <Siv3D.hpp> // OpenSiv3D v0.2.5

Vec2 calcReflectVec(const Vec2& direction, const Vec2& normal)
{
    auto L = -direction.normalized();
    auto LdotNx2 = 2.0 * L.dot(normal);

    return (LdotNx2 * normal - L).normalized();
}

Vec2 calcLineNormal(const Line& line)
{
    auto dirVec = Vec2(line.begin.x - line.end.x, line.begin.y - line.end.y);
    auto normal = Vec2(-dirVec.y, dirVec.x);

    return normal.normalize();
}

class Ball
{

public:
    Ball() = default;

    Ball(Vec2 pos, Vec2 v)
        :circle(pos, 8)
        ,vec(v.normalize())
    {}

    ~Ball() = default;

    void update()
    {
        circle.moveBy(SPEED * vec.normalize());
    }

    void draw()
    {
        circle.draw();
    }

    Circle getCircle() const
    {
        return circle;
    }

    void bound(const Vec2& normal)
    {
        auto n = (vec.dot(normal) >= 0) ? normal : -normal;

        vec = calcReflectVec(vec, n);
    }

private:

    Circle circle;
    const double SPEED = 8.0;
    Vec2 vec;

};

void Main()
{
    Array<Line> lines;
    lines.push_back(Line(0, 0, 0, Window::Height()));
    lines.push_back(Line(Window::Width(), 0, Window::Width(), Window::Height()));
    lines.push_back(Line(0, 0, Window::Width(), 0));
    lines.push_back(Line(0, Window::Height(), Window::Width(), Window::Height()));
    lines.push_back(Line(550, 250, 420, 420));
    lines.push_back(Line(0, 150, 220, 200));

    Ball ball(Window::Center(), Vec2(-0.7, 0.5));

    while (System::Update())
    {
        ball.update();

        for (const auto& line : lines)
        {
            if (line.intersects(ball.getCircle()))
            {
                ball.bound(calcLineNormal(line));
            }
        }

        ball.draw();
        
        for (const auto& line : lines) 
        {
            line.draw();
        }
    }
}

方針

f:id:ai_gaminglife:20181110013833p:plain

-L: 玉の速度ベクトル

N : 壁の法線ベクトル

R : 壁に反射した時の玉の反射ベクトル

-LとNがわかれば以下の式でRを求めることができる。

\vec{R}=2(\vec{L} \cdot \vec{N})\vec{N}-\vec{L}

玉のクラスを作成する

class Ball
{
    public:
    Ball() = default;

    Ball(Vec2 pos, Vec2 v)
        :circle(pos, 8)
        ,vec(v.normalize())
    {}

    ~Ball() = default;

    void update()
    {
        circle.moveBy(SPEED * vec.normalize());
    }

    void draw()
    {
        circle.draw();
    }

    Circle getCircle() const
    {
        return circle;
    }

    void bound(const Vec2& normal)
    {

    }

private:

    Circle circle;
    const double SPEED = 8.0;
    Vec2 vec;

};

bound()は後で実装する。

壁の法線ベクトルを求める

今回、壁はSiv3DのLineクラスを利用する。

壁の端点の座標を減算し、壁の方向ベクトルtを求めることができる。

\vec{t} = (t_x, t_y)

tと壁の法線ベクトルnは内積を取ると0になる。

\vec{t} \cdot \vec{n}=t_x \times n_x + t_y \times n_y = 0

ここからnは二通りの解が得られる。

\vec{n} = (-t_y,t_x) \vec{n} = (t_y,-t_x)

なぜ法線が二種類得られるかというと、壁には表裏があるから。

Vec2 calcLineNormal(const Line& line)
{
    auto dirVec = Vec2(line.begin.x - line.end.x, line.begin.y - line.end.y);
    auto normal = Vec2(-dirVec.y, dirVec.x);

    return normal.normalize();
}

ここで、図をもう一度見返してみる。

f:id:ai_gaminglife:20181110013833p:plain

正しい法線nは、玉の方向ベクトルと鈍角をなす。鈍角をなす2つのベクトルは内積を取ると負の値を取るので、以下の処理を挟んで正しい法線ベクトルを得る必要がある。

auto n = (vec.dot(normal) >= 0) ? normal : -normal;

反射ベクトルを計算する

-L: 玉の速度ベクトル

N : 壁の法線ベクトル

R : 壁に反射した時の玉の反射ベクトル

とした時、

\vec{R}=2(\vec{L} \cdot \vec{N})\vec{N}-\vec{L}

なので、以下の関数で反射ベクトルを求めることができる。

Vec2 calcReflectVec(const Vec2& direction, const Vec2& normal)
{
    auto L = -direction.normalized();
    auto LdotNx2 = 2.0 * L.dot(normal);

    return (LdotNx2 * normal - L).normalized();
}

ここで求めた反射ベクトルをbound()関数で使用する。

void bound(const Vec2& normal)
{
    auto n = (vec.dot(normal) >= 0) ? normal : -normal;

    vec = calcReflectVec(vec, n);
}

壁を作成する

Siv3Dの動的配列Array<T>を利用してLineを格納する。

 Array<Line> lines;
    //ここからウィンドウ四隅の壁
    lines.push_back(Line(0, 0, 0, Window::Height()));
    lines.push_back(Line(Window::Width(), 0, Window::Width(), Window::Height()));
    lines.push_back(Line(0, 0, Window::Width(), 0));
    lines.push_back(Line(0, Window::Height(), Window::Width(), Window::Height()));
    //ここまでウィンドウ四隅の壁

    lines.push_back(Line(550, 250, 420, 420));
    lines.push_back(Line(0, 150, 220, 200));

玉と壁の当たり判定を取る

今回、壁と玉の当たり判定はintersects()関数を利用する。 壁をRange-based forでArray<Line>に格納した壁を取り出し、総当たりで当たり判定を取り、衝突していればbound()を呼び出す。

ball.update();
for (const auto& line : lines)
    {
        if (line.intersects(ball.getCircle()))
        {
            ball.bound(calcLineNormal(line));
        }
    }

描画する

draw()を呼び出して描画する。

ball.draw();
        
    for (const auto& line : lines) 
    {
        line.draw();
    }

結果

f:id:ai_gaminglife:20181110013644g:plain

未解決の問題

壁には厚みがあるので壁の横から衝突した時におかしな動きになってしまう。

まとめ

実装を始めた当初は数式を見てギョッとしたが、冷静に一つ一つ見ていくとそこまで難しいことはやっていない。未解決の問題だけはまだどうしようも出来ていないが。どなたか解決方法があったら教えてください。

参考サイト

qiita.com

C++で浮動小数点の誤差を考慮して等価比較する

<cfloat>ヘッダ中にあるDBL_EPSILONを使えば誤差も考慮して2つのdouble値の等価比較ができる。

float型ならFLT_EPSILONを使えばよい。

#include <cmath>
#include <cfloat>

//double型のaとbを誤差考慮して比較する
bool NearlyEqual(double a, double b)
{
    return abs(a - b) < DBL_EPSILON;
}

int main()
{
    double a,b;

    if(NearlyEqual(a,b))
    {
        return 0;
    }

    return 1;
}

追記(11/07) 故あってUE4の数学ライブラリ読んでたら似たような関数があったので、それに習って書き換えてみる。

struct Math
{

    static constexpr bool NearlyEqual(const float a, const float b, const float err = FLT_EPSILON)
    {
        return Abs<float>(a - b) <= err;
    }

    static constexpr bool NearlyEqual(const double a, const double b, const float err = DBL_EPSILON)
    {
        return Abs<double>(a - b) <= err;
    }

    template<typename T>
    static constexpr inline T Abs(const T a)
    {
        return (a >= (T)0) ? a : -a;
    }
};

Unity2018でTestRunnerを使うための参考リンク

現在Unityでゲームを作ってる。

PlantUMLで設計書から作り始めてるんだし、折角ならテストコードも書いてみたいよなー、とTest Runnerを試してみたのだが、Unity2018でTestRunnerの導入方法が大幅に変わっていて、困ったので参考になったサイトをメモ。

(plantUMLについては以前書いた)

ai-gaminglife.hatenablog.com

Unity2018以降でのTest Runnerの導入方法

ntgame.wpblog.jp

大体このサイト読めば解決すると思う。

Test Runnerの使い方

www.slideshare.net

  • そもそもテストってなんやねんってところから解説している。若干古い(2015年)情報であることは注意。

qiita.com

  • 初めてテストを書く時に参考になる。TestRunnerを使うメリットが分かる。

tsubakit1.hateblo.jp

  • Unityテスト完全に理解した勉強会のスライド集。2018年の情報で(記事執筆時点では)新しい情報なのが嬉しい。

実際使ってみて

  • 一々MonoBehaviorでnewしてAttachさせてDebug.Log()させる手間が省けるのは良い。

  • 設計がカッチリ決めてからの開発には向いているが、場当たり的な開発には向いてなそう。(テスト自体そう)

ほんの少しだけラクに、C++でAtCoderに参加するバッチファイル作った

先々週のABCからAtCoderを始めた。

前回参加時はたまたまTwitterを見ていた時にコンテスト開催中だということに気づき、折角だしやるか、と衝動的に参加したため、VS2017で書いて、動くかどうかは実際提出してみて試す、なんて非効率な事をしていた。

折角やるならちゃんと環境整えて、ローカルでもテストできるようにしたいよなー、ということで比較的簡単に環境構築できるバッチファイルを作った。

※と言ってもただディレクトリ作るだけの奴だけど......

前準備

torus711.hatenablog.com

このサイトを参考に最新のgcc導入まで済ます。

エディタには今回はVSCodeを採用。codeコマンドを叩いてVSCodeが起動すればOK。

バッチファイル作成

適当なフォルダでメモ帳を開いて、名前を "atcoder-init.bat"とする。

以下をコピペしてbatファイルに貼り付ける。(後から編集したい時は右クリック→編集を選択。左クリックするとバッチファイルが実行されてしまう)

cd /d [保存したいディレクトリ、自分で決める]
mkdir %1
cd %1
mkdir A
mkdir B
mkdir C
mkdir D
cd A
type nul > input.txt
type nul > Main.cpp
cd ..
cd B
type nul > input.txt
type nul > Main.cpp
cd ..
cd C
type nul > input.txt
type nul > Main.cpp
cd ..
cd D
type nul > input.txt
type nul > Main.cpp
cd ..

PATHを通す

(Windows10なら)スタートメニューで「環境変数」と入力すれば「環境変数を編集」メニューが出てくるのでそれを選択。(Win10、なんでコントロールパネル深いところに隠したんだろうか……)

出てきたウインドウの上側、ユーザー環境変数で、Pathを選択してから編集をクリック。

新規ボタンをクリックし、先程バッチファイルを保存したパスをコピペして保存する。

使い方

コマンドプロンプト(Not PowerShell)で

> atcoder-init [引数1]

を叩くと引数1に選択したディレクトリ以下にA~D問題を解くための作業ディレクトリが作成される。

A問題を解きたい時は

> code A

を入れればVSCodeが起動し、標準入力用のinput.txtと実際にコードを書くMain.cppを編集できる。input.txtには問題ページからサンプル入力をコピペしてはっつければいい。

Main.cppを編集し終えたら

> g++ A/Main.cpp

コンパイルコンパイルが通ったら、

> a.exe < A/input.txt

で実行する。

まとめ

ごくごく単純なバッチファイルだが、以前よりは確実にラクにテストできるようになった。世の中にはサンプル入力をスクレイピングして自動で拾ってくるようなのもあるらしいが、そんな技術はないので、仕方ない。

もっとラクなのがあったら教えてほしい。

水色までは頑張りたいなあ。

C# if文なしにプロパティで数値に上限/下限を設定する

月とか時間とか、数値に上限下限を設定したいとき、素直に実装するならif文使ってこんな感じにやる。

int month;
void setMonth(int value)
{
    if(value <= 1){
        month = 1;
    }else if(value >= 12){
        month = 12;
    }else{
        month = value;
    }
}

これじゃあやってることに対してコードが冗長すぎ。

解決策何かしらあるだろうなーと思って調べてみたら、Mathf.Min()やMathf.Max()を使えばできるらしいことが判明。折角のC#なのでプロパティを使ってもっとスマートに書いてみる。

public class Month
{
    //packing Field
    private int monthNum = 1;
    public int MonthNum
    {
        get { return monthNum; }
        set { this.monthNum = Mathf.Min(12, Mathf.Max(1, value)); }
    }
}

シェーダー芸やってる人やある程度プログラム書いてる人に取っちゃ当然の書き方なのかもしれないが、知らなかったので自分用にメモ。

まあClamp使えばいいんですが。

VSCode+PlantUMLでクラス設計図が簡単に作れる

 ブログ執筆を始め、ずっとエディタはAtomを使っていたのだが、Gitの設定をいじっていたらパッケージインストールで認証エラーが出るようになった。解決の仕方も分からなかったので、この機会だしVSCodeに移行してみた。

 こんな感じで記事を書いてる。

f:id:ai_gaminglife:20181011000348p:plain

 動作も軽快、UIもわかりやすく、Markdown PreviewCSSがちょっと見辛い以外は不満はない。


 で、本題。これまで私は何本かゲームを作ってきたが、どれも開発期間は長くて1ヶ月。行き当たりばったりでクラスを作り、結果相互参照連発しコンパイル時間が伸びる酷い設計のプログラムを何度も書いた。

 現在作り始めているUnityゲームは相当大規模になりそうで、こんな行き当たりばったり設計しててはあらゆる機能を持ってしまう(所謂)神クラスが誕生してしまいかねない。

 じゃあどうするかっていうと、UMLを使ったクラス図を作って事前に設計したくなる。しかし、以前まではWordかdraw.ioでテキストボックスをちまちま配置し、コネクタ同士を矢印で繋げて、コメント書いて……みたいな方法で作っていた。見た目は凝れるが、あまりに面倒。大規模だと流石にやってられない。

 PlantUMLを使えばそんな面倒な作業からおさらばできる。

plantuml.com

 簡易な文法ながら高機能で、複雑なクラス図を自動で作成、描画できる。

 PlantUMLを使いはじめて2時間位でこれくらいは作れてしまった。

 PlantUMLの優れているところはVSCodeに専用の拡張機能があることで、これを使うことでサイドタブにクラス図を描画させながら快適なVSCode環境で書くことができる。

 導入には以下のサイトが参考になるだろう。

注意

 PlantUMLはコメントを書きたい時は、

' 1行コメントはこれ
/*
複数行のコメントは
こんな感じ
*/

とすれば良い。