mirror of https://github.com/auygun/kaliber.git
528 lines
13 KiB
C++
528 lines
13 KiB
C++
#include "demo/demo.h"
|
|
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <iostream>
|
|
#include <string>
|
|
|
|
#include "base/file.h"
|
|
#include "base/interpolation.h"
|
|
#include "base/log.h"
|
|
#include "base/random.h"
|
|
#include "base/timer.h"
|
|
#include "engine/engine.h"
|
|
#include "engine/game_factory.h"
|
|
#include "engine/input_event.h"
|
|
#include "engine/sound.h"
|
|
|
|
DECLARE_GAME_BEGIN
|
|
DECLARE_GAME(Demo)
|
|
DECLARE_GAME_END
|
|
|
|
// #define RECORD 15
|
|
// #define REPLAY
|
|
|
|
using namespace std::string_literals;
|
|
|
|
using namespace base;
|
|
using namespace eng;
|
|
|
|
namespace {
|
|
|
|
const Vector4f kBgColor = {0, 0, 0, 0.8f};
|
|
constexpr float kFadeSpeed = 0.2f;
|
|
|
|
constexpr int kLaunchCountBeforeAd = 2;
|
|
|
|
const char kSaveFileName[] = "woom";
|
|
const char kHightScore[] = "high_score";
|
|
const char kLastWave[] = "last_wave";
|
|
const char kLaunchCount[] = "launch_count";
|
|
|
|
} // namespace
|
|
|
|
Demo::Demo() = default;
|
|
|
|
Demo::~Demo() {
|
|
saved_data_.Save();
|
|
}
|
|
|
|
bool Demo::Initialize() {
|
|
saved_data_.Load(kSaveFileName);
|
|
|
|
Engine::Get().LoadCustomShader("sky_without_nebula",
|
|
"sky_without_nebula.glsl");
|
|
Engine::Get().LoadCustomShader("sky", "sky.glsl");
|
|
|
|
if (!font_.Load("PixelCaps!.ttf"))
|
|
return false;
|
|
|
|
if (!sky_.Create(false)) {
|
|
LOG << "Could not create the sky.";
|
|
return false;
|
|
}
|
|
|
|
if (!enemy_.Initialize()) {
|
|
LOG << "Failed to create the enemy.";
|
|
return false;
|
|
}
|
|
|
|
if (!player_.Initialize()) {
|
|
LOG << "Failed to create the enemy.";
|
|
return false;
|
|
}
|
|
|
|
if (!hud_.Initialize()) {
|
|
LOG << "Failed to create the hud.";
|
|
return false;
|
|
}
|
|
|
|
if (!menu_.Initialize()) {
|
|
LOG << "Failed to create the menu.";
|
|
return false;
|
|
}
|
|
|
|
if (!credits_.Initialize()) {
|
|
LOG << "Failed to create the credits.";
|
|
return false;
|
|
}
|
|
|
|
auto sound = std::make_unique<Sound>();
|
|
if (!sound->Load("Game_2_Main.mp3", true))
|
|
return false;
|
|
|
|
auto boss_sound = std::make_unique<Sound>();
|
|
if (!boss_sound->Load("Game_2_Boss.mp3", true))
|
|
return false;
|
|
|
|
music_.SetSound(std::move(sound));
|
|
music_.SetMaxAplitude(0.5f);
|
|
|
|
boss_music_.SetSound(std::move(boss_sound));
|
|
boss_music_.SetMaxAplitude(0.5f);
|
|
|
|
if (!saved_data_.root().get("audio", Json::Value(true)).asBool())
|
|
Engine::Get().SetEnableAudio(false);
|
|
else if (saved_data_.root().get("music", Json::Value(true)).asBool())
|
|
music_.Play(true);
|
|
|
|
if (!saved_data_.root().get("vibration", Json::Value(true)).asBool()) {
|
|
Engine::Get().SetEnableVibration(false);
|
|
}
|
|
|
|
dimmer_.SetSize(Engine::Get().GetScreenSize());
|
|
dimmer_.SetZOrder(40);
|
|
dimmer_.SetColor(kBgColor);
|
|
dimmer_.SetVisible(true);
|
|
dimmer_active_ = true;
|
|
dimmer_animator_.Attach(&dimmer_);
|
|
|
|
saved_data_.root()[kLaunchCount] =
|
|
saved_data_.root().get(kLaunchCount, Json::Value(0)).asInt() + 1;
|
|
|
|
EnterMenuState();
|
|
|
|
return true;
|
|
}
|
|
|
|
void Demo::Update(float delta_time) {
|
|
Engine& engine = Engine::Get();
|
|
|
|
if (do_benchmark_) {
|
|
benchmark_time_ += delta_time;
|
|
if (benchmark_time_ > 3) {
|
|
avarage_fps_ += Engine::Get().fps();
|
|
++num_benchmark_samples_;
|
|
}
|
|
if (benchmark_time_ > 5) {
|
|
avarage_fps_ /= num_benchmark_samples_;
|
|
do_benchmark_ = false;
|
|
BenchmarkResult(avarage_fps_);
|
|
}
|
|
}
|
|
|
|
stage_time_ += delta_time;
|
|
|
|
while (std::unique_ptr<InputEvent> event = engine.GetNextInputEvent()) {
|
|
#if 0
|
|
if (event->GetType() == InputEvent::kDragEnd &&
|
|
((engine.GetScreenSize() / 2) * 0.9f -
|
|
event->GetVector() * Vector2f(-1, 1))
|
|
.Length() <= 0.25f)
|
|
Win();
|
|
#endif
|
|
|
|
if (state_ == kMenu)
|
|
menu_.OnInputEvent(std::move(event));
|
|
else if (state_ == kCredits)
|
|
credits_.OnInputEvent(std::move(event));
|
|
else if (state_ != kGameOver)
|
|
player_.OnInputEvent(std::move(event));
|
|
}
|
|
|
|
if (state_ == kMenu)
|
|
UpdateMenuState(delta_time);
|
|
else if (state_ == kGame || state_ == kGameOver)
|
|
UpdateGameState(delta_time);
|
|
}
|
|
|
|
void Demo::ContextLost() {
|
|
if (do_benchmark_) {
|
|
benchmark_time_ = 0;
|
|
num_benchmark_samples_ = 0;
|
|
avarage_fps_ = 0;
|
|
}
|
|
}
|
|
|
|
void Demo::LostFocus() {}
|
|
|
|
void Demo::GainedFocus(bool from_interstitial_ad) {
|
|
DLOG << __func__ << " from_interstitial_ad: " << from_interstitial_ad;
|
|
if (!from_interstitial_ad) {
|
|
if (saved_data_.root().get(kLaunchCount, Json::Value(0)).asInt() >
|
|
kLaunchCountBeforeAd)
|
|
Engine::Get().ShowInterstitialAd();
|
|
if (state_ == kGame)
|
|
EnterMenuState();
|
|
}
|
|
}
|
|
|
|
void Demo::AddScore(size_t score) {
|
|
delta_score_ += score;
|
|
wave_score_ += score;
|
|
}
|
|
|
|
void Demo::SetEnableMusic(bool enable) {
|
|
if (enable) {
|
|
if (boss_fight_)
|
|
boss_music_.Resume(1);
|
|
else
|
|
music_.Resume(1);
|
|
} else {
|
|
music_.Stop(1);
|
|
boss_music_.Stop(1);
|
|
}
|
|
}
|
|
|
|
void Demo::EnterMenuState() {
|
|
saved_data_.Save();
|
|
|
|
if (state_ == kMenu)
|
|
return;
|
|
|
|
player_.OnInputEvent(
|
|
std::make_unique<InputEvent>(InputEvent::kDragCancel, (size_t)0));
|
|
player_.OnInputEvent(
|
|
std::make_unique<InputEvent>(InputEvent::kDragCancel, (size_t)1));
|
|
|
|
Dimmer(true);
|
|
if (state_ == kState_Invalid || state_ == kGame) {
|
|
hud_.Pause(true);
|
|
player_.Pause(true);
|
|
enemy_.Pause(true);
|
|
}
|
|
|
|
if (state_ == kState_Invalid || state_ == kGameOver) {
|
|
menu_.SetOptionEnabled(Menu::kContinue, false);
|
|
menu_.SetOptionEnabled(Menu::kNewGame, true);
|
|
} else if (state_ == kGame) {
|
|
menu_.SetOptionEnabled(Menu::kContinue, true);
|
|
menu_.SetOptionEnabled(Menu::kNewGame, false);
|
|
}
|
|
menu_.Show();
|
|
state_ = kMenu;
|
|
}
|
|
|
|
void Demo::EnterCreditsState() {
|
|
if (state_ == kCredits)
|
|
return;
|
|
|
|
credits_.Show();
|
|
state_ = kCredits;
|
|
}
|
|
|
|
void Demo::EnterGameState() {
|
|
if (state_ == kGame)
|
|
return;
|
|
|
|
Dimmer(false);
|
|
sky_.SetSpeed(0.04f);
|
|
|
|
hud_.Show();
|
|
hud_.Pause(false);
|
|
player_.Pause(false);
|
|
enemy_.Pause(false);
|
|
if (boss_fight_)
|
|
hud_.HideProgress();
|
|
state_ = kGame;
|
|
}
|
|
|
|
void Demo::EnterGameOverState() {
|
|
if (state_ == kGameOver)
|
|
return;
|
|
|
|
saved_data_.Save();
|
|
|
|
enemy_.PauseProgress();
|
|
enemy_.StopAllEnemyUnits();
|
|
sky_.SwitchColor({0, 0, 0, 1});
|
|
hud_.ShowMessage("Game Over", 3);
|
|
state_ = kGameOver;
|
|
|
|
SetDelayedWork(1, [&]() -> void {
|
|
enemy_.RemoveAll();
|
|
SetDelayedWork(3, [&]() -> void {
|
|
if (saved_data_.root().get("music", Json::Value(true)).asBool()) {
|
|
if (boss_fight_) {
|
|
music_.Resume(10);
|
|
boss_music_.Stop(10);
|
|
}
|
|
}
|
|
wave_ = 0;
|
|
boss_fight_ = false;
|
|
EnterMenuState();
|
|
});
|
|
});
|
|
|
|
#if defined(RECORD)
|
|
Engine::Get().EndRecording("replay");
|
|
#endif
|
|
}
|
|
|
|
void Demo::UpdateMenuState(float delta_time) {
|
|
switch (menu_.selected_option()) {
|
|
case Menu::kOption_Invalid:
|
|
break;
|
|
case Menu::kContinue:
|
|
menu_.Hide();
|
|
Continue();
|
|
break;
|
|
case Menu::kNewGame:
|
|
menu_.Hide([&]() {
|
|
if (saved_data_.root().get(kLaunchCount, Json::Value(0)).asInt() >
|
|
kLaunchCountBeforeAd)
|
|
Engine::Get().ShowInterstitialAd();
|
|
StartNewGame();
|
|
});
|
|
break;
|
|
case Menu::kCredits:
|
|
menu_.Hide();
|
|
EnterCreditsState();
|
|
break;
|
|
case Menu::kExit:
|
|
Engine::Get().Exit();
|
|
break;
|
|
default:
|
|
NOTREACHED << "- Unknown menu option: " << menu_.selected_option();
|
|
}
|
|
}
|
|
|
|
void Demo::UpdateGameState(float delta_time) {
|
|
if (delayed_work_timer_ > 0) {
|
|
delayed_work_timer_ -= delta_time;
|
|
if (delayed_work_timer_ <= 0) {
|
|
base::Closure cb = std::move(delayed_work_cb_);
|
|
delayed_work_cb_ = nullptr;
|
|
cb();
|
|
}
|
|
}
|
|
|
|
if (delta_score_ > 0) {
|
|
total_score_ += delta_score_;
|
|
delta_score_ = 0;
|
|
hud_.SetScore(total_score_, true);
|
|
|
|
if (total_score_ > GetHighScore())
|
|
saved_data_.root()[kHightScore] = total_score_;
|
|
}
|
|
|
|
if (wave_ > saved_data_.root().get(kLastWave, Json::Value(0)).asInt())
|
|
saved_data_.root()[kLastWave] = wave_;
|
|
|
|
sky_.Update(delta_time);
|
|
player_.Update(delta_time);
|
|
enemy_.Update(delta_time);
|
|
|
|
if (waiting_for_next_wave_)
|
|
return;
|
|
|
|
if (boss_fight_) {
|
|
if (!enemy_.IsBossAlive())
|
|
StartNextStage(false);
|
|
} else if (enemy_.num_enemies_killed_in_current_wave() !=
|
|
last_num_enemies_killed_) {
|
|
bool no_boss = (last_num_enemies_killed_ == -1);
|
|
if (last_num_enemies_killed_ < enemy_.num_enemies_killed_in_current_wave())
|
|
last_num_enemies_killed_ = enemy_.num_enemies_killed_in_current_wave();
|
|
int enemies_remaining = total_enemies_ - last_num_enemies_killed_;
|
|
|
|
if (enemies_remaining <= 0)
|
|
StartNextStage(wave_ && !(wave_ % 3) && !no_boss);
|
|
else
|
|
hud_.SetProgress((float)enemies_remaining / (float)total_enemies_);
|
|
}
|
|
}
|
|
|
|
void Demo::Continue() {
|
|
EnterGameState();
|
|
}
|
|
|
|
void Demo::StartNewGame() {
|
|
#if defined(RECORD)
|
|
Json::Value game_data;
|
|
game_data["wave"] = RECORD;
|
|
wave_ = RECORD - 1;
|
|
Engine::Get().StartRecording(game_data);
|
|
#elif defined(REPLAY)
|
|
Json::Value game_data;
|
|
Engine::Get().Replay("replay", game_data);
|
|
wave_ = game_data["wave"].asInt() - 1;
|
|
#else
|
|
wave_ = menu_.start_from_wave() - 1;
|
|
#endif
|
|
|
|
wave_score_ = total_score_ = delta_score_ = 0;
|
|
last_num_enemies_killed_ = -1;
|
|
total_enemies_ = 0;
|
|
waiting_for_next_wave_ = false;
|
|
boss_fight_ = false;
|
|
delayed_work_timer_ = 0;
|
|
delayed_work_cb_ = nullptr;
|
|
player_.Reset();
|
|
enemy_.Reset();
|
|
EnterGameState();
|
|
}
|
|
|
|
void Demo::StartNextStage(bool boss) {
|
|
waiting_for_next_wave_ = true;
|
|
hud_.SetProgress(wave_ > 0 ? 0 : 1);
|
|
|
|
DLOG_IF(wave_ > 0 && stage_time_ > 0)
|
|
<< "wave: " << wave_ << " time: " << stage_time_ / 60.0f;
|
|
stage_time_ = 0;
|
|
|
|
enemy_.PauseProgress();
|
|
enemy_.StopAllEnemyUnits();
|
|
|
|
SetDelayedWork(1.25f, [&, boss]() -> void {
|
|
enemy_.KillAllEnemyUnits();
|
|
|
|
SetDelayedWork(boss_fight_ ? 4 : 0.5f, [&, boss]() -> void {
|
|
if (boss) {
|
|
sky_.SwitchColor(sky_.nebula_color() * 0.5f);
|
|
|
|
hud_.HideProgress();
|
|
|
|
if (saved_data_.root().get("music", Json::Value(true)).asBool()) {
|
|
boss_music_.Play(true, 10);
|
|
music_.Stop(10);
|
|
}
|
|
boss_fight_ = true;
|
|
DLOG << "Boss fight.";
|
|
} else {
|
|
size_t bonus_factor = [&]() -> size_t {
|
|
if (wave_ <= 3)
|
|
return 2;
|
|
if (wave_ <= 6)
|
|
return 5;
|
|
if (wave_ <= 9)
|
|
return 15;
|
|
return 100;
|
|
}();
|
|
size_t bonus_score = wave_score_ * (bonus_factor - 1);
|
|
DLOG << "total_score_" << total_score_ << " wave " << wave_
|
|
<< " score: " << wave_score_ << " bonus: " << bonus_score;
|
|
|
|
if (bonus_score > 0) {
|
|
delta_score_ += bonus_score;
|
|
hud_.ShowBonus(bonus_score);
|
|
wave_score_ = 0;
|
|
}
|
|
|
|
Randomf& rnd = Engine::Get().GetRandomGenerator();
|
|
int dominant_channel = rnd.Roll(3) - 1;
|
|
if (dominant_channel == last_dominant_channel_)
|
|
dominant_channel = (dominant_channel + 1) % 3;
|
|
last_dominant_channel_ = dominant_channel;
|
|
|
|
float weights[3] = {0, 0, 0};
|
|
weights[dominant_channel] = 1;
|
|
Vector4f c = {Lerp(0.75f, 0.95f, rnd.Rand()) * weights[0],
|
|
Lerp(0.75f, 0.95f, rnd.Rand()) * weights[1],
|
|
Lerp(0.75f, 0.95f, rnd.Rand()) * weights[2], 1};
|
|
c += {Lerp(0.1f, 0.7f, rnd.Rand()) * (1 - weights[0]),
|
|
Lerp(0.1f, 0.7f, rnd.Rand()) * (1 - weights[1]),
|
|
Lerp(0.1f, 0.7f, rnd.Rand()) * (1 - weights[2]), 1};
|
|
sky_.SwitchColor(c);
|
|
|
|
++wave_;
|
|
hud_.Show();
|
|
hud_.SetProgress(1);
|
|
|
|
if (boss_fight_) {
|
|
player_.TakeDamage(-1);
|
|
if (saved_data_.root().get("music", Json::Value(true)).asBool()) {
|
|
music_.Resume(10);
|
|
boss_music_.Stop(10);
|
|
}
|
|
}
|
|
|
|
total_enemies_ = 23.0897f * log((float)wave_ + 1.0f) - 10.0f;
|
|
last_num_enemies_killed_ = 0;
|
|
boss_fight_ = false;
|
|
DLOG << "wave: " << wave_ << " total_enemies_: " << total_enemies_;
|
|
}
|
|
|
|
hud_.SetScore(total_score_, true);
|
|
hud_.SetWave(wave_, true);
|
|
|
|
enemy_.OnWaveStarted(wave_, boss);
|
|
|
|
waiting_for_next_wave_ = false;
|
|
});
|
|
});
|
|
}
|
|
|
|
void Demo::Win() {
|
|
// Satisfy win conditions.
|
|
if (boss_fight_)
|
|
enemy_.KillBoss();
|
|
else
|
|
last_num_enemies_killed_ = total_enemies_;
|
|
}
|
|
|
|
void Demo::Dimmer(bool enable) {
|
|
if (enable && !dimmer_active_) {
|
|
dimmer_active_ = true;
|
|
dimmer_.SetColor(kBgColor * Vector4f(0, 0, 0, 0));
|
|
dimmer_animator_.SetBlending(kBgColor, kFadeSpeed);
|
|
dimmer_animator_.Play(Animator::kBlending, false);
|
|
dimmer_animator_.SetEndCallback(Animator::kBlending, nullptr);
|
|
dimmer_animator_.SetVisible(true);
|
|
} else if (!enable && dimmer_active_) {
|
|
dimmer_active_ = false;
|
|
dimmer_animator_.SetBlending(kBgColor * Vector4f(0, 0, 0, 0), kFadeSpeed);
|
|
dimmer_animator_.Play(Animator::kBlending, false);
|
|
dimmer_animator_.SetEndCallback(Animator::kBlending, [&]() -> void {
|
|
dimmer_animator_.SetEndCallback(Animator::kBlending, nullptr);
|
|
dimmer_animator_.SetVisible(false);
|
|
});
|
|
}
|
|
}
|
|
|
|
size_t Demo::GetHighScore() const {
|
|
return saved_data_.root().get(kHightScore, Json::Value(0)).asUInt64();
|
|
}
|
|
|
|
void Demo::SetDelayedWork(float seconds, base::Closure cb) {
|
|
DCHECK(delayed_work_cb_ == nullptr);
|
|
delayed_work_cb_ = std::move(cb);
|
|
delayed_work_timer_ = seconds;
|
|
}
|
|
|
|
void Demo::BenchmarkResult(int avarage_fps) {
|
|
LOG << __func__ << " avarage_fps: " << avarage_fps;
|
|
if (avarage_fps < 30)
|
|
sky_.Create(true);
|
|
}
|