LoginSignup
5
4

More than 3 years have passed since last update.

Luaで超簡単にゲームプログラミングができて、Web-APIで実機転送ができちゃうゲーム機の裏側

Posted at

ゲーム機についての表側についてはこちら
https://qiita.com/pluswing/items/021b8ddc0e88ad6f36ac

どーもこんにちは。
今回は、前回の続きで、そんなゲーム機のファームウェア作るにはどーしたらいいの?
ということろを解説していこうと思います。

思ったより簡単(ESP32の機能がスゴイ)なので、これを気に電子工作の世界に足を踏み入れる人が増えると良いなーと思ったりしています。
かくいう私もかれこれ電子工作は暇があればやっていますが、いまだに電子回路についてはよくわからないことが多く、ほぼ行き当たりばったりでやっています。
意外となんとかなるので(電子部品をぶち壊すことはよくある。たまに白煙が出て超焦るw)まぁとりあえずやってみるのが一番かなと思います。

さて、今回作成したゲーム機。「ESP Console」ですが、
Arduino で開発しているので、
組み込みだから、開発環境用意するの面倒なんでしょ?
WIndowsじゃ無いと開発環境ないんでしょ?
といったことは一切ありません。
Arduino IDEをインストールしたら、環境構築はほぼ完了です。
私はMacで開発しています。

技術的トピックとしては以下になるかなと思います。(恣意的抜粋ですが。。)

  • WiFi接続ができる
  • Web API 経由でスクリプトをアップロードできる
  • 組み込みなのにファイルシステムがある
  • Luaを組み込んだ

WiFi接続

ESP32にはWiFiモジュールが内臓されています。
ついでに言うと今回は使っていませんが、Bluetoothモジュールも入っています。

WiFiに接続するには、C言語で何百行と書かないといけないんでしょ?
いいえ、4行です。


WiFi.begin("<SSID>", "<PASSWORD>");
while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
}

はい。これだけ。ぶっちゃけ重要なコードは最初の1行で、あとの3行は接続されるのを待ってるだけです。
ね。簡単でしょ?

multicast DNSにも対応していて、(IPの代わりに、hoge.local とかでその端末にアクセスできるようにする機能)
それを有効にするのも超簡単です。

if (!MDNS.begin("<HOST NAME>")) {
    Serial.println("Error setup MDNS.");
}
MDNS.addService("http", "tcp", 80);

<HOST NAME>espconsoleを指定してあげると、
http://espconsole.local でこの端末にアクセスができるようになります。

Web API 経由でスクリプトをアップロードできる

Webサーバになるのは流石に面倒でしょう!?

AsyncWebServer webServer(80);
webServer.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send(200, "text/plain", "running");
});
webServer.begin();

これはC++なのか・・・と疑いたくなるくらい簡単です。
ちなみに、これは外部ライブラリを使用しているのですが、
セットアップは規定のフォルダgit cloneするだけです。
https://github.com/me-no-dev/ESPAsyncWebServer

組み込みなのにファイルシステムがある

はい。ファイルシステム。
組み込みの世界では、ファイルシステムなんて贅沢なものは無いのが当然なのですが、
(普通はFlashにアドレス指定で読み書きする)
なんと、ESP32には SPIFFSというFlashの領域をファイルストレージとして利用できる機能があります。

SPIFFS.begin();
File f = SPIFFS.open("/.setting.json", "r");
char content[SETTING_BUFFER_SIZE];
f.readBytes(content, SETTING_BUFFER_SIZE);
f.close();

ちょっとCらしさが垣間見えましたが、普通にファイル名でアクセスができちゃいます。
これは便利すぎます。

ちなみにですが、SDカードのファイルも同じような感じで読み書きができてしまいます。

Luaを組み込んだ

はい。本命です。

Luaを組み込むってどういうこと?
という人も多いような気がするので、そこから説明していきます。

ちょっとコードを見た方が理解ができると思うので、こちらをご覧ください。

lua_State *L = luaL_newstate();
luaL_openlibs(L);
luaL_dostring(L, "<Lua script code>");
lua_close(L);

1行目で、VMを作ります。
2行目で、Luaの基本ライブラリを読み込みます。
3行目で、文字列をLuaスクリプトとして評価・実行します。
4行目で、後始末します。

基本はこれだけです。
これを実現するには、
https://www.lua.org/download.html
ここから、一式ダウンロードして、
.inoファイルのあるフォルダに丸っと解凍して

extern "C"
{
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}

これだけです。

・・・とは行かないのが組み込みの世界。
ESP32の話になりますが、
C言語のsystem()関数がないので、loslib.cの該当箇所でエラーが出ます。

-static int os_execute (lua_State *L) {
- lua_pushinteger(L, system(luaL_optstring(L, 1, NULL)));
- return 1;
-}

・・・

static const luaL_Reg syslib[] = {
  {"clock",     os_clock},
  {"date",      os_date},
  {"difftime",  os_difftime},
- {"execute",   os_execute},
  {"exit",      os_exit},
  {"getenv",    os_getenv},
  {"remove",    os_remove},
  {"rename",    os_rename},
  {"setlocale", os_setlocale},
  {"time",      os_time},
  {"tmpname",   os_tmpname},
  {NULL, NULL}
};

こんな感じで、該当処理をまるっと消してしまえば良いです。
他の環境では、別のエラーが出ることがありますが、その環境に合わせて同等の処理に書き直すか、必要ないなら消してしまえば良いです。

LuaからCを呼び出す

ゲーム機なので、描画命令などC側の機能をLuaから呼び出す必要があります。
普通は、Cの関数を呼び出すために、↑で消したsystem()を呼び出すようにグルーコードを書いてあげないといけないんですが、
七面倒くさい。
ESP Consoleでは、tolua++というモノを使って、このグルーコードを自動生成しています。

C++のクラスをLuaで使う手順をざっくり。

まず、ヘッダファイルを用意して、公開したい宣言を
// TOLUA_BEGIN~//TOLUA_ENDで囲みます。
今回の例だと、コンストラクタ、デストラクタは公開したくないので、それ以外を囲っています。

game_api.h
// TOLUA_BEGIN
class GameApi
{
// TOLUA_END
public:
    GameApi();
    virtual ~GameApi();

// TOLUA_BEGIN

    int width();
    int height();
};
// TOLUA_END

次に、pkgファイルというものをを用意します。
これは$hfileに続いてヘッダファイル名を書けばOKです。

game_api.pkg
$hfile "game_api.h"

ここまで準備ができれば、あとは tolua++ コマンドを実行するだけです。

bin/tolua++ -o tolua_game_api.cpp game_api.pkg
sed -i -e s/tolua++.h/toluapp.h/ tolua_game_api.cpp

これで、tolua_game_api.cppというファイルが出来上がるので、
(sedしてるのは、Arduinoの制限でファイル名にが使用できないのでその対応のため。)

そしたら

lua_State *L = luaL_newstate();
luaL_openlibs(L);
tolua_game_api_open(L); // 追加!
luaL_dostring(L, "<Lua script code>");
lua_close(L);

とVMに追加でロードしてあげるだけです。
これで、クラスが使用可能になります。

今回の例だと、コンストラクタを非公開にしているので、このままではGameApiクラスをインスタンス化することができません。

ESP ConsoleではGetAPI()という関数も公開していて、シングルトンなGameApiのインスタンスを取得できるようにしています。↓
https://github.com/pluswing/espconsole/blob/master/game_api.h#L119

CからLuaのfunctionを呼び出す

単純な実行なら、luaL_dostring()を使えば良いのですが、
ESP Consoleは Lua側で定義されている update(), draw() functionを呼び出す必要があります。

この呼び出しは簡単で2行で実現できます。

lua_getglobal(L, "update");
lua_pcall(L, 0, 0, 0);

引数があるfunctionを呼び出す場合、若干コードが増えるのですが、引数なしの場合はこれだけで呼び出せてしまいます。

・・・ということで、ゲーム機のファームウェアをつくるに当たっての秘術的な話はこの辺で。
知ってしまえば、なんだそう言うことか。俺でもできそうだ!と思ったんじゃないでしょうか?

ぶっちゃけ、SSID選択画面つくったり、WiFiパスワード入力画面つくったりする方がよほど面倒なのですが。。

まだ夏休み真っ只中!電子工作に勤しんでみてはいかがでしょうか?

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4