diff --git a/.gitignore b/.gitignore index 259148f..c3bc12c 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,10 @@ *.exe *.out *.app + +# VsCode/PlatformIO +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/HowToMakeResources.md b/HowToMakeResources.md new file mode 100644 index 0000000..7b92f49 --- /dev/null +++ b/HowToMakeResources.md @@ -0,0 +1,200 @@ +# How to make resources + +**Since we cannot release the resources due to rights issues,** +we will provide information on how to make your own. +**We do not accept questions about resources, please be patient.** + +## Overview +Resources required in the game are loaded from the SD card (exclude font source file). +Please place each resource in the designated location. + +## Bitmap font entity source file +The df88_font entity must be prepared. +Source will comile and put to PROGMEM. + +see also df88.hpp +``` +extern const ::lgfx::GFXfont df88_gfx_font; +``` + +### Filename +Source : df88.cpp +Put source to ./src + +### Font source image +ASCII CODE from 0x20 to 0x5F +``` + '"#$%&'()*+,-./0123456789:;<=>?©ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ +``` +### Remarks +You can use [make_lgfxbmpfont.py](https://github.com/GOB52/bitmap_tools) to make source file from bitmap. + +### e.g. + +```C++ +#include +#include +#include +#include "df88.hpp" + +static const std::uint8_t df88_bitmaps[] PROGMEM = +{ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // ' ' + 0x1c,...... + . + . + . + +}; +static const lgfx::GFXglyph df88_glyphs[] PROGMEM = +{ + { 0, 8, 8, 8, 0, -8 }, //' ' + { 8, 8, 8, 8, 0, -8 }, //'!' + . + . + . +}; + +const lgfx::GFXfont df88_font PROGMEM = +{ + (std::uint8_t*)df88_bitmaps, + (lgfx::GFXglyph*)df88_glyphs, + 0x20, // ' ' + 0x5f, // '_' + 8, +}; +``` + +## Graphics resources + +### Format +Windows bitmap(v3) 4bit depth 16 colors. +Palette index 0 is transparent palette, so you can set 15 colors freely each bitmaps. + +### Location +/res/td/ + +### Remarks +You can use [reorder_palette.py](https://github.com/GOB52/bitmap_tools) to sort the bitmap palette colors. + +### Elements + +#### Advertise +##### Logo +Filename: "logo.bmp" +Width: 224 Height: 64 +![logo.bmp](./doc/logo_sample.png) + +#### Character +##### My ship +Player ship and bullet. +Filename: "sh.bmp" +Width: 88 Height: 32 +![sh.bmp](./doc/sh_sample.png) + +##### Enemy +Mine, Yazuka, and Crash +Filename: "enemy.bmp" +Width: 96 Height: 48 +![enemy.bmp](./doc/enemy_sample.png) + +#### Background +##### Wave and spot +Filename: "bg0.bmp" +Width: 64 Height: 160 +Wave cloud patern that horizontal direction must be connected bg0 anf bg1. +No transparent. +Those palette colors must exists in bitmap for animate palette. +![#306040](https://via.placeholder.com/15/306040/000000?text=+) `RGB(48,96,64)` +![#48A068](https://via.placeholder.com/15/48a068/000000?text=+) `RGB(72,160,104)` +![#70E090](https://via.placeholder.com/15/70E090/000000?text=+) `RGB(112,224,144)` + +![bg0.bmp](./doc/bg0_sample.png) + +Filename: "bg1.bmp" +Width: 96 Height: 160 +Great spot patern that horizontal direction must be connected bg0 and bg1. +No transparent. +Those palette colors must exists in bitmap for animate palette. +![#306040](https://via.placeholder.com/15/306040/000000?text=+) `RGB(48,96,64)` +![#48A068](https://via.placeholder.com/15/48a068/000000?text=+) `RGB(72,160,104)` +![#70E090](https://via.placeholder.com/15/70E090/000000?text=+) `RGB(112,224,144)` + +![bg1.bmp](./doc/bg1_sample.png) + +##### Rock surface +Upper/lower rock surfaces. +Filename: "rock.bmp" +Width: 320 Height: 16 +Rock surface that that horizontal direction must be connected. +No transparent. +![bg0.bmp](./doc/rock_sample.png) + +##### Branch rock +Using when choose next stage. +Filename: "branch.bmp" +Width: 160 Height: 16 +Branch rock that that horizontal direction must be connected. +![branch.bmp](./doc/branch_sample.png) + +#### Information +##### Frame for zone,remaining +For information. +Filename: "zone.bmp" +Width: 80 Height: 40 +No transparent. +![zone.bmp](./doc/zone_sample.png) + +#### Effect +##### Explosion +Filename: "bomb.bmp" +Width: 256 Height: 64 +![bomb.bmp](./doc/bomb_sample.png) + +#### Boss +##### Coelacanth +Body,parts and bullet. +Filename: "kf.bmp" +Width: 224 Height: 232 +Those palette colors must exists in bitmap. +Palette of colors will be grayscaled when the boss appears, leaves, and escapes. +![#00185a](https://via.placeholder.com/15/00185a/000000?text=+) `RGB(0,24,90)` +![#005abd](https://via.placeholder.com/15/005abd/000000?text=+) `RGB(0,90,189)` +![#00297b](https://via.placeholder.com/15/00297b/000000?text=+) `RGB(0,41,123)` + +![kf.bmp](./doc/kf_sample.png) + + +## Sound resources + +### Format +Wave file format, linear PCM, 8bit mono + +### Location + +/res/td/wav/ + +### BGM + +|Title|Filename| +|:---|:---| +|Insert coin|IC.wav| +|Insert coin B|ICB.wav| +|Stage A,etc|CN.wav| +|Stage F,etc|CMT.wav| +|Stage C, etc|CAW.wav| +|Stage B, etc|IB.wav| +|Stage E,etc|TS.wav| +|Warning|W!.wav| +|Boss1|B1.wav| +|Boss2|B2.wav| +|Boss3|B3.wav| +|Boss4|B4.wav| +|Boss5|B5.wav| +|Boss6|B6.wav| +|Boss7|B7.wav| +|Round clear|RC.wav| +|Ending|ED.wav| +|Name entry|NAME.wav| +|Gameover|OVER.wav| + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..21c077c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 GOB + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.ja.md b/README.ja.md new file mode 100644 index 0000000..ac7203e --- /dev/null +++ b/README.ja.md @@ -0,0 +1,110 @@ +# TinyDarius + +開発中です。現在ステージAのみ。 +自作ライブラリの[ドッグフーディング](https://ja.wikipedia.org/wiki/%E3%83%89%E3%83%83%E3%82%B0%E3%83%95%E3%83%BC%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0)も兼ねています。 +**権利上の問題で、画像や音のリソースデータは含まれておりません。** +リソースを独自で作成する際の指針は[こちら](HowToMakeResources.md) + + +## 概要 +アーケードゲーム"ダライアス"からボスラッシュを作りました。 +エミュレーター上での動作ではなく、独自のコードで動作しています。 +その為実際の挙動とは異なります。 +オリジナルの開発者ならびに製作会社に対し敬意を評します。 + +## 動作ハード +M5Stack Basic,Gray + +### 必要なもの +M5Stack Faces + GB Face + +## ビルド方法 +[ArduinoIDE](https://www.arduino.cc/en/software) または [Visual Studio Code](https://code.visualstudio.com/) + [PlatformIO](https://platformio.org/) にてビルド可能です。 +各環境の整備方法などはそれぞれのページを参照してくだい。 + +### 必要なライブラリ +[M5Stack](https://github.com/m5stack/M5Stack) 0.4.0 +[LovyanGFX](https://github.com/lovyan03/LovyanGFX) 0.4.17 (support v0,v1) +[SdFat](https://github.com/greiman/SdFat) 2.1.2 +[goblib](https://github.com/GOB52/goblib) 0.1.0 +[goblib_m5s](https://github.com/GOB52/goblib_m5s) 0.1.0 + +### platform.ini for PlatformIO + +#### 設定 + +|項目 |設定値 | +|:---|:---| +|platform | espressif32@3.5.0 | +|board | m5stack-core-esp32 or m5stack-gray| + + +#### ビルド種別 + +|env|説明|備考| +|:---|:---|:---| +|master|マスタービルド (LovyanGFX v0)|デバッグ機能なし| +|master\_v1|マスタービルド (LovyanGFX v1)|デバッグ機能なし| +|release|リリースビルド (LovyanGFX v0)|デバッグ機能あり| +|release\_v1|リリースビルド (LovyanGFX v1)|デバッグ機能あり| +|debug|デバッグビルド (LovyanGFX v0)|デバッグ機能あり| +|debug|デバッグビルド (LovyanGFX v1)|デバッグ機能あり| + +master または master\_v1 でのビルドを推奨します。 +私は ArduinoIDE と PlatformIO で M5Stack を除くライブラリを [lib\_extra\_dirs](https://docs.platformio.org/en/latest/projectconf/section_env_library.html#lib-extra-dirs) を介して共有使用しています。(ライブラリのインストールは ArduinoIDE を使用) +ご自分の環境に合わせて platform.ini を書き換えてください。 + +### TinyDarius.ino for ArduinoIDE + +TinyDarius.ino 自体は空のファイルです。 setup(),loop() は ./src/main.cpp にあります。 +ビルドは platform.ini の release 相当のものとなります。 +他の env 相当でのビルドにするには platform.txt を書き換える必要があります。 +env の記述を参考にオプションを設定してください。 + +#### 設定 + +ボードマネージャー **M5Stack version 1.0.9** + +[Menu] - [Tool] + +|項目|設定値| +|:---|:---| +|Board|M5Stack-Core-ESP32| +|Flash Frequency|80| +|Flash Mode|QIO| +|Partation Scheme|Default| +|Core Debug|any | + + +## 遊び方 + +|ボタン|説明| +|:---|:---| +|セレクト|コイン投入| +|スタート|クレジットがあればゲーム開始| +|十字|自機の移動| +|A| 弾発射(ソフトウェアリピート付)| + +1. コイン投入(Select押下) +1. スタート押下してゲーム開始 +1. 敵が出てくるまで動けません(少々お待ちください) +1. ボスを倒します。(時間切れになるとボスは逃げます) +1. 自機の位置によって次のステージを選びます(障害物に注意) +1. 全てのボスを倒せばゲームクリア + +## 実装予定 +- ボスの追加 +- ラウンドクリア時のタイムボーナス +- 効果音の追加 +- スコアランキングの保存と読み込み +- 自機のミサイルとパワーアップは...作るかもしれないし、しないかも。 + +## 謝辞 +**[@KojiSaito](https://twitter.com/kojisaito)** M5Stack 向けプログラミングをしているのを見て私も M5Stack を買ってしまいました。M5Stack への道を開いてくれたことに感謝します。 +**[@Lovyan03](https://github.com/lovyan03)** クールで有用なライブラリを作り続けている御仁。 LovyanGFX があったからこそゲームを作ることができました。 + +開発中、様々な助言を頂いた皆様にも感謝申し上げます。 + + +Have a happy coding :) + diff --git a/README.md b/README.md index 8628468..61bf2f5 100644 --- a/README.md +++ b/README.md @@ -1 +1,117 @@ -# TinyDarius \ No newline at end of file +# TinyDarius + +[Japanese/日本語](./README.ja.md) + +Work in progress. Now only stage A. +It also serves as [dogfooding](https://en.wikipedia.org/wiki/Eating_your_own_dog_food) for my libraries (goblib,goblib_m5s). + +**Due to rights issues, image and sound resource data are not included.** +For guidelines on creating your own resources [here](HowToMakeResources.md) + +## Overview +Boss rush from arcade game "DARIUS". +We are not running it on an emulator, but on our own code. +Therefore, the behavior is different from the real thing. +I have nothing but respect for the people and company that created "DARIUS". + +## Support hardware +M5Stack Basic,Gray + +### Require +M5Stack Faces + GB Face + +## How to build + +You can build on [ArduinoIDE](https://www.arduino.cc/en/software) or [PlatformIO](https://platformio.org/) with [Visual Studio Code](https://code.visualstudio.com/). +Please refer to the respective Webpage for instructions on how to build each environment. + +### Require +[M5Stack](https://github.com/m5stack/M5Stack) 0.4.0 +[LovyanGFX](https://github.com/lovyan03/LovyanGFX) 0.4.17 (support v0,v1) +[SdFat](https://github.com/greiman/SdFat) 2.1.2 +[goblib](https://github.com/GOB52/goblib) 0.1.0 +[goblib_m5s](https://github.com/GOB52/goblib_m5s) 0.1.0 + +### platform.ini for PlatformIO + +#### Settings + +|Item | Settings| +|:---|:---| +|platform | espressif32@3.5.0 | +|board | m5stack-core-esp32 or m5stack-gray| + +#### Build env + +|env|description|remarks| +|:---|:---|:---| +|master|Master build with LovyanGFX v0| No debugging features| +|master\_v1|Master build with LovyanGFX v1| No debugging features| +|release|Release build with LovyanGFX v0| Debugging features available| +|release\_v1|Release build with LovyanGFX v1| Debugging features available| +|debug|Debug build with LovyanGFX v0|Debugging features available| +|debug|Debug build with LovyanGFX v1|Debugging features available| + +Recommend building with master or master\_v1 +I shared libraries between ArduinoIDE and PlatformIO, so use [lib\_extra\_dirs](https://docs.platformio.org/en/latest/projectconf/section_env_library.html#lib-extra-dirs) and installed libraries (exclude M5Stack) by ArduinoIDE. +Please edit platform.ini according to your environment. + + +### TinyDarius.ino for ArduinoIDE + +TinyDarius.ino is empty file. setup() and loop() in ./src/main.cpp. +The build will be equivalent to release in platform.ini. +If you want to change building env,then you need edit platform.txt +Please refer to the env description to set the options. + + +#### Settings + +BoardManager **M5Stack version 1.0.9** + +[Menu] - [Tool] + +|Menu|Settings| +|:---|:---| +|Board|M5Stack-Core-ESP32| +|Flash Frequency|80| +|Flash Mode|QIO| +|Partation Scheme|Default| +|Core Debug|any | + +## How to play + +|Button|Description| +|:---|:---| +|Select|Insert coin| +|Start|Start game if credits exists| +|Cross|Moving my ship| +|A| Shot(Software rapid shot)| + +1. Please inert coin.(Push select) +1. Push start and start game. +1. Can't move until enemies appear.(Please wait) +1. Defeat the boss in time. (The boss will escape when the time is up) +1. Choose next stage by ship position.(Be careful for obstacles) +1. Game clear when you defeat all boss. + + +## Scheduled for implementation +(Maybe if I have time I will make an effort to do so to the extent possible) + +- Add more boss. +- Add time bonus on round clear. +- Add more SFX. +- Save/load score ranking. +- Add missile and power up for ship... hmm, May or may not be implemented. + + +## Special thanks + +**[@KojiSaito](https://twitter.com/kojisaito)** Influenced by his programming for M5Stack, I also bought M5Stack. Thanks to he has shown me the path to M5Stack development. +**[@Lovyan03](https://github.com/lovyan03)** He is making many cool and useful libraries. Thanks to LovaynGFX I was able to create a game for M5Stack. + +Thanks also to the other people who gave me all kinds of advice during the development. + +Have a happy coding :) + diff --git a/TinyDarius.ino b/TinyDarius.ino new file mode 100644 index 0000000..21c8a3a --- /dev/null +++ b/TinyDarius.ino @@ -0,0 +1,3 @@ + +// setup(),loop() in src/main.cpp + diff --git a/doc/bg0_sample.png b/doc/bg0_sample.png new file mode 100644 index 0000000..d0cf9a2 Binary files /dev/null and b/doc/bg0_sample.png differ diff --git a/doc/bg1_sample.png b/doc/bg1_sample.png new file mode 100644 index 0000000..d438bdb Binary files /dev/null and b/doc/bg1_sample.png differ diff --git a/doc/bomb_sample.png b/doc/bomb_sample.png new file mode 100644 index 0000000..6ea8144 Binary files /dev/null and b/doc/bomb_sample.png differ diff --git a/doc/branch_sample.png b/doc/branch_sample.png new file mode 100644 index 0000000..489e676 Binary files /dev/null and b/doc/branch_sample.png differ diff --git a/doc/enemy_sample.png b/doc/enemy_sample.png new file mode 100644 index 0000000..91c8d3a Binary files /dev/null and b/doc/enemy_sample.png differ diff --git a/doc/kf_sample.png b/doc/kf_sample.png new file mode 100644 index 0000000..b9f8bbe Binary files /dev/null and b/doc/kf_sample.png differ diff --git a/doc/logo_sample.png b/doc/logo_sample.png new file mode 100644 index 0000000..1df3f1d Binary files /dev/null and b/doc/logo_sample.png differ diff --git a/doc/rock_sample.png b/doc/rock_sample.png new file mode 100644 index 0000000..66269bc Binary files /dev/null and b/doc/rock_sample.png differ diff --git a/doc/sh_sample.png b/doc/sh_sample.png new file mode 100644 index 0000000..833bbbd Binary files /dev/null and b/doc/sh_sample.png differ diff --git a/doc/zone_sample.png b/doc/zone_sample.png new file mode 100644 index 0000000..ea661c1 Binary files /dev/null and b/doc/zone_sample.png differ diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..4f6b5a5 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,86 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + + +;----------------------------------------------------------------------- +; TinyDarius +;----------------------------------------------------------------------- + +[env] +platform = espressif32@3.5.0 + +board = m5stack-core-esp32 +board_build.flash_mode = qio +board_build.f_flash = 80000000L +framework = arduino + +;The libraries are shared between ArduinoIDE and PlatformIO. +;lib_extra_dirs is the ArduinoIDE library directory. +lib_extra_dirs = ~/projects/M5Stack/libraries/ +lib_ldf_mode = deep +lib_deps = m5stack/M5Stack@0.4.0 + +monitor_speed = 115200 +;monitor_filters = esp32_exception_decoder,time +monitor_filters = time, esp32_exception_decoder +upload_speed = 921600 + +;extra_scripts = pre:rename_bin.py + +build_flags = -Wall -Wextra -Wreturn-local-addr + +;----------------------------------------------------------------------- +[env:master] +build_type = release +build_flags = ${env.build_flags} + -DLGFX_USE_V0 + -DNDEBUG +build_unflags = -g3 + +[env:master_v1] +build_type = release +build_flags = ${env.build_flags} + -DLGFX_USE_V1 + -DNDEBUG +build_unflags = -g3 + +;----------------------------------------------------------------------- +[env:release] +build_type = release +build_flags = ${env.build_flags} + -DLGFX_USE_V0 + +[env:release_v1] +build_type = release +build_flags = ${env.build_flags} + -DLGFX_USE_V1 + +;----------------------------------------------------------------------- +[env:debug] +build_type = debug +build_flags = ${env.build_flags} + -DDEBUG + -DLGFX_USE_V0 +debug_build_flags = -O0 + -DCORE_DEBUG_LEVEL=5 + +[env:debug_v1] +build_type = debug +build_flags = ${env.build_flags} + -DDEBUG + -DLGFX_USE_V1 +debug_build_flags = -O0 + -DCORE_DEBUG_LEVEL=5 + + +;----------------------------------------------------------------------- +;[env:prepro] +;build_type = release +;build_flags = -E diff --git a/rename_bin.py b/rename_bin.py new file mode 100644 index 0000000..6acc1f6 --- /dev/null +++ b/rename_bin.py @@ -0,0 +1,19 @@ +# rename from firmware.bin to projectname.bin +# +# platform.ini +# extra_scripts = pre:rename_bin.py +# +Import("env") +import os + +project_name = os.path.basename(os.path.dirname(env["PROJECT_CONFIG"])) + +env.Replace(PROGNAME="%s" % project_name) + +# +# If you want to rename to project name + env name then use it. +# +#env.Replace(PROGNAME="%s_%s" % (project_name, env["PIOENV"])); + + + diff --git a/src/app.cpp b/src/app.cpp new file mode 100644 index 0000000..9d96824 --- /dev/null +++ b/src/app.cpp @@ -0,0 +1,368 @@ +/*! + TinyDarius + + @file app.cpp + @brief Application class +*/ +#include +#ifdef min +#undef min +#endif +#include + +#include "debug.hpp" +#include "app.hpp" +#include "game.hpp" +#include "constants.hpp" +#if __has_include("df88.cpp") +#include "df88.hpp" +#endif +#include "renderer.hpp" +#include "sound.hpp" +#include "game_obj.hpp" +#include "typedef.hpp" +#include "scene_manager.hpp" + +#include +#include +#include +#include +using goblib::m5s::CpuUsage; +#include +#include +#include + + +#ifndef NDEBUG +#include "scene/scene_debug.hpp" +#endif +#include "scene/scene_advertise.hpp" +//#include "scene/scene_stage.hpp" +#include "scene/scene_game.hpp" + +#include +#include + +using goblib::m5s::FaceGB; +using goblib::lgfx::GSprite; + +namespace +{ +constexpr std::int32_t SPLIT = 6; +constexpr std::int32_t STRIP_HEIGHT = SCREEN_HEIGHT / SPLIT; +static_assert(SCREEN_HEIGHT % SPLIT == 0, "Please make it divisible by SPLIT."); +} + +std::int32_t ScopedReleaseBus::_nest = 0; + +TinyDarius::TinyDarius() + : goblib::App() + , goblib::Singleton() + , _output(nullptr) + , _sprite{} + , _taskTree(nullptr) + , _sceneManager(nullptr) + , _renderer(nullptr) + , _input(nullptr) + , _game(nullptr) + , _credits(0) +#ifndef NDEBUG + , _freeHeap(0) + , _debug(false) +#endif + {} + +TinyDarius::~TinyDarius() +{ + finalize(); +} + +void TinyDarius::setup(LGFX* output) +{ + assert(output); + + _output = output; + + _output->init(); + _output->setRotation(1); + _output->setBrightness(80); + + _output->fillScreen(TFT_WHITE); + + assert(_output->width() == SCREEN_WIDTH); + assert(_output->height() == SCREEN_HEIGHT); + + for(auto& p : _sprite) + { + p = new GSprite(); + assert(p); + p->setColorDepth(_output->getColorDepth()); + if(!p->createSprite(SCREEN_WIDTH, STRIP_HEIGHT)) + { + abort(); + } +#if __has_include("df88.cpp") + p->setFont(&df88_gfx_font); +#endif + p->setAddrWindow(0, 0, SCREEN_WIDTH, STRIP_HEIGHT); + } + + // Occupy the bus. + _output->startWrite(); + _output->setAddrWindow(0, 0, _output->width(), _output->height()); + + _taskTree = new TdTaskTree(); + assert(_taskTree); + _sceneManager = new SceneManager(*_taskTree, PRIORITY_SCENE_MAANGER); + assert(_sceneManager); + + _renderer = new goblib::graph::Renderer2D(96); + assert(_renderer); + + _input = new FaceGB(); + assert(_input); + _input->setup(); + + _game = new Game(); + assert(_game); + +#ifndef NDEBUG + auto s = new SceneDebug(); + assert(s); + _sceneManager->push(s); + _freeHeap = esp_get_free_heap_size(); + printDebugInformation(); +#else + auto s = new SceneAdvertise(); + assert(s); + _sceneManager->push(s); +#endif +} + +void TinyDarius::finalize() +{ + goblib::safeDelete(_game); + goblib::safeDelete(_input); + goblib::safeDelete(_renderer); + + if(_taskTree) + { + _taskTree->clear(); + } + goblib::safeDelete(_sceneManager); + goblib::safeDelete(_taskTree); + for(auto& e : _sprite) + { + goblib::safeDelete(e); + } +} + + +void TinyDarius::endWrite() +{ + _output->endWrite(); +} + +void TinyDarius::startWrite() +{ + _output->startWrite(); +} + +void TinyDarius::fixedUpdate() +{ + // GOBLIB_SCOPED_PROFILE_HIGH("fixed"); + SoundSystem::instance().pump(); +} + +void TinyDarius::update(float delta) +{ + // GOBLIB_SCOPED_PROFILE_HIGH("update"); + + _input->pump(); + + if(_input->wasPressedEqual(FaceGB::Button::Select) && _credits < CREDIT_MAX) + { + SoundSystem::instance().playSfx(SFX::InsertCoin); + ++_credits; + } + +#ifndef NDEBUG + M5.update(); + if(M5.BtnB.wasPressed()) + { + _debug = !_debug; + GameObj::showHitBox(_debug); + } + if(M5.BtnC.wasPressed()) + { + _taskTree->pauseGlobal(!_taskTree->isPauseGlobal()); + } + if(M5.BtnA.wasPressed()) + { + printDebugInformation(); + } +#endif + + if(!_taskTree->isPauseGlobal()) + { + _game->pump(); + } + _taskTree->pump(delta); +} + +#ifndef NDEBUG +void TinyDarius::printDebugInformation() const +{ + printf("%s\n", "----------------------"); + + _freeHeap = esp_get_free_heap_size(); + printf("H:%u LH:%u FH:%u\n" + , _freeHeap + , heap_caps_get_largest_free_block(MALLOC_CAP_8BIT) + , heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT) + ); + // heap_caps_print_heap_info(MALLOC_CAP_8BIT); + _sceneManager->print(); + _taskTree->print(); + _renderer->print(); + GameObj::print(); +} + +void TinyDarius::drawDebugInformation(GSprite* spr) const +{ + // GOBLIB_SCOPED_LEAK_ABORT(); + + int yoff = SCREEN_HEIGHT/SPLIT - 28; + auto cpu0 = CpuUsage::cpu0(); + auto cpu1 = CpuUsage::cpu1(); + +#define FWID 6 +#define FHGT 8 + spr->setTextColor(TFT_WHITE, TFT_BLACK); + + spr->writeFillRect(0,yoff, 320, 4, TFT_BLACK); + spr->writeFillRect(0,yoff, 1.6f * cpu0, 2, cpu0 < 100.0f ? TFT_WHITE : TFT_MAGENTA); + spr->writeFillRect(0,yoff + 2, 1.6f * cpu1, 2, cpu1 < 100.0f ? TFT_WHITE : TFT_MAGENTA); + + spr->setCursor(0, yoff + FHGT * 1); + //spr->printf("F:%2.1f", fps()); // ATTENTION: Leak memory if output float! + float fi = 0, fp; + fp = std::modf(fps(), &fi); + spr->printf("F:%2d.%-1d", static_cast(fi), static_cast(fp*10)); + + spr->setCursor(FWID * 12, yoff + FHGT * 1); + spr->printf("T:%zu R:%zu", _taskTree->size(), _renderer->size()); + + spr->setCursor(FWID * 12, yoff + FHGT * 2); + //spr->printf("D:%1.3f", delta()); + fp = std::modf(delta(), &fi); + spr->printf("%d.%-4d", static_cast(fi), static_cast(fp*1000)); + + spr->setCursor(0, yoff + FHGT * 2); + spr->printf("H:%u", _freeHeap); + + fp = std::modf(cpu0, &fi); + spr->setCursor(41 * FWID, yoff + 1 * FHGT); + //spr->printf("C0:%3.2f", cpu0); + spr->printf("C0:%3d.%-2d", static_cast(fi), static_cast(fp*100)); + + //spr->setCursor(30 * FWID, yoff + 1 * FHGT); + //spr->printf("C0:%d/%d %3.2f", CpuUsage::get0_i(), CpuUsage::get0_t(), CpuUsage::get0()); + // CpuUsage::reset0(); + fp = std::modf(cpu1, &fi); + spr->setCursor(41 * FWID, yoff + 2 * FHGT); + //spr->printf("C1:%3.2f", cpu1); + spr->printf("C1:%3d.%-2d", static_cast(fi), static_cast(fp*100)); + + if(_taskTree->isPauseGlobal()) + { + spr->setCursor(0,0); + spr->printf("%s", "PAUSE"); + } +} +#endif + +void TinyDarius::render() +{ + // GOBLIB_SCOPED_PROFILE_HIGH("render"); + + constexpr std::size_t tlen = SCREEN_WIDTH * STRIP_HEIGHT; + std::int_fast8_t cnt = SPLIT; + std::int_fast8_t cur = 0; + std::int32_t y = 0; + while(cnt--) + { + _sprite[cur]->clear(); + + RenderArg arg{y, _sprite[cur] }; + _renderer->render(&arg); + + if(cnt == 0) + { + _sprite[cur]->setCursor(SCREEN_WIDTH - 8*10, SCREEN_HEIGHT - 16 + y); + _sprite[cur]->printf("CREDIT: %u", credits()); +#ifndef NDEBUG + if(_debug) + { + drawDebugInformation(_sprite[cur]); + } +#endif + } + + _output->pushPixelsDMA(static_cast(_sprite[cur]->getBuffer()), tlen); + y -= STRIP_HEIGHT; + cur ^= 1; + } + +} + +void TinyDarius::reserveInsertNode(goblib::Task* t, goblib::Task* parent) +{ + _taskTree->reserveInsertNode(t, parent); +} + +void TinyDarius::sendBroadcastMessage(goblib::TaskMessage& m, goblib::Task* t) +{ + _taskTree->sendBroadcastMessage(m, t); +} +; +void TinyDarius::postBroadcastMessage(goblib::TaskMessage& m, goblib::Task* t) +{ + _taskTree->postBroadcastMessage(m, t); +} + +void TinyDarius::insertRenderObj(goblib::graph::RenderObj2D* r) +{ + _renderer->insert(r); +} + +void TinyDarius::removeRenderObj(goblib::graph::RenderObj2D* r) +{ + _renderer->remove(r); +} + +void TinyDarius::startGame() +{ + if(credits() == 0) { return; } + + --_credits; + _game->start(0); + // _sceneManager->push(new SceneStage(_game->stage())); + auto sg = new SceneGame(); + assert(sg); + _sceneManager->push(sg); +} + +void TinyDarius::endGame() +{ + auto a = new SceneAdvertise(); + assert(a); + _sceneManager->push(a); +} + +/* +void TinyDarius::nextStage() +{ +} +*/ + diff --git a/src/app.hpp b/src/app.hpp new file mode 100644 index 0000000..508dd2f --- /dev/null +++ b/src/app.hpp @@ -0,0 +1,142 @@ +/*! + TinyDarius + + @file app.hpp + @brief Application class +*/ +#ifndef TD_APP_HPP +#define TD_APP_HPP + +#include "typedef.hpp" +#include +#include +#include +#include +#include +#include + +#ifdef LGFX_USE_V1 +#include +#else +class LGFX; +#endif + +namespace goblib +{ +class Task; +class SceneTask; +class SceneManageTask; +struct TaskMessage; +templateclass TaskTree; +namespace graph +{ +class RenderObj2D; +class Renderer2D; +} + +namespace m5s +{ +class FaceGB; +} +} +class Game; +using TdTaskTree = goblib::TaskTree; +using AppClock = goblib::m5s::esp_clock; +//using app_clock = std::chrono::steady_clock; + +/*! @brief Application class */ +class TinyDarius : public goblib::App, goblib::Singleton +{ + public: + using PointerType = std::unique_ptr; + using Singleton::instance; + using Singleton::create; + + virtual ~TinyDarius(); + + void setup(LGFX* output); + void finalize(); + + virtual void fixedUpdate() override; + virtual void update(float delta) override; + virtual void render() override; + + std::uint8_t credits() const { return _credits; } + + void startGame(); + void endGame(); + + /// @name Bus release.occupancy + /// @warning Need to release BUS to access SD card. + // @{ + void endWrite(); //!< Release + void startWrite(); //!< Occupy + // @} + + /// @name Any instance + /// @{ + GOBLIB_INLINE goblib::m5s::FaceGB& input() { return *_input; } + GOBLIB_INLINE Game& game() { return *_game; } + GOBLIB_INLINE TdTaskTree& task() { return *_taskTree; } + /// @} + + /// @name Wrapped + /// @{ + void reserveInsertNode(goblib::Task* t, goblib::Task* parent = nullptr); + void sendBroadcastMessage(goblib::TaskMessage& m, goblib::Task* t = nullptr); + void postBroadcastMessage(goblib::TaskMessage& m, goblib::Task* t = nullptr); + + void insertRenderObj(goblib::graph::RenderObj2D* r); + void removeRenderObj(goblib::graph::RenderObj2D* r); + /// @} + + protected: + friend class goblib::Singleton; + TinyDarius(); + + virtual void sleep_until(const std::chrono::time_point& abs_time) override + { + auto us = std::chrono::duration_cast(abs_time - AppClock::now()).count(); + auto ms = us > 0 ? us / 1000 : 0; + delay(ms); + while(AppClock::now() < abs_time){ taskYIELD(); } + } + + private: +#ifndef NDEBUG + void printDebugInformation() const; + void drawDebugInformation(goblib::lgfx::GSprite* spr) const; +#endif + + private: + LGFX* _output; + std::array _sprite; // for translate by DMA. + TdTaskTree* _taskTree; + goblib::SceneManageTask* _sceneManager; + goblib::graph::Renderer2D* _renderer; + goblib::m5s::FaceGB* _input; + Game* _game; + std::uint8_t _credits; + + constexpr static std::uint8_t CREDIT_MAX = 9; + +#ifndef NDEBUG + mutable std::uint32_t _freeHeap; + bool _debug; +#endif + +}; + +/*! Scoped release and occupay BUS */ +class ScopedReleaseBus +{ + public: + ScopedReleaseBus() { if(_nest++ == 0) { TinyDarius::instance().endWrite(); }} + ~ScopedReleaseBus() { if(--_nest == 0) { TinyDarius::instance().startWrite(); }} + + private: + static std::int32_t _nest; +}; +#define SCOPED_RELEASE_BUS() ScopedReleaseBus GOBLIB_CONCAT(sr_,__LINE__) + +#endif diff --git a/src/background/branch_rock.cpp b/src/background/branch_rock.cpp new file mode 100644 index 0000000..ca3390c --- /dev/null +++ b/src/background/branch_rock.cpp @@ -0,0 +1,106 @@ +/*! + TinyDarius + + @file branch_rock.cpp + @brief Rocks for branching that appear when cleared. +*/ +#include + +#include "../debug.hpp" +#include "branch_rock.hpp" +#include "../app.hpp" +#include "../renderer.hpp" +#include "../utility.hpp" +#include "../constants.hpp" + +#include +using goblib::lgfx::GSprite; +using goblib::lgfx::GSprite4; +#include +using goblib::lgfx::AnimatedPalette; +#include + +#include + +namespace +{ +constexpr char BITMAP_PATH[] = "/res/td/branch.bmp"; +constexpr Pos2 INITIAL_POS(SCREEN_WIDTH, SCREEN_HEIGHT/2 - 8); +constexpr Pos2 VELOCITY(-12, 0); +constexpr HitBox HBOX{Pos2(0,0), Rect2(0,0, SCREEN_WIDTH * 2, 16)}; +} + +BranchRock::BranchRock() + : GameObj(PRIORITY_BRANCHROCK, ORDER_BRANCHROCK, CATEGORY_WALL, "branchrock") + , _sprite(nullptr) + , _apalette(nullptr) +{ + _sprite = new GSprite4(); + assert(_sprite); + _apalette = new AnimatedPalette(16, _sprite); + assert(_apalette); + + _hbox = HBOX; + move(INITIAL_POS); +} + +BranchRock::~BranchRock() +{ + // TD_PRINT_FUNCTION(); + goblib::safeDelete(_apalette); + goblib::safeDelete(_sprite); +} + +void BranchRock::onUnchain() +{ + // TD_PRINT_FUNCTION(); + GameObj::onUnchain(); + delete this; +} + +bool BranchRock::onInitialize() +{ + if(createFromBitmap(*_sprite, BITMAP_PATH)) + { + _sprite->makePaletteTable256(); + _apalette->from(*_sprite); + return GameObj::onInitialize(); + } + kill(); + return false; +} + +bool BranchRock::onRelease() +{ + return GameObj::onRelease(); +} + +void BranchRock::onExecute(const float delta) +{ + offset(VELOCITY); + if(x() < -_sprite->width()) + { + offset(Pos2(_sprite->width(), 0)); + // keep hitbox in screen + _hbox.move(Pos2::pos_type(0), y()); + } + + if(_apalette->pump()) + { + _sprite->makePaletteTable256(); + } +} + +void BranchRock::render(void* arg) +{ + RenderArg* rarg = static_cast(arg); + GSprite* target = rarg->sprite; + std::int16_t xx = static_cast(x()); + std::int16_t yy = static_cast(y()); + + _sprite->pushSpriteTo16(target, xx, yy + rarg->yorigin, 0); + _sprite->pushSpriteTo16(target, xx + _sprite->width() * 1, yy + rarg->yorigin, 0); + _sprite->pushSpriteTo16(target, xx + _sprite->width() * 2, yy + rarg->yorigin, 0); + + GameObj::render(arg); +} diff --git a/src/background/branch_rock.hpp b/src/background/branch_rock.hpp new file mode 100644 index 0000000..6869d99 --- /dev/null +++ b/src/background/branch_rock.hpp @@ -0,0 +1,45 @@ +/*! + TinyDarius + + @file branch_rock.hpp + @brief Rocks for branching that appear when cleared. +*/ +#pragma once +#ifndef TD_BRANCH_ROCK_HPP +#define TD_BRANCH_ROCK_HPP + +#include +#include "../game_obj.hpp" + +namespace goblib { namespace lgfx { +class GSprite4; +class AnimatedPalette; +}} + +/*! @brief Rock for branching */ +class BranchRock : public GameObj +{ + public: + BranchRock(); + virtual ~BranchRock(); + + virtual void render(void* arg) override; + + GOBLIB_INLINE virtual void show(bool b) override { RenderObj2D::show(b); setHitable(b); } + GOBLIB_INLINE void show() { show(true); } + GOBLIB_INLINE void hide() { show(false); } + + GOBLIB_INLINE goblib::lgfx::AnimatedPalette* animatedPalette() { return _apalette; } + + protected: + virtual void onUnchain() override; + virtual bool onInitialize() override; + virtual bool onRelease() override; + virtual void onExecute(const float delta) override; + + protected: + goblib::lgfx::GSprite4* _sprite; + goblib::lgfx::AnimatedPalette* _apalette; +}; + +#endif diff --git a/src/background/mask.cpp b/src/background/mask.cpp new file mode 100644 index 0000000..617b53c --- /dev/null +++ b/src/background/mask.cpp @@ -0,0 +1,34 @@ +/*! + TinyDarius + + @file mask.cpp + @brief Masking +*/ +#include + +#include "mask.hpp" +#include "../debug.hpp" +#include "../renderer.hpp" + +#include +using goblib::lgfx::GSprite; + +Mask::~Mask() +{ + // TD_PRINT_FUNCTION(); +} + +void Mask::onUnchain() +{ + // TD_PRINT_FUNCTION(); + GameObj::onUnchain(); + delete this; +} + +void Mask::render(void* arg) +{ + RenderArg* rarg = static_cast(arg); + GSprite* target = rarg->sprite; + target->fillRect(_rect.left(), _rect.top() + rarg->yorigin, _rect.width(), _rect.height(), CLR_BLACK); + // target->fillRect(_rect.left(), _rect.top() + rarg->yorigin, _rect.width(), _rect.height(), CLR_RED); +} diff --git a/src/background/mask.hpp b/src/background/mask.hpp new file mode 100644 index 0000000..5153d9d --- /dev/null +++ b/src/background/mask.hpp @@ -0,0 +1,30 @@ +/*! + TinyDarius + + @file mask.hpp + @brief Masking +*/ +#pragma once +#ifndef TD_MASK_HPP +#define TD_MASK_HPP + +#include "../game_obj.hpp" +#include "../typedef.hpp" +#include "../constants.hpp" + +class Mask : public GameObj +{ + public: + explicit Mask(const Rect2& rect) : GameObj(PRIORITY_MASK, ORDER_MASK, CATEGORY_NONE, "mask"),_rect(rect) {} + virtual ~Mask(); + + virtual void render(void* arg) override; + + protected: + virtual void onUnchain() override; + + protected: + Rect2 _rect; +}; + +#endif diff --git a/src/background/rock_surface.cpp b/src/background/rock_surface.cpp new file mode 100644 index 0000000..6525fbc --- /dev/null +++ b/src/background/rock_surface.cpp @@ -0,0 +1,149 @@ +/*! + TinyDarius + + @file rock_surface.hpp + @brief upper/lower rock surface +*/ +#include + +#include "rock_surface.hpp" +#include "../debug.hpp" +#include "../app.hpp" +#include "../renderer.hpp" +#include "../utility.hpp" +#include "../constants.hpp" + +#include +using goblib::lgfx::GSprite; +using goblib::lgfx::GSprite4; +#include +using goblib::lgfx::AnimatedPalette; +#include + +#include + +namespace +{ +constexpr char BITMAP_PATH[] = "/res/td/rock.bmp"; +constexpr HitBox HIT_BOX{ Pos2(0,0), Rect2(0,0, SCREEN_WIDTH, 16) }; +} +const std::array RockSurfaceUpper::VELOCITY_TABLE = +{ + Pos2(8, 0), + Pos2(12, 0), +}; + +RockSurfaceUpper:: RockSurfaceUpper(std::int32_t left, std::int32_t top) + : GameObj(PRIORITY_ROCKSURFACE, ORDER_ROCKSURFACE, CATEGORY_WALL, "rocksurface") + , _sprite(nullptr) + , _apalette(nullptr) + , _velocity(VELOCITY_TABLE[Velocity::Normal]) +{ + _sprite = new goblib::lgfx::GSprite4(); + assert(_sprite); + _apalette = new AnimatedPalette(16, _sprite); + assert(_apalette); + + _hbox = HIT_BOX;; + move(Pos2(left, top)); +} + +RockSurfaceUpper:: ~RockSurfaceUpper() +{ + // TD_PRINT_FUNCTION(); + goblib::safeDelete(_apalette); + goblib::safeDelete(_sprite); +} + +void RockSurfaceUpper::onUnchain() +{ + // TD_PRINT_FUNCTION(); + GameObj::onUnchain(); + delete this; +} + +bool RockSurfaceUpper::onInitialize() +{ + if(createFromBitmap(*_sprite, BITMAP_PATH)) + { + _sprite->makePaletteTable256(); + _apalette->from(*_sprite); + return GameObj::onInitialize(); + } + kill(); + return false; +} + +bool RockSurfaceUpper::onRelease() +{ + return GameObj::onRelease(); +} + +void RockSurfaceUpper::onExecute(const float delta) +{ + // loop + auto np = pos() + _velocity; + if(np.x() > RENDER_WIDTH) + { + auto left = static_cast(np.x()) % RENDER_WIDTH; + np = Pos2(Pos2::pos_type(left), y()); + } + move(np); + + if(_apalette->pump()) + { + _sprite->makePaletteTable256(); + } + // keep hitbox in screen + _hbox.move(Pos2::pos_type(0), y()); +} + +void RockSurfaceUpper::render(void* arg) +{ + RenderArg* rarg = static_cast(arg); + GSprite* target = rarg->sprite; + std::int32_t left = -(static_cast(x()) % RENDER_WIDTH); + std::int32_t yy = static_cast(y()); + + assert(left >= -320 && "Illegal left"); + + _sprite->pushSpriteTo16(target, left, yy + rarg->yorigin); + _sprite->pushSpriteTo16(target, left + SCREEN_WIDTH, yy + rarg->yorigin); + + GameObj::render(arg); +} + +RockSurfaceLower:: RockSurfaceLower(std::int32_t left, std::int32_t top) + : RockSurfaceUpper(left, top) +{ + // keep hitbox in screen + _hbox = HIT_BOX; + _hbox.move(Pos2(0, top)); +} + +bool RockSurfaceLower::onInitialize() +{ + goblib::lgfx::GSprite4 tmp; + + if(createFromBitmap(tmp, BITMAP_PATH)) + { + // Create reversed virtical, because reverce rendering on th fly is slow. + auto wid = tmp.width(); + auto hgt = tmp.height(); + _sprite->setColorDepth(tmp.getColorDepth()); + _sprite->createSprite(wid, hgt); + tmp.setPivot(wid * 0.5f - 0.5f, hgt * 0.5f - 0.5f); + tmp.pushRotateZoom(_sprite, wid/2 - 0.5f, hgt/2 - 0.5f, 0, 1.0f, -1.0f); + auto* ps = tmp.getPalette(); + auto* ds = _sprite->getPalette(); + auto len = tmp.getPaletteCount();; + while(len--) { *ds++ = *ps++; } + + _sprite->makePaletteTable256(); + _apalette->from(*_sprite); + + return GameObj::onInitialize(); + } + kill(); + return false; +} diff --git a/src/background/rock_surface.hpp b/src/background/rock_surface.hpp new file mode 100644 index 0000000..519b340 --- /dev/null +++ b/src/background/rock_surface.hpp @@ -0,0 +1,57 @@ +/*! + TinyDarius + + @file rock_surface.hpp + @brief upper/lower rock surface +*/ +#pragma once +#ifndef TD_ROCK_SURFACE_HPP +#define TD_ROCK_SURFACE_HPP + +#include "../game_obj.hpp" + +namespace goblib { namespace lgfx { +class GSprite4; +class AnimatedPalette; +}} + + +class RockSurfaceUpper : public GameObj +{ + public: + explicit RockSurfaceUpper(std::int32_t left, std::int32_t top); + virtual ~RockSurfaceUpper(); + + virtual void render(void* arg) override; + + enum Velocity : std::uint8_t { Normal, Boss, Max }; + void setVelocity(const Velocity v) { assert(v < Velocity::Max && "Too big"); _velocity = VELOCITY_TABLE[v]; } + + goblib::lgfx::AnimatedPalette* animatedPalette() { return _apalette; } + + protected: + virtual void onUnchain() override; + virtual bool onInitialize() override; + virtual bool onRelease() override; + //virtual void onReceive (const Task::Message& /*msg*/) override {} + virtual void onExecute(const float delta) override; + + protected: + goblib::lgfx::GSprite4* _sprite; // array ptr + goblib::lgfx::AnimatedPalette* _apalette; + Pos2 _velocity; + + static const std::array VELOCITY_TABLE; + constexpr static std::int32_t RENDER_WIDTH = 320; +}; + +class RockSurfaceLower : public RockSurfaceUpper +{ + public: + explicit RockSurfaceLower(std::int32_t left, std::int32_t top); + protected: + virtual bool onInitialize() override; + +}; + +#endif diff --git a/src/background/wave_spot.cpp b/src/background/wave_spot.cpp new file mode 100644 index 0000000..7655902 --- /dev/null +++ b/src/background/wave_spot.cpp @@ -0,0 +1,213 @@ +/*! + TinyDarius + + @file wave_spot.cpp + @brief Background wave and spot +*/ +#include + +#include "wave_spot.hpp" +#include "../debug.hpp" +#include "../app.hpp" +#include "../renderer.hpp" +#include "../utility.hpp" +#include "../constants.hpp" +#include "../stage.hpp" + +#include +using goblib::lgfx::GSprite; +using goblib::lgfx::GSprite4; +#include +using goblib::lgfx::AnimatedPalette; +#include + +#include +#include + +using WSColorChange = std::array; + +namespace +{ +constexpr char BITMAP_PATH_0[] = "/res/td/bg0.bmp"; +constexpr char BITMAP_PATH_1[] = "/res/td/bg1.bmp"; + +/* orignal + RGBColor(48,96,64), + RGBColor(72,160,104), + RGBColor(112,224,144) +*/ + +constexpr WSColorChange wscc_blue = +{ + ColorChange{ RGBColor(48,96,64), RGBColor(48,66,96) }, + ColorChange{ RGBColor(72,160,104), RGBColor(72,104,160) }, + ColorChange{ RGBColor(112,224,144), RGBColor(112,153,224) } +}; + +constexpr WSColorChange wscc_orange = +{ + ColorChange{ RGBColor(48,96,64), RGBColor(96,66,48) }, + ColorChange{ RGBColor(72,160,104), RGBColor(160,104,72) }, + ColorChange{ RGBColor(112,224,144), RGBColor(224,153,112), } +}; + +constexpr WSColorChange wscc_purple = +{ + ColorChange{ RGBColor(48,96,64), RGBColor(96,48,96) }, + ColorChange{ RGBColor(72,160,104), RGBColor(160,104,160) }, + ColorChange{ RGBColor(112,224,144), RGBColor(224,144,224) } +}; + +const WSColorChange wscc_table[Stage::MAX] = +{ + wscc_blue, //A + // + wscc_purple, //B + wscc_orange, //C + // + wscc_blue, // D + wscc_blue, // E + wscc_blue, // F + // + wscc_blue, // G + wscc_blue, // H + wscc_blue, // I + wscc_blue, // J + // + wscc_blue, // K + wscc_blue, // L + wscc_blue, // M + wscc_blue, // N + wscc_blue, // O + // + wscc_blue, // P + wscc_blue, // Q + wscc_blue, // R + wscc_blue, // S + wscc_blue, // T + wscc_blue, // U + // + wscc_blue, // V + wscc_blue, // W + wscc_blue, // X + wscc_blue, // Y + wscc_blue, // Z + wscc_blue, // V' + wscc_blue, // Z' +}; + +constexpr std::int32_t ROUND_TRIP_TIMES = 180; + +constexpr Pos2 VELOCITY = Pos2(4, 0); +constexpr Pos2 INITIAL_POS = Pos2(0, 40); + +constexpr std::int32_t WIDTH_0 = 64; +constexpr std::int32_t WIDTH_1 = 96; +constexpr std::int32_t WIDTH_ALL = WIDTH_0 * 3 + WIDTH_1 * 2; +// +} + +WaveSpot::WaveSpot() + : GameObj(PRIORITY_WAVESPOT, ORDER_WAVESPOT, CATEGORY_WALL, "wavespot") + , _sprites(nullptr) + , _apalette(nullptr) + , _opalettes() +{ + _sprites = new GSprite4[SpriteMax]; + assert(_sprites); + _apalette = new AnimatedPalette(16, _sprites); + assert(_apalette); + + move(INITIAL_POS); + _opalettes.fill(RGBColor()); +} + +WaveSpot:: ~WaveSpot() +{ + // TD_PRINT_FUNCTION(); + goblib::safeDelete(_apalette); + goblib::safeDeleteArray(_sprites); +} + +void WaveSpot::onUnchain() +{ + // TD_PRINT_FUNCTION(); + GameObj::onUnchain(); + delete this; +} + +bool WaveSpot::onInitialize() +{ + if(!createFromBitmap(_sprites[Sprite_0], BITMAP_PATH_0)) + { + kill(); + return false; + } + if(!createFromBitmap(_sprites[Sprite_1], BITMAP_PATH_1)) + { + kill(); + return false; + } + + getPaletteColors(_opalettes.data(), _opalettes.size(), _sprites[Sprite_0]); + + _apalette->from(_sprites[Sprite_0]); + _sprites[Sprite_0].makePaletteTable256(); + _sprites[Sprite_1].makePaletteTable256(); + + return GameObj::onInitialize(); +} +bool WaveSpot::onRelease() +{ + return GameObj::onRelease(); +} + +void WaveSpot::onExecute(const float delta) +{ + // move(Pos2::pos_type(static_cast(x() + VELOCITY_X)), y()); + // loop + auto np = pos() + VELOCITY; + if(np.x() > WIDTH_ALL) + { + auto left = static_cast(np.x()) % WIDTH_ALL; + np = Pos2(Pos2::pos_type(left), y()); + } + move(np); + + offset(VELOCITY); + + if(_apalette->pump()) + { + _apalette->to(_sprites[Sprite_1]); + _sprites[Sprite_0].makePaletteTable256(); + _sprites[Sprite_1].makePaletteTable256(); + } +} + +void WaveSpot::render(void* arg) +{ + RenderArg* rarg = static_cast(arg); + GSprite* target = rarg->sprite; + std::int32_t left = -(static_cast(x()) % WIDTH_ALL); + std::int32_t top = static_cast(y()) + rarg->yorigin; + + _sprites[Sprite_0].pushSpriteTo16(target, left, top); left += WIDTH_0; + _sprites[Sprite_1].pushSpriteTo16(target, left, top); left += WIDTH_1; + _sprites[Sprite_0].pushSpriteTo16(target, left, top); left += WIDTH_0; + _sprites[Sprite_0].pushSpriteTo16(target, left, top); left += WIDTH_0; + _sprites[Sprite_1].pushSpriteTo16(target, left, top); left += WIDTH_1; + + _sprites[Sprite_0].pushSpriteTo16(target, left, top); left += WIDTH_0; + _sprites[Sprite_1].pushSpriteTo16(target, left, top); left += WIDTH_1; + _sprites[Sprite_0].pushSpriteTo16(target, left, top); left += WIDTH_0; + _sprites[Sprite_0].pushSpriteTo16(target, left, top); left += WIDTH_0; + _sprites[Sprite_1].pushSpriteTo16(target, left, top); left += WIDTH_1; +} + +void WaveSpot::roundTripPalette(std::uint8_t stageNo) +{ + assert(stageNo < goblib::size(wscc_table) && "Invalid stageno"); + + auto table = &wscc_table[goblib::clamp(static_cast(stageNo), 0U, goblib::size(wscc_table))]; + roundTripPaletteColors(*_apalette, table->data(), table->size(), ROUND_TRIP_TIMES); +} diff --git a/src/background/wave_spot.hpp b/src/background/wave_spot.hpp new file mode 100644 index 0000000..2428871 --- /dev/null +++ b/src/background/wave_spot.hpp @@ -0,0 +1,43 @@ +/*! + TinyDarius + + @file wave_spot.hpp + @brief Background wave and spot +*/ +#pragma once +#ifndef TD_WAVE_SPOT_HPP +#define TD_WAVE_SPOT_HPP + +#include "../game_obj.hpp" + +namespace goblib { namespace lgfx { +class GSprite4; +class AnimatedPalette; +}} + +class WaveSpot : public GameObj +{ + public: + WaveSpot(); + virtual ~WaveSpot(); + + virtual void render(void* arg) override; + + goblib::lgfx::AnimatedPalette* animatedPalette() { return _apalette; } + void roundTripPalette(std::uint8_t stageNo); + + protected: + virtual void onUnchain() override; + virtual bool onInitialize() override; + virtual bool onRelease() override; + //virtual void onReceive (const Task::Message& /*msg*/) override {} + virtual void onExecute(const float delta) override; + + private: + enum { Sprite_0, Sprite_1, SpriteMax }; + goblib::lgfx::GSprite4* _sprites; + goblib::lgfx::AnimatedPalette* _apalette; + std::array _opalettes; // original palette colors. +}; + +#endif diff --git a/src/behavior.cpp b/src/behavior.cpp new file mode 100644 index 0000000..a2aad09 --- /dev/null +++ b/src/behavior.cpp @@ -0,0 +1,79 @@ +/*! + TinyDarius + + @file behavior.cpp + @brief Behavior for objects +*/ +#include "behavior.hpp" +#include "game_obj.hpp" +#include "constants.hpp" + +#include +#include +#include +#include +#include + +// +void UniformLinearBehavior::pump(const float delta) +{ + _attach.offset(Pos2(std::cos(_radian) * _velocity, std::sin(_radian) * _velocity)); + Behavior::pump(delta); +} + +#if 0 +// +void HomingBehavior::pump(const float delta) +{ + Pos2 vpos = _target.pos() - _attach.pos(); + _radian = std::atan2(vpos.y(), vpos.x()); + + AccelarationBehavior::pump(delta); +} +#endif + +// +void FlutteringBehavior::pump(const float delta) +{ + _attach.move(_bpos.x() + goblib::roundTrip(counter(), _range), + _bpos.y() + _range - goblib::roundTrip(counter() + _range, _range * 2)); + Behavior::pump(delta); +} + +OrbitalBehavior::OrbitalBehavior(GameObj& attach, fx16 a, fx16 b, Tangle startAngle, fx16 avelocity, VecFx16 velocity, Tangle rotate) + : Behavior(attach) + , _pos(VecFx16::pos_type(attach.x()), VecFx16::pos_type(attach.y())) + , _a(a), _b(b) + , _angle(startAngle), _avelocity(avelocity), _off(velocity), _rotate(rotate) +{ + // printf("%s:%d,%d\n", __func__, _pos.x().value(), _pos.y().value()); +} + +void OrbitalBehavior::pump(const float delta) +{ + fx16 rc = table_cos(_rotate.value()); + fx16 rs = table_sin(_rotate.value()); + fx16 ac = table_cos(_angle.value()); + fx16 as = table_sin(_angle.value()); + + _pos += VecFx16( _a * ac * rc - _b * as * rs, + _a * ac * rs + _b * as * rc); + _pos += _off; + + // printf("%d,%d : %f\n", _pos.x().value(), _pos.y().value(), _angle.value()); + + _attach.move(static_cast(_pos.x()), static_cast(_pos.y())); + + _angle += _avelocity; +} + +// +MineBehavior::MineBehavior(GameObj& attach, fx16 a, fx16 b, Tangle startAngle, fx16 avelocity, VecFx16 velocity, Tangle rotate) + : OrbitalBehavior(attach, a, b, startAngle, avelocity, velocity, rotate) +{} + +void MineBehavior::pump(const float delta) +{ + OrbitalBehavior::pump(delta); + if(_attach.x() < 64) { _off.zero(); } +} diff --git a/src/behavior.hpp b/src/behavior.hpp new file mode 100644 index 0000000..c7aed2b --- /dev/null +++ b/src/behavior.hpp @@ -0,0 +1,152 @@ +/*! + TinyDarius + + @file behavior.hpp + @brief Behavior for objects +*/ +#pragma once +#ifndef TD_BEHAVIOR_HPP +#define TD_BEHAVIOR_HPP + +#include "typedef.hpp" +#include "math_table.hpp" +#include +#include +#include +#include +#include +#include + +class GameObj; + + +/*! @brief Behavior base */ +class Behavior +{ + public: + explicit Behavior(GameObj& attach) : _attach(attach), _counter(0) {} + virtual ~Behavior(){} + + GOBLIB_INLINE virtual void pump(const float /*delta*/){ ++_counter; } + void reset() { _counter = 0; } + + protected: + std::uint32_t counter() const { return _counter; } + + protected: + GameObj& _attach; + + private: + std::uint32_t _counter; +}; + +/*! @brief No movement */ +class FixedBehavior : public Behavior +{ + public: + FixedBehavior(GameObj& attach) : Behavior(attach) {} +}; + +/*! @brief Uniform linear motion */ +class UniformLinearBehavior : public Behavior +{ + public: + UniformLinearBehavior(GameObj& attach, float velocity, float radian) + : Behavior(attach), _velocity(velocity), _radian(radian) {} + + virtual void pump(const float delta) override; + + protected: + float _velocity; + float _radian; +}; + +/*! @brief Linear motion with accelaration */ +class AccelarationBehavior : public UniformLinearBehavior +{ + public: + AccelarationBehavior(GameObj& attach, float velocity, float radian, float acc) + : UniformLinearBehavior(attach, velocity, radian), _acc(acc) {} + + virtual void pump(const float delta) override + { + UniformLinearBehavior::pump(delta); + _velocity += _acc; + } + + protected: + float _acc; +}; + +#if 0 +// @brief Homing to target. +class HomingBehavior : public AccelarationBehavior +{ + public: + HomingBehavior(GameObj& attach, float velocity, float radian, float acc, GameObj& target) + : AccelarationBehavior(attach, velocity, radian, acc), _target(target) {} + + virtual void pump(const float delta) override; + + protected: + GameObj& _target; +}; +#endif + +// @brief Flutter +class FlutteringBehavior : public Behavior +{ + public: + FlutteringBehavior(GameObj& attach, const Pos2& bpos, std::uint32_t range) + : Behavior(attach) + , _bpos(bpos) + , _range(range) + { assert(goblib::math::is_powerof2(range) && "range must be power of 2"); } + + virtual void pump(const float delta) override; + + protected: + const Pos2 _bpos; // base position + std::uint32_t _range; +}; + +// @brief Orbital +class OrbitalBehavior : public Behavior +{ + public: + /*! + @param attach target object + @param a ellipse circumference step a + @param b ellipse circumference step b + @param startAngle start angle for calculate circumference + @param avelocity angular velocity + @param offset velocity + @rotate ellipse rotation angle + */ + OrbitalBehavior(GameObj& attach, fx16 a, fx16 b, Tangle startAngle, fx16 avelocity, VecFx16 velocity, Tangle rotate = 0); + + virtual void pump(const float delta) override; + + protected: + VecFx16 _pos; + fx16 _a, _b; // radius A,B + fx16 _angle; // calc for elliptice circumference. + fx16 _avelocity; + VecFx16 _off; + fx16 _rotate; // ellipse rotate angle. +}; + + + +// @brief for Mine +class MineBehavior : public OrbitalBehavior +{ + public: + MineBehavior(GameObj& attach, fx16 a, fx16 b, Tangle startAngle, fx16 avelocity, VecFx16 velocity, Tangle rotate = 0); + virtual void pump(const float delta) override; + + private: + bool _orbit; +}; + +#endif diff --git a/src/boss/boss.cpp b/src/boss/boss.cpp new file mode 100644 index 0000000..62b0ef6 --- /dev/null +++ b/src/boss/boss.cpp @@ -0,0 +1,478 @@ +/*! + TinyDarius + + @file boss.hpp + @brief Boss base +*/ +#include + +#include "boss.hpp" +#include "../debug.hpp" +#include "../app.hpp" +#include "../game.hpp" +#include "../renderer.hpp" +#include "../utility.hpp" +#include "../constants.hpp" +#include "../behavior.hpp" +#include "../effect/explosion.hpp" +#include "../enemy/enemy.hpp" + +#include "king_fossil.hpp" +//#include "electric_fan.hpp" + +#include +using goblib::lgfx::GSprite; +#include +using goblib::lgfx::GCellSprite4; +#include +#include +#include + +#include + +namespace +{ +constexpr Pos2 INITIAL_POS(SCREEN_WIDTH, SCREEN_HEIGHT); +constexpr Pos2 LEAVE_TO(80, 240); +constexpr std::int32_t LEAVE_TIMES = 6_fsec; +}; + +Boss* Boss::create(std::int8_t stage) +{ + Boss* p = nullptr; + switch(stage) + { + case 0: p = new KingFossil(); break; // A + // case 1: p = new ElectricFan(ElectricFan::Type::B); break; // B + // case 2: p = new ElectricFan(ElectricFan::Type::C); break; // C + /* + case 3: p = new DualShears(DualShears::Type::D); break; // D + case 4: p = new DualShears(DualShears::Type::E); break; // E + case 5: p = new DualShears(DualShears::Type::F); break; // F + */ + default: p = new KingFossil(); break; + } + return p; +} + +Boss::Boss(std::uint32_t pts) + : GameObj(PRIORITY_BOSS, ORDER_BOSS_BODY, CATEGORY_BOSS, "boss") + , _sprite(nullptr) + , _bodyRect() + , _behavior(nullptr) + , _hp(0) + , _partials() + , _apalette(nullptr) + , _palettes{} + , _function(&Boss::onNop) + , _from(), _to() + , _to_times(0) + , _status(Status::Nop) + , _score(pts) +{ + _sprite = new GCellSprite4(); + assert(_sprite); + + _apalette = new goblib::lgfx::AnimatedPalette(16, _sprite); + assert(_apalette); + + _score.insertObserver(TinyDarius::instance().game()); + + move(INITIAL_POS); +} + +Boss::~Boss() +{ + // TD_PRINT_FUNCTION(); + goblib::safeDelete(_apalette); + goblib::safeDelete(_behavior); + goblib::safeDelete(_sprite); +} + +void Boss::onUnchain() +{ + // TD_PRINT_FUNCTION(); + GameObj::onUnchain(); + delete this; +} + +bool Boss::onInitialize() +{ + // Store original palettes. + getPaletteColors(_palettes.data(), _palettes.size(), *_sprite); + _sprite->makePaletteTable256(); + + return GameObj::onInitialize(); +} + +bool Boss::onRelease() +{ + return GameObj::onRelease(); +} + +void Boss::onExecute(const float delta) +{ + (this->*_function)(delta); + if(_apalette->pump()) + { + _sprite->makePaletteTable256(); + } + GameObj::onExecute(delta); +} + +void Boss::onHit(GameObj* o, const Rect2& hit) +{ + if(o->category() == CATEGORY_BULLET) + { + --_hp; + } +} + +void Boss::render(void* arg) +{ + RenderArg* rarg = static_cast(arg); + GSprite* target = rarg->sprite; + _sprite->pushCellTo16(target, _bodyRect, static_cast(x()), static_cast(y()) + rarg->yorigin, 0); + + GameObj::render(arg); +} + +void Boss::_appear(const ColorChange* gray, std::size_t gsz, const Pos2& to, std::uint32_t times) +{ + TD_PRINTF("%s\n", __PRETTY_FUNCTION__); + _function = &Boss::onAppear; + _status = Status::Appear; + + move(INITIAL_POS); + resetAnimation(); + disableHit(); + resume(); + pausePartials(); + show(); + + applyPaletteColors(*_sprite, gray, gsz); + _apalette->from(*_sprite); + _sprite->makePaletteTable256(); + + moveTo(to, times); +} + +void Boss::onAppear(const float delta) +{ + if(pumpMove()) + { + move(_to); + prepare(); + } +} + +void Boss::prepare() +{ + TD_PRINTF("%s\n", __PRETTY_FUNCTION__); + _function = &Boss::onPrepare; + _status = Status::Prepare;; + towardOriginalColor(*_sprite, 60); +} + +void Boss::onPrepare(const float delta) +{ + if(_apalette->empty()) + { + battle(); + } +} + +void Boss::battle() +{ + TD_PRINTF("%s\n", __PRETTY_FUNCTION__); + _function = &Boss::onBattle; + _status = Status::Battle; + resumePartials(); + enableHit(); + resetCounter(); + if(_behavior) { _behavior->reset(); } +} + +void Boss::onBattle(const float delta) +{ + if(_behavior) { _behavior->pump(delta); } + if(_hp <= 0) + { + defeat(); + } +} + +void Boss::_defeat(const ColorChange* gray, std::size_t gsz, const ExplosionSetting* eset, std::size_t esz) +{ + TD_PRINTF("%s\n", __PRETTY_FUNCTION__); + _function = &Boss::onDefeat; + _status = Status::Defeat; + resetCounter(); + + pausePartials(); + disableHit(); + _apalette->pause(); + + goblib::TaskMessage msg; + msg.msg = MSG_DEFEAT_BOSS; + msg.arg = this; + TinyDarius::instance().postBroadcastMessage(msg); + + towardPaletteColors(*_apalette, gray, gsz, 60); + explode(eset, esz); + + _score.notify(); +} + +void Boss::onDefeat(const float delta) +{ + if(counter() > 3_fsec) + { + _apalette->resume(); + if(_apalette->empty()) + { + leave(); + } + } +} + +void Boss::_escape(const ColorChange* gray, std::size_t gsz) +{ + TD_PRINTF("%s\n", __PRETTY_FUNCTION__); + _function = &Boss::onEscape; + _status = Status::Escape; + resetCounter(); + _hp = 0; + + disableHit(); + pausePartials(); + _apalette->resume(); + towardPaletteColors(*_apalette, gray, gsz, 60); +} + + +void Boss::onEscape(const float delta) +{ + if(counter() > 4_fsec) + { + if(_apalette->empty()) + { + leave(); + } + } +} + +void Boss::leave() +{ + TD_PRINTF("%s\n", __PRETTY_FUNCTION__); + moveTo(LEAVE_TO, LEAVE_TIMES); + _function = &Boss::onLeave; + _status = Status::Leave; + resetCounter(); +} + +void Boss::onLeave(const float delta) +{ + if(pumpMove()) + { + move(_to); + nop(); + } +} + +bool Boss::pumpMove() +{ + auto t = goblib::easing::linear(static_cast(counter()) / _to_times); + auto diff = _to - _from; + diff *= t; + move(_from + diff); + + return counter() >= _to_times; +} + +void Boss::explode(const ExplosionSetting* table, std::size_t size) +{ + while(size--) + { + auto e = new Explosion(table->rpos.x(), table->rpos.y(), ORDER_EXPLOSION_BOSS, static_cast(table->type), + this, -1, true); + assert(e); + TinyDarius::instance().reserveInsertNode(e, this); + ++table; + } +} + +void Boss::hideEffect() +{ + goblib::TaskMessage msg; + msg.msg = MSG_HIDE_EFFECT; + msg.arg = this; + TinyDarius::instance().sendBroadcastMessage(msg, this); +} + +void Boss::towardOriginalColor(goblib::lgfx::GCellSprite4& sprite, std::uint32_t times) +{ + int_fast8_t i = 0; + for(auto& e : _palettes) + { + _apalette->toward(i++, e, times); + } +} + +// +Boss::Partial::Partial(Boss& parent, goblib::lgfx::GCellSprite4& sprite, const PartialAnimation& animation, std::int32_t relative_priority, std::int32_t relative_order, std::int32_t hp, std::uint32_t pts) + : GameObj(PRIORITY_BOSS + relative_priority, ORDER_BOSS_BODY + relative_order, CATEGORY_BOSS, "bosspartial") + , _parent(parent) + , _sprite(sprite) + , _animation(animation) + , _hp(hp) + , _score(pts) +{ + pauseAnimation(); + _score.insertObserver(TinyDarius::instance().game()); +} + +Boss::Partial::~Partial() +{ + // TD_PRINT_FUNCTION(); +} + +void Boss::Partial::onUnchain() +{ + // TD_PRINT_FUNCTION(); + GameObj::onUnchain(); + delete this; +} + +bool Boss::Partial::onInitialize() +{ + return GameObj::onInitialize(); +} + +bool Boss::Partial::onRelease() +{ + return GameObj::onRelease(); +} +void Boss::Partial::onExecute(const float delta) +{ + _animation.pump(); + move(_parent.x() + _animation.rx + _animation.offsetX(), _parent.y() + _animation.ry + _animation.offsetY()); +} + +void Boss::Partial::onHit(GameObj* o, const Rect2& hit) +{ + if(o->category() == CATEGORY_BULLET) + { + Pos2i spos(hit.center().x() - static_cast(pos().x()), + hit.center().y() - static_cast(pos().y())); + + explode(spos); + + if(--_hp <= 0) + { + _score.notify(); + disableHit(); + hide(); + } + } +} + +void Boss::Partial::explode(const Pos2& pos) +{ + auto exp = new Explosion(pos, ORDER_EXPLOSION_BOSS, Explosion::Random, this); + assert(exp); + TinyDarius::instance().reserveInsertNode(exp, this); +} + +void Boss::Partial::reflect(const Pos2& pos) +{ + auto r = new Reflection(pos.x(), pos.y()); + assert(r); + TinyDarius::instance().reserveInsertNode(r, this); +} + +void Boss::Partial::render(void* arg) +{ + RenderArg* rarg = static_cast(arg); + GSprite* target = rarg->sprite; + _sprite.pushCellTo16(target, _animation.rect(), static_cast(x()), static_cast(y()) + rarg->yorigin, 0); + + GameObj::render(arg); +} + +// +Boss::Bullet::Bullet(GameObj& parent, goblib::lgfx::GCellSprite4& sprite, const PartialAnimation& animation) + : GameObj(PRIORITY_BOSS_BULLET, ORDER_BOSS_BULLET, CATEGORY_ENEMY_BULLET, "bossbullet") + , _parent(parent) + , _sprite(sprite) + , _animation(animation) + , _behavior(nullptr) +{ +} + +Boss::Bullet::~Bullet() +{ + // TD_PRINT_FUNCTION(); + goblib::safeDelete(_behavior); +} + +void Boss::Bullet::onUnchain() +{ + // TD_PRINT_FUNCTION(); + GameObj::onUnchain(); + delete this; + +} + +bool Boss::Bullet::onInitialize() +{ + return GameObj::onInitialize(); +} + +bool Boss::Bullet::onRelease() +{ + return GameObj::onRelease(); +} + +void Boss::Bullet::onExecute(const float delta) +{ + _animation.pump(); + if(_behavior) { _behavior->pump(delta); } +} + +void Boss::Bullet::render(void* arg) +{ + RenderArg* rarg = static_cast(arg); + GSprite* target = rarg->sprite; + _sprite.pushCellTo16(target, _animation.rect(), static_cast(x()), static_cast(y()) + rarg->yorigin, 0); + + GameObj::render(arg); +} + +// +Boss::Hit::Hit(Boss& parent, const HitBox& hbox) + : GameObj(PRIORITY_BOSS, ORDER_BOSS_BODY - 2, CATEGORY_BIT_OBSTACLE, "bosshitbox") + , _parent(parent) + , _chbox(hbox) +{ + _hbox = hbox; + move(_parent.x(), _parent.y()); +} + +void Boss::Hit::onUnchain() +{ + // TD_PRINT_FUNCTION(); + GameObj::onUnchain(); + delete this; + +} + +void Boss::Hit::onExecute(const float) +{ + move(_parent.x(), _parent.y()); +} + +void Boss::Hit::render(void* arg) +{ + GameObj::render(arg); +} diff --git a/src/boss/boss.hpp b/src/boss/boss.hpp new file mode 100644 index 0000000..f8c5672 --- /dev/null +++ b/src/boss/boss.hpp @@ -0,0 +1,285 @@ +/*! + TinyDarius + + @file boss.hpp + @brief Boss base +*/ +#pragma once +#ifndef TD_BOSS_HPP +#define TD_BOSS_HPP + +#include "../game_obj.hpp" +#include "../partial_animation.hpp" +#include "../score.hpp" +#include "../hit_box.hpp" +#include +#include +#include + +namespace goblib { namespace lgfx { +class GCellSprite4; +class AnimatedPalette; +}} +class Behavior; +struct ColorChange; + +/*! @brief Boss base class */ +class Boss : public GameObj +{ + public: + enum Status : int32_t + { + Nop, + Appear, + Prepare, + Battle, + Defeat, + Escape, + Leave, + }; + constexpr static std::size_t PARTIAL_MAX = 12; + + /*! @brief Partial parts of Boss */ + class Partial : public GameObj + { + public: + /*! + @param parent Boss object + @param sprite Shared sprite + @param animation animation cell and sequences + @param relative_priority relative priority with parent + @param relative_order relative rendering order with parent + @param hp Hitpoint + @param pts Score if breaked + */ + Partial(Boss& parent, goblib::lgfx::GCellSprite4& sprite, const PartialAnimation& animation,std::int32_t relative_priority, std::int32_t relative_order, std::int32_t hp = 1, std::uint32_t pts = 0); + virtual ~Partial(); + + GOBLIB_INLINE void pauseAnimation(bool b) { _animation.pause(b); } + GOBLIB_INLINE void pauseAnimation() { pauseAnimation(true); } + GOBLIB_INLINE void resumeAnimation() { pauseAnimation(false); } + GOBLIB_INLINE void resetAnimation() { _animation.reset(); } + + virtual void render(void* arg) override; + + GOBLIB_INLINE std::int32_t hp() const { return _hp; } + + protected: + virtual void onUnchain() override; + virtual bool onInitialize() override; + virtual bool onRelease() override; + virtual void onExecute(const float delta) override; + + virtual void onHit(GameObj* o, const Rect2& hit) override; + + void explode(const Pos2& pos); + void reflect(const Pos2& pos); + + protected: + Boss& _parent; + goblib::lgfx::GCellSprite4& _sprite; + PartialAnimation _animation; + std::int32_t _hp; + Score _score; + }; + + /*! @brief Bullets of Boss */ + class Bullet : public GameObj + { + public: + /*! + @param parent Parent object + @param sprite Shared sprite + @param animation animation cell and sequences + */ + Bullet(GameObj& parent, goblib::lgfx::GCellSprite4& sprite, const PartialAnimation& animation); + virtual ~Bullet(); + + virtual void render(void* arg) override; + + protected: + virtual void onUnchain() override; + virtual bool onInitialize() override; + virtual bool onRelease() override; + virtual void onExecute(const float delta) override; + + protected: + GameObj& _parent; + goblib::lgfx::GCellSprite4& _sprite; + PartialAnimation _animation; + Behavior* _behavior; + }; + + /*! @brief Hitbox (NO rendering) */ + class Hit : public GameObj + { + public: + Hit(Boss& parent, const HitBox& hbox /* relative */); + virtual void render(void* arg) override; + protected: + virtual void onUnchain() override; + virtual void onExecute(const float delta) override; + private: + Boss& _parent; + const HitBox _chbox; + }; + + /*! @brief Explosions at defeat */ + struct ExplosionSetting + { + Pos2 rpos; // relative position from body + std::int32_t type; // Explosion::Type + }; + + // + static Boss* create(std::int8_t stage); + virtual ~Boss(); + + virtual void render(void* arg) override; + + /// @name Property + /// @{ + virtual const char* name() const = 0; + GOBLIB_INLINE goblib::lgfx::GCellSprite4* sprite() { return _sprite; } + GOBLIB_INLINE Status status() const { return _status; } + GOBLIB_INLINE bool isDead() const { return _hp <= 0; } + GOBLIB_INLINE bool isNop() const { return status() == Status::Nop; } + /// @} + + virtual void show(bool b) override + { + for(auto& e : _partials) { e->show(b && (e->hp() > 0)); } + RenderObj2D::show(b); + } + GOBLIB_INLINE void show() { show(true); } + GOBLIB_INLINE void hide() { show(false); } + + GOBLIB_INLINE void pause(bool b) + { + Task::pause(b, false); + pausePartials(b); + } + GOBLIB_INLINE void pause() { pause(true); } + GOBLIB_INLINE void resume() { pause(false); } + + GOBLIB_INLINE void virtual setHitable(bool b) override + { + GameObj::setHitable(b); + setHitablePartials(b); + setHitableHits(b); + } + GOBLIB_INLINE void enableHit() { setHitable(true); } + GOBLIB_INLINE void disableHit() { setHitable(false); } + + void hideEffect(); + + GOBLIB_INLINE std::int32_t hp() const { return _hp; } + + /// @name Change status + /// @{ + virtual void appear() = 0; + virtual void escape() = 0; + /// @} + + virtual void onHit(GameObj* o, const Rect2& hit) override; + + protected: + explicit Boss(std::uint32_t pts); + + virtual void onUnchain() override; + virtual bool onInitialize() override; + virtual bool onRelease() override; + virtual void onExecute(const float delta) override; + + /// @name function for status + /// @{ + virtual void onAppear(const float delta); + virtual void onPrepare(const float delta); + virtual void onBattle(const float delta); + virtual void onDefeat(const float delta); + virtual void onEscape(const float delta); + virtual void onLeave(const float delta); + /// @} + + /// @name Change status + /// @{ + void prepare(); + void battle(); + virtual void defeat() = 0; + void leave(); + + void _appear(const ColorChange* gray, std::size_t gsz, const Pos2& to, std::uint32_t times); + void _defeat(const ColorChange* gray, std::size_t gsz, const ExplosionSetting* eset, std::size_t esz); + void _escape(const ColorChange* gray, std::size_t gsz); + /// @} + + void explode(const ExplosionSetting* table, std::size_t size); + + void pausePartials(bool b) + { + for(auto& e : _partials) { e->pauseAnimation(b); } + } + GOBLIB_INLINE void pausePartials() { pausePartials(true); } + GOBLIB_INLINE void resumePartials() { pausePartials(false); } + + GOBLIB_INLINE void setHitablePartials(bool b) + { + for(auto& e : _partials) { e->setHitable(b); } + } + GOBLIB_INLINE void enableHitPartials() { setHitablePartials(true); } + GOBLIB_INLINE void disableHitPartials() { setHitablePartials(true); } + + GOBLIB_INLINE void setHitableHits(bool b) + { + for(auto& e : _hits) { e->setHitable(b); } + } + GOBLIB_INLINE void enableHitHits() { setHitableHits(true); } + GOBLIB_INLINE void disableHitHits() { setHitableHits(true); } + + + GOBLIB_INLINE void resetAnimation() + { + for(auto& e : _partials) { e->resetAnimation(); } + } + + void moveTo(const Pos2& to, std::int32_t times) + { + _from = pos(); + _to = to; + _to_times = times; + resetCounter(); + } + + void towardOriginalColor(goblib::lgfx::GCellSprite4& sprite, std::uint32_t times); + + private: + void nop() + { + _function = &Boss::onNop; + _status = Status::Nop; + resetCounter(); + } + void onNop(const float) {} + + bool pumpMove(); + + protected: + goblib::lgfx::GCellSprite4* _sprite; + goblib::lgfx::CellRect _bodyRect; + Behavior* _behavior; + std::int32_t _hp; + goblib::FixedVector _partials; + goblib::FixedVector _hits; + goblib::lgfx::AnimatedPalette* _apalette; + std::array _palettes; // Original palettes. + + using pump_function = void(Boss::*)(float); + pump_function _function; + + Pos2 _from, _to; + std::int32_t _to_times; + Status _status; + Score _score; +}; + +#endif diff --git a/src/boss/king_fossil.cpp b/src/boss/king_fossil.cpp new file mode 100644 index 0000000..7fe894c --- /dev/null +++ b/src/boss/king_fossil.cpp @@ -0,0 +1,476 @@ +/*! + TinyDarius + + @file king_fossil.cpp + @brief Coelacanth +*/ +#include + +#include "king_fossil.hpp" +#include "../debug.hpp" +#include "../app.hpp" +#include "../utility.hpp" +#include "../constants.hpp" +#include "../behavior.hpp" +#include "../effect/explosion.hpp" + +#include +using goblib::lgfx::GSprite; +using goblib::lgfx::GCellSprite4; +#include +#include +#include +#include +using goblib::suffix::operator"" _u8; +using goblib::suffix::operator"" _i16; + +#include + +using goblib::lgfx::CellRect; +using Seq = goblib::graph::Sequence; + +namespace +{ +constexpr char BITMAP_PATH[] = "/res/td/kf.bmp"; +constexpr CellRect BODY_RECT = {0, 0, 152, 72}; + +constexpr Pos2 APPEAR_TO(SCREEN_WIDTH - 196, SCREEN_HEIGHT/2 - 40); +constexpr std::int32_t APPEAR_TIMES = 6_fsec; + +// Hitpoint +#if 1 +constexpr std::int32_t HP_BODY = 30; +#else +constexpr std::int32_t HP_BODY = 1; +#endif +constexpr std::int32_t HP_DORSAFIN = 16; +constexpr std::int32_t HP_ANALFIN = 16; + +// Partial gray scale +constexpr ColorChange GRAY_SCALE[] = +{ + { RGBColor(0,24,90), RGBColor(32,32,64) }, + { RGBColor(0,90,189), RGBColor(80,80,128) }, + { RGBColor(0,41,123), RGBColor(64,64,96) }, +}; + + +// Explosion on defeat +constexpr Boss::ExplosionSetting EXPLOSION_TABLE[] = +{ + { Pos2( 16, 32), Explosion::Type::Type_1 }, + { Pos2( 80, 16), Explosion::Type::Type_0 }, + { Pos2( 64, 48), Explosion::Type::Type_2 }, + { Pos2(152, 16), Explosion::Type::Type_1 }, + { Pos2(128, 56), Explosion::Type::Type_0 }, + { Pos2(184, 40), Explosion::Type::Type_2 }, +}; + +// Hitbox +constexpr HitBox HBOX_BULLET = { Pos2(1,1), Rect2(0,0,8-1*2, 8-1*2)}; +constexpr HitBox HBOX_HEAD = { Pos2(24,-8), Rect2(0, 0, 24, 32) }; +constexpr HitBox HBOX_DORSA = { Pos2(8,4), Rect2(0, 0, 32, 12) }; +constexpr HitBox HBOX_ANAL = { Pos2(8,0), Rect2(0, 0, 32, 16) }; +constexpr HitBox HBOX_BODY[] = +{ + { Pos2( 40, 16), Rect2(0, 0, 80, 48) }, + { Pos2(120, 24), Rect2(0, 0, 72, 32) }, + { Pos2(88, 0), Rect2(0, 0, 32, 16) }, +}; + +// +} + +// +class KingFossil::Head : public Boss::Partial +{ + public: + Head(KingFossil* parent) + : Boss::Partial(*parent, *parent->sprite(), + { + -8, 32, + { + CellRect( 0, 72, 56, 40), + CellRect( 56, 72, 56, 40), + CellRect(112, 72, 56, 48), + CellRect(168, 72, 56, 56), + }, + { + Seq(Seq::Draw, 0, 12_u8), // 0 + Seq(Seq::Draw, 1, 6_u8), // 12 + Seq(Seq::Draw, 2, 6_u8), // 18 + Seq(Seq::Draw, 3, 48_u8), // 24 + Seq(Seq::Draw, 2, 6_u8), // 72 + Seq(Seq::Draw, 1, 6_u8), // 78 + Seq(Seq::Draw, 0, 12_u8), // 84 + Seq(Seq::Goto, 0_u8), // 96 + } + }, + 1, -1) + { + _hbox = HBOX_HEAD; + } + + protected: + void onHit(GameObj* o, const Rect2& r) override + { + if(o->category() != CATEGORY_BULLET) { return; } + + if(_animation.index() == 3) + { + Pos2i spos(r.center().x() - static_cast(pos().x()), + r.center().y() - static_cast(pos().y())); + explode(spos); + + _parent.onHit(o, r); + if(_parent.hp() <= 0) { disableHit(); } + } + else + { + reflect(r.center()); + } + } +}; + +// +class KingFossil::PelvicFin : public Boss::Partial +{ + public: + PelvicFin(KingFossil* parent) + : Boss::Partial(*parent, *parent->sprite(), + { 48, 56, + { + CellRect( 0, 168, 48, 24), + CellRect( 48, 168, 48, 24), + CellRect( 96, 168, 48, 16), + }, + { + Seq(Seq::Draw, 0, 12_u8), + Seq(Seq::Draw, 1, 12_u8), + Seq(Seq::Draw, 2, 12_u8), + Seq(Seq::Draw, 1, 12_u8), + Seq(Seq::Goto, 0_u8), + } + }, + 1, -1) + {} +}; + +// +class KingFossil::DorsaFin : public Boss::Partial +{ + public: + DorsaFin(KingFossil* parent) + : Boss::Partial(*parent, *parent->sprite(), + { 88, -16, + { + CellRect(0, 200, 64, 32) + }, + { // No animation + Seq(Seq::Draw, 0, 255_u8), + Seq(Seq::Goto, 0_u8), + + } + }, + 1, -1, + HP_DORSAFIN, + PTS_BOSS_PARTIAL) + { + _hbox = HBOX_DORSA; + } +}; + +// +class KingFossil::AnalFin : public Boss::Partial +{ + public: + AnalFin(KingFossil* parent) + : Boss::Partial(*parent, *parent->sprite(), + { 112, 56, + { + CellRect(152, 184, 64, 24), + CellRect(152, 128, 64, 24), + CellRect(152, 152, 64, 32), + }, + { + Seq(Seq::Draw, 0, 6_u8), + Seq(Seq::Draw, 1, 6_u8), + Seq(Seq::Draw, 2, 16_u8), + Seq(Seq::Draw, 1, 6_u8), + Seq(Seq::Draw, 0, 6_u8), + Seq(Seq::Goto, 0_u8), + } + }, + 1, -1, + HP_ANALFIN, + PTS_BOSS_PARTIAL) + { + _hbox = HBOX_ANAL; + } +}; + +// +class KingFossil::AdiposeFin : public Boss::Partial +{ + public: + AdiposeFin(KingFossil* parent) + : Boss::Partial(*parent, *parent->sprite(), + { 124, 0, + { + CellRect( 64, 200, 32, 24), +#if 0 + CellRect( 96, 200, 32, 24), + CellRect(128, 200, 32, 24), +#endif + }, + { + Seq(Seq::Draw, 0, 2_u8), +#if 0 + Seq(Seq::Draw, 1, 4_u8), + Seq(Seq::Draw, 2, 4_u8), + Seq(Seq::Draw, 1, 4_u8), + Seq(Seq::Draw, 0, 2_u8), +#endif + Seq(Seq::Goto, 0_u8), + } + }, + 1, -1) + {} +}; + +// +class KingFossil::Tail : public Boss::Partial +{ + public: + Tail(KingFossil* parent) + : Boss::Partial(*parent, *parent->sprite(), + { 152, 16, + { + CellRect(152, 16, 64, 48), + CellRect( 0, 112, 56, 48), + CellRect( 56, 112, 48, 48), + }, + { + Seq(Seq::Draw, 0, 8_u8), + Seq(Seq::Draw, 1, 4_u8), + Seq(Seq::Draw, 2, 8_u8), + Seq(Seq::Draw, 1, 4_u8), + Seq(Seq::Draw, 0, 8_u8), + Seq(Seq::Goto, 0_u8), + } + }, + 1, -1) + {} +}; + +// +KingFossil::Bullet::Bullet(GameObj& parent, GCellSprite4& sprite, float radian, KingFossil& kf) + : Boss::Bullet(parent, sprite, + { -4, 4, + { + CellRect(176,208, 8, 8), + CellRect(184,208, 8, 8), + CellRect(192,208, 8, 8), + CellRect(200,208, 8, 8), + CellRect(208,208, 8, 8), + CellRect(216,208, 8, 8), + }, + { + Seq(Seq::Draw, 0, 4_u8), + Seq(Seq::Draw, 1, 4_u8), + Seq(Seq::Draw, 2, 4_u8), + Seq(Seq::Draw, 3, 4_u8), + Seq(Seq::Draw, 4, 4_u8), + Seq(Seq::Draw, 5, 4_u8), + Seq(Seq::Goto, 3_u8), + } + }) + , _kf(kf) +{ + _behavior = new UniformLinearBehavior(*this, VELOCITY, radian); + assert(_behavior); + _hbox = HBOX_BULLET; + move(parent.x() + _animation.rx, parent.y() + _animation.ry); +} +KingFossil::Bullet::~Bullet() +{ + // TD_PRINT_FUNCTION(); + goblib::safeDelete(_behavior); +} + +void KingFossil::Bullet::onUnchain() +{ + // TD_PRINT_FUNCTION(); + // Don't delete, return to object pool. + _kf._bullets.destruct(this); + GameObj::onUnchain(); +} + +void KingFossil::Bullet::onExecute(const float delta) +{ + Rect2 r = Rect2(pos(), 8, 8); + Boss::Bullet::onExecute(delta); + if(!r.overlaps(FIELD_RECT)) { release(); } +} + +// +class KingFossil::Launcher : public Boss::Partial +{ + public: + Launcher(KingFossil* parent) + : Boss::Partial(*parent, *parent->sprite(), + { 16, 40, + { + CellRect(104,120, 32, 16), + CellRect(104,136, 32, 16), + CellRect(104,152, 32, 16) + }, + { // Synchronize with the animation of head. + Seq(Seq::Offset, 0_i16, 0_i16), + Seq(Seq::Draw, 0, 12_u8), // 0 + Seq(Seq::Offset, -4_i16, 0_i16), + Seq(Seq::Draw, 0, 3_u8), // 12 + Seq(Seq::Offset, -8_i16, 0_i16), + Seq(Seq::Draw, 0, 3_u8), // 15 + Seq(Seq::Offset, -12_i16, 0_i16), + Seq(Seq::Draw, 0, 3_u8), // 18 + Seq(Seq::Offset, -16_i16, 0_i16), + Seq(Seq::Draw, 0, 3_u8), // 21 + Seq(Seq::LoopS, 3), + Seq(Seq::Draw, 1, 4_u8), // 24,40,56 + Seq(Seq::Draw, 2, 4_u8), // 28,44,60 + Seq(Seq::Callback), + Seq(Seq::Draw, 2, 4_u8), // 32,48,64 + Seq(Seq::Draw, 0, 4_u8), // 36,52,68 + Seq(Seq::LoopE), + Seq(Seq::Offset, -12_i16, 0_i16), + Seq(Seq::Draw, 0, 3_u8), // 72 + Seq(Seq::Offset, -8_i16, 0_i16), + Seq(Seq::Draw, 0, 3_u8), // 75 + Seq(Seq::Offset, -4_i16, 0_i16), + Seq(Seq::Draw, 0, 3_u8), // 78 + Seq(Seq::Offset, -0_i16, 0_i16), + Seq(Seq::Draw, 0, 3_u8), // 81 + Seq(Seq::Draw, 0, 12_u8), // 84 + Seq(Seq::Goto, 0_u8), // 96 + } + }, + 1, 1) + , _kf(*parent) + { + _animation.sequencer.setCallback(shoot, this); + } + + static void shoot(void* arg, std::uint8_t cur) + { + static_cast(arg)->_shoot(cur); + } + + void _shoot(std::uint8_t /*cur*/) + { + for(std::int_fast8_t i=0; i < 5; ++i) + { + // auto bullet = new KingFossil::Bullet(*this, _sprite, goblib::math::deg2rad(180.0f - 45.0f + i * 22.5f)); + auto bullet = _kf._bullets.construct(*this, _sprite, goblib::math::deg2rad(180.0f - 45.0f + i * 22.5f),_kf); + assert(bullet); + TinyDarius::instance().reserveInsertNode(bullet, this); + } + } + + private: + KingFossil& _kf; +}; + + +// +const char KingFossil::_name[] = "KING FOSSIL"; + +KingFossil::KingFossil() : Boss(PTS_BOSS_BASE * 1), _bullets(BULLET_MAX) + +{ + _bodyRect = BODY_RECT; + _hp = HP_BODY; + + _behavior = new FlutteringBehavior(*this, APPEAR_TO, 32); +} + +KingFossil::~KingFossil() +{ + // TD_PRINT_FUNCTION(); +} + +bool KingFossil::onInitialize() +{ + // *1 Wait until all children are initialized. + if(_sprite->getBuffer()) + { + return std::all_of(_partials.begin(), _partials.end(), [](const Partial* p) { return p->isExecute(); }) + && std::all_of(_hits.begin(), _hits.end(), [](const Hit* p) { return p->isExecute(); }); + } + + if(createFromBitmap(*_sprite, BITMAP_PATH)) + { + _partials.emplace_back(new Head(this)); + _partials.emplace_back(new PelvicFin(this)); + _partials.emplace_back(new DorsaFin(this)); + _partials.emplace_back(new AnalFin(this)); + _partials.emplace_back(new AdiposeFin(this)); + _partials.emplace_back(new Tail(this)); + _partials.emplace_back(new Launcher(this)); + + TinyDarius& app = TinyDarius::instance(); + for(auto& e : _partials) + { + assert(e); + app.reserveInsertNode(e, this); + } + + std::for_each(std::begin(HBOX_BODY), std::end(HBOX_BODY), [this](const HitBox& h) + { + auto hb = new Boss::Hit(*this, h); + assert(hb); + this->_hits.push_back(hb); + TinyDarius::instance().reserveInsertNode(hb, this); + }); + + Boss::onInitialize(); + + applyPaletteColors(*_sprite, GRAY_SCALE, goblib::size(GRAY_SCALE)); + _apalette->from(*_sprite); + _sprite->makePaletteTable256(); + return false; // see also *1 + } + + kill(); + return false; +} + +bool KingFossil::onRelease() +{ + return Boss::onRelease(); +} + +void KingFossil::onExecute(const float delta) +{ + Boss::onExecute(delta); +} + +void KingFossil::render(void* arg) +{ + Boss::render(arg); +} + +void KingFossil::appear() +{ + Boss::_appear(GRAY_SCALE, goblib::size(GRAY_SCALE), APPEAR_TO, APPEAR_TIMES); +} + +void KingFossil::defeat() +{ + Boss::_defeat(GRAY_SCALE, goblib::size(GRAY_SCALE), EXPLOSION_TABLE, goblib::size(EXPLOSION_TABLE)); +} + +void KingFossil::escape() +{ + Boss::_escape(GRAY_SCALE, goblib::size(GRAY_SCALE)); +} diff --git a/src/boss/king_fossil.hpp b/src/boss/king_fossil.hpp new file mode 100644 index 0000000..c4bce28 --- /dev/null +++ b/src/boss/king_fossil.hpp @@ -0,0 +1,70 @@ +/*! + TinyDarius + + @file king_fossil.hpp + @brief Coelacanth +*/ +#pragma once +#ifndef TD_KING_FOSSIL_HPP +#define TD_KING_FOSSIL_HPP + +#include "boss.hpp" +#include + +namespace goblib { namespace lgfx { class GCellSprite4; }} + + +class KingFossil : public Boss +{ + public: + class Head; + class PelvicFin; + class DorsaFin; + class AnalFin; + class AdiposeFin; + class Tail; + class Launcher; + + class Bullet : public Boss::Bullet + { + constexpr static float VELOCITY = 6.0f; + + public: + Bullet(GameObj& parent, goblib::lgfx::GCellSprite4& sprite, const float radian, KingFossil& kf); + ~Bullet(); + + protected: + virtual void onUnchain() override; + virtual void onExecute(const float delta) override; + + private: + KingFossil& _kf; + }; + + KingFossil(); + virtual ~KingFossil(); + + virtual const char* name() const { return _name; } + + virtual void render(void* arg) override; + + protected: + virtual bool onInitialize() override; + virtual bool onRelease() override; + //virtual void onReceive (const Task::Message& /*msg*/) override {} + virtual void onExecute(const float delta) override; + + virtual void appear() override; + virtual void escape() override; + virtual void defeat() override; + + private: + + goblib::ObjectPool _bullets; + + constexpr static std::size_t BULLET_MAX = 15; // 5way * 3times + static const char _name[]; + // constexpr static std::int32_t _width = 200; + // constexpr static std::int32_t _height = 80; +}; +#endif diff --git a/src/constants.hpp b/src/constants.hpp new file mode 100644 index 0000000..f410f17 --- /dev/null +++ b/src/constants.hpp @@ -0,0 +1,149 @@ +/*! + TinyDarius + + @file constants.hpp + @brief Constants definition +*/ +#ifndef TD_CONSTANTS_HPP +#define TD_CONSTANTS_HPP + +#include +#include "typedef.hpp" + +// Screen width/height +constexpr std::int32_t SCREEN_WIDTH = 320; +constexpr std::int32_t SCREEN_HEIGHT = 240; + +// Palette definition using RGB888. +constexpr std::uint32_t CLR_BLACK = 0x00000000U; +constexpr std::uint32_t CLR_WHITE = 0x00FFFFFFU; +constexpr std::uint32_t CLR_GRAY = 0x00808080U; +constexpr std::uint32_t CLR_RED = 0x00FF0000U; +constexpr std::uint32_t CLR_YELLOW = 0x00FFFF00U; +constexpr std::uint32_t CLR_MAGENTA = 0x00FF00FFU; +constexpr std::uint32_t CLR_GREEN = 0x0000FF00U; + +// Transpalent palette index +constexpr std::int32_t TRANPARENT_PALETTE_INDEX = 0; + +// Task priority +constexpr std::uint32_t PRIORITY_DEBUG = 0; +constexpr std::uint32_t PRIORITY_SCENE_MAANGER = 0; +constexpr std::uint32_t PRIORITY_ADVERTISE = 100; +constexpr std::uint32_t PRIORITY_GAME = 100; +constexpr std::uint32_t PRIORITY_STAGE = 100; +constexpr std::uint32_t PRIORITY_WAVESPOT = 200; +constexpr std::uint32_t PRIORITY_ROCKSURFACE = 210; +constexpr std::uint32_t PRIORITY_BRANCHROCK = 210; +constexpr std::uint32_t PRIORITY_SILVERHAWK = 1000; +constexpr std::uint32_t PRIORITY_BULLET = 1001; +constexpr std::uint32_t PRIORITY_BOSS = 1100; +constexpr std::uint32_t PRIORITY_BOSS_BULLET = 1110; +constexpr std::uint32_t PRIORITY_ENEMY = 1200; +constexpr std::uint32_t PRIORITY_ENEMY_BULLET = 1210; +constexpr std::uint32_t PRIORITY_EXPLOSION = 2000; +constexpr std::uint32_t PRIORITY_MESSAGE = 2100; +constexpr std::uint32_t PRIORITY_INFORMATION = 2200; +constexpr std::uint32_t PRIORITY_MASK = 2300; + +// Task category(for Hitcheck) +constexpr std::uint32_t CATEGORY_BIT_BULLET = 0X80000000; +constexpr std::uint32_t CATEGORY_BIT_BOSS = 0X40000000; +constexpr std::uint32_t CATEGORY_BIT_ENEMY = 0X20000000; +constexpr std::uint32_t CATEGORY_BIT_OBSTACLE= 0X10000000; +constexpr std::uint32_t CATEGORY_BIT_PLAYER = 0X08000000; +// +constexpr std::uint32_t CATEGORY_NONE = 0X00000000; +constexpr std::uint32_t CATEGORY_PLAYER = CATEGORY_BIT_PLAYER;; +constexpr std::uint32_t CATEGORY_BULLET = CATEGORY_BIT_PLAYER | CATEGORY_BIT_BULLET; +constexpr std::uint32_t CATEGORY_BOSS = CATEGORY_BIT_BOSS; +constexpr std::uint32_t CATEGORY_ENEMY = CATEGORY_BIT_ENEMY; +constexpr std::uint32_t CATEGORY_ENEMY_BULLET = CATEGORY_BIT_ENEMY | CATEGORY_BIT_BULLET; +constexpr std::uint32_t CATEGORY_WALL = CATEGORY_BIT_OBSTACLE; + + +// Rendering order (like the Z length) +constexpr std::uint32_t ORDER_DEBUG = 0; +constexpr std::uint32_t ORDER_ADVERTISEE = 1000; +constexpr std::uint32_t ORDER_MESSAGE = 40; +constexpr std::uint32_t ORDER_INFORMATION = 50; +constexpr std::uint32_t ORDER_MASK = 75;; +constexpr std::uint32_t ORDER_EXPLOSION = 100; +constexpr std::uint32_t ORDER_ROCKSURFACE = 200; +constexpr std::uint32_t ORDER_BRANCHROCK = 200; +constexpr std::uint32_t ORDER_EXPLOSION_BOSS = 250; +constexpr std::uint32_t ORDER_EXPLOSION_ENEMY = 260; +constexpr std::uint32_t ORDER_BOSS_BULLET = 500; +constexpr std::uint32_t ORDER_ENEMY = 510; +constexpr std::uint32_t ORDER_ENEMY_BULLET = 520; +constexpr std::uint32_t ORDER_BULLET = 900; +constexpr std::uint32_t ORDER_SILVERHAWK = 1000; +constexpr std::uint32_t ORDER_BOSS_BODY = 2000; +constexpr std::uint32_t ORDER_WAVESPOT = 5000; + +// Clipping rect +constexpr Rect2 SCREEN_RECT = { 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT }; +constexpr Rect2 FIELD_RECT = { 0, 24, SCREEN_WIDTH, SCREEN_HEIGHT - 24*2 }; + +// Task message +constexpr std::uint32_t MSG_DEFEAT_BOSS = 52; +constexpr std::uint32_t MSG_VANISH_ENEMY = 53; +constexpr std::uint32_t MSG_HIDE_EFFECT = 54; +constexpr std::uint32_t MSG_ROUND_CLEAR = 55; +constexpr std::uint32_t MSG_GAME_OVER = 56; + +// Scene ID +constexpr std::uint32_t SCENE_DEBUG = 1; +constexpr std::uint32_t SCENE_DEBUG_SOUND = 2; +constexpr std::uint32_t SCENE_ADVERTISE = 10; +constexpr std::uint32_t SCENE_GAME = 100; +constexpr std::uint32_t SCENE_STAGE = 101; + +// BGM ID (Streaming by SD file) +enum BGM : std::uint8_t +{ + InsertCoin, + InsertCoinB, + // for stage + CaptainNeo, + ChaosMainTheme, + CosmicAirWay, + InorganicBeat, + TheSea, + // for boss + Warning, + BOSS_1, + BOSS_2, + BOSS_3, + BOSS_4, + BOSS_5, + BOSS_6, + BOSS_7, + RoundClear, + // for gameover + Ending, + Name, + Gameover, + Max +}; + +// SFX ID +enum class SFX : std::uint8_t +{ + InsertCoin, + InsertCoinB, + Shhot, + Explosion, + Max +}; + + +// Score +constexpr std::uint32_t PTS_REMAIN_BONUS = 100000; +constexpr std::uint32_t PTS_TIME_BONUS = 100; +constexpr std::uint32_t PTS_MINE = 100; +constexpr std::uint32_t PTS_YAZUKA = 200; +constexpr std::uint32_t PTS_BOSS_PARTIAL = 5000; +constexpr std::uint32_t PTS_BOSS_BASE = 10000; + +#endif diff --git a/src/debug.hpp b/src/debug.hpp new file mode 100644 index 0000000..076f05a --- /dev/null +++ b/src/debug.hpp @@ -0,0 +1,40 @@ +/*! + TinyDarius + + @file debug.hpp + @brief For debug +*/ +#pragma once +#ifndef TD_DEBUG_HPP +#define TD_DEBUG_HPP + +#if !defined(NDEBUG) || defined(DOXYGEN_PROCESS) + +#include +#include +#include + + +#define TD_PRINT_FUNCTION() do { printf("%s\n", __PRETTY_FUNCTION__); }while(0) +#define TD_PRINTF(fmt, ...) do { printf((fmt), __VA_ARGS__); }while(0) + +#else + +#define TD_PRINT_FUNCTION() /* nop */ +#define TD_PRINTF(fmt, ...) /* nop */ + +#endif + + +#if defined(GOBLIB_ENABLE_PROFILE) +using EspInstrument = goblib::profile::MeasuringInstrument; +#define TD_SCOPED_PROFILE(tag) EspInstrument GOBLIB_CONCAT(pf_,__LINE__)((#tag)) + +#else + +#define TD_SCOPED_PROFILE(tag) /* nop */ + +#endif + +// +#endif diff --git a/src/df88.hpp b/src/df88.hpp new file mode 100644 index 0000000..d53af8e --- /dev/null +++ b/src/df88.hpp @@ -0,0 +1,13 @@ +/*! + TinyDarius + + @file df88.hpp + @brief 8x8 Font +*/ +#ifdef LGFX_USE_V1 +#include +#else +#include +#endif + +extern const ::lgfx::GFXfont df88_gfx_font; diff --git a/src/effect/block_mask.cpp b/src/effect/block_mask.cpp new file mode 100644 index 0000000..d59d523 --- /dev/null +++ b/src/effect/block_mask.cpp @@ -0,0 +1,112 @@ +/*! + TinyDarius + + @file block_mask.cpp + @brief Mask by block +*/ +#include + +#include "block_mask.hpp" +#include "../debug.hpp" +#include "../renderer.hpp" +#include "../constants.hpp" +#include +using goblib::lgfx::GSprite; + +namespace +{ +/* fill order + 00,01,02,03 + 11,12,13,04 + 10,15,14,05 + 09,08,07,06 +*/ +constexpr Pos2i MASK_TABLE[16] = +{ + Pos2i(BlockMask::INNER_BLOCK_WIDTH * 0, BlockMask::INNER_BLOCK_HEIGHT * 0), + Pos2i(BlockMask::INNER_BLOCK_WIDTH * 1, BlockMask::INNER_BLOCK_HEIGHT * 0), + Pos2i(BlockMask::INNER_BLOCK_WIDTH * 2, BlockMask::INNER_BLOCK_HEIGHT * 0), + Pos2i(BlockMask::INNER_BLOCK_WIDTH * 3, BlockMask::INNER_BLOCK_HEIGHT * 0), + // + Pos2i(BlockMask::INNER_BLOCK_WIDTH * 3, BlockMask::INNER_BLOCK_HEIGHT * 1), + Pos2i(BlockMask::INNER_BLOCK_WIDTH * 3, BlockMask::INNER_BLOCK_HEIGHT * 2), + Pos2i(BlockMask::INNER_BLOCK_WIDTH * 3, BlockMask::INNER_BLOCK_HEIGHT * 3), + // + Pos2i(BlockMask::INNER_BLOCK_WIDTH * 2, BlockMask::INNER_BLOCK_HEIGHT * 3), + Pos2i(BlockMask::INNER_BLOCK_WIDTH * 1, BlockMask::INNER_BLOCK_HEIGHT * 3), + Pos2i(BlockMask::INNER_BLOCK_WIDTH * 0, BlockMask::INNER_BLOCK_HEIGHT * 3), + // + Pos2i(BlockMask::INNER_BLOCK_WIDTH * 0, BlockMask::INNER_BLOCK_HEIGHT * 2), + Pos2i(BlockMask::INNER_BLOCK_WIDTH * 0, BlockMask::INNER_BLOCK_HEIGHT * 1), + // + Pos2i(BlockMask::INNER_BLOCK_WIDTH * 1, BlockMask::INNER_BLOCK_HEIGHT * 1), + Pos2i(BlockMask::INNER_BLOCK_WIDTH * 2, BlockMask::INNER_BLOCK_HEIGHT * 1), + // + Pos2i(BlockMask::INNER_BLOCK_WIDTH * 2, BlockMask::INNER_BLOCK_HEIGHT * 2), + // + Pos2i(BlockMask::INNER_BLOCK_WIDTH * 1, BlockMask::INNER_BLOCK_HEIGHT * 2), +}; + +// If the bit is standing, draw inner block. +constexpr std::uint32_t STEP_BIT[16] = +{ + 0xFFFF, 0xFFFE, 0xFFFC,0xFFF8, + 0xFFF0, 0xFFE0, 0xFFC0,0xFF80, + 0xFF00, 0xFE00, 0xFC00,0xF800, + 0xF000, 0xE000, 0xC000,0x8000, +}; + + +} +BlockMask::BlockMask() + : GameObj(PRIORITY_MASK, ORDER_MASK, CATEGORY_NONE, "blockmask") +{ +} + +BlockMask::~BlockMask() +{ + // TD_PRINT_FUNCTION(); +} + +void BlockMask::onUnchain() +{ + // TD_PRINT_FUNCTION(); + GameObj::onUnchain(); + delete this; +} + +void BlockMask::onExecute(const float delta) +{ + if(!busy()) { release(); } + GameObj::onExecute(delta); +} + +void BlockMask::render(void* arg) +{ + RenderArg* rarg = static_cast(arg); + GSprite* target = rarg->sprite; + ScopedClip(*target, FIELD_RECT, rarg->yorigin); + + for(std::int32_t y = 0; y < FIELD_RECT.height(); y += BLOCK_HEIGHT) + { + for(std::int32_t x = 0; x < FIELD_RECT.width(); x += BLOCK_WIDTH) + { + fill(target, x, y + rarg->yorigin, step()); + } + } +} + +void BlockMask::fill(GSprite* sprite, const std::int32_t x, const std::int32_t y, const std::uint32_t step) +{ + if(step >=16) { return; } + + std::uint32_t bit = STEP_BIT[step]; + + for(std::int_fast8_t i = 0; i < 16; ++i) + { + if(bit & (1UL << i)) + { + sprite->fillRect(x + MASK_TABLE[i].x(), y + MASK_TABLE[i].y(), INNER_BLOCK_WIDTH, INNER_BLOCK_HEIGHT, CLR_BLACK); + } + } +} diff --git a/src/effect/block_mask.hpp b/src/effect/block_mask.hpp new file mode 100644 index 0000000..f8130d6 --- /dev/null +++ b/src/effect/block_mask.hpp @@ -0,0 +1,39 @@ +/*! + TinyDarius + + @file block_mask.hpp + @brief Mask by block +*/ +#pragma once +#ifndef TD_BLOCK_MASK_HPP +#define TD_BLOCK_MASK_HPP + +#include +#include "../game_obj.hpp" +#include + +/*! @brief Masking by spiral blocks */ +class BlockMask : public GameObj +{ + public: + constexpr static std::int32_t BLOCK_WIDTH = 16; + constexpr static std::int32_t BLOCK_HEIGHT = 16; + constexpr static std::int32_t INNER_BLOCK_WIDTH = 4; + constexpr static std::int32_t INNER_BLOCK_HEIGHT = 4; + + BlockMask(); + virtual~BlockMask(); + virtual void render(void* arg) override; + + GOBLIB_INLINE bool busy() const { return step() < 16; } + + protected: + virtual void onUnchain() override; + virtual void onExecute(const float delta); + + + private: + GOBLIB_INLINE std::uint32_t step() const { return counter() >> 1; } + void fill(goblib::lgfx::GSprite* sprite, const std::int32_t x, const std::int32_t y, const std::uint32_t step); +}; +#endif diff --git a/src/effect/explosion.cpp b/src/effect/explosion.cpp new file mode 100644 index 0000000..33274d8 --- /dev/null +++ b/src/effect/explosion.cpp @@ -0,0 +1,200 @@ +/*! + TinyDarius + + @file explosion.cpp + @brief Explosion +*/ +#include + +#include "explosion.hpp" +#include "../debug.hpp" +#include "../app.hpp" +#include "../renderer.hpp" +#include "../utility.hpp" +#include "../constants.hpp" + +#include +#include +using goblib::lgfx::GSprite; +using goblib::lgfx::GCellSprite4; +#include +#include +using goblib::suffix::operator"" _u8; + +#include +#include +#include + +using goblib::lgfx::CellRect; +using Seq = goblib::graph::Sequence; + +namespace +{ +constexpr static char BITMAP_PATH[] = "/res/td/bomb.bmp"; +} + +const PartialAnimation Explosion::_animations[Explosion::Type::Max] = +{ + // type 0 + { + 0,0, + { + CellRect( 0, 0, 32, 32), + CellRect( 32, 0, 32, 32), + CellRect( 64, 0, 32, 32), + CellRect( 96, 0, 32, 32), + CellRect(128, 0, 32, 32), + }, + { + Seq(Seq::Draw, 0, 4_u8), + Seq(Seq::Draw, 1, 4_u8), + Seq(Seq::Draw, 2, 6_u8), + Seq(Seq::Draw, 3, 6_u8), + Seq(Seq::Draw, 4, 4_u8), + } + }, + // type 1 + { + 0,0, + { + CellRect( 0, 32, 32, 32), + CellRect( 32, 32, 32, 32), + CellRect( 64, 32, 32, 32), + }, + { + Seq(Seq::Draw, 0, 4_u8), + Seq(Seq::Draw, 1, 6_u8), + Seq(Seq::Draw, 2, 4_u8), + } + }, + // type 2 + { + 0,0, + { + CellRect( 96, 32, 32, 32), + CellRect(128, 32, 32, 32), + CellRect(160, 32, 32, 32), + CellRect(192, 32, 32, 32), + CellRect(224, 32, 32, 32), + }, + { + Seq(Seq::Draw, 0, 4_u8), + Seq(Seq::Draw, 1, 4_u8), + Seq(Seq::Draw, 2, 6_u8), + Seq(Seq::Draw, 3, 6_u8), + Seq(Seq::Draw, 4, 4_u8), + } + } +}; + + +goblib::lgfx::GCellSprite4* Explosion::_sprite; + + +void Explosion::setup() +{ + ScopedReleaseBus(); + + if(_sprite) { return; } + + _sprite = new goblib::lgfx::GCellSprite4(); + assert(_sprite); + if(!createFromBitmap(*_sprite, BITMAP_PATH)) { assert(0); } + _sprite->makePaletteTable256(); +} + +void Explosion::finalize() +{ + goblib::safeDelete(_sprite); + +} + +Explosion::Explosion(const Pos2::pos_type cx, const Pos2::pos_type cy, std::uint32_t order, Type t, GameObj* follow, std::int32_t apos, bool repeat) + : GameObj(PRIORITY_EXPLOSION, order, CATEGORY_NONE, "explosion") + , _cpos(cx,cy) + , _animation() + , _follow(follow) + , _repeat(repeat) +{ + std::mt19937 re(esp_random()); // esp_random() is hardware RNG. + if(t == Type::Random) + { + std::uniform_int_distribution<> dist(0, goblib::to_underlying(Type::Max)-1); + t = static_cast(dist(re)); + } + assert(t >=0 ); + assert(t < Type::Max); + t = static_cast(goblib::clamp(goblib::to_underlying(t), goblib::to_underlying(Type::Type_0), goblib::to_underlying(Type::Type_2))); + _animation = _animations[t]; + + auto left = cx + (_follow ? _follow->x() : Pos2::pos_type(0)) - WIDTH/2; + auto top = cy + (_follow ? _follow->y() : Pos2::pos_type(0)) - HEIGHT/2; + move(left, top); + + if(apos < 0) + { + std::uniform_int_distribution<> dist(0U, _animation.sequencer.stepSize() -1); + apos = dist(re); + } + while(apos-- > 0) { _animation.pump(); } + +} + +Explosion:: ~Explosion() +{ + // TD_PRINT_FUNCTION(); +} + +void Explosion::onUnchain() +{ + // TD_PRINT_FUNCTION(); + GameObj::onUnchain(); + delete this; +} + +bool Explosion::onInitialize() +{ + return GameObj::onInitialize(); +} + +bool Explosion::onRelease() +{ + return GameObj::onRelease(); +} + +void Explosion::onReceive(const goblib::TaskMessage& msg) +{ + if(msg.msg == MSG_HIDE_EFFECT) + { + release(); + } +} + +void Explosion::onExecute(const float delta) +{ + if(_follow) + { + move(_cpos.x() + _follow->x() - WIDTH/2, _cpos.y() + _follow->y() - HEIGHT/2); + } + + _animation.pump(); + + if(_animation.isFinish()) + { + if(_repeat) + { + _animation.reset(); + } + else + { + release(); + } + } +} + +void Explosion::render(void* arg) +{ + RenderArg* rarg = static_cast(arg); + GSprite* target = rarg->sprite; + _sprite->pushCellTo16(target, _animation.rect(), static_cast(x()), static_cast(y()) + rarg->yorigin, 0); +} diff --git a/src/effect/explosion.hpp b/src/effect/explosion.hpp new file mode 100644 index 0000000..bdb2620 --- /dev/null +++ b/src/effect/explosion.hpp @@ -0,0 +1,72 @@ +/*! + TinyDarius + + @file explosion.hpp + @brief Explosion +*/ +#pragma once +#ifndef TD_EXPLOSION_HPP +#define TD_EXPLOSION_HPP + +#include "../game_obj.hpp" +#include "../typedef.hpp" +#include "../partial_animation.hpp" +#include "../constants.hpp" +#include + +namespace goblib { namespace lgfx { +class GCellSprite4; +}} + +class Explosion : public GameObj +{ + public: + enum Type : std::int32_t + { + Random = -1, + Type_0, + Type_1, + Type_2, + Max + }; + + static void setup(); + static void finalize(); + + Explosion(const Pos2& pos, std::uint32_t order, Type t = Type::Random, GameObj* follow = nullptr, std::int32_t apos = 0, bool repeat = false) + : Explosion(pos.x(), pos.y(), order, t, follow, apos, repeat) {} + /* + @param cx Relative center-left from following GameObj (Absolute if follow is nullptr) + @param cy Relative center-top from following GameObj (Absolute if follow is nullptr) + @param order rendering order + @param t Animation type + @param follow GameObj to follow + @param apos Start sequence position for animation(start random position if negative) + @param repeat Playing animation loop inifinty? + */ + Explosion(const Pos2::pos_type cx, const Pos2::pos_type cy, std::uint32_t order, Type t = Type::Random, GameObj* follow = nullptr, std::int32_t apos = 0, bool repeat = false); + virtual ~Explosion(); + + virtual void render(void* arg) override; + + protected: + virtual void onUnchain() override; + virtual bool onInitialize() override; + virtual bool onRelease() override; + virtual void onReceive (const goblib::TaskMessage& msg) override; + virtual void onExecute(const float delta) override; + + static goblib::lgfx::GCellSprite4* _sprite;; + static const PartialAnimation _animations[Type::Max]; + + private: + const Pos2 _cpos; + PartialAnimation _animation; + const GameObj* _follow; + const bool _repeat; + + constexpr static std::int32_t WIDTH = 32; + constexpr static std::int32_t HEIGHT = 32; +}; + +#endif diff --git a/src/enemy/enemy.cpp b/src/enemy/enemy.cpp new file mode 100644 index 0000000..6656f9e --- /dev/null +++ b/src/enemy/enemy.cpp @@ -0,0 +1,437 @@ +/*! + TinyDarius + + @file enemy.cpp + @brief Enemies and effect +*/ +#include + +#include "enemy.hpp" +#include "../debug.hpp" +#include "../app.hpp" +#include "../game.hpp" +#include "../renderer.hpp" +#include "../utility.hpp" +#include "../constants.hpp" +#include "../behavior.hpp" + +#include +using goblib::lgfx::GSprite; +using goblib::lgfx::GCellSprite4; +#include +using goblib::suffix::operator"" _u8; +#include +#include + +#include +#include +#include + +using goblib::lgfx::CellRect; +using Seq = goblib::graph::Sequence; + +namespace +{ +constexpr char BITMAP_PATH[] = "/res/td/enemy.bmp"; + +constexpr HitBox HBOX_MINE = { Pos2(2,2), Rect2(0, 0, 16-2*2, 16-2*2) }; +constexpr HitBox HBOX_YAZUKA = { Pos2(2,2), Rect2(0, 0, 16-2*2, 16-2*2) }; +} + +goblib::lgfx::GCellSprite4* Enemy::_sprite; + +const PartialAnimation Enemy::_animation_table[] = +{ + { // Mine + 0,0, + { + CellRect( 24, 32, 16, 16), + CellRect( 40, 32, 16, 16), + CellRect( 56, 32, 16, 16), + CellRect( 72, 32, 16, 16), + }, + { + Seq(Seq::Draw, 0, 4_u8), + Seq(Seq::Draw, 1, 4_u8), + Seq(Seq::Draw, 2, 4_u8), + Seq(Seq::Draw, 3, 8_u8), + Seq(Seq::Draw, 2, 4_u8), + Seq(Seq::Draw, 1, 4_u8), + Seq(Seq::Goto, 0_u8), + }, + }, + { // Yazuka(CW) + 0,0, + { + CellRect( 0, 16, 16, 16), + CellRect( 16, 16, 16, 16), + CellRect( 32, 16, 16, 16), + CellRect( 48, 16, 16, 16), + CellRect( 64, 16, 16, 16), + CellRect( 80, 16, 16, 16), + }, + { + Seq(Seq::Draw, 0, 4_u8), + Seq(Seq::Draw, 1, 4_u8), + Seq(Seq::Draw, 2, 4_u8), + Seq(Seq::Draw, 3, 4_u8), + Seq(Seq::Draw, 4, 4_u8), + Seq(Seq::Draw, 5, 4_u8), + Seq(Seq::Goto, 0_u8), + } + }, + { // Yazuka(CCW) + 0,0, + { + CellRect( 0, 16, 16, 16), + CellRect( 16, 16, 16, 16), + CellRect( 32, 16, 16, 16), + CellRect( 48, 16, 16, 16), + CellRect( 64, 16, 16, 16), + CellRect( 80, 16, 16, 16), + }, + { + Seq(Seq::Draw, 0, 4_u8), + Seq(Seq::Draw, 5, 4_u8), + Seq(Seq::Draw, 4, 4_u8), + Seq(Seq::Draw, 3, 4_u8), + Seq(Seq::Draw, 2, 4_u8), + Seq(Seq::Draw, 1, 4_u8), + Seq(Seq::Goto, 0_u8), + } + }, + { // Crash + 0,0, + { + CellRect( 32, 0, 16, 16), + CellRect( 48, 0, 16, 16), + CellRect( 64, 0, 16, 16), + CellRect( 80, 0, 16, 16), + }, + { + Seq(Seq::Draw, 0, 2_u8), + Seq(Seq::Draw, 1, 2_u8), + Seq(Seq::Draw, 2, 3_u8), + Seq(Seq::Draw, 3, 4_u8), + } + }, + { // Reflection + 0,0, + { + CellRect( 0, 0, 16, 16), + CellRect( 16, 0, 16, 16), + }, + { + Seq(Seq::Draw, 0, 1_u8), + Seq(Seq::Draw, 1, 4_u8), + } + } +}; + +void Enemy::setup() +{ + ScopedReleaseBus(); + if(_sprite) { return; } + + _sprite = new goblib::lgfx::GCellSprite4(); + assert(_sprite); + if(!createFromBitmap(*_sprite, BITMAP_PATH)) { assert(0); } + _sprite->makePaletteTable256(); +} + +void Enemy::finalize() +{ + goblib::safeDelete(_sprite); +} + +Enemy::Enemy(const Pos2::pos_type cx, const Pos2::pos_type cy, const PartialAnimation& animation, + const std::uint32_t priority, const std::uint32_t order, const std::uint32_t category, const std::uint32_t score, const char* tag) + : GameObj(priority, order, category, tag) + , _animation(animation) + , _behavior(nullptr) + , _score(score) + , _alive(true) +{ + move(cx, cy); + _score.insertObserver(TinyDarius::instance().game()); +} + +Enemy::~Enemy() +{ + // TD_PRINT_FUNCTION(); + goblib::safeDelete(_behavior); +} + +void Enemy::onUnchain() +{ + // TD_PRINT_FUNCTION(); + GameObj::onUnchain(); + delete this; +} + +bool Enemy::onInitialize() +{ + return GameObj::onInitialize(); +} + +bool Enemy::onRelease() +{ + return GameObj::onRelease(); +} + +void Enemy::onReceive (const goblib::TaskMessage& msg) +{ + if(msg.msg == MSG_VANISH_ENEMY) + { + defeat(); + } +} + +void Enemy::onExecute(const float delta) +{ + if(_behavior) { _behavior->pump(delta); } + _animation.pump(); + if(_animation.isFinish()) { release(); } + GameObj::onExecute(delta); +} + +void Enemy::render(void* arg) +{ + RenderArg* rarg = static_cast(arg); + GSprite* target = rarg->sprite; + + _sprite->pushCellTo16(target, _animation.rect(), static_cast(x()), static_cast(y()) + rarg->yorigin, 0); + GameObj::render(arg); +} + +void Enemy::onHit(GameObj* o, const Rect2& r) +{ + if(o->category() == CATEGORY_BULLET) + { + _alive = false; + _score.notify(); + defeat(); + } +} + +void Enemy::defeat() +{ + disableHit(); + hide(); + + auto crash = new ::Crash(*this); + assert(crash); + TinyDarius::instance().reserveInsertNode(crash, this); +} + + +// +Crash::Crash(const Pos2::pos_type cx, const Pos2::pos_type cy) + : Enemy(cx,cy, _animation_table[Type::Crash], PRIORITY_EXPLOSION, ORDER_EXPLOSION_ENEMY, CATEGORY_NONE, 0, "crash") + , _attach(nullptr) +{ + move(cx - WIDTH/2, cy - HEIGHT/2); +} + +Crash::Crash(GameObj& attach) + : Enemy(attach.x(), attach.y(), _animation_table[Type::Crash], PRIORITY_EXPLOSION, ORDER_EXPLOSION_ENEMY, CATEGORY_NONE, 0, "crash") + , _attach(&attach) +{ +} + +Crash::~Crash() +{ + // TD_PRINT_FUNCTION(); +} + +void Crash::onUnchain() +{ + // TD_PRINT_FUNCTION(); + GameObj::onUnchain(); + delete this; +} + +void Crash::onExecute(const float delta) +{ + Enemy::onExecute(delta); + if(_attach) + { + move(_attach->x(), _attach->y()); + if(isRelease() || isKill()) + { + _attach->release(); + } + } +} + +// +Reflection::Reflection(const Pos2::pos_type cx, const Pos2::pos_type cy) + : Enemy(cx,cy, _animation_table[Type::Reflection], PRIORITY_EXPLOSION, ORDER_EXPLOSION, CATEGORY_NONE, 0, "reflection") +{ + move(cx - WIDTH/2, cy - HEIGHT/2); +} + +Reflection::~Reflection() +{ + // TD_PRINT_FUNCTION(); +} + +void Reflection::onUnchain() +{ + // TD_PRINT_FUNCTION(); + GameObj::onUnchain(); + delete this; +} + +// +Mine::Mine(MineGenerator& generator, const Pos2& cpos, const std::int32_t split) + : Enemy(cpos, _animation_table[Type::Mine], PRIORITY_ENEMY, ORDER_ENEMY, CATEGORY_ENEMY, PTS_MINE, "mine") + , _generator(generator) + , _split(split) +{ + _hbox = HBOX_MINE; + + auto range = 5 - goblib::clamp(split, 0, 4); // 1~5 + + std::mt19937 gen(esp_random()); + std::uniform_real_distribution<> aa(range * 0.2f, range * 1.2f); + std::uniform_real_distribution<> angle(0.25f, 1.0f); + std::uniform_int_distribution<> rot(0,31); + + auto a = fx16(aa(gen)); + auto b = fx16(aa(gen)); + auto av = fx16(angle(gen)); + if(gen()&1) { av = -av; } + auto sa = rot(gen); + auto erot = rot(gen); + + _behavior = new MineBehavior(*this, a, b, sa, av, VecFx16(fx16(-1.0f/4.0f), fx16()), erot); + assert(_behavior); +} + +Mine::~Mine() +{ + // TD_PRINT_FUNCTION(); +} + +void Mine::onUnchain() +{ + // TD_PRINT_FUNCTION(); + // Don't delete, return to object pool. + _generator._pool.destruct(this); + GameObj::onUnchain(); +} + +void Mine::onHit(GameObj* o, const Rect2& r) +{ + Enemy::onHit(o, r); + if(_split <= 0) { return; } + split(); + split(); +} + +void Mine::onExecute(const float delta) +{ + Enemy::onExecute(delta); + if(isExecute() && _split > 0 && counter() >= SPLIT_TIME) + { + split(); + split(); + _split = 0; + } +} + +void Mine::split() +{ + if(_split > 0) + { + std::mt19937 gen(esp_random()); + std::uniform_int_distribution<> dist(-8, 8); + _generator.spawn(Pos2(x() + dist(gen), y() + dist(gen)), _split - 1); + } +} + +void MineGenerator::spawn() +{ + spawn(Pos2(SCREEN_WIDTH, SCREEN_HEIGHT/2), 4); +} + +void MineGenerator::spawn(const Pos2& cpos, const std::int32_t split) +{ + auto p = _pool.construct(*this, cpos, split); + if(p){ TinyDarius::instance().reserveInsertNode(p, &_belongto); } +} + + +Yazuka::Yazuka(YazukaGenerator& generator, const Pos2& cpos, bool cw, + fx16 a, fx16 b, Tangle start, fx16 av, VecFx16 ov, Tangle rotate, std::int32_t wait) + : Enemy(cpos, _animation_table[cw ? Type::YazukaCW : Type::YazukaCCW], PRIORITY_ENEMY, ORDER_ENEMY, CATEGORY_ENEMY, PTS_YAZUKA, (cw ? "yazukaCW" : "yazukaCCW") ) + , _generator(generator) + , _wait(wait) +{ + _hbox = HBOX_YAZUKA; + _behavior = new OrbitalBehavior(*this, a, b, start, av, ov, rotate); + assert(_behavior); + show(wait <= 0); +} + +Yazuka::~Yazuka() +{ + // TD_PRINT_FUNCTION(); +} + +void Yazuka::onUnchain() +{ + // TD_PRINT_FUNCTION(); + // Don't delete, return to object pool. + _generator._pool.destruct(this); + GameObj::onUnchain(); +} + +void Yazuka::onExecute(const float delta) +{ + show(_alive && _wait <= 0); + + if(_wait <= 0) + { + Enemy::onExecute(delta); + if(x() < -_animation.rect().width() ) { release(); return; } + } + --_wait; +} + +// +#ifdef DEBUG +const std::uint32_t YazukaGenerator::MEMBER_MAX = 6; +#endif + +void YazukaGenerator::spawn() +{ + std::mt19937 gen(esp_random()); + std::uniform_int_distribution<> du(0,1); + bool upper = du(gen); + + std::uniform_int_distribution<> upperY(FIELD_RECT.top(), FIELD_RECT.top() + 16); + std::uniform_int_distribution<> lowerY(FIELD_RECT.bottom() -16, FIELD_RECT.bottom()); + std::uniform_int_distribution<>& ypos = upper ? upperY : lowerY; + + auto members = goblib::clamp(3 + _generated++/2, 3U, MEMBER_MAX); + Pos2 spos( FIELD_RECT.right(), ypos(gen)); + + std::uniform_real_distribution<> aa(2.0f, 6.0f); + std::uniform_real_distribution<> bb(0.5f, 3.0f); + std::uniform_int_distribution<> rot_upper(9,15); + std::uniform_int_distribution<> rot_lower(1,7); + std::uniform_int_distribution<>& rot = upper ? rot_upper : rot_lower; + + auto a = fx16(aa(gen)); + auto b = fx16(bb(gen)); + auto av = upper ? fx16(0.5f) : -fx16(0.5f); + auto er = rot(gen); + + for(decltype(members) i=0; i < members; ++i) + { + auto p = _pool.construct(*this, spos, upper, a, b, 0, av, VecFx16(fx16(-1),fx16(0)), er, i * 4); + if(p) {TinyDarius::instance().reserveInsertNode(p, &_belongto); } + } +} diff --git a/src/enemy/enemy.hpp b/src/enemy/enemy.hpp new file mode 100644 index 0000000..c800d03 --- /dev/null +++ b/src/enemy/enemy.hpp @@ -0,0 +1,179 @@ +/*! + TinyDarius + + @file enemy.hpp + @brief Enemies and effect +*/ +#pragma once +#ifndef TD_ENEMY_HPP +#define TD_ENEMY_HPP + +#include "../typedef.hpp" +#include "../math_table.hpp" +#include "../game_obj.hpp" +#include "../partial_animation.hpp" +#include "../score.hpp" +#include +#include +#include + +namespace goblib { namespace lgfx { class GCellSprite4; }} +class Behavior; + +/*! @brief Enemy base */ +class Enemy : public GameObj +{ + public: + enum Type { Mine, YazukaCW, YazukaCCW, Crash, Reflection, Max }; + + Enemy(const Pos2 pos, const PartialAnimation& animation, + const std::uint32_t priority, const std::uint32_t order, const std::uint32_t category, const std::uint32_t score, const char* tag = "enemy") + : Enemy(pos.x(), pos.y(), animation, priority, order, category, score, tag) {} + Enemy(const Pos2::pos_type cx, const Pos2::pos_type cy, const PartialAnimation& animation, + const std::uint32_t priority, const std::uint32_t order, const std::uint32_t category, const std::uint32_t score, const char* tag = "enemy"); + virtual ~Enemy(); + + static void setup(); + static void finalize(); + + virtual void render(void* arg) override; + virtual void onHit(GameObj* o, const Rect2& r) override; + + protected: + virtual void onUnchain() override; + virtual bool onInitialize() override; + virtual bool onRelease() override; + virtual void onReceive (const goblib::TaskMessage& msg) override; + virtual void onExecute(const float delta) override; + + void defeat(); + GOBLIB_INLINE bool alive() const { return _alive; } + + protected: + PartialAnimation _animation; + Behavior* _behavior; + Score _score; + bool _alive; + + static goblib::lgfx::GCellSprite4* _sprite; // shared sprite + static const PartialAnimation _animation_table[Type::Max]; +}; + +/*! Crash effect (Shared enemy sprite) + */ +class Crash : public Enemy +{ + constexpr static std::int16_t WIDTH = 16; + constexpr static std::int16_t HEIGHT = 16; + + public: + Crash(const Pos2& pos) : Crash(pos.x(), pos.y()) {} + Crash(const Pos2::pos_type cx, const Pos2::pos_type cy); + Crash(GameObj& attach); + virtual ~Crash(); + + protected: + virtual void onUnchain() override; + virtual void onExecute(const float delta) override; + + private: + GameObj* _attach; + +}; + +/*! Reflection effect (Shared enemy sprite) + */ +class Reflection : public Enemy +{ + constexpr static std::int16_t WIDTH = 16; + constexpr static std::int16_t HEIGHT = 16; + + public: + Reflection(const Pos2& pos) : Reflection(pos.x(), pos.y()) {} + Reflection(const Pos2::pos_type cx, const Pos2::pos_type cy); + virtual ~Reflection(); + + protected: + virtual void onUnchain() override; +}; + + +class MineGenerator; + +/*! Floating mine */ +class Mine : public Enemy +{ + public: + Mine(MineGenerator& generator, const Pos2& cpos, const std::int32_t split); + virtual ~Mine(); + + virtual void onHit(GameObj* o, const Rect2& r) override; + + protected: + virtual void onUnchain() override; + virtual void onExecute(const float delta) override; + void split(); + + private: + MineGenerator& _generator; + std::int32_t _split; + constexpr static std::uint32_t SPLIT_TIME = 3_fsec; +}; + + +/*! Generator for Mine */ +class MineGenerator +{ + public: + explicit MineGenerator(goblib::Task& t) : _belongto(t), _pool(POOL_MAX) {} + void spawn(); + void spawn(const Pos2& pos, const std::int32_t split); + + private: + friend class Mine; + goblib::Task& _belongto; + goblib::ObjectPool _pool; + constexpr static std::size_t POOL_MAX = 24; +}; + +class YazukaGenerator; + +/*! Yazuka */ +class Yazuka : public Enemy +{ + public: + Yazuka(YazukaGenerator& generator, const Pos2& cpos, bool cw, + fx16 a, fx16 b, Tangle start, fx16 av, VecFx16 ov, Tangle rotate, std::int32_t wait); + virtual ~Yazuka(); + + protected: + virtual void onUnchain() override; + virtual void onExecute(const float delta) override; + + private: + YazukaGenerator& _generator; + std::int32_t _wait; +}; + +/*! Generator for Yazuka */ +class YazukaGenerator +{ + public: + explicit YazukaGenerator(goblib::Task& t) : _belongto(t), _pool(POOL_MAX), _generated(0) {} + void reset() { _generated = 0; } + void spawn(); + + private: + friend class Yazuka; + goblib::Task& _belongto; + goblib::ObjectPool _pool; + std::uint32_t _generated; + constexpr static std::size_t POOL_MAX = 18; +#ifndef DEBUG + constexpr static std::uint32_t MEMBER_MAX = 6; +#else + static const std::uint32_t MEMBER_MAX; +#endif +}; + +#endif diff --git a/src/game.hpp b/src/game.hpp new file mode 100644 index 0000000..b642bb1 --- /dev/null +++ b/src/game.hpp @@ -0,0 +1,106 @@ +/*! + TinyDarius + + @file game.hpp + @brief Game status and management +*/ +#pragma once +#ifndef TD_GAME_HPP +#define TD_GAME_HPP + +#include "stage.hpp" +#include "score.hpp" +#include +#include +#include +#include + + +class Game : public goblib::Observer +{ + public: + constexpr static std::uint32_t INITIAL_REMAINING = 3; +#ifdef DEBUG + enum : std::int8_t { HISTORY_END = -1 }; +#else + constexpr static std::int8_t HISTORY_END = -1; +#endif + using History = std::array; + + Game() : _score(0), _time(0), _stage(HISTORY_END), _index(0), _remaining(INITIAL_REMAINING), _pause(true) + { + _history.fill(HISTORY_END); + } + ~Game(){} + + /// @name Property + /// @{ + const Stage& stage() const { return _stage >= 0 ? Stage::table[_stage] : Stage::terminator; } + const History& history() const { return _history; } + + GOBLIB_INLINE std::uint8_t remaining() const { return _remaining; } + GOBLIB_INLINE std::int32_t remainingTime() const { return _time; } + GOBLIB_INLINE std::uint32_t score() const { return _score; } + /// @} + + GOBLIB_INLINE bool isPause() const { return _pause; } + GOBLIB_INLINE bool isTimeup() const { return _time <= 0; } + GOBLIB_INLINE bool isGameover() const { return _remaining <= 0; } + + GOBLIB_INLINE void pump() { if(!isPause() && _time > 0) { --_time; } } + + /// @name Operation + /// @{ + void start(std::int32_t sindex = 0) + { + assert(sindex >= 0 && sindex < Stage::MAX && "sindex out of range"); + _stage = sindex; + _score = 0; + _remaining = INITIAL_REMAINING; + _index = 0; + _history.fill(HISTORY_END); + _history[_index] = _stage; + _time = stage().time; + pause(true); + } + + GOBLIB_INLINE void pause(bool b) { _pause = b; } + GOBLIB_INLINE void pause() { pause(true); } + GOBLIB_INLINE void resume() { pause(false); } + + GOBLIB_INLINE void extend() { if(_remaining < 255) {++_remaining; }} + GOBLIB_INLINE void dead() { if(_remaining) {--_remaining; } } + + void chooseNext(bool upper) + { + auto s = stage(); + if(_index >= Stage::HISTORY_MAX || s.current < 0) { return; } + + auto next = s.next(); + _stage = upper ? next.first : next.second; + _history[++_index] = _stage; + _time = stage().time; + pause(true); + } + /// @} + + /// @name Receive notify + /// @{ + // Score + GOBLIB_INLINE virtual void onNotify(const Score* s,void* arg) { _score += s->score(); } + /// @} + + private: + std::uint32_t _score; + std::int32_t _time; + std::int8_t _stage; // current stage index + std::int8_t _index; // history index + std::uint8_t _remaining; + std::int8_t _pad[1]; + History _history; + bool _pause; + + static Stage _stages[Stage::MAX]; +}; + +#endif diff --git a/src/game_obj.cpp b/src/game_obj.cpp new file mode 100644 index 0000000..e4450ad --- /dev/null +++ b/src/game_obj.cpp @@ -0,0 +1,76 @@ +/*! + TinyDarius + + @file game_obj.cpp + @brief Game object +*/ +#include +#include "game_obj.hpp" +#include "app.hpp" +#include "renderer.hpp" +#include "constants.hpp" +#include +using goblib::lgfx::GSprite; + +#include + +bool GameObj::_showHitBox = false; + +//goblib::FixedVector GameObj::_collideObjects; +GameObj::CollideObjects GameObj::_collideObjects; + +bool GameObj::onInitialize() +{ + TinyDarius::instance().insertRenderObj(this); + return true; +} + +bool GameObj::onRelease() +{ + TinyDarius::instance().removeRenderObj(this); + return true; +} + +// Render bounding +void GameObj::render(void* arg) +{ + if(isShowHitBox()) + { + _hbox.render(arg, hitable() ? CLR_MAGENTA : CLR_GREEN); + } +} + + +bool GameObj::checkHit(GameObj* obj) +{ + if(!obj->hitable()) { return false; } + + return std::any_of(_collideObjects.begin(), _collideObjects.end(), + [&obj](GameObj* o) + { + if(obj != o && o->hitable() && (obj->category() & o->category()) == 0) + { + auto r = obj->hitBox() & o->hitBox(); + if(r) + { + obj->onHit(o, r); + o->onHit(obj, r); + return true; + } + } + return false; + }); +} + +void GameObj::print() +{ + printf("GameObj: collide:%zu\n", _collideObjects.size()); + for(auto& e: _collideObjects) + { + printf(" >[%16s]:%d:(%d,%d) - {(%d,%d) [%d,%d]}\n", e->tag(), e->hitable(), + static_cast(e->_hbox._rpos.x()), static_cast(e->_hbox._rpos.y()), + e->_hbox._box.left(), e->_hbox._box.top(), + e->_hbox._box.width(), e->_hbox._box.height()); + } + +} diff --git a/src/game_obj.hpp b/src/game_obj.hpp new file mode 100644 index 0000000..a02b224 --- /dev/null +++ b/src/game_obj.hpp @@ -0,0 +1,118 @@ +/*! + TinyDarius + + @file game_obj.hpp + @brief Game object +*/ +#pragma once +#ifndef TD_GAME_OBJ_HPP +#define TD_GAME_OBJ_HPP + +#include "typedef.hpp" +#include "hit_box.hpp" +#include +#include +#include +#include +#include + +class GameObj : public goblib::Task, public goblib::graph::RenderObj2D +{ + public: + using Category = std::uint32_t; + static constexpr std::size_t COLLIDE_OBJECTS_MAX = 80; + using CollideObjects = goblib::FixedVector; + + GameObj(goblib::Task::PriorityType pri, goblib::graph::RenderObj2D::OrderType order, Category category, const char* tag = "") + : goblib::Task(pri, tag), goblib::graph::RenderObj2D(order) + , _hbox{}, _pos() ,_category(category), _counter(0), _hitable(true) + {} + virtual ~GameObj(){} + + virtual void render(void* arg) override; + + GOBLIB_INLINE Pos2::pos_type x() const { return _pos.x(); } + GOBLIB_INLINE Pos2::pos_type y() const { return _pos.y(); } + GOBLIB_INLINE Pos2 pos() const { return _pos; } + GOBLIB_INLINE Category category() const { return _category; } + + GOBLIB_INLINE const HitBox hitBox() const { return _hbox; } + GOBLIB_INLINE bool hitable() const{ return _hitable; } + GOBLIB_INLINE void virtual setHitable(bool b) { _hitable = b; } + GOBLIB_INLINE void enableHit() { setHitable(true); } + GOBLIB_INLINE void disableHit() { setHitable(false); } + + GOBLIB_INLINE void move(const Pos2::pos_type& x, const Pos2::pos_type& y) + { + _pos.move(x, y); + _hbox.move(x, y); + } + GOBLIB_INLINE void move(const Pos2& to) + { + _pos = to; + _hbox.move(to.x(), to.y()); + } + GOBLIB_INLINE void offset(const Pos2::pos_type& ox, const Pos2::pos_type& oy) + { + _pos.offset(ox, oy); + // _hbox.offset(ox, oy); + _hbox.move(_pos); + } + GOBLIB_INLINE void offset(const Pos2& off) + { + _pos += off; + // _hbox.offset(off); + _hbox.move(_pos); + } + + static CollideObjects& collideObjects() { return _collideObjects; } + static bool checkHit(GameObj* obj); + + static void showHitBox(bool b) { _showHitBox = b; } + static bool isShowHitBox() { return _showHitBox; } + static void print(); + + protected: + virtual bool onInitialize() override; + virtual bool onRelease() override; + // virtual void onReceive (const TaskMessage& /*msg*/) override {} + virtual void onExecute(const float /*delta*/) override { ++_counter; } + + virtual void onChain() override + { + if(category()) + { + assert(_collideObjects.size() < _collideObjects.capacity() && "Too many objects"); + _collideObjects.push_back(this); + } + } + virtual void onUnchain() override + { + _collideObjects.erase(std::remove_if(_collideObjects.begin(), _collideObjects.end(), + [this](GameObj* o) { return this == o; }), + _collideObjects.end()); + } + virtual void onHit(GameObj* o, const Rect2& hit) {} + + void resetCounter() { _counter = 0; } + std::uint32_t counter() const { return _counter; } + + private: + GameObj() = delete; + GameObj(const GameObj&) = delete; + GameObj& operator=(const GameObj&) = delete; + + protected: + HitBox _hbox; + + private: + Pos2 _pos; + Category _category; + std::uint32_t _counter; + bool _hitable; + + static CollideObjects _collideObjects; + static bool _showHitBox; +}; + +#endif diff --git a/src/hit_box.cpp b/src/hit_box.cpp new file mode 100644 index 0000000..8117a30 --- /dev/null +++ b/src/hit_box.cpp @@ -0,0 +1,26 @@ +/*! + TinyDarius + + @file hit_box.cpp + @brief Collision +*/ +#include +#include "hit_box.hpp" +#include "renderer.hpp" +#include +using goblib::lgfx::GSprite; + + +void HitBox::render(void* arg, std::uint32_t clr) +{ +#ifndef NDEBUG + RenderArg* rarg = static_cast(arg); + GSprite* target = rarg->sprite; + + if(_box) + { + target->drawRect(_box.left(), _box.top() + rarg->yorigin, + _box.width(), _box.height(), clr); + } +#endif +} diff --git a/src/hit_box.hpp b/src/hit_box.hpp new file mode 100644 index 0000000..57cd740 --- /dev/null +++ b/src/hit_box.hpp @@ -0,0 +1,45 @@ +/*! + TinyDarius + + @file hit_box.hpp + @brief Collision +*/ +#pragma once +#ifndef TD_HIT_BOX_HPP +#define TD_HIT_BOX_HPP + +#include "typedef.hpp" +#include + +struct HitBox +{ + // Pos2i + Pos2 _rpos; // relative + Rect2 _box; // absolute + + constexpr HitBox() : HitBox(Pos2(), Rect2()) {} + constexpr HitBox(const Pos2& pos, const Rect2& r) : _rpos(pos), _box(r) {} + + GOBLIB_INLINE void move(const Pos2& pos) { _box.move(_rpos + pos); } + GOBLIB_INLINE void move(const Pos2::pos_type x, const Pos2::pos_type y) + { + move(Pos2(x,y)); + } + + GOBLIB_INLINE void offset(const Pos2::pos_type x, const Pos2::pos_type y) + { + _box.offset(static_cast(x), static_cast(y)); + } + GOBLIB_INLINE void offset(const Pos2& pos) { offset(pos.x(), pos.y()); } + + GOBLIB_INLINE Rect2 isHit(const HitBox& x) const { return (_box & x._box); } + + void render(void* arg, std::uint32_t clr); +}; + +GOBLIB_INLINE Rect2 operator&(const HitBox& a, const HitBox& b) +{ + return a.isHit(b); +} + +#endif diff --git a/src/info/information.cpp b/src/info/information.cpp new file mode 100644 index 0000000..de0385c --- /dev/null +++ b/src/info/information.cpp @@ -0,0 +1,109 @@ +/*! + TinyDarius + + @file information.cpp + @brief Information on play +*/ +#include + +#include "information.hpp" +#include "../debug.hpp" +#include "../app.hpp" +#include "../game.hpp" +#include "../typedef.hpp" +#include "../renderer.hpp" +#include "../utility.hpp" +#include "../constants.hpp" + +#include +using goblib::lgfx::GSprite; +using goblib::lgfx::GSprite4; +#include + +#include // std::log10 +#include + +namespace +{ +constexpr char BITMAP_PATH[] = "/res/td/zone.bmp"; + +constexpr Pos2i ZONE_POS(160 - 8*7, 0); +constexpr Pos2i ZONE_NAME_POS(SCREEN_WIDTH/2 - 8, 20); +constexpr Pos2i REMAINING_POS(SCREEN_WIDTH/2-56+16+4, 24); +constexpr Pos2i SCORE_POS(8,16); +constexpr Pos2i TIME_POS(SCREEN_WIDTH - (12*8), 16); +} + +Information::Information() + : GameObj(PRIORITY_INFORMATION, ORDER_INFORMATION, CATEGORY_NONE, "information") + , _sprite(nullptr) +{ + _sprite = new GSprite4(); + assert(_sprite); +} + +Information::~Information() +{ + // TD_PRINT_FUNCTION(); + goblib::safeDelete(_sprite); +} + +void Information::onUnchain() +{ + // TD_PRINT_FUNCTION(); + GameObj::onUnchain(); + delete this; +} + +bool Information::onInitialize() +{ + if(!createFromBitmap(*_sprite, BITMAP_PATH)) + { + kill(); + return false; + } + _sprite->makePaletteTable256(); + return GameObj::onInitialize(); +} + +bool Information::onRelease() +{ + return GameObj::onRelease(); +} + +void Information::onExecute(const float delta) +{ +} + +void Information::render(void* arg) +{ + static const char* score_tbl[] = {"", "0", "00","000", "0000", "00000", "000000", "0000000", "00000000" }; + Game& game = TinyDarius::instance().game(); + + RenderArg* rarg = static_cast(arg); + GSprite* target = rarg->sprite; + _sprite->pushSpriteTo16(target, ZONE_POS.x(), ZONE_POS.y() + rarg->yorigin); + + target->setTextColor(CLR_WHITE, CLR_BLACK); + target->setTextSize(2); + target->setCursor(ZONE_NAME_POS.x(), ZONE_NAME_POS.y() + rarg->yorigin); + target->printf("%c", game.stage().toChar()); + target->setTextSize(1); + + target->setCursor(REMAINING_POS.x(), REMAINING_POS.y() + rarg->yorigin); + target->printf("%u", game.remaining()); + + auto pts = game.score(); + auto column = (pts == 0) ? 7 : 8 - (static_cast(std::log10(static_cast(pts)) + 1)); + target->setTextColor(CLR_GRAY, CLR_BLACK); + target->drawString(score_tbl[column], SCORE_POS.x(),SCORE_POS.y() + rarg->yorigin); + target->setTextColor(CLR_WHITE); + target->setCursor(SCORE_POS.x(),SCORE_POS.y() + rarg->yorigin); + target->printf("%8u", pts); + + target->setCursor(TIME_POS.x(), TIME_POS.y() + rarg->yorigin); + auto tm = game.remainingTime(); + if(tm <= 60_fsec) { target->setTextColor(CLR_RED, CLR_BLACK); } + target->printf("TIME:%02u:%02u", tm/(1_fmin), (tm/APP_FPS) % 60); + target->setTextColor(CLR_WHITE, CLR_BLACK); +} diff --git a/src/info/information.hpp b/src/info/information.hpp new file mode 100644 index 0000000..1ddc743 --- /dev/null +++ b/src/info/information.hpp @@ -0,0 +1,37 @@ +/*! + TinyDarius + + @file information.hpp + @brief Information on play +*/ +#pragma once +#ifndef TD_INFORMATION_HPP +#define TD_INFORMATION_HPP + +#include "../game_obj.hpp" + +namespace goblib { namespace lgfx { +class GSprite4; +}} + +class Information : public GameObj +{ + public: + Information(); + virtual ~Information(); + + virtual void render(void* arg) override; + + protected: + virtual void onUnchain() override; + virtual bool onInitialize() override; + virtual bool onRelease() override; + //virtual void onReceive (const Task::Message& /*msg*/) override {} + virtual void onExecute(const float delta) override; + + private: + goblib::lgfx::GSprite4* _sprite; + +}; + +#endif diff --git a/src/info/message.cpp b/src/info/message.cpp new file mode 100644 index 0000000..3d364c0 --- /dev/null +++ b/src/info/message.cpp @@ -0,0 +1,236 @@ +/*! + TinyDarius + + @file message.cpp + @brief Display message +*/ +#include + +#include "message.hpp" +#include "../debug.hpp" +#include "../renderer.hpp" +#include "../constants.hpp" +#if __has_include("../df88.cpp") +#include "../df88.hpp" +#endif +#include +using goblib::lgfx::GSprite; +#include +using goblib::lgfx::GSprite4; +#include +#include +#include +#include + +CenteringMessage::Message::Message(const char* str, std::int16_t sy, std::uint32_t c, std::uint32_t w) + : source{0,}, dest{0} + , ps(source), pd(dest) + , x(0), y(sy), clr(c) + , counter(w), wait(w) +{ + std::strncpy(source, str, sizeof(source)); + source[sizeof(source)-1] = '\0'; + // Keep strlen(dest) equal strlen(source) + std::fill(std::begin(dest), std::begin(dest) + sizeof(dest), ' '); + dest[std::strlen(source)] = '\0'; + + auto width = std::strlen(str) * 8; + x = (SCREEN_WIDTH - width) / 2; +} + +CenteringMessage::CenteringMessage() + : GameObj(PRIORITY_MESSAGE, ORDER_MESSAGE, CATEGORY_NONE, "message") + , _messages() +{} + + +CenteringMessage::CenteringMessage(const char* str, std::int32_t sy, std::uint32_t clr, std::uint32_t wait) + : GameObj(PRIORITY_MESSAGE, ORDER_MESSAGE, CATEGORY_NONE, "message") + , _messages() +{ + insert(str, sy, clr, wait); +} + +CenteringMessage::~CenteringMessage() +{ + // TD_PRINT_FUNCTION(); +} + +void CenteringMessage::onUnchain() +{ + // TD_PRINT_FUNCTION(); + GameObj::onUnchain(); + delete this; +} + +bool CenteringMessage::onInitialize() +{ + return GameObj::onInitialize(); +} + +bool CenteringMessage::onRelease() +{ + return GameObj::onRelease(); +} + +void CenteringMessage::onExecute(const float delta) +{ + for(auto& e : _messages) + { + if(!e.pump()) { break; } + } + GameObj::onExecute(delta); +} + +void CenteringMessage::render(void* arg) +{ + RenderArg* rarg = static_cast(arg); + GSprite* target = rarg->sprite; + + auto old = target->getTextDatum(); + // target->setTextDatum(textdatum_t::middle_center); + target->setTextDatum(textdatum_t::middle_left); + + for(auto& e : _messages) + { + target->setTextColor(e.clr); + target->drawString(e.dest, e.x, e.y + rarg->yorigin); + } + target->setTextDatum(old); +} + +// +BlinkingMessage::BlinkingMessage(const char* source, std::int16_t sy, std::uint32_t clr, std::int32_t cycle) + : GameObj(PRIORITY_MESSAGE, ORDER_MESSAGE, CATEGORY_NONE, "blinkingmsg") + , _sprite(nullptr) + , _buffer{0} + , _clr(clr) + , _cycle(cycle) + , _show(false) +{ + std::strncpy(_buffer, source, sizeof(_buffer)); + _buffer[sizeof(_buffer)-1] = '\0'; + + _sprite = new GSprite4(std::strlen(_buffer)*16, 16); + assert(_sprite); +#if __has_include("../df88.cpp") + _sprite->setFont(&df88_gfx_font); +#endif + _sprite->setPaletteColor(1, _clr); // 0:transparent 1;text color + _sprite->makePaletteTable256(); + auto xx = (SCREEN_WIDTH - strlen(_buffer) * 16) / 2; + + move(Pos2(xx, sy)); +} + +BlinkingMessage:: ~BlinkingMessage() +{ + goblib::safeDelete(_sprite); + // TD_PRINT_FUNCTION(); +} + +void BlinkingMessage::onUnchain() +{ + // TD_PRINT_FUNCTION(); + GameObj::onUnchain(); + delete this; +} + +bool BlinkingMessage::onInitialize() +{ + _sprite->setTextSize(2); // double size + _sprite->setTextColor(_clr); + _sprite->drawString(_buffer, 0, 0); + return GameObj::onInitialize(); +} + +bool BlinkingMessage::onRelease() +{ + return GameObj::onRelease(); +} + +void BlinkingMessage::onExecute(const float delta) +{ + _show = (_cycle == 0) ? true : ((counter() % _cycle) == 0 ? !_show : _show); + show(_show); + GameObj::onExecute(delta); +} + +void BlinkingMessage::render(void* arg) +{ + RenderArg* rarg = static_cast(arg); + GSprite* target = rarg->sprite; + + /* + auto old = target->getTextDatum(); + target->setTextDatum(textdatum_t::middle_center); + target->setTextSize(2); + target->setTextColor(_clr); + target->drawString(_buffer, x(), y() + rarg->yorigin); + target->setTextSize(1); + target->setTextDatum(old); + */ + + _sprite->pushSpriteTo16(target, static_cast(x()) , static_cast(y()) - 8 + rarg->yorigin, 0); + +} + + +// +RemainBonusMessage::RemainBonusMessage(std::int32_t pts, std::int16_t sy, std::uint32_t clr) + : GameObj(PRIORITY_MESSAGE, ORDER_MESSAGE, CATEGORY_NONE, "bonusmessage") + , _buffer{0} + , _pts(pts) + ,_clr(clr) +{ + apply(_pts); + + move(Pos2(SCREEN_WIDTH/2, sy)); +} + +RemainBonusMessage:: ~RemainBonusMessage() +{ + // TD_PRINT_FUNCTION(); +} + +void RemainBonusMessage::apply(std::int32_t pts) +{ + snprintf(_buffer, sizeof(_buffer), FORMAT_STR, pts); + _buffer[sizeof(_buffer)-1] = '\0'; +} + +void RemainBonusMessage::onUnchain() +{ + // TD_PRINT_FUNCTION(); + GameObj::onUnchain(); + delete this; +} + +bool RemainBonusMessage::onInitialize() +{ + return GameObj::onInitialize(); +} + +bool RemainBonusMessage::onRelease() +{ + return GameObj::onRelease(); +} + +void RemainBonusMessage::onExecute(const float delta) +{ + GameObj::onExecute(delta); +} + +void RemainBonusMessage::render(void* arg) +{ + RenderArg* rarg = static_cast(arg); + GSprite* target = rarg->sprite; + + auto old = target->getTextDatum(); + target->setTextDatum(textdatum_t::middle_center); + target->setTextSize(2); + target->setTextColor(_clr); + target->drawString(_buffer, static_cast(x()), static_cast(y()) + rarg->yorigin); + target->setTextSize(1); + target->setTextDatum(old); +} diff --git a/src/info/message.hpp b/src/info/message.hpp new file mode 100644 index 0000000..55aac46 --- /dev/null +++ b/src/info/message.hpp @@ -0,0 +1,146 @@ +/*! + TinyDarius + + @file message.hpp + @brief Display message +*/ +#pragma once +#ifndef TD_MESSAGE_HPP +#define TD_MESSAGE_HPP + +#include "../game_obj.hpp" +#include "../constants.hpp" +#include +#include +#include +#include + +namespace goblib { namespace lgfx { class GSprite4; }} + +/*! @brief Centering messages to be displayed one character at a time */ +class CenteringMessage : public GameObj +{ + public: + constexpr static std::size_t MESSAGE_MAX = 3; + + /*! @brief Message displayed one character at a time */ + struct Message + { + public: + constexpr static std::size_t BUFFER_LENGTH = SCREEN_WIDTH/8 + 1; + char source[BUFFER_LENGTH]; + char dest[BUFFER_LENGTH]; + const char* ps; + char* pd; // display str. + std::int16_t x, y; + std::uint32_t clr; + std::int32_t counter; + const std::int32_t wait; + + Message(const char* str, std::int16_t sy, std::uint32_t c, std::uint32_t w = 4); + + GOBLIB_INLINE bool isFinish() const { return !ps || *ps == '\0'; } + + bool pump() + { + if(!--counter) + { + counter = wait; + if(ps && *ps && pd) { *pd++ = *ps++; } + } + return isFinish(); + } + }; + + CenteringMessage(); + CenteringMessage(const char* source, std::int32_t sy, std::uint32_t clr, std::uint32_t wait = 4); + virtual ~CenteringMessage(); + + GOBLIB_INLINE void insert(const char* str, std::int32_t sy, std::uint32_t c, std::uint32_t w = 4) + { + assert(_messages.size() < MESSAGE_MAX); + _messages.emplace_back(str, sy, c, w); + } + + GOBLIB_INLINE void clear() { _messages.clear(); } + GOBLIB_INLINE bool isFinish() const + { + return _messages.empty() + || std::all_of(_messages.begin(), _messages.end(), [](const Message& m) { return m.isFinish(); }); + } + + virtual void render(void* arg) override; + + protected: + virtual void onUnchain() override; + virtual bool onInitialize() override; + virtual bool onRelease() override; + virtual void onExecute(const float delta) override; + + private: + goblib::FixedVector _messages; +}; + +/*! @brief Blinking centering message */ +class BlinkingMessage : public GameObj +{ + public: + constexpr static std::size_t BUFFER_LENGTH = SCREEN_WIDTH/8 + 1; + + /*! + @param source message string + @param sx center position + @param sy top position + @param cycle show/hide cycle. + */ + BlinkingMessage(const char* source, std::int16_t sy, std::uint32_t clr, std::int32_t cycle = 16); + virtual ~BlinkingMessage(); + + virtual void render(void* arg) override; + + protected: + virtual void onUnchain() override; + virtual bool onInitialize() override; + virtual bool onRelease() override; + virtual void onExecute(const float delta) override; + + private: + goblib::lgfx::GSprite4* _sprite; + + char _buffer[BUFFER_LENGTH]; + const std::uint32_t _clr; + const std::int32_t _cycle; + bool _show; +}; + +/*! @brief Remain bonus message */ +class RemainBonusMessage : public GameObj +{ + public: + constexpr static std::size_t BUFFER_LENGTH = SCREEN_WIDTH/8 + 1; + + RemainBonusMessage(std::int32_t pts, std::int16_t sy, std::uint32_t clr); + virtual ~RemainBonusMessage(); + + virtual void render(void* arg) override; + + void setPts(std::int32_t pts) { _pts = pts; apply(_pts); } + void increasePts(std::int32_t pts) { _pts += pts; apply(_pts); } + + protected: + virtual void onUnchain() override; + virtual bool onInitialize() override; + virtual bool onRelease() override; + virtual void onExecute(const float delta) override; + + private: + void apply(std::int32_t pts); + + private: + char _buffer[BUFFER_LENGTH]; + std::int32_t _pts; + const std::uint32_t _clr; + constexpr static const char* FORMAT_STR = "%07d POINTS"; +}; + +#endif diff --git a/src/info/name_entry.cpp b/src/info/name_entry.cpp new file mode 100644 index 0000000..2a835e3 --- /dev/null +++ b/src/info/name_entry.cpp @@ -0,0 +1,166 @@ +/*! + TinyDarius + + @file name_entry.cpp + @brief Name entry +*/ +#include +#include "name_entry.hpp" +#include "../debug.hpp" +#include "../app.hpp" +#include "../renderer.hpp" +#include "../constants.hpp" +#include "../typedef.hpp" + +#include +#include +using goblib::lgfx::GSprite; +#include +using goblib::m5s::FaceGB; + +#include + +namespace +{ +constexpr const char VALID_CHAR[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ. "; +constexpr const char* ENTER_MESSAGE[] = +{ + "ENTER YOUR INITIALS !", + "RANK SCORE NAME", +}; +constexpr const char* ENTRY_FORMAT = "PROCO %4d %8d "; +constexpr std::int32_t INPUT_OFFSET_X = 60; +constexpr const char* DEFAULT_NAME = "M5S"; +constexpr std::int32_t INPUT_TIME = 60_fsec; +} + +NameEntry::NameEntry(std::int32_t score, std::int32_t rank, std::int16_t sy) + : GameObj(PRIORITY_MESSAGE, ORDER_MESSAGE, CATEGORY_NONE, "inputranking") + , _score(score) + , _rank(rank) + , _timer(INPUT_TIME) + , _cursor(0) + , _name{0} + , _char(0) + , _saveRC{} +{ + move(Pos2(SCREEN_WIDTH/2, sy)); +} + +NameEntry::~NameEntry() +{ + // TD_PRINT_FUNCTION(); +} + +void NameEntry::onUnchain() +{ + // TD_PRINT_FUNCTION(); + GameObj::onUnchain(); + delete this; +} + +bool NameEntry::onInitialize() +{ + FaceGB& input = TinyDarius::instance().input(); + _saveRC[0] = input.getRepeatCycle(FaceGB::Button::Left); + _saveRC[1] = input.getRepeatCycle(FaceGB::Button::Right); + input.setRepeatCycle(FaceGB::Button::Up, 10); + input.setRepeatCycle(FaceGB::Button::Down, 10); + + return GameObj::onInitialize(); +} + +bool NameEntry::onRelease() +{ + FaceGB& input = TinyDarius::instance().input(); + input.setRepeatCycle(FaceGB::Button::Left, _saveRC[0]); + input.setRepeatCycle(FaceGB::Button::Right, _saveRC[1]); + return GameObj::onRelease(); +} + +void NameEntry::onExecute(const float delta) +{ + // Cut off when time is over. + if(_timer <= 0) + { + _name[_cursor] = '\0'; + // Unentered? + if(_name[0] == '\0') + { + std::strncpy(_name, DEFAULT_NAME, sizeof(_name)); + _name[sizeof(_name)-1] = '\0'; + } + pause(true); + } + + FaceGB& input = TinyDarius::instance().input(); + + // Change character + if(input.wasRepeated(FaceGB::Button::Left)) + { + _char = (_char - 1 + goblib::size(VALID_CHAR)) % goblib::size(VALID_CHAR); + } + else if(input.wasRepeated(FaceGB::Button::Right)) + { + _char = (_char + 1) % goblib::size(VALID_CHAR); + } + // Delete + if(input.wasPressed(FaceGB::Button::B)) + { + if(_cursor > 0) + { + _name[--_cursor] = '\0'; + _char = 0; + } + } + // Input + else if(input.wasPressed(FaceGB::Button::A)) + { + _name[_cursor++] = VALID_CHAR[_char]; + _char = 0; + + // Done + if(_cursor >= NAME_LENGTH) + { + _name[_cursor] = '\0'; + pause(true); + } + } + + --_timer; + GameObj::onExecute(delta); +} + +void NameEntry::render(void* arg) +{ + RenderArg* rarg = static_cast(arg); + GSprite* target = rarg->sprite; + std::int32_t xx = static_cast(x()); + std::int32_t yy = static_cast(y()); + + auto old = target->getTextDatum(); + target->setTextDatum(textdatum_t::middle_center); + target->setTextColor(CLR_WHITE); + + target->drawString(ENTER_MESSAGE[0], xx, yy + rarg->yorigin); + target->drawString(ENTER_MESSAGE[1], xx, yy + 16 + rarg->yorigin); + + char tmp[64]; + snprintf(tmp, sizeof(tmp), ENTRY_FORMAT, _rank, _score); + tmp[sizeof(tmp)-1] = '\0'; + target->drawString(tmp, xx, yy + 32 + rarg->yorigin); + + target->setTextDatum(textdatum_t::middle_left); + target->drawString(_name, xx + INPUT_OFFSET_X, yy + 32 + rarg->yorigin); + + if(_cursor < NAME_LENGTH) + { + snprintf(tmp, sizeof(tmp), "%c", VALID_CHAR[_char]); + tmp[sizeof(tmp)-1] = '\0'; + target->setTextColor((counter() & 8) ? CLR_RED : CLR_WHITE); + target->drawString(tmp, xx + INPUT_OFFSET_X + 8 *_cursor, yy + 32 + rarg->yorigin); + } + + target->setTextColor(CLR_WHITE); + target->setTextDatum(old); +} diff --git a/src/info/name_entry.hpp b/src/info/name_entry.hpp new file mode 100644 index 0000000..2206caa --- /dev/null +++ b/src/info/name_entry.hpp @@ -0,0 +1,47 @@ +/*! + TinyDarius + + @file name_entry.hpp + @brief Name entry +*/ +#pragma once +#ifndef TD_NAME_ENTRY_HPP +#define TD_NAME_ENTRY_HPP + +#include "../game_obj.hpp" +#include "../constants.hpp" +#include +#include + + +class NameEntry : public GameObj +{ + public: + constexpr static std::int32_t NAME_LENGTH = 3; + + NameEntry(std::int32_t score, std::int32_t rank, std::int16_t sy); + virtual ~NameEntry(); + + GOBLIB_INLINE bool isFinish() const { return _timer <= 0 || _cursor >= NAME_LENGTH; } + + GOBLIB_INLINE const char* name() const { return _name; } + + virtual void render(void* arg) override; + + protected: + virtual void onUnchain() override; + virtual bool onInitialize() override; + virtual bool onRelease() override; + virtual void onExecute(const float delta) override; + + private: + std::int32_t _score; + std::int32_t _rank; + std::int32_t _timer; + std::int32_t _cursor; + char _name[NAME_LENGTH + 1]; // 3 char + '\0' + std::int32_t _char; + std::uint8_t _saveRC[2]; +}; + +#endif diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..73ebfa8 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,78 @@ +/*! + TinyDarius + + @file main.cpp + @brief Program entry +*/ + +#include +#ifdef min +#undef min +#endif +#include + +#include +#include +#include +#if __has_include () +#include +#else // esp_idf_version.h has been introduced in Arduino 1.0.5 (ESP-IDF3.3) +#define ESP_IDF_VERSION_VAL(major, minor, patch) ((major << 16) | (minor << 8) | (patch)) +#define ESP_IDF_VERSION ESP_IDF_VERSION_VAL(3,2,0) +#endif + +#include "app.hpp" +#include "sound.hpp" +#include +#include +#include +#include + +static LGFX lcd; + +void setup() +{ + auto presetup_heap = esp_get_free_heap_size(); + + // Heap memory increases. + // We release the memory for BT as it is pre-allocated depending on the environment and configuration. + esp_bluedroid_disable(); + esp_bluedroid_deinit(); + esp_bt_controller_disable(); + esp_bt_controller_deinit(); + esp_bt_mem_release(ESP_BT_MODE_BTDM); + // esp_bt_controller_mem_release(ESP_BT_MODE_BTDM); + + M5.begin(false /* LCD */, false /* SD */ , true /* Serial */); + Wire.begin(); + while(!Serial){ delay(10); } + + SdFat& sd = goblib::m5s::SD::instance().sd(); + while(!sd.begin((unsigned)TFCARD_CS_PIN, SD_SCK_MHZ(25))) { delay(10); } + + auto m5setup_heap = esp_get_free_heap_size(); + + TinyDarius::instance().setup(&lcd); + (void)SoundSystem::instance(); + goblib::m5s::CpuUsage::setup(); + + printf("Free heap before M5.begin: %u\n", presetup_heap); + printf("Free heap after begin: %u\n", m5setup_heap); + printf("Free heap after my setup: %u\n", esp_get_free_heap_size()); + + printf("ESP-IDF Version %d.%d.%d\n", + (ESP_IDF_VERSION>>16) & 0xFF, (ESP_IDF_VERSION>>8)&0xFF, ESP_IDF_VERSION & 0xFF); +#ifdef LGFX_USE_V1 + const char* lgfx_v = "v1"; +#else + const char* lgfx_v = "v0"; +#endif + printf("LovyanGFX %s\n", lgfx_v); + printf("goblib %s:%08xH\n", GOBLIB_VERSION_STRING, GOBLIB_VERSION_VALUE); + printf("goblib_m5s %s:%08xH\n", GOBLIB_M5S_VERSION_STRING, GOBLIB_M5S_VERSION_VALUE); +} + +void loop() +{ + TinyDarius::instance().pump(); +} diff --git a/src/math_table.cpp b/src/math_table.cpp new file mode 100644 index 0000000..594603e --- /dev/null +++ b/src/math_table.cpp @@ -0,0 +1,41 @@ +/*! + TinyDarius + + @file math_table.cpp + @brief Table based mathmatics. +*/ +#include "math_table.hpp" +#include + +namespace +{ +constexpr fx16 tsin(int x) { return fx16(std::sin(goblib::math::deg2rad(x*360.0f/TANGLE_DIV))); } +constexpr auto sin_table = goblib::template_helper::table::generator(tsin); + +constexpr fx16 tcos(int x) { return fx16(std::cos(goblib::math::deg2rad(x*360.0f/TANGLE_DIV))); } +constexpr auto cos_table = goblib::template_helper::table::generator(tcos); + +constexpr Tangle tatan2(std::size_t v) { return goblib::math::round(goblib::math::rad2deg(std::atan2(v/NUM_OF_HOR, v%NUM_OF_HOR)) / (360/TANGLE_DIV)); } +constexpr auto atan2_table = goblib::template_helper::table::generator(tatan2); +// +} + +fx16 table_sin(const Tangle a) { return sin_table[a % TANGLE_DIV]; } + +fx16 table_cos(const Tangle a) { return cos_table[a % TANGLE_DIV]; } + +Tangle table_atan2(const std::int16_t y, const std::int16_t x) +{ + if(y == x && y == 0) { return Tangle(0); } + + auto xx = (x + goblib::math::sign(x) * SCREEN_DIV_WIDTH - 1) / SCREEN_DIV_WIDTH; + auto yy = (y + goblib::math::sign(y) * SCREEN_DIV_HEIGHT - 1) / SCREEN_DIV_HEIGHT;; + + auto v = atan2_table[ std::abs(yy) * NUM_OF_HOR + std::abs(xx) ]; + + if(xx < 0) { v = (TANGLE_DIV/2) - v; } + if(yy < 0) { v = TANGLE_DIV - v; } + + return v; +} + diff --git a/src/math_table.hpp b/src/math_table.hpp new file mode 100644 index 0000000..0f568fc --- /dev/null +++ b/src/math_table.hpp @@ -0,0 +1,57 @@ +/*! + TinyDarius + + @file math_table.hpp + @brief Table based mathmatics. +*/ +#pragma once +#ifndef TD_MATH_TABLE_HPP +#define TD_MATH_TABLE_HPP + +#include "typedef.hpp" +#include "constants.hpp" +#include +#include +#include +#include + + +/*! @brief sin/cos division for table based angle + 0 : deg(0) + TANGLE_DIV*1/4 : deg(90) + TANGLE_DIV*1/2 : deg(180) + TANGLE_DIV*3/4 : deg(270) +*/ +constexpr std::size_t TANGLE_DIV = 32; + +/*! @brief Table based angle definition */ +using Tangle = std::int8_t; + +/*! @brief tabel based atan2 block width */ +constexpr std::int16_t SCREEN_DIV_WIDTH = 16; +/*! @brief tabel based atan2 block height */ +constexpr std::int16_t SCREEN_DIV_HEIGHT = 16; + +constexpr std::int16_t NUM_OF_HOR = SCREEN_WIDTH / SCREEN_DIV_WIDTH; +constexpr std::int16_t NUM_OF_VER = SCREEN_HEIGHT / SCREEN_DIV_HEIGHT; + +/*! @brief table based sin + @param a Tangle + @return fx16 +*/ +fx16 table_sin(const Tangle a); + +/*! @brief table based cos + @param a Tangle + @return fx16 +*/ +fx16 table_cos(const Tangle a); + +/*! atan2 rerurn table based angle. + @param y y cordinate + @param x x cordinate + @return [0 ~ TANGLE_DIV) +*/ +Tangle table_atan2(const std::int16_t y, const std::int16_t x); + +#endif diff --git a/src/partial_animation.hpp b/src/partial_animation.hpp new file mode 100644 index 0000000..1061257 --- /dev/null +++ b/src/partial_animation.hpp @@ -0,0 +1,35 @@ +/*! + TinyDarius + + @file partial_animation.hpp + @brief Partial animation +*/ +#pragma once +#ifndef TD_PARTIAL_ANIMATION_HPP +#define TD_PARTIAL_ANIMATION_HPP + +#include +#include +#include +#include + +struct PartialAnimation +{ + GOBLIB_INLINE std::uint8_t cell() const { return sequencer.cell(); } + GOBLIB_INLINE const goblib::lgfx::CellRect& rect() const { return rects[sequencer.cell()]; } + GOBLIB_INLINE std::int32_t offsetX() const { return sequencer.offsetX(); } + GOBLIB_INLINE std::int32_t offsetY() const { return sequencer.offsetY(); } + + GOBLIB_INLINE bool isFinish() const { return sequencer.isFinish(); } + GOBLIB_INLINE void pump() { sequencer.pump(); } + GOBLIB_INLINE void reset() { sequencer.reset(); } + GOBLIB_INLINE void pause(bool b) { sequencer.pause(b); } + GOBLIB_INLINE std::uint8_t index() const { return sequencer.index(); } + + // + std::int32_t rx,ry; // Relative position by parent. + std::vector rects; // Cell rectangles. + goblib::graph::AnimationSequencer sequencer; +}; + +#endif diff --git a/src/player/silver_hawk.cpp b/src/player/silver_hawk.cpp new file mode 100644 index 0000000..dc91cce --- /dev/null +++ b/src/player/silver_hawk.cpp @@ -0,0 +1,275 @@ +/*! + TinyDarius + + @file silver_hawk.cpp + @brief My ship +*/ +#include + +#include "silver_hawk.hpp" +#include "../debug.hpp" +#include "../app.hpp" +#include "../renderer.hpp" +#include "../typedef.hpp" +#include "../utility.hpp" +#include "../effect/explosion.hpp" + +#include +#include +using goblib::suffix::operator"" _u8; +#include +#include +using goblib::lgfx::GSprite; +using goblib::lgfx::GSprite4; +#include +using goblib::m5s::FaceGB; + +#include +#include + +using goblib::lgfx::CellRect; +using Seq = goblib::graph::Sequence; + +namespace +{ +constexpr char BITMAP_PATH[] = "/res/td/sh.bmp"; + +constexpr CellRect BODY_RECT(8, 0, 24, 16); +constexpr CellRect BULLET_RECT(32, 0, 16, 8); +constexpr std::int32_t BULLET_VELOCITY_X = 8; + +constexpr Pos2 INITIAL_POS(8, 80); +constexpr std::int32_t REWIND_TIMES = 3_fsec; + +constexpr HitBox HBOX_BODY = { Pos2(1,3), Rect2(0,0, 20, 10) }; +constexpr HitBox HBOX_BULLET = { Pos2(2,0), Rect2(0,0, 16-2, 8) }; + +constexpr std::int16_t VELOCITY = 4; +constexpr float CS45 = 0.70710678118f; // sin(deg2rad(45)), cos(deg2rad(45)) +constexpr std::array MOVE_TABLE = +{ + Pos2(), + Pos2(0, -VELOCITY), // up + Pos2(0, VELOCITY), // down + Pos2(), + Pos2(-VELOCITY, 0), // left + Pos2(-VELOCITY * CS45, -VELOCITY * CS45), // left up + Pos2(-VELOCITY * CS45, VELOCITY * CS45), // left down + Pos2(), + Pos2(VELOCITY, 0), // right + Pos2(VELOCITY * CS45, -VELOCITY * CS45), // right up + Pos2(VELOCITY * CS45, VELOCITY * CS45), // right down + Pos2(), + Pos2(), + Pos2(), + Pos2(), + Pos2(), +}; +// +} + +SilverHawk::SilverHawk() + : GameObj(PRIORITY_SILVERHAWK, ORDER_SILVERHAWK, CATEGORY_PLAYER,"silverhawk") + , _sprite(nullptr) + , _function(&SilverHawk::onPlay) + , _burner +{ + -8, 0, + { + CellRect(0, 0, 8, 16), + CellRect(0, 16, 8, 16), + }, + { + Seq(Seq::Draw, 0, 3_u8), + Seq(Seq::Draw, 1, 3_u8), + Seq(Seq::Goto, 0_u8), + } +} + , _enable(true) + , _bullets(BULLET_MAX) +{ + _sprite = new goblib::lgfx::GCellSprite4(); + assert(_sprite); + _hbox = HBOX_BODY; +} + +SilverHawk:: ~SilverHawk() +{ + // TD_PRINT_FUNCTION(); + goblib::safeDelete(_sprite); +} + +void SilverHawk::onUnchain() +{ + // TD_PRINT_FUNCTION(); + GameObj::onUnchain(); + delete this; +} + +bool SilverHawk::onInitialize() +{ + if(createFromBitmap(*_sprite, BITMAP_PATH)) + { + _sprite->makePaletteTable256(); + return GameObj::onInitialize(); + } + kill(); + return false; +} + +bool SilverHawk::onRelease() +{ + return GameObj::onRelease(); +} + +void SilverHawk::onExecute(const float delta) +{ + _burner.pump(); + (this->*_function)(delta); + GameObj::onExecute(delta); +} + +void SilverHawk::onPlay(const float delta) +{ + if(!_enable) { return; } + + FaceGB& input = TinyDarius::instance().input(); + auto cross = input.getNow() & FaceGB::MASK_CROSS; // 0x00~0x0F + assert(cross < 16 && "Invalid input"); + if(input.wasRepeated(FaceGB::Button::A)) + { + shoot(); + } + + auto p = pos(); + p += MOVE_TABLE[cross]; + std::int16_t xx = goblib::clamp(static_cast(p.x()), + FIELD_RECT.left(), static_cast(FIELD_RECT.right() - BODY_RECT.width())); + std::int16_t yy = goblib::clamp(static_cast(p.y()), + FIELD_RECT.top(), static_cast(FIELD_RECT.bottom() - BODY_RECT.height())); + move(Pos2(xx,yy)); +} + +void SilverHawk::onRewind(const float delta) +{ + auto t = goblib::easing::linear(static_cast(counter()) / REWIND_TIMES); + auto diff = _to - _from; + diff *= t; + move(_from + diff); + + if(t >= 1.0f) + { + _function = &SilverHawk::onPlay; + } +} + +void SilverHawk::onDead(const float delta) +{ + if(counter() == 10) + { + hide(); + } +} + +void SilverHawk::render(void* arg) +{ + RenderArg* rarg = static_cast(arg); + GSprite* target = rarg->sprite; + std::int16_t xx = static_cast(x()); + std::int16_t yy = static_cast(y()); + + _sprite->pushCellTo16(target, BODY_RECT, xx, yy + rarg->yorigin, 0); + _sprite->pushCellTo16(target, _burner.rect(), xx + _burner.rx, yy + _burner.ry + rarg->yorigin, 0); + + GameObj::render(arg); +} + +void SilverHawk::shoot() +{ + Bullet* b = _bullets.construct(*this); + if(b) + { + b->move(x() + BODY_RECT.width() - 8, y() + 4); + TinyDarius::instance().reserveInsertNode(b, this); + } +} + +void SilverHawk::restart() +{ + _function = &SilverHawk::onPlay; + enable(false); + move(INITIAL_POS); +} + +void SilverHawk::rewind() +{ + _function = &SilverHawk::onRewind; + enable(false); + _from = pos(); + _to = INITIAL_POS; + resetCounter(); +} + +void SilverHawk::dead() +{ + _function = &SilverHawk::onDead; + resetCounter(); + enable(false); + + auto exp = new Explosion(x()+12, y()+8, ORDER_EXPLOSION); + assert(exp); + TinyDarius::instance().reserveInsertNode(exp, this); +} + +// + +SilverHawk::Bullet::Bullet(SilverHawk& sh) + : GameObj(PRIORITY_BULLET, ORDER_BULLET, CATEGORY_BULLET, "bullet"), _sh(sh) +{ + _hbox = HBOX_BULLET; +} + +SilverHawk::Bullet::~Bullet() +{ + // TD_PRINT_FUNCTION(); +} + +void SilverHawk::Bullet::onUnchain() +{ + // TD_PRINT_FUNCTION(); + // Don't delete, return to object pool. + _sh._bullets.destruct(this); + GameObj::onUnchain(); +} + +bool SilverHawk::Bullet::onInitialize() +{ + return GameObj::onInitialize(); +} + +bool SilverHawk::Bullet::onRelease() +{ + return GameObj::onRelease(); +} + +void SilverHawk::Bullet::onExecute(const float delta) +{ + offset(Pos2(BULLET_VELOCITY_X, 0)); + + std::int16_t xx = static_cast(x()); + std::int16_t yy = static_cast(y()); + if(xx > SCREEN_WIDTH || xx < -BULLET_RECT.width() || yy > SCREEN_HEIGHT || yy < -BULLET_RECT.height()) + { + release(); + } + GameObj::onExecute(delta); +} + +void SilverHawk::Bullet::render(void* arg) +{ + RenderArg* rarg = static_cast(arg); + GSprite* target = rarg->sprite; + + _sh._sprite->pushCellTo16(target, BULLET_RECT, static_cast(x()), static_cast(y()) + rarg->yorigin, 0); + GameObj::render(arg); +} diff --git a/src/player/silver_hawk.hpp b/src/player/silver_hawk.hpp new file mode 100644 index 0000000..35f6298 --- /dev/null +++ b/src/player/silver_hawk.hpp @@ -0,0 +1,105 @@ +/*! + TinyDarius + + @file silver_hawk.hpp + @brief My ship +*/ +#pragma once +#ifndef TD_SILVER_HAWK_HPP +#define TD_SILVER_HAWK_HPP + +#include "../game_obj.hpp" +#include "../constants.hpp" +#include "../partial_animation.hpp" +#include +#include + +#include +#include + +namespace goblib { namespace lgfx { +class GCellSprite4; +}} + +/*! Silverhawk */ +class SilverHawk : public GameObj +{ + public: + constexpr static std::size_t BULLET_MAX = 4; + + /*! Bullet of shilverhawk */ + class Bullet : public GameObj + { + public: + explicit Bullet(SilverHawk& sh); + virtual ~Bullet(); + + virtual void render(void* arg) override; + + protected: + virtual void onUnchain() override; + virtual bool onInitialize() override; + virtual bool onRelease() override; + //virtual void onReceive (const Task::Message& /*msg*/) override {} + virtual void onExecute(const float delta) override; + virtual void onHit(GameObj* o, const Rect2&) override + { + if(o->category() & (CATEGORY_BIT_ENEMY | CATEGORY_BIT_BOSS)) + { + release(); + } + } + + private: + SilverHawk& _sh; // shared sprite and return to pool. + }; + + SilverHawk(); + virtual ~SilverHawk(); + virtual void render(void* arg) override; + + GOBLIB_INLINE bool alive() const { return _function == &SilverHawk::onPlay; } + + /// @name enable input + /// @{ + GOBLIB_INLINE bool isEnable() const { return _enable; } + GOBLIB_INLINE void enable(bool b) { _enable = b; } + GOBLIB_INLINE void enable() { enable(true); } + GOBLIB_INLINE void disable() { enable(false); } + /// @} + + /// @name Instructions + /// @{ + void restart(); + void rewind(); + void dead(); + /// @} + + protected: + virtual void onUnchain() override; + virtual bool onInitialize() override; + virtual bool onRelease() override; + //virtual void onReceive (const Task::Message& /*msg*/) override {} + virtual void onExecute(const float delta) override; + + virtual void onHit(GameObj* o, const Rect2& hit) override { dead(); } + + private: + void onPlay(const float); + void onRewind(const float); + void onDead(const float); + void shoot(); + + void moveTo(const Pos2& to); + + private: + using Function = void(SilverHawk::*)(float); + goblib::lgfx::GCellSprite4* _sprite; + Function _function; + PartialAnimation _burner; + Pos2 _from, _to; + bool _enable; + goblib::ObjectPool _bullets; +}; + +#endif diff --git a/src/renderer.cpp b/src/renderer.cpp new file mode 100644 index 0000000..9b97943 --- /dev/null +++ b/src/renderer.cpp @@ -0,0 +1,32 @@ +/*! + TinyDarius + + @file renderer.cpp + @brief utility for rendering +*/ +#include +#include "renderer.hpp" +#include +using goblib::lgfx::GSprite; + +ScopedClip::ScopedClip(GSprite& target, const Rect2& clip, const std::int32_t yorigin) + : _sprite(target), _rect() +{ + std::int32_t x,y,w,h; + _sprite.getClipRect(&x, &y, &w, &h); + auto r = _rect = decltype(_rect)(x, y, w, h); + r.offset(0, -yorigin); + + _clip = r & clip; + _clip.offset(0, yorigin); + if(_clip.valid()) + { + _sprite.setClipRect(_clip.left(), _clip.top(), _clip.width(), _clip.height()); + } +} + +ScopedClip::~ScopedClip() +{ + // restore clip rect. + _sprite.setClipRect(_rect.left(), _rect.top(), _rect.width(), _rect.height()); +} diff --git a/src/renderer.hpp b/src/renderer.hpp new file mode 100644 index 0000000..979d21b --- /dev/null +++ b/src/renderer.hpp @@ -0,0 +1,36 @@ +/*! + TinyDarius + + @file renderer.hpp + @brief utility for rendering +*/ +#pragma once +#ifndef TD_RENDERER_HPP +#define TD_RENDERER_HPP + +#include +#include +#include "typedef.hpp" + +// Argument for render +struct RenderArg +{ + std::int32_t yorigin; + goblib::lgfx::GSprite* sprite; +}; + +// Clip sprite +struct ScopedClip +{ + public: + ScopedClip(goblib::lgfx::GSprite& target, const Rect2& clip, const std::int32_t yorigin); + ~ScopedClip(); + + Rect2 clip() const { return _clip; } + + private: + goblib::lgfx::GSprite& _sprite; + Rect2 _rect, _clip; +}; + +#endif diff --git a/src/scene/scene_advertise.cpp b/src/scene/scene_advertise.cpp new file mode 100644 index 0000000..b38b8e9 --- /dev/null +++ b/src/scene/scene_advertise.cpp @@ -0,0 +1,107 @@ +/*! + TinyDarius + + @file scene_advertise.cpp + @brief Advertise +*/ +#include "scene_advertise.hpp" +#include "../app.hpp" +#include "../constants.hpp" +#include "../renderer.hpp" +#include "../version.hpp" +#include "../utility.hpp" +using goblib::SceneTask; + +#include +#include +using goblib::lgfx::GSprite; +#include +using goblib::m5s::FaceGB; + +namespace +{ +constexpr const char* BITMAP_PATH = "res/td/logo.bmp"; +} + +SceneAdvertise::SceneAdvertise() + : SceneTask(SCENE_ADVERTISE, PRIORITY_ADVERTISE, "advertise") + , RenderObj2D(ORDER_ADVERTISEE) + , _sprite(nullptr) +{ + _sprite = new goblib::lgfx::GCellSprite4(); + assert(_sprite); +} + +SceneAdvertise:: ~SceneAdvertise() +{ + goblib::safeDelete(_sprite); +} + +bool SceneAdvertise::onInitialize() +{ + if(createFromBitmap(*_sprite, BITMAP_PATH)) + { + _sprite->makePaletteTable256(); + return true; + } + kill(); + return false; +} + +bool SceneAdvertise::onRelease() +{ + return true; +} + +void SceneAdvertise::onExecute(const float delta) +{ + FaceGB& input = TinyDarius::instance().input(); + if(input.wasPressed(FaceGB::Button::Start) && TinyDarius::instance().credits()) + { + popScene(); + TinyDarius::instance().startGame(); + } +#ifndef NDEBUG + else if(input.wasPressed(FaceGB::Button::B)) + { + popScene(); + } +#endif +} + +void SceneAdvertise::onEnterCurrentScene(SceneType pre, bool resume) +{ + TinyDarius::instance().insertRenderObj(this); +} + +void SceneAdvertise::onLeaveCurrentScene(SceneType cur) +{ + TinyDarius::instance().removeRenderObj(this); +} + +void SceneAdvertise::render(void* arg) +{ + RenderArg* rarg = static_cast(arg); + GSprite* target = rarg->sprite; + + _sprite->pushSpriteTo16(target, (SCREEN_WIDTH - _sprite->width()) / 2, 64 + rarg->yorigin, 0); + + auto old = target->getTextDatum(); + + target->setTextSize(3); + target->setTextColor(CLR_BLACK); // shadow + target->drawString("TINY",16+2, 56+2 + rarg->yorigin); + target->setTextColor(CLR_WHITE); + target->drawString("TINY",16+0, 56+0 + rarg->yorigin); + + target->setTextSize(1); + const char m[] = "DEVELOPED ON M5STACK"; + std::size_t slen = goblib::size(m); + target->drawString(m, (SCREEN_WIDTH - 8*slen)/2, 152 + rarg->yorigin); + + const char v[] = TD_VERSION_STRING; + slen = goblib::size(v); + target->drawString(v, (SCREEN_WIDTH - 8*slen)/2, 160 + rarg->yorigin); + + target->setTextDatum(old); +} diff --git a/src/scene/scene_advertise.hpp b/src/scene/scene_advertise.hpp new file mode 100644 index 0000000..61978fb --- /dev/null +++ b/src/scene/scene_advertise.hpp @@ -0,0 +1,40 @@ +/*! + TinyDarius + + @file scene_advertise.hpp + @brief Advertise +*/ +#pragma once +#ifndef TD_SCENE_ADVERTISE_HPP +#define TD_SCENE_ADVERTISE_HPP + +#include +#include +#include + +namespace goblib { namespace lgfx { +class GSprite4; +}} + +class SceneAdvertise : public goblib::SceneTask, public goblib::graph::RenderObj2D +{ + public: + SceneAdvertise(); + virtual ~SceneAdvertise(); + + virtual void render(void* arg) override; + + protected: + virtual void onUnchain() override { delete this; } + virtual bool onInitialize() override; + virtual bool onRelease() override; + virtual void onExecute(const float delta) override; + + virtual void onEnterCurrentScene(SceneType pre, bool resume) override; + virtual void onLeaveCurrentScene(SceneType cur) override; + + private: + goblib::lgfx::GSprite4* _sprite; +}; + +#endif diff --git a/src/scene/scene_debug.cpp b/src/scene/scene_debug.cpp new file mode 100644 index 0000000..fc589c9 --- /dev/null +++ b/src/scene/scene_debug.cpp @@ -0,0 +1,166 @@ +/*! + TinyDarius + + @file scene_debug.cpp + @brief For debug +*/ +#ifndef NDEBUG + +#include +#include "scene_debug.hpp" +#include "scene_debug_sound.hpp" +#include "scene_game.hpp" +#include "scene_advertise.hpp" +#include "../renderer.hpp" +#include "../app.hpp" +#include "../game.hpp" +#include "../constants.hpp" +#include "../stage.hpp" +#include "../math_table.hpp" + +#include +using goblib::lgfx::GSprite; +#include +using goblib::m5s::FaceGB; +#include + +#include +#include +#include +#include +#include + + +namespace +{ +constexpr const char* MENU_TBL[] = +{ + "ADVERTISE(ENTRY ON MASTER BUILD)", + "STAGE:", + "SOUND TEST", +}; +// +} + +SceneDebug::SceneDebug() + : goblib::SceneTask(SCENE_DEBUG, PRIORITY_DEBUG, "debug") + , goblib::graph::RenderObj2D(ORDER_DEBUG) + , _cursorY(0) ,_stageIndex(0), _function(nullptr) +{} + +bool SceneDebug::onInitialize() +{ + return true; +} + +bool SceneDebug::onRelease() +{ + return true; +} + +void SceneDebug::onExecute(const float delta) +{ + Function f_tbl[] = + { + &SceneDebug::onExecuteAdvertise, + &SceneDebug::onExecuteStage, + &SceneDebug::onExecuteSound, + }; + + FaceGB& input = TinyDarius::instance().input(); + + if(input.wasPressed(FaceGB::Button::Up)) + { + _cursorY = (_cursorY + Menu::Max - 1) % Menu::Max; + } + else if(input.wasPressed(FaceGB::Button::Down)) + { + _cursorY = (_cursorY + 1) % Menu::Max; + } + + _function = f_tbl[_cursorY]; + (this->*_function)(); +} + +void SceneDebug::onExecuteStage() +{ + FaceGB& input = TinyDarius::instance().input(); + + if(input.wasRepeated(FaceGB::Button::Left)) + { + _stageIndex = (_stageIndex - 1 + Stage::MAX) % Stage::MAX; + } + if(input.wasRepeated(FaceGB::Button::Right)) + { + _stageIndex = (_stageIndex + 1) % Stage::MAX; + } + + if(input.wasPressed(FaceGB::Button::A)) + { + TinyDarius::instance().game().start(_stageIndex); + pushScene(new SceneGame( Stage::table[_stageIndex] ) ); + } + +} + +void SceneDebug::onExecuteSound() +{ + FaceGB& input = TinyDarius::instance().input(); + + if(input.wasPressed(FaceGB::Button::A)) + { + pushScene(new SceneDebugSound() ); + } +} + +void SceneDebug::onExecuteAdvertise() +{ + FaceGB& input = TinyDarius::instance().input(); + + if(input.wasPressed(FaceGB::Button::A)) + { + pushScene(new SceneAdvertise() ); + } +} + + +void SceneDebug::onEnterCurrentScene(SceneType pre, bool resume) +{ + TinyDarius::instance().insertRenderObj(this); +} + +void SceneDebug::onLeaveCurrentScene(SceneType cur) +{ + TinyDarius::instance().removeRenderObj(this); +} + +void SceneDebug::render(void* arg) +{ + RenderArg* rarg = static_cast(arg); + GSprite* sprite = rarg->sprite; + + sprite->setTextColor(TFT_WHITE, TFT_BLACK); + sprite->setCursor(16,32 + rarg->yorigin); + sprite->printf("DEBUG MENU"); + + std::int32_t y = 48 + rarg->yorigin; + + sprite->setCursor(16, y + _cursorY * 8); + sprite->printf("#"); + + for(std::int32_t i = 0; i < goblib::size(MENU_TBL); ++i) + { + sprite->setCursor(24, y); + sprite->printf(MENU_TBL[i]); + sprite->setCursor(24 + 48, y); + switch(i) + { + case Menu::Stage: + sprite->printf("%c:(%u)", Stage::table[_stageIndex].toChar(), _stageIndex); + break; + } + y += 8; + } +} + +#endif diff --git a/src/scene/scene_debug.hpp b/src/scene/scene_debug.hpp new file mode 100644 index 0000000..41efc73 --- /dev/null +++ b/src/scene/scene_debug.hpp @@ -0,0 +1,47 @@ +/*! + TinyDarius + + @file scene_debug.hpp + @brief For debug +*/ +#pragma once +#ifndef TD_SCENE_DEBUG_HPP +#define TD_SCENE_DEBUG_HPP + +#ifndef NDEBUG + +#include +#include + +class SceneDebug : public goblib::SceneTask, public goblib::graph::RenderObj2D +{ + public: + SceneDebug(); + virtual ~SceneDebug(){} + + virtual void render(void* arg) override; + + protected: + virtual void onUnchain() override { delete this; } + virtual bool onInitialize() override; + virtual bool onRelease() override; + virtual void onExecute(const float delta) override; + + virtual void onEnterCurrentScene(SceneType pre, bool resume) override; + virtual void onLeaveCurrentScene(SceneType cur) override; + + void onExecuteStage(); + void onExecuteSound(); + void onExecuteAdvertise(); + + using Function = void(SceneDebug::*)(void); + + private: + enum Menu : int32_t { Advertise, Stage, Sound, Max }; + std::uint32_t _cursorY; + std::int32_t _stageIndex; + Function _function; +}; + +#endif +#endif diff --git a/src/scene/scene_debug_sound.cpp b/src/scene/scene_debug_sound.cpp new file mode 100644 index 0000000..cb8e152 --- /dev/null +++ b/src/scene/scene_debug_sound.cpp @@ -0,0 +1,176 @@ +/*! + TinyDarius + + @file scene_debug_sound.cpp + @brief Sound test +*/ +#ifndef NDEBUG + +#include +#include "scene_debug_sound.hpp" +#include "../renderer.hpp" +#include "../app.hpp" +#include "../sound.hpp" +#include +using goblib::m5s::FaceGB; +#include +using goblib::lgfx::GSprite; +#include + +namespace +{ +constexpr const char *BGM_TABLE[BGM::Max] = +{ + "INSERT COIN", + "INSERT COIN B", + "CAPTAIN NEO", + "CHAOS MAIN THEME", + "COSMIC AIRWAY", + "INORGANIC BEAT", + "THE SEA", + "WARNING", + "BOSS 1", + "BOSS 2", + "BOSS 3", + "BOSS 4", + "BOSS 5", + "BOSS 6", + "BOSS 7", + "ROUND CLEAR", + "ENDING", + "NAME", + "GAMEOVER", +}; + +constexpr const char* MENU_TABLE[] = +{ + "NAME:", + "GAIN:", + " RETURN", +}; +} + +bool SceneDebugSound::onInitialize() +{ + TinyDarius::instance().insertRenderObj(this); + return true; +} + +bool SceneDebugSound::onRelease() +{ + TinyDarius::instance().removeRenderObj(this); + return true; +} + +void SceneDebugSound::onExecute(const float delta) +{ + Function f_tbl[] = + { + &SceneDebugSound::onExecuteBgm, + &SceneDebugSound::onExecuteGain, + &SceneDebugSound::onExecuteReturn, + }; + + FaceGB& input = TinyDarius::instance().input(); + + if(input.wasPressed(FaceGB::Button::Up)) + { + _cursorY = (_cursorY + Menu::Max - 1) % Menu::Max; + } + else if(input.wasPressed(FaceGB::Button::Down)) + { + _cursorY = (_cursorY + 1) % Menu::Max; + } + + _function = f_tbl[_cursorY]; + (this->*_function)(); +} + +void SceneDebugSound::onExecuteBgm() +{ + FaceGB& input = TinyDarius::instance().input(); + + if(input.wasPressed(FaceGB::Button::Left)) + { + _bgm = (_bgm + BGM::Max - 1) % BGM::Max; + } + else if(input.wasPressed(FaceGB::Button::Right)) + { + _bgm = (_bgm + 1) % BGM::Max; + } + if(input.wasPressedEqual(FaceGB::Button::A)) + { + SoundSystem::instance().playBgm(static_cast(_bgm)); + } + if(input.wasPressedEqual(FaceGB::Button::B)) + { + SoundSystem::instance().stopBgm(); + } + +} + +void SceneDebugSound::onExecuteGain() +{ + FaceGB& input = TinyDarius::instance().input(); + SoundSystem& ss = SoundSystem::instance(); + + if(input.wasPressed(FaceGB::Button::Left)) + { + ss.setVolume(ss.volume() - 0.1f); + } + else if(input.wasPressed(FaceGB::Button::Right)) + { + ss.setVolume(ss.volume() + 0.1f); + } +} + +void SceneDebugSound::onExecuteReturn() +{ + FaceGB& input = TinyDarius::instance().input(); + if(input.wasPressed(FaceGB::Button::A)) + { + SoundSystem::instance().stopBgm(); + popScene(); + } +} + +void SceneDebugSound::render(void* arg) +{ + RenderArg* rarg = static_cast(arg); + GSprite* sprite = rarg->sprite; + SoundSystem& ss = SoundSystem::instance(); + + sprite->setTextColor(TFT_WHITE, TFT_BLACK); + sprite->setCursor(16,32 + rarg->yorigin); + sprite->printf("SOUND TEST"); + + std::int32_t y = 48 + rarg->yorigin; + + sprite->setCursor(16, y + _cursorY * 8); + sprite->printf("#"); + + for(std::int32_t i = 0; i < goblib::size(MENU_TABLE); ++i) + { + sprite->setCursor(24, y); + sprite->printf(MENU_TABLE[i]); + sprite->setCursor(24 + 48, y); + switch(i) + { + case Menu::Bgm: + sprite->printf(BGM_TABLE[_bgm]); + break; + case Menu::Gain: + { + float fi, fp; + fp = std::modf(ss.volume(), &fi); + sprite->printf("%d.%02d", (int)fi, int(fp*100)); + } + break; + } + y += 8; + } + + sprite->setCursor(16, 120 + rarg->yorigin); + sprite->printf("PLAYABLE:%zu AVAILABLE:%zu\n", ss.playable(), ss.available()); +} +#endif diff --git a/src/scene/scene_debug_sound.hpp b/src/scene/scene_debug_sound.hpp new file mode 100644 index 0000000..b01ed46 --- /dev/null +++ b/src/scene/scene_debug_sound.hpp @@ -0,0 +1,51 @@ +/*! + TinyDarius + + @file scene_debug_sound.hpp + @brief Sound test +*/ +#pragma once +#ifndef TD_SCENE_DEBUG_SOUND_HPP +#define TD_SCENE_DEBUG_SOUND_HPP + +#ifndef NDEBUG + +#include "../constants.hpp" +#include +#include + + +class SceneDebugSound : public goblib::SceneTask, public goblib::graph::RenderObj2D +{ + public: + SceneDebugSound() : goblib::SceneTask(SCENE_DEBUG_SOUND, PRIORITY_DEBUG, "debug_sound") + , goblib::graph::RenderObj2D(ORDER_DEBUG) + , _bgm(BGM::InsertCoin) + , _cursorY(0) + , _function(&SceneDebugSound::onExecuteBgm) + {} + virtual ~SceneDebugSound(){} + + virtual void render(void* arg) override; + + protected: + virtual void onUnchain() override { printf("%s\n", __PRETTY_FUNCTION__); delete this; } + virtual bool onInitialize() override; + virtual bool onRelease() override; + virtual void onExecute(const float delta) override; + + void onExecuteBgm(); + void onExecuteGain(); + void onExecuteReturn(); + using Function = void(SceneDebugSound::*)(void); + + private: + enum Menu : int32_t { Bgm, Gain, Return, Max }; + std::underlying_type::type _bgm; + std::int32_t _cursorY; + Function _function; + +}; + +#endif +#endif diff --git a/src/scene/scene_game.cpp b/src/scene/scene_game.cpp new file mode 100644 index 0000000..fb5dbff --- /dev/null +++ b/src/scene/scene_game.cpp @@ -0,0 +1,130 @@ +/*! +TinyDarius + + @file scene_game.cpp + @brief Game main +*/ +#include "scene_game.hpp" +#include "scene_stage.hpp" +#include "../debug.hpp" +#include "../player/silver_hawk.hpp" +#include "../enemy/enemy.hpp" +#include "../effect/explosion.hpp" +#include "../app.hpp" +#include "../game.hpp" + +#include +#include +#include + +#if 0 +#include "../sound.hpp" +#include "../typedef.hpp" +#include "../constants.hpp" + +#include "../boss/boss.hpp" +#include "../background/rock_surface.hpp" +#include "../background/wave_spot.hpp" +#include "../background/branch_rock.hpp" +#include "../info/information.hpp" +#include "../info/message.hpp" +#include "../info/name_entry.hpp" +#include "../background/mask.hpp" +#include "../effect/block_mask.hpp" + +#include +#include + + +#include +#include +#endif + +SceneGame::SceneGame() + : goblib::SceneTask(SCENE_GAME, PRIORITY_GAME, "game") + , _player(nullptr) +#ifndef NDEBUG + , _firstStage(nullptr) +#endif +{ + Explosion::setup(); + Enemy::setup(); + _player = new SilverHawk(); + assert(_player); + _player->hide(); +} + +#ifndef NDEBUG +SceneGame::SceneGame(const Stage& stage) + : goblib::SceneTask(SCENE_GAME, PRIORITY_GAME, "game") + , _player(nullptr) + , _firstStage(&stage) +{ + Explosion::setup(); + Enemy::setup(); + _player = new SilverHawk(); + assert(_player); + _player->hide(); +} +#endif + +SceneGame:: ~SceneGame() +{ + // TD_PRINT_FUNCTION(); + Enemy::finalize(); + Explosion::finalize(); +} + +void SceneGame::onUnchain() +{ + // TD_PRINT_FUNCTION(); + delete this; +} + +bool SceneGame::onInitialize() +{ + TinyDarius& app = TinyDarius::instance(); + app.reserveInsertNode(_player, this); + + auto s = +#ifndef NDEBUG + new SceneStage(*this, _firstStage ? *_firstStage : app.game().stage(), *_player); +#else + new SceneStage(*this, app.game().stage(), *_player); +#endif + assert(s); + app.reserveInsertNode(s, this); + + return true; +} + +bool SceneGame::onRelease() +{ + return true; +} + +void SceneGame::onExecute(const float delta) +{ +} + +void SceneGame::onReceive (const goblib::TaskMessage& msg) +{ + switch(msg.msg) + { + case MSG_ROUND_CLEAR: + { + TinyDarius& app = TinyDarius::instance(); + app.game().chooseNext( static_cast(msg.arg) ); + auto s = new SceneStage(*this, app.game().stage(), *_player, false); + assert(s); + app.reserveInsertNode(s, this); + } + break; + case MSG_GAME_OVER: + popScene(); + TinyDarius::instance().endGame(); + break; + } + + +} diff --git a/src/scene/scene_game.hpp b/src/scene/scene_game.hpp new file mode 100644 index 0000000..7aa9aa9 --- /dev/null +++ b/src/scene/scene_game.hpp @@ -0,0 +1,44 @@ +/*! + TinyDarius + + @file scene_game.hpp + @brief Game main +*/ +#pragma once +#ifndef TD_SCENE_GAME_HPP +#define TD_SCENE_GAME_HPP + +#include +#ifndef NDEBUG +#include "../stage.hpp" +#endif + +class SilverHawk; + +// Management of shared objects at each stage. +class SceneGame : public goblib::SceneTask +{ + public: + SceneGame(); +#ifndef NDEBUG + explicit SceneGame(const Stage& stage); +#endif + virtual ~SceneGame(); + + protected: + virtual void onUnchain() override; + virtual bool onInitialize() override; + virtual bool onRelease() override; + virtual void onReceive (const goblib::TaskMessage& /*msg*/) override; + virtual void onExecute(const float delta) override; + + private: + SilverHawk* _player; +#ifndef NDEBUG + const Stage* _firstStage; +#endif + + +}; + +#endif diff --git a/src/scene/scene_stage.cpp b/src/scene/scene_stage.cpp new file mode 100644 index 0000000..3a25340 --- /dev/null +++ b/src/scene/scene_stage.cpp @@ -0,0 +1,889 @@ +/*! + TinyDarius + + @file scene_stage.cpp + @brief Stage +*/ +#include "scene_stage.hpp" +#include "scene_game.hpp" +#include "../debug.hpp" +#include "../app.hpp" +#include "../game.hpp" +#include "../sound.hpp" +#include "../typedef.hpp" +#include "../constants.hpp" +#include "../player/silver_hawk.hpp" +#include "../boss/boss.hpp" +#include "../background/rock_surface.hpp" +#include "../background/wave_spot.hpp" +#include "../background/branch_rock.hpp" +#include "../info/information.hpp" +#include "../info/message.hpp" +#include "../info/name_entry.hpp" +#include "../background/mask.hpp" +#include "../effect/block_mask.hpp" +#include "../enemy/enemy.hpp" +#include "../effect/explosion.hpp" + +#include +#include +#include + +#include +#include + +namespace +{ +constexpr Pos2i UPPER_ROCK_POS(0, 24); +constexpr Pos2i LOWER_ROCK_POS(128,200); + +constexpr std::int16_t MESSAGE_POS_Y[] = +{ + 88, // for Blink message + 120, + 136, + 152, + 168 // for remain bonus pts +}; + +constexpr const char* WARNING_MESSAGE[] = +{ + "WARNING!", // blink + "A HUGE BATTLESHIP", + "", // boss name + "IS APPROACHING FAST." +}; + +constexpr const char* ROUND_CLEAR_MESSAGE[] = +{ + "\"%c\" ZONE IS OVER", // blink + "WE ARE NOW RUSHING INTO \"%c\" ZONE.", + "BE ON YOUR GUARD", +}; + +constexpr const char* GAME_CLEAR_MESSAGE[] = +{ + "FINAL ZONE IS OVER", // blink + "YOU MADE IT!", + "REMAINED PLAYER BONUS", + /* remained bonus points */ +}; + +constexpr const char* TIME_UP_MESSAGE[] = +{ + "TIMEUP!", // blink + "A HUGE BATTLESHIP", + "", // bossname + "HAS BEEN ESCAPED." +}; + +constexpr const char* GAMEOVER_MESSAGE = "GAME OVER"; +// +} + +SceneStage::SceneStage(SceneGame& sg, const Stage& stage, SilverHawk& player, bool firstStage) + : goblib::SceneTask(SCENE_STAGE, PRIORITY_STAGE, "stage") + , _sgame(sg) + , _stage(stage) + , _player(player) + , _boss(nullptr) + , _upperSurface(nullptr) + , _lowerSurface(nullptr) + , _wavespot(nullptr) + , _branch(nullptr) + , _info(nullptr) + , _mask{} + , _mineGenerator(nullptr) + , _yazukaGenerator(nullptr) + , _blockMask(nullptr) + , _bmessage(nullptr) + , _cmessage(nullptr) + , _rmessage(nullptr) + , _entry(nullptr) + , _function(&SceneStage::nop) + , _counter(0) + , _firstStage(firstStage) + , _function_of_dead(&SceneStage::nop) +#ifndef NDEBUG + , _leak(__PRETTY_FUNCTION__, __LINE__, false) +#endif +{ + _boss = Boss::create(stage.current); + assert(_boss); + _boss->pause(); + _boss->hide(); + + _upperSurface = new RockSurfaceUpper(UPPER_ROCK_POS.x(), UPPER_ROCK_POS.y()); + assert(_upperSurface); + _lowerSurface = new RockSurfaceLower(LOWER_ROCK_POS.x(), LOWER_ROCK_POS.y()); + assert(_lowerSurface); + + _wavespot = new WaveSpot(); + assert(_wavespot); + + _branch = new BranchRock(); + assert(_branch); + + _info = new Information(); + assert(_info); + + _mask[0] = new Mask(Rect2(0, 0, FIELD_RECT.width(), FIELD_RECT.top())); + assert(_mask[0]); + _mask[1] = new Mask(Rect2(0, FIELD_RECT.bottom() + 1, FIELD_RECT.width(), SCREEN_RECT.bottom() - FIELD_RECT.bottom())); + assert(_mask[1]); + + // + _mineGenerator = new MineGenerator(*this); + assert(_mineGenerator); + _yazukaGenerator = new YazukaGenerator(*this); + assert(_yazukaGenerator); +} + +SceneStage:: ~SceneStage() +{ + TD_PRINT_FUNCTION(); + goblib::safeDelete(_yazukaGenerator); + goblib::safeDelete(_mineGenerator); +} + +void SceneStage::onUnchain() +{ + TD_PRINT_FUNCTION(); + delete this; +} + +bool SceneStage::onInitialize() +{ + TD_PRINT_FUNCTION(); + TinyDarius& app = TinyDarius::instance(); + + app.reserveInsertNode(_boss, this); + app.reserveInsertNode(_upperSurface, this); + app.reserveInsertNode(_lowerSurface, this); + app.reserveInsertNode(_wavespot, this); + app.reserveInsertNode(_branch, this); + app.reserveInsertNode(_info, this); + app.reserveInsertNode(_mask[0], this); + app.reserveInsertNode(_mask[1], this); + + if(_firstStage) + { + start(); + } + else + { + rewind(); + } + return true; +} + +bool SceneStage::onRelease() +{ + TD_PRINT_FUNCTION(); + SoundSystem::instance().stop(); + return true; +} + +void SceneStage::onReceive (const goblib::TaskMessage& msg) +{ + switch(msg.msg) + { + case MSG_DEFEAT_BOSS: + if(_player.alive()) + { + defeatBoss(); + } + break; + } +} + +void SceneStage::onExecute(const float delta) +{ + (this->*_function)(delta); + hitCheck(); + ++_counter; +} + +// Phase: start +void SceneStage::start() +{ + TD_PRINTF("====%s:%d\n", __PRETTY_FUNCTION__, _player.visible()); + _counter = 0; + _player.restart(); + _function = &SceneStage::onStart; + + _upperSurface->setVelocity(RockSurfaceUpper::Velocity::Normal); + _lowerSurface->setVelocity(RockSurfaceUpper::Velocity::Normal); + _upperSurface->pause(); + _lowerSurface->pause(); + + _wavespot->pause(); + _wavespot->hide(); + + _branch->pause(); + _branch->hide(); + + _blockMask = new BlockMask(); + assert(_blockMask); + TinyDarius::instance().reserveInsertNode(_blockMask, this); + + SoundSystem::instance().playBgm(_stage.bgm); +} + +void SceneStage::onStart(const float) +{ + if(!_player.visible() && _blockMask->isExecute()) + { + _player.show(); + } + + if(_blockMask && !_blockMask->busy()) + { + idle(); + } +} + +// Phase: rewind +void SceneStage::rewind() +{ + TD_PRINTF("====%s\n", __PRETTY_FUNCTION__); + _counter = 0; + _player.rewind(); + _function = &SceneStage::onRewind; + + _branch->hide(); + _wavespot->pause(); + _wavespot->hide(); + + SoundSystem::instance().playBgm(_stage.bgm); +} + +void SceneStage::onRewind(const float) +{ + if(_counter >= 3_fsec) + { + idle(); + } +} + +// Phase: restart +void SceneStage::restart() +{ + TD_PRINTF("====%s\n", __PRETTY_FUNCTION__); + _counter = 0; + _function = &SceneStage::onRestart; + + _player.restart(); + _player.show(); + + _boss->hideEffect(); + _lowerSurface->show(); + _upperSurface->show(); + _wavespot->show(); + + _branch->show(_function_of_dead == &SceneStage::onBranch); + + _blockMask = new BlockMask(); + assert(_blockMask); + TinyDarius::instance().reserveInsertNode(_blockMask, this); +} + +void SceneStage::onRestart(const float) +{ + if(_blockMask && !_blockMask->busy()) + { + if(_function_of_dead == &SceneStage::onDefeatBoss) + { + TD_PRINTF("%s:Restart defeated\n", __PRETTY_FUNCTION__); + printf("Restart:defeat\n"); + _player.enable(); + _upperSurface->resume(); + _lowerSurface->resume(); + _wavespot->resume(); + branch(); + SoundSystem::instance().playBgm(BGM::RoundClear); + }else if(_function_of_dead == &SceneStage::onBranch) + { + TD_PRINTF("%s:Restart branch\n", __PRETTY_FUNCTION__); + _branch->resume(); + _upperSurface->resume(); + _lowerSurface->resume(); + _wavespot->resume(); + _function = _function_of_dead; + SoundSystem::instance().playBgm(BGM::RoundClear); + } + else + { + TD_PRINTF("%s:Restart battle\n", __PRETTY_FUNCTION__); + _upperSurface->resume(); + _lowerSurface->resume(); + _wavespot->resume(); + battleEnemy(true); + SoundSystem::instance().playBgm(_stage.bossBgm()); + } + _function_of_dead = nullptr; + } +} + +// Phase: idle +void SceneStage::idle() +{ + TD_PRINTF("====%s\n", __PRETTY_FUNCTION__); + _counter = 0; + _player.restart(); + _function = &SceneStage::onIdle; + + _upperSurface->resume(); + _lowerSurface->resume(); +} + +void SceneStage::onIdle(const float) +{ +#ifdef NDEBUG + if(_counter >= 5_fsec) +#else + if(_counter >= 1_fsec) +#endif + { + warning(); + } +} + +// Phase: warning +void SceneStage::warning() +{ + TD_PRINTF("====%s\n", __PRETTY_FUNCTION__); + + TinyDarius& app = TinyDarius::instance(); + + _counter = 0; + _function = &SceneStage::onWarning; + + _bmessage = new BlinkingMessage(WARNING_MESSAGE[0], MESSAGE_POS_Y[0], CLR_WHITE); + assert(_bmessage); + app.reserveInsertNode(_bmessage, this); + + _cmessage = new CenteringMessage(); + assert(_cmessage); + char tmp[64]; + snprintf(tmp, sizeof(tmp), "%s-%c", _boss->name(), _stage.toChar()); + _cmessage->insert(WARNING_MESSAGE[1], MESSAGE_POS_Y[1], CLR_WHITE); + _cmessage->insert(tmp, MESSAGE_POS_Y[2], CLR_RED); + _cmessage->insert(WARNING_MESSAGE[3], MESSAGE_POS_Y[3], CLR_WHITE); + app.reserveInsertNode(_cmessage, this); + + _upperSurface->animatedPalette()->fadeOut(60); + _lowerSurface->animatedPalette()->fadeOut(60); + + SoundSystem::instance().playBgm(BGM::Warning); +} + +void SceneStage::onWarning(const float) +{ + if(_counter >= 9_fsec) + { + battleEnemy(); + } +} + +// Phase battle Enemy +void SceneStage::battleEnemy(bool restart) +{ + TD_PRINTF("====%s\n", __PRETTY_FUNCTION__); + _counter = 0; + _function = &SceneStage::onBattleEnemy; + + if(_bmessage) + { + _bmessage->release(); + _bmessage = nullptr; + } + if(_cmessage) + { + _cmessage->release(); + _cmessage = nullptr; + } + + if(!restart) + { + _upperSurface->setVelocity(RockSurfaceUpper::Velocity::Boss); + _lowerSurface->setVelocity(RockSurfaceUpper::Velocity::Boss); + _upperSurface->resume(); + _lowerSurface->resume(); + _wavespot->resume(); + _wavespot->show(); + + _upperSurface->animatedPalette()->fadeAll(60); + _lowerSurface->animatedPalette()->fadeAll(60); + _wavespot->animatedPalette()->fadeIn(60); + } + + TinyDarius::instance().game().resume(); + _player.enable(); + + _mineGenerator->spawn(); + + SoundSystem::instance().playBgm(_stage.bossBgm()); +} + +void SceneStage::onBattleEnemy(const float delta) +{ + if(_counter >= 10_fsec) + { + appearBoss(); + } +} + +// Phase apper boss +void SceneStage::appearBoss() +{ + TD_PRINTF("====%s\n", __PRETTY_FUNCTION__); + _counter = 0; + _function = &SceneStage::onAppearBoss; + + _boss->appear(); +} + +void SceneStage::onAppearBoss(const float) +{ + if(_boss->status() == Boss::Status::Battle) + { + battleBoss(); + } + +} + +// Phase: battle boss +void SceneStage::battleBoss() +{ + TD_PRINTF("====%s\n", __PRETTY_FUNCTION__); + _counter = 0; + _function = &SceneStage::onBattleBoss; + + _wavespot->animatedPalette()->clear(); + _wavespot->roundTripPalette(_stage.current); + +} + +void SceneStage::onBattleBoss(const float) +{ + if(TinyDarius::instance().game().isTimeup()) + { + escapeBoss(); + return; + } + + auto tm = TinyDarius::instance().game().remainingTime(); + if(tm <= 60_fsec) + { + if((tm % (6_fsec)) == 0) + { + _yazukaGenerator->spawn(); + } + } +} + +// Phase: defeat +void SceneStage::defeatBoss() +{ + TD_PRINTF("====%s\n", __PRETTY_FUNCTION__); + _counter = 0; + _function = &SceneStage::onDefeatBoss; + + vanishEnemies(); + + TinyDarius::instance().game().pause(); + SoundSystem::instance().stopBgm(); +} + +void SceneStage::onDefeatBoss(const float) +{ + if(_counter == 4_fsec) + { + SoundSystem::instance().playBgm(BGM::RoundClear); + } + + // Boss has been frameout. + if(_boss->isNop()) + { + if(_stage.isTail()) + { + gameClear(); + } + else + { + branch(); + } + } +} + +// Phase: escape +void SceneStage::escapeBoss() +{ + TD_PRINTF("====%s\n", __PRETTY_FUNCTION__); + _counter = 0; + _function = &SceneStage::onEscapeBoss; + + vanishEnemies(); + + TinyDarius::instance().game().pause(); + SoundSystem::instance().stopBgm(); + + TinyDarius& app = TinyDarius::instance(); + _bmessage = new BlinkingMessage(TIME_UP_MESSAGE[0], MESSAGE_POS_Y[0], CLR_WHITE); + assert(_bmessage); + app.reserveInsertNode(_bmessage, this); + + _cmessage = new CenteringMessage(); + assert(_cmessage); + char tmp[64]; + snprintf(tmp, sizeof(tmp), "%s-%c", _boss->name(), _stage.toChar()); + _cmessage->insert(TIME_UP_MESSAGE[1], MESSAGE_POS_Y[1], CLR_WHITE); + _cmessage->insert(tmp, MESSAGE_POS_Y[2], CLR_RED); + _cmessage->insert(TIME_UP_MESSAGE[3], MESSAGE_POS_Y[3], CLR_WHITE); + app.reserveInsertNode(_cmessage, this); + + _boss->escape(); + SoundSystem::instance().playBgm(BGM::Warning); +} + +void SceneStage::onEscapeBoss(const float) +{ + // Boss has been frameout. + if(_counter >= 9_fsec && _boss->isNop() && _cmessage->isFinish()) + { + SoundSystem::instance().playBgm(BGM::RoundClear); + if(_stage.isTail()) + { + gameClear(); + } + else + { + branch(); + } + } +} + +// Phase: branch +void SceneStage::branch() +{ + TD_PRINTF("====%s\n", __PRETTY_FUNCTION__); + _counter = 0; + _function = &SceneStage::onBranch; + + if(_bmessage) + { + _bmessage->release(); + _bmessage = nullptr; + } + if(_cmessage) + { + _cmessage->release(); + _cmessage = nullptr; + } +} + +void SceneStage::onBranch(const float) +{ + if(_counter >= 4_fsec) + { + _branch->resume(); + _branch->show(); + } + + if(_counter >= 8_fsec) + { + roundClear(); + } +} + +// Phase roundclear +void SceneStage::roundClear() +{ + TD_PRINTF("====%s\n", __PRETTY_FUNCTION__); + + _counter = 0; + _function = &SceneStage::onRoundClear; + + _player.disable(); + auto ns = _stage.next(); + auto no = _player.pos().y() < SCREEN_HEIGHT/2 ? ns.first : ns.second; + const Stage* nstage = &Stage::table[no]; + + TD_PRINTF("next: %d:%c\n", nstage->current, nstage->toChar()); + + TinyDarius& app = TinyDarius::instance(); + + char tmp[64]; + snprintf(tmp, sizeof(tmp), ROUND_CLEAR_MESSAGE[0], _stage.toChar()); + _bmessage = new BlinkingMessage(tmp, MESSAGE_POS_Y[0], CLR_WHITE); + assert(_bmessage); + app.reserveInsertNode(_bmessage, this); + + _cmessage = new CenteringMessage(); + assert(_cmessage); + snprintf(tmp, sizeof(tmp), ROUND_CLEAR_MESSAGE[1], nstage->toChar()); + _cmessage->insert(tmp, MESSAGE_POS_Y[1], CLR_WHITE); + _cmessage->insert(ROUND_CLEAR_MESSAGE[2], MESSAGE_POS_Y[2], CLR_WHITE); + app.reserveInsertNode(_cmessage, this); + + _wavespot->animatedPalette()->fadeOut(4_fsec); + _branch->animatedPalette()->fadeOut(4_fsec); +} + +void SceneStage::onRoundClear(const float) +{ + if(_counter >= 10_fsec) + { + // %%%%% + // TinyDarius::instance().game().chooseNext( _player.pos().y() < SCREEN_HEIGHT/2 ); + // popScene(); + goblib::TaskMessage msg(MSG_ROUND_CLEAR, _player.pos().y() < SCREEN_HEIGHT/2 ); + TinyDarius::instance().task().postMessage(msg, &_sgame); + release(true); + } +} + +// Phase: gameclear +void SceneStage::gameClear() +{ + TD_PRINTF("====%s\n", __PRETTY_FUNCTION__); + + _counter = 0; + _function = &SceneStage::onGameClear; + + _player.disable(); + + TinyDarius& app = TinyDarius::instance(); + + if(_bmessage) + { + _bmessage->release(); + _bmessage = nullptr; + } + if(_cmessage) + { + _cmessage->release(); + _cmessage = nullptr; + } + + _bmessage = new BlinkingMessage(GAME_CLEAR_MESSAGE[0], MESSAGE_POS_Y[0], CLR_WHITE); + assert(_bmessage); + app.reserveInsertNode(_bmessage, this); + + _cmessage = new CenteringMessage(); + _cmessage->insert(GAME_CLEAR_MESSAGE[1], MESSAGE_POS_Y[1], CLR_WHITE); + _cmessage->insert(GAME_CLEAR_MESSAGE[2], MESSAGE_POS_Y[2], CLR_WHITE); + + app.reserveInsertNode(_cmessage, this); + + _rmessage = new RemainBonusMessage(0, MESSAGE_POS_Y[4], CLR_WHITE); + assert(_rmessage); + _rmessage->pause(); + _rmessage->hide(); + app.reserveInsertNode(_rmessage, this); + + _upperSurface->animatedPalette()->fadeOut(2_fsec); + _lowerSurface->animatedPalette()->fadeOut(2_fsec); + _wavespot->animatedPalette()->fadeOut(2_fsec); +} + +void SceneStage::onGameClear(const float) +{ + if(_rmessage->isPause() && _cmessage->isFinish()) + { + _rmessage->resume(); + _rmessage->show(); + _counter = 0; + return; + } + + if(!_rmessage->isPause() && _counter % (2_fsec) == 0) + { + Game& game = TinyDarius::instance().game(); + if(game.remaining()) + { + Score s(PTS_REMAIN_BONUS); + s.insertObserver(game); + s.notify(); + _rmessage->increasePts(s.score()); + game.dead(); + } + else + { + nameEntry(); + } + } +} + +// Phase: dead +void SceneStage::dead() +{ + TD_PRINTF("====%s\n", __PRETTY_FUNCTION__); + _counter = 0; + _function_of_dead = _function; + _function = &SceneStage::onDead; + + // cancel fadein in onAppear + //_upperSurface->animatedPalette()->apply(); + //_lowerSurface->animatedPalette()->apply(); + + TinyDarius::instance().game().dead(); + TinyDarius::instance().game().pause(); +} + +void SceneStage::onDead(const float) +{ + if(_counter >= 2_fsec) + { + if(TinyDarius::instance().game().isGameover()) + { + nameEntry(); + return; + } + + _lowerSurface->pause(); + _upperSurface->pause(); + _wavespot->pause(); + _lowerSurface->hide(); + _upperSurface->hide(); + _wavespot->hide(); + + _branch->pause(); + _branch->hide(); + + _boss->hide(); + _boss->disableHit(); + _boss->pause(); + + releaseCategoryObj(CATEGORY_ENEMY | CATEGORY_ENEMY_BULLET); + restart(); + } +} + +// Phase: name entry +void SceneStage::nameEntry() +{ + TD_PRINTF("====%s\n", __PRETTY_FUNCTION__); + + if(_bmessage) + { + _bmessage->release(); + _bmessage = nullptr; + } + if(_cmessage) + { + _cmessage->release(); + _cmessage = nullptr; + } + if(_rmessage) + { + _rmessage->release(); + _rmessage = nullptr; + } + + _player.hide(); + // + // if(outofranking) { gameover(); return; } + // + + _counter = 0; + _function = &SceneStage::onNameEntry; + + _entry = new NameEntry(TinyDarius::instance().game().score(), 52 /* FIXME : dummy rank*/ , MESSAGE_POS_Y[1]); + assert(_entry); + TinyDarius::instance().reserveInsertNode(_entry, this); + + SoundSystem::instance().playBgm(BGM::Name); +} + +void SceneStage::onNameEntry(const float) +{ + if(_entry->isFinish()) + { + _entry->hide(); + _entry->release(); + _entry = nullptr; + gameover(); + } +} + +// Phase: gameover +void SceneStage::gameover() +{ + TD_PRINTF("====%s\n", __PRETTY_FUNCTION__); + _counter = 0; + _function = &SceneStage::onGameover; + + _player.disable(); + _player.hide(); + + TinyDarius& app = TinyDarius::instance(); + + _bmessage = new BlinkingMessage(GAMEOVER_MESSAGE, MESSAGE_POS_Y[2], CLR_WHITE, 0); + assert(_bmessage); + app.reserveInsertNode(_bmessage, this); + + SoundSystem::instance().playBgm(BGM::Gameover, false); +} + +void SceneStage::onGameover(const float) +{ + if(_counter >= 7_fsec) + { + goblib::TaskMessage msg(MSG_GAME_OVER); + TinyDarius::instance().task().postMessage(msg, &_sgame); + release(true); + } +} + +void SceneStage::hitCheck() +{ + if(!_player.isEnable()) { return; } + + GameObj::CollideObjects& objes = GameObj::collideObjects(); + + // Comment out if you want to make it invincible. +#if 1 + // player vs any + if(GameObj::checkHit(&_player)) + { + dead(); + return; + } +#endif + + // player bullets vs any + std::vector bullets; + std::copy_if(objes.begin(), objes.end(), std::back_inserter(bullets), + [](GameObj* e) + { + return e->category() == CATEGORY_BULLET; + }); + if(bullets.empty()) { return; } + + for(auto& e : bullets) + { + GameObj::checkHit(e); + } +} + +void SceneStage::releaseCategoryObj(std::uint32_t category) +{ + GameObj::CollideObjects& objes = GameObj::collideObjects(); + for(auto& e : objes) + { + if(e->category() & category) + { + e->release(); + } + } +} + +void SceneStage::vanishEnemies() +{ + goblib::TaskMessage msg; + msg.msg = MSG_VANISH_ENEMY; + msg.arg = this; + TinyDarius::instance().sendBroadcastMessage(msg, this); +} diff --git a/src/scene/scene_stage.hpp b/src/scene/scene_stage.hpp new file mode 100644 index 0000000..be0feb6 --- /dev/null +++ b/src/scene/scene_stage.hpp @@ -0,0 +1,125 @@ +/*! + TinyDarius + + @file scene_stage.hpp + @brief Stage +*/ +#pragma once +#ifndef TD_SCENE_STAGE_HPP +#define TD_SCENE_STAGE_HPP + +#include +#include "../stage.hpp" +#include + +class SceneGame; +class SilverHawk; +class Boss; +class RockSurfaceUpper; +class RockSurfaceLower; +class WaveSpot; +class BranchRock; +class Information; +class Mask; +class BlockMask; +class BlinkingMessage; +class CenteringMessage; +class RemainBonusMessage; +class NameEntry; +class MineGenerator; +class YazukaGenerator; + +class SceneStage : public goblib::SceneTask +{ + public: + SceneStage(SceneGame& sg, const Stage& stage, SilverHawk& player, bool firstStage = true); + virtual ~SceneStage(); + + protected: + virtual void onUnchain() override; + virtual bool onInitialize() override; + virtual bool onRelease() override; + virtual void onReceive (const goblib::TaskMessage& /*msg*/) override; + virtual void onExecute(const float delta) override; + + private: + void start(); + void rewind(); + void restart(); + void idle(); + void warning(); + void battleEnemy(bool restart = false); + void appearBoss(); + void battleBoss(); + void defeatBoss(); + void escapeBoss(); + void branch(); + void roundClear(); + void gameClear(); + void dead(); + void nameEntry(); + void gameover(); + + using phase_function = void(SceneStage::*)(const float); + void nop(const float) {} + void onStart(const float); + void onRewind(const float); + void onRestart(const float); + void onIdle(const float); + void onWarning(const float); + void onBattleEnemy(const float); + void onAppearBoss(const float); + void onBattleBoss(const float); + void onDefeatBoss(const float); + void onEscapeBoss(const float); + void onBranch(const float); + void onRoundClear(const float); + void onGameClear(const float); + void onGameover(const float); + void onNameEntry(const float); + void onDead(const float); + + void hitCheck(); + void releaseCategoryObj(std::uint32_t category); + void vanishEnemies(); + + private: + SceneGame& _sgame; + const Stage& _stage; + SilverHawk& _player; + + /// @name DON'T DELETE THEM in this class. + /// They are delete themselves. + /// @{ + Boss* _boss; + RockSurfaceUpper* _upperSurface; + RockSurfaceLower* _lowerSurface; + WaveSpot* _wavespot; + BranchRock* _branch; + Information* _info; + Mask* _mask[2]; + /// @} + + MineGenerator* _mineGenerator; + YazukaGenerator* _yazukaGenerator; + + /// @name DON'T DELETE THEM in this class. + /// @{ + BlockMask* _blockMask; + BlinkingMessage* _bmessage; + CenteringMessage* _cmessage; + RemainBonusMessage* _rmessage; + NameEntry* _entry; + /// @} + + phase_function _function; + std::uint32_t _counter; + bool _firstStage; + phase_function _function_of_dead; + +#ifndef NDEBUG + goblib::m5s::ScopedLeakCheck _leak; +#endif +}; + +#endif diff --git a/src/scene_manager.cpp b/src/scene_manager.cpp new file mode 100644 index 0000000..e69eef5 --- /dev/null +++ b/src/scene_manager.cpp @@ -0,0 +1,43 @@ +/*! + TinyDarius + + @file scene_manager.cpp + @brief Scene management +*/ +#include "scene_manager.hpp" +#include "constants.hpp" +#include "app.hpp" +#include "game.hpp" +#include "scene/scene_advertise.hpp" +#include "scene/scene_stage.hpp" + +void SceneManager::onEnterCurrentScene(goblib::SceneTask::SceneType cur, goblib::SceneTask::SceneType pre) +{ +} + +void SceneManager::onLeaveCurrentScene(goblib::SceneTask::SceneType cur, goblib::SceneTask::SceneType pre) +{ + // Postmessage!! +#if 0 + // if(cur == scene_debug) { return; } + + printf("%s: %u <= %u\n", __func__, cur, pre); + + if(pre == scene_stage) + { + Game& game = TinyDarius::instance().game(); + + if(game.isGameover()) + { + auto s = new SceneAdvertise(); + assert(s); + push(s); + return; + } + + auto s = new SceneStage(game.stage()); + assert(s); + push(s); + } +#endif +} diff --git a/src/scene_manager.hpp b/src/scene_manager.hpp new file mode 100644 index 0000000..e650532 --- /dev/null +++ b/src/scene_manager.hpp @@ -0,0 +1,24 @@ +/*! + TinyDarius + + @file scene_manager.hpp + @brief Scene management +*/ +#pragma once +#ifndef TD_SCENE_MANAGER_HPP +#define TD_SCENE_MANAGER_HPP + +#include + +class SceneManager : public goblib::SceneManageTask +{ + public: + SceneManager(goblib::TaskTree& tree, goblib::Task::PriorityType pri, goblib::Task* parent = nullptr) + : goblib::SceneManageTask(tree, pri, parent) {} + + protected: + virtual void onEnterCurrentScene(goblib::SceneTask::SceneType cur, goblib::SceneTask::SceneType pre); + virtual void onLeaveCurrentScene(goblib::SceneTask::SceneType cur, goblib::SceneTask::SceneType pre); +}; + +#endif diff --git a/src/score.hpp b/src/score.hpp new file mode 100644 index 0000000..9b2ef81 --- /dev/null +++ b/src/score.hpp @@ -0,0 +1,23 @@ +/*! + TinyDarius + + @file score.hpp + @brief Score subject +*/ + +#pragma once +#ifndef TD_SCORE_HPP +#define TD_SCORE_HPP + +#include + +class Score : public goblib::Subject +{ + public: + explicit Score(std::uint32_t pts) : _pts(pts) {} + GOBLIB_INLINE std::uint32_t score() const { return _pts; } + private: + const std::uint32_t _pts; +}; + +#endif diff --git a/src/sound.cpp b/src/sound.cpp new file mode 100644 index 0000000..5d5dc08 --- /dev/null +++ b/src/sound.cpp @@ -0,0 +1,192 @@ +/*! + TinyDarius + + @file sound.cpp + @brief Sound system +*/ +#include "sound.hpp" +#include "debug.hpp" +#include "app.hpp" +#include +#include +#include +#include + +namespace +{ +constexpr const char* BGM_PATH[BGM::Max] = +{ + "/res/td/wav/IC.wav", + "/res/td/wav/ICB.wav", + // + "/res/td/wav/CN.wav", + "/res/td/wav/CMT.wav", + "/res/td/wav/CAW.wav", + "/res/td/wav/IB.wav", + "/res/td/wav/TS.wav", + "/res/td/wav/W!.wav", + "/res/td/wav/B1.wav", + "/res/td/wav/B2.wav", + "/res/td/wav/B3.wav", + "/res/td/wav/B4.wav", + "/res/td/wav/B5.wav", + "/res/td/wav/B6.wav", + "/res/td/wav/B7.wav", + "/res/td/wav/RC.wav", + // + "/res/td/wav/ED.WAv", + "/res/td/wav/NAME.WAV", + "/res/td/wav/OVER.WAV", +}; +} + +using goblib::PcmStream; + +SoundSystem::SoundSystem() + : _speaker(nullptr) + , _bgmStream(nullptr) + , _creditStream(nullptr) + , _bgmPcm(nullptr) + , _creditPcm(nullptr) + , _pool(SIMULTANEOUS_PLAYS - 1) // exclude bgm + , _bgm(BGM::Max) // initialize by invalid value +{ + _speaker = new goblib::m5s::Speaker(SIMULTANEOUS_PLAYS); + assert(_speaker); + //setVolume(INITIAL_VOLUME); + setVolume(0.5f); + _speaker->setStreamingCallback(callbackStreaming, this); + _speaker->begin(); + + _bgmStream = new goblib::m5s::FileStream(); + assert(_bgmStream); + _creditStream = new goblib::m5s::FileStream(); + assert(_creditStream); + + _bgmPcm = new PcmStream(_bgmStream); + assert(_bgmPcm); + _creditPcm = new PcmStream(_creditStream); + assert(_creditPcm); +} + +SoundSystem::~SoundSystem() +{ + stop(); + goblib::safeDelete(_creditPcm); + goblib::safeDelete(_bgmPcm); + goblib::safeDelete(_creditStream); + goblib::safeDelete(_bgmStream); + goblib::safeDelete(_speaker); +} + + +void SoundSystem::setupGame() +{ +} + +void SoundSystem::finalizeGame() +{ +} + +float SoundSystem::volume() const +{ + return _speaker->getVolume() / 255.0f; +} + +void SoundSystem::setVolume(const float vol) +{ + _speaker->setVolume(static_cast(255 * goblib::clamp(vol, 0.0f, 1.0f))); +} + +std::size_t SoundSystem::playable() const +{ + return SIMULTANEOUS_PLAYS - _speaker->getPlayingChannels(); +} + +void SoundSystem::pump() +{ + SCOPED_RELEASE_BUS(); + _speaker->pump(); +} + +void SoundSystem::stop() +{ + stopBgm(); +} + +bool SoundSystem::playBgm(const BGM bgm, bool loop) +{ + if(bgm >= BGM::Max) { return false; } + if(_bgm == bgm) { return true; } + + _speaker->stop(BGM_CHANNEL); + + SCOPED_RELEASE_BUS(); + if(_bgmStream->open(BGM_PATH[bgm])) + { + _bgmPcm->assign(_bgmStream); + if(_bgmPcm->valid() && _speaker->play(*_bgmPcm, loop ? -1 : 1, BGM_CHANNEL)) + { + _bgm = bgm; + return true; + } + } + return false; +} + +void SoundSystem::stopBgm() +{ + _speaker->stop(BGM_CHANNEL); + _bgm = BGM::Max; +} + +bool SoundSystem::playSfx(const SFX sfx) +{ + // FileStream + if(sfx == SFX::InsertCoin || sfx == SFX::InsertCoinB) + { + SCOPED_RELEASE_BUS(); + if(!_creditPcm->valid()) + { + _creditStream->open(BGM_PATH[BGM::InsertCoin]); + _creditPcm->assign(_creditStream); + } + return play(*_creditPcm); + } + return false; +} + + +// play +bool SoundSystem::play(goblib::Stream& s) +{ + SCOPED_RELEASE_BUS(); + PcmStream* ps = _pool.construct(&s); + if(!ps){ return false; } + + ps->assign(&s); + return play(*ps); +} + +bool SoundSystem::play(PcmStream& ps) +{ + if(!ps.valid()) { return false; } + + for(int ch = 0; ch < SIMULTANEOUS_PLAYS; ++ch) + { + if(ch != BGM_CHANNEL && !_speaker->isPlaying(ch)) + { + return _speaker->play(ps, ch); + } + } + return false; +} + +void SoundSystem::_callbackStreaming(std::uint8_t ch, bool start) +{ + // End of streaming bgm. + if(!start && ch == BGM_CHANNEL) + { + _bgm = BGM::Max; + } +} diff --git a/src/sound.hpp b/src/sound.hpp new file mode 100644 index 0000000..d65cba0 --- /dev/null +++ b/src/sound.hpp @@ -0,0 +1,83 @@ +/*! + TinyDarius + + @file sound.hpp + @brief Sound system +*/ +#pragma once +#ifndef TD_SOUND_HPP +#define TD_SOUND_HPP + + +#include "constants.hpp" +#include +#include +#include +#include + +namespace goblib +{ +class Stream; +class PcmStream; +namespace m5s +{ +class FileStream; +class Speaker; +} +} + +class SoundSystem : public goblib::Singleton +{ + public: + constexpr static std::size_t SIMULTANEOUS_PLAYS = 6; // bgm + 5 se + constexpr static float INITIAL_VOLUME = 0.30f; + constexpr static int BGM_CHANNEL = 0; // BGM occupies channel + constexpr static std::uint8_t SE_CHANNEL_BIt = ((1 << SIMULTANEOUS_PLAYS) -1) & ~(1 << BGM_CHANNEL); // SE occupies channel bit + + virtual ~SoundSystem(); + + void setupGame(); + void finalizeGame(); + + float volume() const; + void setVolume(const float vol); + + void pump(); + + // stop all. + void stop(); + + // BGM + bool playBgm(const BGM bgm, bool loop = true); + void stopBgm(); + + // SFX + bool playSfx(const SFX sfx); + + std::size_t playable() const; + std::size_t available() const { return _pool.available(); } + + protected: + friend class goblib::Singleton; + SoundSystem(); + + bool play(goblib::Stream& s); + bool play(goblib::PcmStream& s); + + static void callbackStreaming(void* arg, std::uint8_t ch, bool start) + { + static_cast(arg)->_callbackStreaming(ch, start); + } + void _callbackStreaming(std::uint8_t ch, bool start); + + private: + goblib::m5s::Speaker* _speaker; + goblib::m5s::FileStream* _bgmStream; + goblib::m5s::FileStream* _creditStream; + goblib::PcmStream* _bgmPcm; + goblib::PcmStream* _creditPcm; + goblib::ObjectPool _pool; + BGM _bgm; +}; + +#endif diff --git a/src/stage.cpp b/src/stage.cpp new file mode 100644 index 0000000..ab3f2bf --- /dev/null +++ b/src/stage.cpp @@ -0,0 +1,76 @@ +/*! + TinyDarius + + @file stage.cpp + @brief Stage information +*/ +#include "stage.hpp" +#include "typedef.hpp" + +int Stage::_extra[] = { 'V', 'Z' }; // 26,27 (V',Z') +const Stage Stage::terminator = { -1, -1, -1, -1, 0, BGM(0) }; + +const Stage Stage::table[Stage::MAX] = +{ + // ---- 0 +#if 0 + { 0, 0, 1, 2, 2_fmin, BGM::CaptainNeo }, // A ->BC +#else + { 0, 0, -1, -1, 2_fmin, BGM::CaptainNeo }, // A +#endif + // ---- 1 +#if 0 + { 1, 1, 3, 4, 3_fmin, BGM::InorganicBeat }, // B ->DE + { 2, 1, 4, 5, 3_fmin, BGM::CosmicAirWay }, // C ->EF +#else + { 1, 1, -1, -1, 3_fmin, BGM::InorganicBeat }, // B + { 2, 1, -1, -1, 3_fmin, BGM::CosmicAirWay }, // C +#endif + // ---- 2 + { 3, 2, 6, 7, 3_fmin, BGM::CosmicAirWay }, // D ->GH + { 4, 2, 7, 8, 3_fmin, BGM::TheSea}, // E ->HI + { 5, 2, 8, 9, 3_fmin ,BGM::ChaosMainTheme}, // F ->IJ + // ---- 3 + { 6, 3, 10, 11, 4_fmin, BGM::ChaosMainTheme }, // G ->KL + { 7, 3, 11, 12, 4_fmin, BGM::CaptainNeo }, // H ->LM + { 8, 3, 12, 13, 4_fmin, BGM::InorganicBeat }, // I ->MN + { 9, 3, 13, 14, 4_fmin, BGM::TheSea }, // J ->NO + // ---- 4 + { 10, 4, 15, 16, 4_fmin, BGM::TheSea }, // K ->PQ + { 11, 4, 16, 17, 4_fmin, BGM::InorganicBeat }, // L ->QR + { 12, 4, 17, 18, 4_fmin, BGM::ChaosMainTheme }, // M ->RS + { 13, 4, 18, 19, 4_fmin, BGM::CaptainNeo }, // M ->ST + { 14, 4, 19, 20, 4_fmin, BGM::CosmicAirWay }, // O ->TU + // ---- 5 + { 15, 5, 27, 21, 5_fmin, BGM::CaptainNeo }, // P ->Z'V + { 16, 5, 21, 22, 5_fmin, BGM::CosmicAirWay }, // Q ->VW + { 17, 5, 22, 23, 5_fmin, BGM::TheSea }, // R ->WX + { 18, 5, 23, 24, 5_fmin, BGM::TheSea }, // S ->XY + { 19, 5, 24, 25, 5_fmin, BGM::ChaosMainTheme }, // T ->YZ + { 20, 5, 25, 26, 5_fmin, BGM::InorganicBeat }, // U ->ZV' + // ---- 6 + { 21, 6, -1, -1, 6_fmin, BGM::ChaosMainTheme }, // V + { 22, 6, -1, -1, 6_fmin, BGM::CaptainNeo }, // W + { 23, 6, -1, -1, 6_fmin, BGM::CosmicAirWay}, // X + { 24, 6, -1, -1, 6_fmin, BGM::InorganicBeat }, // Y + { 25, 6, -1, -1, 6_fmin, BGM::TheSea }, // Z + { 26, 6, -1, -1, 6_fmin, BGM::ChaosMainTheme }, // V' + { 27, 6, -1, -1, 6_fmin, BGM::TheSea }, // Z' +}; + +/* + [Digression] + + If you want index of triangular numbers sequence to depth on the fly, + + std::int32_t index2depth(std::int32_t index) + { + return static_cast( -1 + std::sqrt(1 + 8.0f * i) * 0.5f); + } + + 0 - depth 0 + 1 2 - depth 1 + 3 4 5 - depth 2 + 6 7 8 9 - depth 3 +......... +*/ diff --git a/src/stage.hpp b/src/stage.hpp new file mode 100644 index 0000000..b075b7c --- /dev/null +++ b/src/stage.hpp @@ -0,0 +1,42 @@ +/*! + TinyDarius + + @file stage.hpp + @brief Stage information +*/ +#pragma once +#ifndef TD_STAGE_HPP +#define TD_STAGE_HPP + +#include +#include +#include "constants.hpp" +#include + +struct Stage +{ + using Next = std::pair; // first is upper stage, second is lower stage. + + constexpr static std::uint8_t MAX = 28; + constexpr static std::uint8_t HISTORY_MAX = 8; // 7+terminator (e.g. A-C-F-J-O-T-Z-terminator) + + static const Stage terminator; + static const Stage table[MAX]; + + const std::int8_t current; + const std::int8_t depth; + const std::int8_t upper; + const std::int8_t lower; + const std::int32_t time; + const BGM bgm; + + int toChar() const { return current < 0 ? ' ' : (current < 26) ? ('A' + current) : _extra[ current - 26 ]; } + Next next() const { return Next(upper, lower); } + BGM bossBgm() const { return static_cast(goblib::to_underlying(BGM::BOSS_1) + depth); } + bool isTail() const { return upper < 0 || lower < 0; } + + private: + static int _extra[]; // Stage charactor for V'/Z' +}; + +#endif diff --git a/src/typedef.hpp b/src/typedef.hpp new file mode 100644 index 0000000..d7dd383 --- /dev/null +++ b/src/typedef.hpp @@ -0,0 +1,38 @@ +/*! + TinyDarius + + @file typedef.hpp + @brief type definitions +*/ +#ifndef TD_TYPEDEF_HPP +#define TD_TYPEDEF_HPP + +#include +#include +#include +#include +#include +#include + +using fx16 = goblib::FixedPointNumber; // 1:sign, 10(0~511):int, 6[0~2^-6):fraction */ +using VecFx16 = goblib::shape2d::Vector2; + +#if 0 +// using int +using Pos2 = goblib::shape2d::Point; +using Rect2 = goblib::shape2d::Rectangle; +#else +// using fixed point number +using Pos2 = goblib::shape2d::Point; +using Rect2 = goblib::shape2d::Rectangle; +#endif +using Pos2i = goblib::shape2d::Point; + +constexpr std::int32_t APP_FPS = 30; + +// seconds to frame times. +constexpr std::int32_t operator "" _fsec(unsigned long long x) { return static_cast(x * APP_FPS); } +// minutes to ftame times. +constexpr std::int32_t operator "" _fmin(unsigned long long x) { return static_cast(x * 60_fsec); } + +#endif diff --git a/src/utility.cpp b/src/utility.cpp new file mode 100644 index 0000000..a47c77f --- /dev/null +++ b/src/utility.cpp @@ -0,0 +1,102 @@ +/*! + TinyDarius + + @file utility.cpp + @brief Utilities +*/ +#include +#include "utility.hpp" +#include "app.hpp" +#include +#include +#include +#include + +bool createFromBitmap(goblib::lgfx::GSprite& sprite,const char* bitmap_path) +{ + SCOPED_RELEASE_BUS(); + + goblib::m5s::File file; + file.open(bitmap_path, O_READ); + + if(!file) + { + ESP_LOGE(__func__, "Unable to open file %s\n", bitmap_path); + return false; + } + + std::size_t len = file.available(); + if(len > 0) + { + auto bmp = new std::uint8_t[len]; + if(!bmp) { return false; } + + file.read(bmp, len); + sprite.createFromBmp(bmp, len); + delete[] bmp; + } + return true; +} + +std::size_t getPaletteColors(RGBColor* dest, std::size_t sz, goblib::lgfx::GSprite& sprite) +{ + auto s = sprite.getPalette(); + sz = std::min(sz, sprite.getPaletteCount()); + auto ret = sz; + while(sz--) { *dest++ = *s++; } + + return ret; +} + +namespace +{ +using palclr_func = std::function< void(const int_fast8_t, const ColorChange&) >; + +void functionPaletteColors(goblib::lgfx::GSprite& sprite, const ColorChange* table, const std::size_t size, palclr_func func) +{ + RGBColor* pal = sprite.getPalette(); + auto psz = sprite.getPaletteCount(); + + for(std::size_t i = 0; i < size; ++i) + { + for(int_fast8_t j = 0; j < psz; ++j) + { + if(table[i].original == pal[j]) + { + func(j, table[i]); + } + } + } +} +// +} + +void applyPaletteColors(goblib::lgfx::GSprite& sprite, const ColorChange* table, const std::size_t size) +{ + assert(table); + functionPaletteColors(sprite, table, size, [&sprite](const int_fast8_t idx, const ColorChange& cc) + { + sprite.setPaletteColor(idx, cc.toward); + }); +} + +void towardPaletteColors(goblib::lgfx::AnimatedPalette& apalette, const ColorChange* table, const std::size_t size, const std::uint32_t times) +{ + assert(table); + assert(apalette.sprite()); + functionPaletteColors(*apalette.sprite(), table, size, [&apalette, ×](const int_fast8_t idx, const ColorChange& cc) + { + apalette.toward(idx, cc.toward, times); + }); +} + + +void roundTripPaletteColors(goblib::lgfx::AnimatedPalette& apalette, const ColorChange* table, const std::size_t size, const std::uint32_t times) +{ + assert(table); + assert(apalette.sprite()); + functionPaletteColors(*apalette.sprite(), table, size, [&apalette, ×](const int_fast8_t idx, const ColorChange& cc) + { + apalette.roundTrip(idx, cc.toward, times); + }); +} diff --git a/src/utility.hpp b/src/utility.hpp new file mode 100644 index 0000000..6c31243 --- /dev/null +++ b/src/utility.hpp @@ -0,0 +1,30 @@ +/*! + TinyDarius + + @file utility.hpp + @brief Utilities +*/ +#ifndef TD_UTILITY_HPP +#define TD_UTILITY_HPP + +#include +namespace goblib { namespace lgfx { class AnimatedPalette; }} + +bool createFromBitmap(goblib::lgfx::GSprite& sprite,const char* bitmap_path); +std::size_t getPaletteColors(RGBColor* dest, std::size_t sz, goblib::lgfx::GSprite& sprite); + +/*! @brief for partial color change */ +struct ColorChange +{ + RGBColor original; + RGBColor toward; +}; +/*! apply from ColorChange.original to toward */ +void applyPaletteColors(goblib::lgfx::GSprite& sprite, const ColorChange* table, const std::size_t size); + +/*! animated palette from ColorChange.original to toward */ +void towardPaletteColors(goblib::lgfx::AnimatedPalette& apalette, const ColorChange* table, const std::size_t size, const std::uint32_t times); +/*! roundtrip animated palette between ColorChange.original and toward */ +void roundTripPaletteColors(goblib::lgfx::AnimatedPalette& apalette, const ColorChange* table, const std::size_t size, const std::uint32_t times); + +#endif diff --git a/src/version.hpp b/src/version.hpp new file mode 100644 index 0000000..cb05dc2 --- /dev/null +++ b/src/version.hpp @@ -0,0 +1,24 @@ +/*! + TinyDarius + + @file version.hpp + @brief Semantic Versioning +*/ +#pragma once +#ifndef TD_VERSION_HPP +#define TD_VERSION_HPP + +#include + +#define TD_VERSION_MAJOR 0 +#define TD_VERSION_MINOR 1 +#define TD_VERSION_PATCH 0 + +/*! @def TD_VERSION_VALUE + @brief Version value */ +#define TD_VERSION_VALUE ((TD_VERSION_MAJOR << 16) | (TD_VERSION_MINOR << 8) | (TD_VERSION_PATCH)) +/*! @def TD_VERSION_STRING + @brief Version string */ +#define TD_VERSION_STRING GOBLIB_STRINGIFY(TD_VERSION_MAJOR.TD_VERSION_MINOR.TD_VERSION_PATCH) + +#endif