近況について (2016.8)

久しぶりの更新です。

Selene のサイトが消滅してしまってから結構経ちましたね。最近は、Siv3D というゲーム・メディアアート向けの C++ 用ライブラリを使って遊んでいます。最新の C++ に準拠した形で作られており、各機能がクラスに分けられていてとても使いやすいです。Siv3D Game Jam という不定期イベントにもちょくちょく参加したりして、LASERREIMU の時のように同人ゲームをがっつり作る感じではないですが、プログラムを書く機会をちょっとずつ増やしてみています。

Siv3D Game Jam へ投稿したプログラムは GitHub で公開しています
https://github.com/voidproc

それから普段プログラムを書いている中で便利だなと思ったちょっとしたプログラムは、GitHub Gist で公開するようにしてみました。その中でも Siv3D に関連するものに関しては、Siv3D ミニサンプル集 として別サイトにまとめていますので興味のある方はご覧になってみてください。

.

おきみやげのブックマークレット

かわいいおきつねがどんどんでてくるゲームの合成ページで、
合成待ちの置土産が何個あるのかをぱっと確認したい時のブックマークレット。

↓↓↓↓↓↓
◆こちらからどうぞ
↑↑↑↑↑↑

・きつねが多くてページが分割されてても全部読みに行きます
・CCSでページ結合しててもOK
・「全部」ページから実行すると、すべての置土産を一覧表示
・勢力ごとのページから実行すると、その勢力の分を一覧表示

一応整形されたソースも載せておきますです。
javascriptあんまよくわかってないんでこんなんで良いのかどうか…
ここだめとか指摘ありましたらお願いします。
一応Firefox25とChrome33で動作確認しました。IEは多分むりです。

// 与えられたDOM要素内の、置土産の個数を数える
// 種類ごとの個数を連想配列で返す(こんな感じで→{"ひよこ":2, "転生の秘宝":15, …})
function getGiftCount(elem)
{
    var a=elem.querySelectorAll('form>table>tbody>tr>td:nth-child(2)');
    var map = {};
    
    for (var i=0; i<a.length; i++) {
        var giftname = a[i].children[6].nextSibling.textContent;
        if (map[giftname] == null) {
            map[giftname] = 1;
        }
        else {
            map[giftname]++;
        }
    }
    
    return map;
}

// xhrでとってきたXMLをcallback関数に渡す
function XHR(url,callback)
{
    var xhr = new XMLHttpRequest();
    xhr.open('GET', url);
    xhr.onreadystatechange = function(){
        if (xhr.readyState == 4) {
            if (xhr.status == 200) {
                callback(xhr.responseXML);
            }
            else {
                getResultBox().innerHTML = "つながりまへん";
            }
        }
    }
    xhr.responseType = "document";
    xhr.send();
}

// 合成画面のページ分割のリンク先を配列で返す
function getConvLinks()
{
    var link = [];
    var pagelist = document.querySelector('div.page_list');
    var alist = pagelist.querySelectorAll('span>a');
    for (var i=0; i<alist.length; i++) {
    	// CCSで結合されてたら無視する
        if (alist[i].style.display != "none") {
            link.push(alist[i].href);
        }
    }
    return link;
}

// 結果を表示するエリアをなければ作る
// てきとう
function createResultBox()
{
    var box = getResultBox();
    if (!box) {
        box = document.createElement("div");
        box.id = "gifts_result_box";
        box.style.position = "absolute";
        box.style.right = 0;
        box.style.top = 0;
        //box.style.width = "100px";
        box.style.backgroundColor = "#444";
        box.style.fontSize = "70%";
        document.body.appendChild(box);
    }
    box.innerHTML = "よみこみちう...";
}
createResultBox();


// 結果を表示するエリアの要素を返す
function getResultBox()
{
	return document.querySelector('div#gifts_result_box');
}


var done = 0;        //全てのページから読み込み終わりましたか?の判定
var gifts_all = {};  //分割されたページごとの置土産リスト


// 置土産リスト(getGiftCountでとってくるやつ)を足し合わせる
// 結果はgifts_allへ
function mergeGifts(gifts)
{
    for (key in gifts) {
        if (gifts_all[key] == null) {
            gifts_all[key] = gifts[key];
        }
        else {
            gifts_all[key] += gifts[key];
        }
    }
}

// 結果表示エリアに結果を表示する
function showResult()
{
    var box = getResultBox();
    var htmlstr = "<table>";
    for (key in gifts_all) {
        htmlstr += "<tr><td>" + key + "</td><td style='text-align:right;'>" + gifts_all[key] + "</td></tr>";
    }
    box.innerHTML = htmlstr + "</table>";
}


// いま合成のページにいますか
if (/^\/conv/.test(location.pathname)) {
	// 最初に現在のページ分
	mergeGifts(getGiftCount(document));
	var links = getConvLinks();
	// ページ分割ない場合はここで結果表示して終了
	if (links.length == 0) {
	    showResult();
	}
	else {
		// ページ分割の分
	    for (var i=0; i<links.length; i++) {
	        XHR(links[i], function (res) {
	            mergeGifts(getGiftCount(res));
	            done++;
	            if (done == links.length) {
	                showResult();
	            }
	        });
	    }
	}
}
else {
	getResultBox().innerHTML = "合成のページじゃないです";
}

イベント参加見合わせなどについて

まだはっきりとは分からないのですが、来年度はちょっと慌ただしい感じになりそうでして、H26例大祭およびコミックマーケットへは参加を見合わせることにしました。
余裕が出てきましたら、他のイベントへ参加したりするかもしれませんが、とりあえずはそんな感じで。

LASERREIMU完成版の方は、さんげっとさん、およびメロンブックスさんの方にまだそこそこの在庫がありますので、欲しい方がいましたら、そちらからお求めください。

ツイッターを眺めているとたまにLASERREIMUの話題があったりして、こちらから全然反応できてないですが、プレイしてくださってるんだな~という感じでとても嬉しいです。
あとはバグ報告なんかもあったりして、大いにに助かっております、ありがとうございます。

C++で定義したenumをそのままの名前でLuaで使いたい(Boost.Preprocessor)

例えばC++で次のように列挙体を定義して、そのままの名前でLuaスクリプト側でも使いたいわけです。

enum Enum1 {
    E1 = 0x200,
    E2,
    E3,
    E4,
};

↓↓↓こう書きたい↓↓↓

if foo == E2 then
    bar()
end

でもそれをやるには、定義した列挙体を全部Luaのグローバル変数として登録しなければなりません。

luabind::object g = luabind::globals(L);
g["E1"] = E1;
g["E2"] = E2;
g["E3"] = E2;
…

enum定義した後で、Luaの方にも登録しなければなりません。
列挙体を1個増やすと、ソースを2箇所直さないといけない…。
これをいちいちやるのは、列挙体がたくさんあるととても面倒なので、なんとかならないものかと思っていたのですが、Boost.Preprocessorを使って楽になりそうだったので試してみました。

で、こんなふうになりました。
なにをやってるかというと、列挙体として定義したいものをシーケンスに入れておいて、
・SEQ_TO_ENUMマクロで、シーケンスの内容から列挙体を定義するコードをつくる
・SEQ_TO_LUAGLOBALSマクロで、シーケンスの内容からLuaグローバル変数を登録するコードをつくる
という感じです。

#include <iostream>
#include <boost/preprocessor.hpp>
#include <lua.hpp>
#include <luabind/luabind.hpp>

// シーケンスを渡してenum定義をつくる
// seq: 定義したい列挙体の要素名を並べたシーケンス
// name: 列挙体の名前
// value: 先頭の要素の値
#define SEQ_TO_ENUM(seq, name, value) \
    enum name { \
    BOOST_PP_SEQ_HEAD(seq) = value, \
    BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TAIL(seq)) \
    };

#define SEQ_TO_LUAGLOBALS_ELEM(r, data, elem) data[BOOST_PP_STRINGIZE(elem)]=elem;

// シーケンスからLuaグローバル変数への登録
// seq: 定義したい列挙体の要素名を並べたシーケンス
// L: Lua VM
#define SEQ_TO_LUAGLOBALS(seq, L) { \
    luabind::object o=luabind::globals(L); \
    BOOST_PP_SEQ_FOR_EACH(SEQ_TO_LUAGLOBALS_ELEM, o, seq) \
    }

// 列挙体を定義する。0x200からはじまるEnum1
// あとはここをいじるだけで良い!
#define ENUM1 (E1)(E2)(E3)(E4)
SEQ_TO_ENUM(ENUM1, Enum1, 0x200)

int main()
{
    lua_State* L = lua_open();
    luaL_openlibs(L);
    luabind::open(L);

    // 列挙体Enum1をLuaに登録する
    SEQ_TO_LUAGLOBALS(ENUM1, L);
    // ためしに表示してみる
    luaL_dostring(L, "print(E1,E2,E3,E4)");

    return 0;
}

こうすると、列挙体の数を増やしたいと思ったら、上記のソースで言えばENUM1のところを変更するだけで完了、となって、最初の例よりもかなり楽になりました。

と、本当はluabindのソースコードを読み解いている途中だったんですが、脇道にそれてしまいました。
boostを使ったコードを読むのは、ほんと難しいですね…。

リプレイアップローダー兼スコアランキング(お試し版)を公開してみました

ということで。
LASERREIMU リプレイアップローダー兼スコアランキング(お試し版)

リプレイファイルをアップロードすると、スコアなどが登録されて、スコア順に表示されます。
難易度で絞り込みもできるようになっています。

スコアランキング、とはありますが、点数稼ぎするしないに関係なく、気軽にアップロードしてもらえれば。
一人で何個もアップロードしちゃっても全然構いませんし、○面まで行った記念、とか、変な死に方した、
とかそんなノリでも結構ですので、たくさんのリプレイが集まると良いなと思います。

アップローダー自体にバグあんよ!って時には、Twitterとかブログで教えていただければと思います;

C85を終えて

コミックマーケット85から帰還してまいりました。
スペースに来てくださった方々、ありがとうございました。
励ましの言葉をいただいたり、差し入れをいただいたりして、恐縮しつつ…(おいしくいただきました!)。

もし、今回の頒布物に関して不具合などありましたら、このブログやツイッター(@voidproc)宛に報告していただければと思います。プレイした感想なども、大歓迎ですので是非!

と、ここからは頒布したゲームの内容について説明不足かなと思ったところ、あるいは細かすぎる仕様について書いていこうと思います。クリアに関する条件等に触れるかと思いますので、ネタバレを避けたい方は以下を読まれないことをおすすめします…
Continue reading →

書店委託について(1)

LASERREIMUを、三月兎さん、メロンブックスさんの方で取り扱って頂けることになりました。

→三月兎さんのページはこちら

→メロンブックスさんのページはこちら

今のところ、メロンブックスさんの方への納品がすこし遅くなりそうなのですが…詳細はまた後ほどお知らせします。三月兎さんとメロンブックスさん、どちらにも同時に納品となる予定ですので、よろしくお願いします。

完成版のPVを作ってみました

PV、できました。
体験版の動画とまったく変わり映えしないですが、最新版の動画が有るということが大事なのだろうと思います。

東方Project二次創作ゲーム LASERREIMU 完成版PV [C85]

えーでるわいすさんのいつものまとめ動画には間に合いそうになかったので、
こちらにはスクリーンショットだけ載っけてもらっています。

~~~

それから。LASERREIMUは、書店委託を考えています。
もう少ししたら、詳細についてお伝えできると思いますので、もう少々お待ちください。
書店委託について(1)

マスターアップしました&C85参加について

いろいろと遅くなりましたが、ひとまずご報告ということで。

タイトルのとおり、LASERREIMUは無事マスターアップいたしました。わーわー

それから、コミックマーケット85にサークル参加します。
スペースは、二日目(12/30) 東 ピ-56a 「voidProc」、LASERREIMUを頒布する予定です。
媒体は通常のプラケース+CD-ROM 1枚で、頒布価格は500円を予定しています。

と、今はとりあえずこんな感じで。何か変更がありましたら、この記事に追記していきます。
それでは、致命的なバグが存在しないことを祈りつつ、残りの作業へ戻ります…。

夏コミを終えて

コミックマーケット84に、サークル参加してきました。
スペースに来てくださった方々、ありがとうございました。
午後、完売後に来てくださった方には、申し訳ありませんでした。

直接、励ましの言葉や感想などを頂けて、とてもうれしかったです。
完成に向けて、より一層がんばらねばと感じました…。

今回はまた、体験版の頒布となりましたが、自分が盛り込みたいと考えていた機能をいくつか追加でき、またステージももうすこし先まで進むことができるようになりました。
今のところは、ゲームの進行に支障が出るレベルの不具合はなさそうなのですが、いかがでしょうか。
もし不具合や、あるいは不具合に限らず、感想・意見など何でもいいですので、何かありましたら是非このブログのコメントや、Twitter・メールなどで教えて頂けるとありがたいです。