Add full game code and assets.

This commit is contained in:
Attila Uygun 2021-08-31 23:21:29 +02:00
parent e669f2b474
commit c6501323cf
56 changed files with 2649 additions and 399 deletions

1
.gitignore vendored
View File

@ -1,4 +1,3 @@
.vscode
build/android/.gradle build/android/.gradle
build/android/app/.cxx build/android/app/.cxx
build/android/app/build build/android/app/build

27
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,27 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) Launch",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/build/linux/gltest_x86_64_debug",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}/build/linux",
"environment": [],
"console": "externalTerminal",
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
}
]
}

View File

@ -2,8 +2,7 @@ A simple, cross-platform 2D game engine with OpenGL and Vulkan renderers.
Supports Linux and Android (lolipop+) platforms. Supports Linux and Android (lolipop+) platforms.
This is a personal hobby project. I've published a little game on This is a personal hobby project. I've published a little game on
[Google Play](https://play.google.com/store/apps/details?id=com.woom.game) [Google Play](https://play.google.com/store/apps/details?id=com.woom.game)
based on this engine. The demo included in this repository is an early prototype based on this engine. Full game code and assets are included in this repository.
of the game.
#### Building the demo #### Building the demo
Linux: Linux:
```text ```text

BIN
assets/Boss_ok.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

BIN
assets/Boss_ok_lvl2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

BIN
assets/Boss_ok_lvl3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

BIN
assets/Game_2_Boss.mp3 Normal file

Binary file not shown.

BIN
assets/Game_2_Main.mp3 Normal file

Binary file not shown.

BIN
assets/bead.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 507 B

BIN
assets/boss_explosion.mp3 Normal file

Binary file not shown.

BIN
assets/boss_intro.mp3 Normal file

Binary file not shown.

View File

@ -0,0 +1,27 @@
#ifdef GL_ES
precision mediump float;
#endif
IN(0) vec2 tex_coord_0;
UNIFORM_BEGIN
UNIFORM_V(vec2 scale)
UNIFORM_V(vec2 offset)
UNIFORM_V(vec2 rotation)
UNIFORM_V(vec2 tex_offset)
UNIFORM_V(vec2 tex_scale)
UNIFORM_F(float aberration_offset)
UNIFORM_V(mat4 projection)
UNIFORM_F(vec4 color)
UNIFORM_END
SAMPLER(0, sampler2D texture_0)
FRAG_COLOR_OUT(frag_color)
void main() {
vec4 r = TEXTURE(texture_0, tex_coord_0 - vec2(PARAM(aberration_offset), 0));
vec4 g = TEXTURE(texture_0, tex_coord_0 - vec2(-PARAM(aberration_offset), 0));
vec4 b = TEXTURE(texture_0, tex_coord_0 - vec2(0, PARAM(aberration_offset)));
FRAG_COLOR(frag_color) = vec4(r.x, g.y, b.z, (r.w + g.w + b.w) / 3.0) * PARAM(color);
}

View File

@ -0,0 +1,28 @@
IN(0) vec2 in_position;
IN(1) vec2 in_tex_coord_0;
UNIFORM_BEGIN
UNIFORM_V(vec2 scale)
UNIFORM_V(vec2 offset)
UNIFORM_V(vec2 rotation)
UNIFORM_V(vec2 tex_offset)
UNIFORM_V(vec2 tex_scale)
UNIFORM_F(float aberration_offset)
UNIFORM_V(mat4 projection)
UNIFORM_F(vec4 color)
UNIFORM_END
OUT(0) vec2 tex_coord_0;
void main() {
// Simple 2d transform.
vec2 position = in_position;
position *= PARAM(scale);
position = vec2(position.x * PARAM(rotation).y + position.y * PARAM(rotation).x,
position.y * PARAM(rotation).y - position.x * PARAM(rotation).x);
position += PARAM(offset);
tex_coord_0 = (in_tex_coord_0 + PARAM(tex_offset)) * PARAM(tex_scale);
gl_Position = PARAM(projection) * vec4(position, 0.0, 1.0);
}

BIN
assets/crate.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

BIN
assets/hit.mp3 Normal file

Binary file not shown.

BIN
assets/laser.mp3 Normal file

Binary file not shown.

BIN
assets/menu_click.mp3 Normal file

Binary file not shown.

BIN
assets/menu_icons.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
assets/no_nuke.mp3 Normal file

Binary file not shown.

BIN
assets/nuke.mp3 Normal file

Binary file not shown.

BIN
assets/nuke_frames.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
assets/nuke_pack_OK.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
assets/powerup-pick.mp3 Normal file

Binary file not shown.

BIN
assets/powerup-spawn.mp3 Normal file

Binary file not shown.

BIN
assets/shield.mp3 Normal file

Binary file not shown.

View File

@ -0,0 +1,47 @@
#ifdef GL_ES
precision highp float;
#else
#define lowp
#define mediump
#define highp
#endif
IN(0) vec2 tex_coord_0;
UNIFORM_BEGIN
UNIFORM_V(vec2 scale)
UNIFORM_V(mat4 projection)
UNIFORM_F(vec2 sky_offset)
UNIFORM_END
FRAG_COLOR_OUT(frag_color)
float random(vec2 p) {
float sd = sin(dot(p, vec2(54.90898, 18.233)));
return fract(sd * 2671.6182);
}
float stars(in vec2 p, float num_cells, float size) {
vec2 n = p * num_cells;
vec2 i = floor(n);
vec2 a = n - i - random(i);
a /= num_cells * size;
float e = dot(a, a);
return smoothstep(0.94, 1.0, (1.0 - e * 35.0));
}
void main() {
vec2 layer1_coord = tex_coord_0 + PARAM(sky_offset);
vec2 layer2_coord = tex_coord_0 + PARAM(sky_offset) * 0.7;
mediump vec3 result = vec3(0.);
float c = stars(layer1_coord, 8.0, 0.05);
result += vec3(0.97, 0.74, 0.74) * c;
c = stars(layer2_coord, 16.0, 0.025) * 0.5;
result += vec3(0.9, 0.9, 0.95) * c;
FRAG_COLOR(frag_color) = vec4(result, 1.0);
}

View File

@ -0,0 +1,20 @@
IN(0) vec2 in_position;
IN(1) vec2 in_tex_coord_0;
UNIFORM_BEGIN
UNIFORM_V(vec2 scale)
UNIFORM_V(mat4 projection)
UNIFORM_F(vec2 sky_offset)
UNIFORM_END
OUT(0) vec2 tex_coord_0;
void main() {
// Simple 2d transform.
vec2 position = in_position;
position *= PARAM(scale);
tex_coord_0 = in_tex_coord_0;
gl_Position = PARAM(projection) * vec4(position, 0.0, 1.0);
}

BIN
assets/stealth.mp3 Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

View File

@ -5,7 +5,7 @@ android {
ndkVersion '21.3.6528147' ndkVersion '21.3.6528147'
defaultConfig { defaultConfig {
applicationId = 'com.kaliber.demo' applicationId = 'com.woom.game'
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 29 targetSdkVersion 29
externalNativeBuild { externalNativeBuild {

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.kaliber.demo" package="com.woom.game"
android:versionCode="1" android:versionCode="16"
android:versionName="1.0"> android:versionName="1.0.1">
<application <application
android:allowBackup="false" android:allowBackup="false"
@ -36,7 +36,7 @@
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="com.codepath.fileprovider" android:authorities="com.woom.game.fileprovider"
android:exported="false" android:exported="false"
android:grantUriPermissions="true"> android:grantUriPermissions="true">
<meta-data <meta-data

View File

@ -16,7 +16,7 @@ import androidx.core.content.FileProvider;
import com.google.android.gms.ads.AdListener; import com.google.android.gms.ads.AdListener;
import com.google.android.gms.ads.AdRequest; import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.InterstitialAd; import com.google.android.gms.ads.InterstitialAd;
import com.kaliber.demo.R; import com.woom.game.R;
import java.io.File; import java.io.File;
@ -107,7 +107,7 @@ public class KaliberActivity extends NativeActivity {
File dir = getExternalFilesDir(null); File dir = getExternalFilesDir(null);
File file = new File(dir, fileName); File file = new File(dir, fileName);
Uri uri = FileProvider.getUriForFile(KaliberActivity.this, Uri uri = FileProvider.getUriForFile(KaliberActivity.this,
"com.codepath.fileprovider", file); "com.woom.game.fileprovider", file);
Intent emailIntent = new Intent(); Intent emailIntent = new Intent();
emailIntent.setAction(Intent.ACTION_SEND); emailIntent.setAction(Intent.ACTION_SEND);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">demo</string> <string name="app_name">woom</string>
<string name="interstitial_ad_unit_id">ca-app-pub-3940256099942544/1033173712</string> <string name="interstitial_ad_unit_id">ca-app-pub-1321063817979967/8373182022</string>
</resources> </resources>

View File

@ -1,9 +0,0 @@
## This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
#Fri Oct 16 12:48:26 CEST 2020
ndk.dir=/home/auygun/spinning/Android/Sdk/ndk/21.3.6528147
sdk.dir=/home/auygun/spinning/Android/Sdk

2
build/android/sign Executable file
View File

@ -0,0 +1,2 @@
jarsigner -keystore ~/key_store/apk_key_store.jks -storepass xxx -keypass xxx -signedjar app-release.apk ./app/build/outputs/apk/release/app-release-unsigned.apk upload
zipalign -f 4 ./app-release.apk ./app-release-aligned.apk

View File

@ -13,10 +13,15 @@ using namespace eng;
namespace { namespace {
constexpr char kCreditsLines[Credits::kNumLines][15] = { constexpr char kCreditsLines[Credits::kNumLines][40] = {
"Credits", "Code:", "Attila Uygun", "Graphics:", "Erkan Ertürk"}; "Credits", "Code",
"Attila Uygun", "Graphics",
"Erkan Ertürk", "Music",
"Patrik Häggblad", "Special thanks",
"Peter Pettersson", "github.com/auygun/kaliber"};
constexpr float kLineSpaces[Credits::kNumLines - 1] = {1.5f, 0.5f, 1.5f, 0.5f}; constexpr float kLineSpaces[Credits::kNumLines - 1] = {
1.5f, 0.5f, 1.5f, 0.5f, 1.5f, 0.5f, 1.5f, 0.5f, 1.5f};
const Vector4f kTextColor = {0.80f, 0.87f, 0.93f, 1}; const Vector4f kTextColor = {0.80f, 0.87f, 0.93f, 1};
constexpr float kFadeSpeed = 0.2f; constexpr float kFadeSpeed = 0.2f;

View File

@ -14,7 +14,7 @@ class InputEvent;
class Credits { class Credits {
public: public:
static constexpr int kNumLines = 5; static constexpr int kNumLines = 10;
Credits(); Credits();
~Credits(); ~Credits();

View File

@ -11,10 +11,23 @@ enum DamageType {
enum EnemyType { enum EnemyType {
kEnemyType_Invalid = -1, kEnemyType_Invalid = -1,
kEnemyType_Skull, // Enemy units (waves and boss adds).
kEnemyType_Bug, kEnemyType_LightSkull,
kEnemyType_DarkSkull,
kEnemyType_Tank, kEnemyType_Tank,
kEnemyType_Bug,
kEnemyType_Unit_Last = kEnemyType_Bug,
// Boss.
kEnemyType_PowerUp,
kEnemyType_Boss,
kEnemyType_Max kEnemyType_Max
}; };
enum SpeedType {
kSpeedType_Invalid = -1,
kSpeedType_Slow,
kSpeedType_Fast,
kSpeedType_Max
};
#endif // DAMAGE_TYPE_H #endif // DAMAGE_TYPE_H

View File

@ -1,27 +1,59 @@
#include "demo.h" #include "demo.h"
#include <algorithm> #include <algorithm>
#include <atomic>
#include <iostream>
#include <string> #include <string>
#include "../base/file.h"
#include "../base/interpolation.h" #include "../base/interpolation.h"
#include "../base/log.h" #include "../base/log.h"
#include "../base/random.h" #include "../base/random.h"
#include "../base/timer.h"
#include "../engine/engine.h" #include "../engine/engine.h"
#include "../engine/game_factory.h" #include "../engine/game_factory.h"
#include "../engine/input_event.h" #include "../engine/input_event.h"
#include "../engine/sound.h"
DECLARE_GAME_BEGIN DECLARE_GAME_BEGIN
DECLARE_GAME(Demo) DECLARE_GAME(Demo)
DECLARE_GAME_END DECLARE_GAME_END
// #define RECORD 15
// #define REPLAY
using namespace std::string_literals;
using namespace base; using namespace base;
using namespace eng; 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() { bool Demo::Initialize() {
saved_data_.Load(kSaveFileName);
if (!font_.Load("PixelCaps!.ttf")) if (!font_.Load("PixelCaps!.ttf"))
return false; return false;
if (!sky_.Create()) { if (!sky_.Create(false)) {
LOG << "Could not create the sky."; LOG << "Could not create the sky.";
return false; return false;
} }
@ -51,6 +83,39 @@ bool Demo::Initialize() {
return false; 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(); EnterMenuState();
return true; return true;
@ -59,68 +124,101 @@ bool Demo::Initialize() {
void Demo::Update(float delta_time) { void Demo::Update(float delta_time) {
Engine& engine = Engine::Get(); Engine& engine = Engine::Get();
if (do_benchmark_) {
benchmark_time_ += delta_time;
if (benchmark_time_ > 1) {
avarage_fps_ += Engine::Get().fps();
++num_benchmark_samples_;
}
if (benchmark_time_ > 6) {
avarage_fps_ /= num_benchmark_samples_;
do_benchmark_ = false;
BenchmarkResult(avarage_fps_);
}
}
stage_time_ += delta_time;
while (std::unique_ptr<InputEvent> event = engine.GetNextInputEvent()) { 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) if (state_ == kMenu)
menu_.OnInputEvent(std::move(event)); menu_.OnInputEvent(std::move(event));
else if (state_ == kCredits) else if (state_ == kCredits)
credits_.OnInputEvent(std::move(event)); credits_.OnInputEvent(std::move(event));
else else if (state_ != kGameOver)
player_.OnInputEvent(std::move(event)); player_.OnInputEvent(std::move(event));
} }
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 (add_score_ > 0) {
score_ += add_score_;
add_score_ = 0;
hud_.SetScore(score_, true);
}
sky_.Update(delta_time);
player_.Update(delta_time);
enemy_.Update(delta_time);
if (state_ == kMenu) if (state_ == kMenu)
UpdateMenuState(delta_time); UpdateMenuState(delta_time);
else if (state_ == kGame) else if (state_ == kGame || state_ == kGameOver)
UpdateGameState(delta_time); UpdateGameState(delta_time);
} }
void Demo::ContextLost() { void Demo::ContextLost() {
sky_.ContextLost(); sky_.ContextLost();
enemy_.ContextLost();
} }
void Demo::LostFocus() { 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) if (state_ == kGame)
EnterMenuState(); EnterMenuState();
} }
}
void Demo::GainedFocus(bool from_interstitial_ad) {} void Demo::AddScore(size_t score) {
delta_score_ += score;
wave_score_ += score;
}
void Demo::AddScore(int score) { void Demo::SetEnableMusic(bool enable) {
add_score_ += score; if (enable) {
if (boss_fight_)
boss_music_.Resume(1);
else
music_.Resume(1);
} else {
music_.Stop(1);
boss_music_.Stop(1);
}
} }
void Demo::EnterMenuState() { void Demo::EnterMenuState() {
saved_data_.Save();
if (state_ == kMenu) if (state_ == kMenu)
return; 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) { if (state_ == kState_Invalid || state_ == kGame) {
sky_.SetSpeed(0); hud_.Pause(true);
player_.Pause(true); player_.Pause(true);
enemy_.Pause(true); enemy_.Pause(true);
} }
if (wave_ == 0) { if (state_ == kState_Invalid || state_ == kGameOver) {
menu_.SetOptionEnabled(Menu::kContinue, false); menu_.SetOptionEnabled(Menu::kContinue, false);
} else { menu_.SetOptionEnabled(Menu::kNewGame, true);
} else if (state_ == kGame) {
menu_.SetOptionEnabled(Menu::kContinue, true); menu_.SetOptionEnabled(Menu::kContinue, true);
menu_.SetOptionEnabled(Menu::kNewGame, false); menu_.SetOptionEnabled(Menu::kNewGame, false);
} }
@ -131,6 +229,7 @@ void Demo::EnterMenuState() {
void Demo::EnterCreditsState() { void Demo::EnterCreditsState() {
if (state_ == kCredits) if (state_ == kCredits)
return; return;
credits_.Show(); credits_.Show();
state_ = kCredits; state_ = kCredits;
} }
@ -139,13 +238,50 @@ void Demo::EnterGameState() {
if (state_ == kGame) if (state_ == kGame)
return; return;
Dimmer(false);
sky_.SetSpeed(0.04f); sky_.SetSpeed(0.04f);
hud_.Show(); hud_.Show();
hud_.Pause(false);
player_.Pause(false); player_.Pause(false);
enemy_.Pause(false); enemy_.Pause(false);
if (boss_fight_)
hud_.HideProgress();
state_ = kGame; 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;
if (boss_fight_) {
music_.Resume(10);
boss_music_.Stop(10);
}
SetDelayedWork(1, [&]() -> void {
enemy_.RemoveAll();
// hud_.Hide();
SetDelayedWork(3, [&]() -> void {
wave_ = 0;
boss_fight_ = false;
EnterMenuState();
});
});
#if defined(RECORD)
Engine::Get().EndRecording("replay");
#endif
}
void Demo::UpdateMenuState(float delta_time) { void Demo::UpdateMenuState(float delta_time) {
switch (menu_.selected_option()) { switch (menu_.selected_option()) {
case Menu::kOption_Invalid: case Menu::kOption_Invalid:
@ -155,8 +291,12 @@ void Demo::UpdateMenuState(float delta_time) {
Continue(); Continue();
break; break;
case Menu::kNewGame: case Menu::kNewGame:
menu_.Hide(); menu_.Hide([&]() {
if (saved_data_.root().get(kLaunchCount, Json::Value(0)).asInt() >
kLaunchCountBeforeAd)
Engine::Get().ShowInterstitialAd();
StartNewGame(); StartNewGame();
});
break; break;
case Menu::kCredits: case Menu::kCredits:
menu_.Hide(); menu_.Hide();
@ -171,20 +311,131 @@ void Demo::UpdateMenuState(float delta_time) {
} }
void Demo::UpdateGameState(float delta_time) { 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_) if (waiting_for_next_wave_)
return; return;
if (enemy_.num_enemies_killed_in_current_wave() != last_num_enemies_killed_) { 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(); last_num_enemies_killed_ = enemy_.num_enemies_killed_in_current_wave();
int enemies_remaining = total_enemies_ - last_num_enemies_killed_; int enemies_remaining = total_enemies_ - last_num_enemies_killed_;
if (enemies_remaining <= 0) { 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; waiting_for_next_wave_ = true;
hud_.SetProgress(wave_ > 0 ? 0 : 1); hud_.SetProgress(wave_ > 0 ? 0 : 1);
enemy_.OnWaveFinished(); DLOG_IF(wave_ > 0 && stage_time_ > 0)
<< "wave: " << wave_ << " time: " << stage_time_ / 60.0f;
stage_time_ = 0;
enemy_.PauseProgress();
enemy_.StopAllEnemyUnits();
if (boss) {
boss_music_.Play(true, 10);
music_.Stop(10);
} else if (boss_fight_) {
music_.Resume(10);
boss_music_.Stop(10);
}
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();
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;
}
SetDelayedWork(1, [&]() -> void {
Random& rnd = Engine::Get().GetRandomGenerator(); Random& rnd = Engine::Get().GetRandomGenerator();
int dominant_channel = rnd.Roll(3) - 1; int dominant_channel = rnd.Roll(3) - 1;
if (dominant_channel == last_dominant_channel_) if (dominant_channel == last_dominant_channel_)
@ -202,39 +453,56 @@ void Demo::UpdateGameState(float delta_time) {
sky_.SwitchColor(c); sky_.SwitchColor(c);
++wave_; ++wave_;
hud_.SetScore(score_, true); hud_.Show();
hud_.SetWave(wave_, true);
hud_.SetProgress(1); hud_.SetProgress(1);
float factor = 3 * (log10(5 * (float)wave_) / log10(1.2f)) - 25; if (boss_fight_)
total_enemies_ = (int)(6 * factor); player_.TakeDamage(-3);
last_num_enemies_killed_ = 0;
DLOG << "wave: " << wave_ << " total_enemies_: " << total_enemies_;
enemy_.OnWaveStarted(wave_); total_enemies_ = 20.0f + 23.0897f * log((float)wave_);
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; waiting_for_next_wave_ = false;
}); });
} else { });
hud_.SetProgress((float)enemies_remaining / (float)total_enemies_);
} }
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_.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);
});
} }
} }
void Demo::Continue() { size_t Demo::GetHighScore() const {
EnterGameState(); return saved_data_.root().get(kHightScore, Json::Value(0)).asUInt64();
}
void Demo::StartNewGame() {
score_ = 0;
add_score_ = 0;
wave_ = 0;
last_num_enemies_killed_ = -1;
total_enemies_ = 0;
waiting_for_next_wave_ = false;
delayed_work_timer_ = 0;
delayed_work_cb_ = nullptr;
EnterGameState();
} }
void Demo::SetDelayedWork(float seconds, base::Closure cb) { void Demo::SetDelayedWork(float seconds, base::Closure cb) {
@ -242,3 +510,9 @@ void Demo::SetDelayedWork(float seconds, base::Closure cb) {
delayed_work_cb_ = std::move(cb); delayed_work_cb_ = std::move(cb);
delayed_work_timer_ = seconds; delayed_work_timer_ = seconds;
} }
void Demo::BenchmarkResult(int avarage_fps) {
LOG << __func__ << " avarage_fps: " << avarage_fps;
if (avarage_fps < 30)
sky_.Create(true);
}

View File

@ -2,8 +2,12 @@
#define DEMO_H #define DEMO_H
#include "../base/closure.h" #include "../base/closure.h"
#include "../engine/animator.h"
#include "../engine/font.h" #include "../engine/font.h"
#include "../engine/game.h" #include "../engine/game.h"
#include "../engine/persistent_data.h"
#include "../engine/solid_quad.h"
#include "../engine/sound_player.h"
#include "credits.h" #include "credits.h"
#include "enemy.h" #include "enemy.h"
#include "hud.h" #include "hud.h"
@ -11,10 +15,12 @@
#include "player.h" #include "player.h"
#include "sky_quad.h" #include "sky_quad.h"
// #define LOAD_TEST
class Demo : public eng::Game { class Demo : public eng::Game {
public: public:
Demo() = default; Demo();
~Demo() override = default; ~Demo() override;
bool Initialize() override; bool Initialize() override;
@ -26,21 +32,38 @@ class Demo : public eng::Game {
void GainedFocus(bool from_interstitial_ad) override; void GainedFocus(bool from_interstitial_ad) override;
void AddScore(int score); void AddScore(size_t score);
void SetEnableMusic(bool enable);
void EnterMenuState(); void EnterMenuState();
void EnterCreditsState(); void EnterCreditsState();
void EnterGameState(); void EnterGameState();
void EnterGameOverState();
const eng::Font& GetFont() { return font_; } const eng::Font& GetFont() { return font_; }
Player& GetPlayer() { return player_; } Player& GetPlayer() { return player_; }
Enemy& GetEnemy() { return enemy_; } Enemy& GetEnemy() { return enemy_; }
int wave() { return wave_; } int wave() const { return wave_; }
size_t GetHighScore() const;
float stage_time() const { return stage_time_; }
eng::PersistentData& saved_data() { return saved_data_; }
const eng::PersistentData& saved_data() const { return saved_data_; }
private: private:
enum State { kState_Invalid = -1, kMenu, kGame, kCredits, kState_Max }; enum State {
kState_Invalid = -1,
kMenu,
kGame,
kCredits,
kGameOver,
kState_Max
};
State state_ = kState_Invalid; State state_ = kState_Invalid;
@ -55,8 +78,9 @@ class Demo : public eng::Game {
eng::Font font_; eng::Font font_;
int score_ = 0; size_t wave_score_ = 0;
int add_score_ = 0; size_t total_score_ = 0;
size_t delta_score_ = 0;
int wave_ = 0; int wave_ = 0;
@ -65,16 +89,42 @@ class Demo : public eng::Game {
int waiting_for_next_wave_ = false; int waiting_for_next_wave_ = false;
bool boss_fight_ = false;
float stage_time_ = 0;
eng::SoundPlayer music_;
eng::SoundPlayer boss_music_;
eng::SolidQuad dimmer_;
eng::Animator dimmer_animator_;
bool dimmer_active_ = false;
float delayed_work_timer_ = 0; float delayed_work_timer_ = 0;
base::Closure delayed_work_cb_; base::Closure delayed_work_cb_;
eng::PersistentData saved_data_;
bool do_benchmark_ = true;
float benchmark_time_ = 0;
int num_benchmark_samples_ = 0;
int avarage_fps_ = 0;
void UpdateMenuState(float delta_time); void UpdateMenuState(float delta_time);
void UpdateGameState(float delta_time); void UpdateGameState(float delta_time);
void Continue(); void Continue();
void StartNewGame(); void StartNewGame();
void StartNextStage(bool boss);
void Win();
void Dimmer(bool enable);
void SetDelayedWork(float seconds, base::Closure cb); void SetDelayedWork(float seconds, base::Closure cb);
void BenchmarkResult(int avarage_fps);
}; };
#endif // DEMO_H #endif // DEMO_H

File diff suppressed because it is too large Load Diff

View File

@ -14,6 +14,7 @@
namespace eng { namespace eng {
class Image; class Image;
class Shader;
class Sound; class Sound;
} // namespace eng } // namespace eng
@ -28,21 +29,34 @@ class Enemy {
void Pause(bool pause); void Pause(bool pause);
void ContextLost();
bool HasTarget(DamageType damage_type); bool HasTarget(DamageType damage_type);
base::Vector2f GetTargetPos(DamageType damage_type); base::Vector2f GetTargetPos(DamageType damage_type);
void SelectTarget(DamageType damage_type, void SelectTarget(DamageType damage_type,
const base::Vector2f& origin, const base::Vector2f& origin,
const base::Vector2f& dir, const base::Vector2f& dir);
float snap_factor);
void DeselectTarget(DamageType damage_type); void DeselectTarget(DamageType damage_type);
void HitTarget(DamageType damage_type); void HitTarget(DamageType damage_type);
void OnWaveFinished(); bool IsBossAlive() const;
void OnWaveStarted(int wave);
int num_enemies_killed_in_current_wave() { void PauseProgress();
void ResumeProgress();
void OnWaveStarted(int wave, bool boss_figt);
void StopAllEnemyUnits(bool chromatic_aberration_effect = false);
void KillAllEnemyUnits(bool randomize_order = true);
void RemoveAll();
void KillBoss();
void Reset();
int num_enemies_killed_in_current_wave() const {
return num_enemies_killed_in_current_wave_; return num_enemies_killed_in_current_wave_;
} }
@ -50,15 +64,28 @@ class Enemy {
struct EnemyUnit { struct EnemyUnit {
EnemyType enemy_type = kEnemyType_Invalid; EnemyType enemy_type = kEnemyType_Invalid;
DamageType damage_type = kDamageType_Invalid; DamageType damage_type = kDamageType_Invalid;
SpeedType speed_type = kSpeedType_Invalid;
bool marked_for_removal = false; bool marked_for_removal = false;
DamageType targetted_by_weapon_ = kDamageType_Invalid; bool targetted_by_weapon_[2] = {false, false};
int total_health = 0; int total_health = 0;
int hit_points = 0; int hit_points = 0;
float kill_timer = 0;
bool idle2_anim = false;
bool stealth_active = false;
bool shield_active = false;
bool freeze_ = false;
bool chromatic_aberration_active_ = false;
eng::ImageQuad sprite; eng::ImageQuad sprite;
eng::ImageQuad target; eng::ImageQuad target;
eng::ImageQuad blast; eng::ImageQuad blast;
eng::ImageQuad shield;
eng::ImageQuad score; eng::ImageQuad score;
eng::SolidQuad health_base; eng::SolidQuad health_base;
eng::SolidQuad health_bar; eng::SolidQuad health_bar;
@ -67,38 +94,72 @@ class Enemy {
eng::Animator sprite_animator; eng::Animator sprite_animator;
eng::Animator target_animator; eng::Animator target_animator;
eng::Animator blast_animator; eng::Animator blast_animator;
eng::Animator shield_animator;
eng::Animator health_animator; eng::Animator health_animator;
eng::Animator score_animator; eng::Animator score_animator;
eng::SoundPlayer explosion_; eng::SoundPlayer spawn;
eng::SoundPlayer explosion;
eng::SoundPlayer stealth;
eng::SoundPlayer shield_on;
eng::SoundPlayer hit;
}; };
std::shared_ptr<eng::Shader> chromatic_aberration_;
float chromatic_aberration_offset_ = 0;
eng::ImageQuad boss_;
eng::Animator boss_animator_;
eng::SoundPlayer boss_intro_;
std::shared_ptr<eng::Sound> boss_intro_sound_;
std::shared_ptr<eng::Sound> boss_explosion_sound_;
std::shared_ptr<eng::Sound> explosion_sound_; std::shared_ptr<eng::Sound> explosion_sound_;
std::shared_ptr<eng::Sound> stealth_sound_;
std::shared_ptr<eng::Sound> shield_on_sound_;
std::shared_ptr<eng::Sound> hit_sound_;
std::shared_ptr<eng::Sound> power_up_spawn_sound_;
std::shared_ptr<eng::Sound> power_up_pick_sound_;
std::list<EnemyUnit> enemies_; std::list<EnemyUnit> enemies_;
int num_enemies_killed_in_current_wave_ = 0; int num_enemies_killed_in_current_wave_ = 0;
std::array<float, kEnemyType_Max> seconds_since_last_spawn_ = {0, 0, 0}; std::array<float, kEnemyType_Unit_Last + 1> seconds_since_last_spawn_ = {
std::array<float, kEnemyType_Max> seconds_to_next_spawn_ = {0, 0, 0}; 0, 0, 0, 0};
std::array<float, kEnemyType_Unit_Last + 1> seconds_to_next_spawn_ = {0, 0, 0,
0};
float spawn_factor_ = 0; float spawn_factor_ = 0;
float spawn_factor_interpolator_ = 0;
bool waiting_for_next_wave_ = false; float boss_spawn_time_ = 0;
float boss_spawn_time_factor_ = 0;
float seconds_since_last_power_up_ = 0;
float seconds_to_next_power_up_ = 0;
bool progress_paused_ = true;
int last_spawn_col_ = 0; int last_spawn_col_ = 0;
bool paused_ = false; int wave_ = 0;
bool boss_fight_ = false;
bool CheckSpawnPos(base::Vector2f pos, SpeedType speed_type);
bool CheckTeleportPos(EnemyUnit* enemy);
void SpawnUnit(EnemyType enemy_type,
DamageType damage_type,
const base::Vector2f& pos,
float speed,
SpeedType speed_type = kSpeedType_Invalid);
void SpawnBoss();
void TakeDamage(EnemyUnit* target, int damage); void TakeDamage(EnemyUnit* target, int damage);
void SpawnNextEnemy(); void UpdateWave(float delta_time);
void UpdateBoss(float delta_time);
void Spawn(EnemyType enemy_type,
DamageType damage_type,
const base::Vector2f& pos,
float speed);
EnemyUnit* GetTarget(DamageType damage_type); EnemyUnit* GetTarget(DamageType damage_type);
@ -107,6 +168,9 @@ class Enemy {
std::unique_ptr<eng::Image> GetScoreImage(EnemyType enemy_type); std::unique_ptr<eng::Image> GetScoreImage(EnemyType enemy_type);
bool CreateRenderResources(); bool CreateRenderResources();
bool CreateShaders();
void TranslateEnemyUnit(EnemyUnit& e, const base::Vector2f& delta);
}; };
#endif // ENEMY_H #endif // ENEMY_H

View File

@ -22,6 +22,17 @@ const Vector4f kPprogressBarColor[2] = {{0.256f, 0.434f, 0.72f, 1},
{0.905f, 0.493f, 0.194f, 1}}; {0.905f, 0.493f, 0.194f, 1}};
const Vector4f kTextColor = {0.895f, 0.692f, 0.24f, 1}; const Vector4f kTextColor = {0.895f, 0.692f, 0.24f, 1};
void SetupFadeOutAnim(Animator& animator, float delay) {
animator.SetEndCallback(Animator::kTimer, [&]() -> void {
animator.SetBlending({1, 1, 1, 0}, 0.5f,
std::bind(Acceleration, std::placeholders::_1, -1));
animator.Play(Animator::kBlending, false);
});
animator.SetEndCallback(Animator::kBlending,
[&]() -> void { animator.SetVisible(false); });
animator.SetTimer(delay);
}
} // namespace } // namespace
Hud::Hud() = default; Hud::Hud() = default;
@ -38,6 +49,10 @@ bool Hud::Initialize() {
Engine::Get().SetImageSource("text0", Engine::Get().SetImageSource("text0",
std::bind(&Hud::CreateScoreImage, this)); std::bind(&Hud::CreateScoreImage, this));
Engine::Get().SetImageSource("text1", std::bind(&Hud::CreateWaveImage, this)); Engine::Get().SetImageSource("text1", std::bind(&Hud::CreateWaveImage, this));
Engine::Get().SetImageSource("message",
std::bind(&Hud::CreateMessageImage, this));
Engine::Get().SetImageSource("bonus_tex",
std::bind(&Hud::CreateBonusImage, this));
for (int i = 0; i < 2; ++i) { for (int i = 0; i < 2; ++i) {
text_[i].Create("text"s + std::to_string(i)); text_[i].Create("text"s + std::to_string(i));
@ -71,22 +86,81 @@ bool Hud::Initialize() {
text_animator_[i].Attach(&text_[i]); text_animator_[i].Attach(&text_[i]);
} }
message_.Create("message");
message_.SetZOrder(30);
message_animator_.SetEndCallback(Animator::kTimer, [&]() -> void {
message_animator_.SetEndCallback(Animator::kBlending, [&]() -> void {
message_animator_.SetVisible(false);
});
message_animator_.SetBlending({1, 1, 1, 0}, 0.5f);
message_animator_.Play(Animator::kBlending, false);
});
message_animator_.Attach(&message_);
bonus_.Create("bonus_tex");
bonus_.SetZOrder(30);
SetupFadeOutAnim(bonus_animator_, 1.0f);
bonus_animator_.SetMovement({0, Engine::Get().GetScreenSize().y / 2}, 2.0f);
bonus_animator_.Attach(&bonus_);
return true; return true;
} }
void Hud::Pause(bool pause) {
message_animator_.PauseOrResumeAll(pause);
bonus_animator_.PauseOrResumeAll(pause);
}
void Hud::Show() { void Hud::Show() {
if (text_[0].IsVisible()) if (text_[0].IsVisible() && text_[1].IsVisible() &&
progress_bar_[0].IsVisible() && progress_bar_[1].IsVisible())
return; return;
for (int i = 0; i < 2; ++i) { for (int i = 0; i < 2; ++i) {
progress_bar_[i].SetVisible(true); progress_bar_[i].SetVisible(true);
text_[i].SetVisible(true); text_[i].SetVisible(true);
progress_bar_animator_[i].SetBlending(kPprogressBarColor[i], 0.3f); progress_bar_animator_[i].SetBlending(kPprogressBarColor[i], 0.5f);
progress_bar_animator_[i].Play(Animator::kBlending, false); progress_bar_animator_[i].Play(Animator::kBlending, false);
} }
} }
void Hud::SetScore(int score, bool flash) { void Hud::Hide() {
if (!text_[0].IsVisible() && !text_[1].IsVisible() &&
!progress_bar_[0].IsVisible() && !progress_bar_[1].IsVisible())
return;
for (int i = 0; i < 2; ++i) {
text_animator_[i].SetEndCallback(Animator::kBlending, [&, i]() -> void {
text_animator_[i].SetEndCallback(Animator::kBlending, nullptr);
text_[i].SetVisible(false);
});
text_animator_[i].SetBlending(kTextColor * Vector4f(1, 1, 1, 0), 0.5f);
text_animator_[i].Play(Animator::kBlending, false);
}
HideProgress();
}
void Hud::HideProgress() {
if (!progress_bar_[0].IsVisible())
return;
for (int i = 0; i < 2; ++i) {
progress_bar_animator_[i].SetEndCallback(
Animator::kBlending, [&, i]() -> void {
progress_bar_animator_[i].SetEndCallback(Animator::kBlending,
nullptr);
progress_bar_animator_[1].SetVisible(false);
});
progress_bar_animator_[i].SetBlending(
kPprogressBarColor[i] * Vector4f(1, 1, 1, 0), 0.5f);
progress_bar_animator_[i].Play(Animator::kBlending, false);
}
}
void Hud::SetScore(size_t score, bool flash) {
last_score_ = score; last_score_ = score;
Engine::Get().RefreshImage("text0"); Engine::Get().RefreshImage("text0");
@ -104,7 +178,8 @@ void Hud::SetWave(int wave, bool flash) {
if (flash) { if (flash) {
text_animator_[1].SetEndCallback(Animator::kBlending, text_animator_cb_[1]); text_animator_[1].SetEndCallback(Animator::kBlending, text_animator_cb_[1]);
text_animator_[1].SetBlending({1, 1, 1, 1}, 0.08f); text_animator_[1].SetBlending(
{1, 1, 1, 1}, 0.1f, std::bind(Acceleration, std::placeholders::_1, 1));
text_animator_[1].Play(Animator::kBlending, false); text_animator_[1].Play(Animator::kBlending, false);
} }
} }
@ -118,6 +193,34 @@ void Hud::SetProgress(float progress) {
progress_bar_[1].Translate({t, 0}); progress_bar_[1].Translate({t, 0});
} }
void Hud::ShowMessage(const std::string& text, float duration) {
message_text_ = text;
Engine::Get().RefreshImage("message");
message_.AutoScale();
message_.Scale(1.5f);
message_.SetColor({1, 1, 1, 0});
message_.SetVisible(true);
message_animator_.SetEndCallback(
Animator::kBlending, [&, duration]() -> void {
message_animator_.SetTimer(duration);
message_animator_.Play(Animator::kTimer, false);
});
message_animator_.SetBlending({1, 1, 1, 1}, 0.5f);
message_animator_.Play(Animator::kBlending, false);
}
void Hud::ShowBonus(size_t bonus) {
bonus_score_ = bonus;
Engine::Get().RefreshImage("bonus_tex");
bonus_.AutoScale();
bonus_.Scale(1.3f);
bonus_.SetColor({1, 1, 1, 1});
bonus_.SetVisible(true);
bonus_animator_.Play(Animator::kTimer | Animator::kMovement, false);
}
std::unique_ptr<eng::Image> Hud::CreateScoreImage() { std::unique_ptr<eng::Image> Hud::CreateScoreImage() {
return Print(0, std::to_string(last_score_)); return Print(0, std::to_string(last_score_));
} }
@ -126,6 +229,45 @@ std::unique_ptr<eng::Image> Hud::CreateWaveImage() {
return Print(1, "wave "s + std::to_string(last_wave_)); return Print(1, "wave "s + std::to_string(last_wave_));
} }
std::unique_ptr<Image> Hud::CreateMessageImage() {
const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont();
auto image = std::make_unique<Image>();
image->Create(max_text_width_, font.GetLineHeight());
image->GradientV({0.80f, 0.87f, 0.93f, 0}, {0.003f, 0.91f, 0.99f, 0},
font.GetLineHeight());
int w, h;
font.CalculateBoundingBox(message_text_.c_str(), w, h);
float x = (image->GetWidth() - w) / 2;
font.Print(x, 0, message_text_.c_str(), image->GetBuffer(),
image->GetWidth());
image->Compress();
return image;
}
std::unique_ptr<Image> Hud::CreateBonusImage() {
const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont();
if (bonus_score_ == 0)
return nullptr;
std::string text = std::to_string(bonus_score_);
int width, height;
font.CalculateBoundingBox(text.c_str(), width, height);
auto image = std::make_unique<Image>();
image->Create(width, height);
image->Clear({1, 1, 1, 0});
font.Print(0, 0, text.c_str(), image->GetBuffer(), image->GetWidth());
image->Compress();
return image;
}
std::unique_ptr<Image> Hud::Print(int i, const std::string& text) { std::unique_ptr<Image> Hud::Print(int i, const std::string& text) {
const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont(); const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont();

View File

@ -20,28 +20,46 @@ class Hud {
bool Initialize(); bool Initialize();
void Show(); void Pause(bool pause);
void SetScore(int score, bool flash); void Show();
void Hide();
void HideProgress();
void SetScore(size_t score, bool flash);
void SetWave(int wave, bool flash); void SetWave(int wave, bool flash);
void SetProgress(float progress); void SetProgress(float progress);
void ShowMessage(const std::string& text, float duration);
void ShowBonus(size_t bonus);
private: private:
eng::SolidQuad progress_bar_[2]; eng::SolidQuad progress_bar_[2];
eng::ImageQuad text_[2]; eng::ImageQuad text_[2];
eng::ImageQuad message_;
eng::ImageQuad bonus_;
eng::Animator progress_bar_animator_[2]; eng::Animator progress_bar_animator_[2];
eng::Animator text_animator_[2]; eng::Animator text_animator_[2];
eng::Animator message_animator_;
base::Closure text_animator_cb_[2]; base::Closure text_animator_cb_[2];
eng::Animator bonus_animator_;
int max_text_width_ = 0; int max_text_width_ = 0;
int last_score_ = 0; size_t last_score_ = 0;
int last_wave_ = 0; int last_wave_ = 0;
float last_progress_ = 0; float last_progress_ = 0;
std::string message_text_;
size_t bonus_score_ = 0;
std::unique_ptr<eng::Image> CreateScoreImage(); std::unique_ptr<eng::Image> CreateScoreImage();
std::unique_ptr<eng::Image> CreateWaveImage(); std::unique_ptr<eng::Image> CreateWaveImage();
std::unique_ptr<eng::Image> CreateMessageImage();
std::unique_ptr<eng::Image> CreateBonusImage();
std::unique_ptr<eng::Image> Print(int i, const std::string& text); std::unique_ptr<eng::Image> Print(int i, const std::string& text);

View File

@ -1,6 +1,7 @@
#include "menu.h" #include "menu.h"
#include <cmath> #include <cmath>
#include <string>
#include <vector> #include <vector>
#include "../base/collusion_test.h" #include "../base/collusion_test.h"
@ -10,25 +11,39 @@
#include "../engine/font.h" #include "../engine/font.h"
#include "../engine/image.h" #include "../engine/image.h"
#include "../engine/input_event.h" #include "../engine/input_event.h"
#include "../engine/sound.h"
#include "demo.h" #include "demo.h"
using namespace std::string_literals;
using namespace base; using namespace base;
using namespace eng; using namespace eng;
namespace { namespace {
constexpr char kVersionStr[] = "Version 1.0.1";
constexpr char kMenuOption[Menu::kOption_Max][10] = {"continue", "start", constexpr char kMenuOption[Menu::kOption_Max][10] = {"continue", "start",
"credits", "exit"}; "credits", "exit"};
constexpr float kMenuOptionSpace = 1.5f; constexpr float kMenuOptionSpace = 1.5f;
const Vector4f kColorNormal = {1, 1, 1, 1}; const Vector4f kColorNormal = {1, 1, 1, 1};
const Vector4f kColorHighlight = {5, 5, 5, 1}; const Vector4f kColorHighlight = {20, 20, 20, 1};
constexpr float kBlendingSpeed = 0.12f; constexpr float kBlendingSpeed = 0.12f;
const Vector4f kColorSwitch[2] = {{0.003f, 0.91f, 0.99f, 1},
{0.33f, 0.47, 0.51f, 1}};
const Vector4f kColorFadeOut = {1, 1, 1, 0}; const Vector4f kColorFadeOut = {1, 1, 1, 0};
constexpr float kFadeSpeed = 0.2f; constexpr float kFadeSpeed = 0.2f;
const Vector4f kHighScoreColor = {0.895f, 0.692f, 0.24f, 1};
const char kLastWave[] = "last_wave";
constexpr int kMaxStartWave = 10;
} // namespace } // namespace
Menu::Menu() = default; Menu::Menu() = default;
@ -36,7 +51,13 @@ Menu::Menu() = default;
Menu::~Menu() = default; Menu::~Menu() = default;
bool Menu::Initialize() { bool Menu::Initialize() {
const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont(); click_sound_ = std::make_shared<Sound>();
if (!click_sound_->Load("menu_click.mp3", false))
return false;
Demo* game = static_cast<Demo*>(Engine::Get().GetGame());
const Font& font = game->GetFont();
max_text_width_ = -1; max_text_width_ = -1;
for (int i = 0; i < kOption_Max; ++i) { for (int i = 0; i < kOption_Max; ++i) {
@ -51,31 +72,166 @@ bool Menu::Initialize() {
for (int i = 0; i < kOption_Max; ++i) { for (int i = 0; i < kOption_Max; ++i) {
items_[i].text.Create("menu_tex", {1, 4}); items_[i].text.Create("menu_tex", {1, 4});
items_[i].text.SetZOrder(40); items_[i].text.SetZOrder(41);
items_[i].text.Scale(1.5f); items_[i].text.Scale(1.5f);
items_[i].text.SetColor(kColorFadeOut); items_[i].text.SetColor(kColorFadeOut);
items_[i].text.SetVisible(false); items_[i].text.SetVisible(false);
items_[i].text.SetFrame(i); items_[i].text.SetFrame(i);
items_[i].select_item_cb_ = [&, i]() -> void { items_[i].select_item_cb_ = [&, i]() -> void {
items_[i].text_animator.SetEndCallback( items_[i].text_animator.SetEndCallback(Animator::kBlending, nullptr);
Animator::kBlending, [&, i]() -> void {
items_[i].text_animator.SetEndCallback(Animator::kBlending,
nullptr);
selected_option_ = (Option)i; selected_option_ = (Option)i;
});
items_[i].text_animator.SetBlending(kColorNormal, kBlendingSpeed);
items_[i].text_animator.Play(Animator::kBlending, false);
}; };
items_[i].text_animator.Attach(&items_[i].text); items_[i].text_animator.Attach(&items_[i].text);
} }
// Get the item positions calculated. // Get the item positions calculated.
SetOptionEnabled(kContinue, true); SetOptionEnabled(kContinue, true);
click_.SetSound(click_sound_);
click_.SetVariate(false);
click_.SetSimulateStereo(false);
click_.SetMaxAplitude(1.5f);
logo_[0].Create("logo_tex0", {3, 8});
logo_[0].SetZOrder(41);
logo_[0].SetPosition(Engine::Get().GetScreenSize() * Vector2f(0, 0.35f));
logo_[1].Create("logo_tex1", {3, 7});
logo_[1].SetZOrder(41);
logo_[1].SetPosition(Engine::Get().GetScreenSize() * Vector2f(0, 0.35f));
logo_animator_[0].Attach(&logo_[0]);
logo_animator_[0].SetFrames(24, 20);
logo_animator_[0].SetEndCallback(Animator::kFrames, [&]() -> void {
logo_[0].SetVisible(false);
logo_[1].SetVisible(true);
logo_[1].SetFrame(0);
logo_animator_[1].SetFrames(12, 20);
logo_animator_[1].SetTimer(
Lerp(3.0f, 8.0f, Engine::Get().GetRandomGenerator().GetFloat()));
logo_animator_[1].Play(Animator::kFrames | Animator::kTimer, true);
});
logo_animator_[1].Attach(&logo_[1]);
logo_animator_[1].SetEndCallback(Animator::kTimer, [&]() -> void {
logo_animator_[1].Stop(Animator::kFrames);
logo_[1].SetFrame(12);
logo_animator_[1].SetFrames(9, 30);
logo_animator_[1].SetTimer(
Lerp(3.0f, 8.0f, Engine::Get().GetRandomGenerator().GetFloat()));
logo_animator_[1].Play(Animator::kFrames | Animator::kTimer, false);
});
logo_animator_[1].SetEndCallback(Animator::kFrames, [&]() -> void {
logo_[1].SetFrame(0);
logo_animator_[1].SetFrames(12, 20);
logo_animator_[1].Play(Animator::kFrames, true);
});
toggle_audio_.Create(
"buttons_tex", {4, 2}, 2, 6,
[&] {
Engine::Get().SetEnableAudio(toggle_audio_.enabled());
Demo* game = static_cast<Demo*>(Engine::Get().GetGame());
if (toggle_audio_.enabled()) {
if (toggle_music_.enabled())
game->SetEnableMusic(true);
else
click_.Play(false);
} else {
game->SetEnableMusic(false);
}
game->saved_data().root()["audio"] = toggle_audio_.enabled();
},
true, game->saved_data().root().get("audio", Json::Value(true)).asBool());
toggle_audio_.image().SetPosition(Engine::Get().GetScreenSize() *
Vector2f(0, -0.25f));
toggle_audio_.image().Scale(0.7f);
toggle_music_.Create(
"buttons_tex", {4, 2}, 1, 5,
[&] {
Demo* game = static_cast<Demo*>(Engine::Get().GetGame());
game->SetEnableMusic(toggle_music_.enabled());
game->saved_data().root()["music"] = toggle_music_.enabled();
},
true, game->saved_data().root().get("music", Json::Value(true)).asBool());
toggle_music_.image().SetPosition(Engine::Get().GetScreenSize() *
Vector2f(0, -0.25f));
toggle_music_.image().Scale(0.7f);
toggle_vibration_.Create(
"buttons_tex", {4, 2}, 3, 7,
[&] {
Engine::Get().SetEnableVibration(toggle_vibration_.enabled());
if (toggle_vibration_.enabled())
Engine::Get().Vibrate(50);
Demo* game = static_cast<Demo*>(Engine::Get().GetGame());
game->saved_data().root()["vibration"] = toggle_vibration_.enabled();
},
true,
game->saved_data().root().get("vibration", Json::Value(true)).asBool());
toggle_vibration_.image().SetPosition(Engine::Get().GetScreenSize() *
Vector2f(0, -0.25f));
toggle_vibration_.image().Scale(0.7f);
toggle_audio_.image().PlaceToLeftOf(toggle_music_.image());
toggle_audio_.image().Translate({toggle_music_.image().GetSize().x / -2, 0});
toggle_vibration_.image().PlaceToRightOf(toggle_music_.image());
toggle_vibration_.image().Translate(
{toggle_music_.image().GetSize().x / 2, 0});
high_score_value_ = game->GetHighScore();
high_score_.Create("high_score_tex");
high_score_.SetZOrder(41);
high_score_.Scale(0.8f);
high_score_.SetPosition(Engine::Get().GetScreenSize() * Vector2f(0, 0.225f));
high_score_.SetColor(kHighScoreColor * Vector4f(1, 1, 1, 0));
high_score_.SetVisible(false);
high_score_animator_.Attach(&high_score_);
version_.Create("version_tex");
version_.SetZOrder(41);
version_.Scale(0.6f);
version_.SetPosition(Engine::Get().GetScreenSize() * Vector2f(0, -0.5f) +
version_.GetSize() * Vector2f(0, 2));
version_.SetColor(kHighScoreColor * Vector4f(1, 1, 1, 0));
version_.SetVisible(false);
version_animator_.Attach(&version_);
start_from_wave_ = 1;
starting_wave_.Create("starting_wave");
wave_up_.Create(
"wave_up_tex", {1, 1}, 0, 0,
[&] {
Demo* game = static_cast<Demo*>(Engine::Get().GetGame());
start_from_wave_ += 3;
if (start_from_wave_ > game->saved_data()
.root()
.get(kLastWave, Json::Value(1))
.asInt() ||
start_from_wave_ > kMaxStartWave)
start_from_wave_ = 1;
starting_wave_.image().SetFrame(start_from_wave_ / 3);
click_.Play(false);
},
false, true);
wave_up_.image().Scale(1.5f);
return true; return true;
} }
void Menu::OnInputEvent(std::unique_ptr<InputEvent> event) { void Menu::OnInputEvent(std::unique_ptr<InputEvent> event) {
if (toggle_audio_.OnInputEvent(event.get()) ||
toggle_music_.OnInputEvent(event.get()) ||
toggle_vibration_.OnInputEvent(event.get()) ||
(wave_up_.image().IsVisible() && wave_up_.OnInputEvent(event.get())))
return;
if (event->GetType() == InputEvent::kDragStart) { if (event->GetType() == InputEvent::kDragStart) {
tap_pos_[0] = event->GetVector(); tap_pos_[0] = event->GetVector();
tap_pos_[1] = event->GetVector(); tap_pos_[1] = event->GetVector();
@ -102,6 +258,8 @@ void Menu::OnInputEvent(std::unique_ptr<InputEvent> event) {
items_[i].select_item_cb_); items_[i].select_item_cb_);
items_[i].text_animator.SetBlending(kColorHighlight, kBlendingSpeed); items_[i].text_animator.SetBlending(kColorHighlight, kBlendingSpeed);
items_[i].text_animator.Play(Animator::kBlending, false); items_[i].text_animator.Play(Animator::kBlending, false);
click_.Play(false);
} }
} }
@ -135,6 +293,40 @@ void Menu::SetOptionEnabled(Option o, bool enable) {
} }
void Menu::Show() { void Menu::Show() {
logo_[1].SetColor(kColorNormal);
logo_animator_[0].SetVisible(true);
logo_animator_[0].SetBlending(kColorNormal, kFadeSpeed);
logo_animator_[0].Play(Animator::kBlending | Animator::kFrames, false);
if (high_score_value_ !=
static_cast<Demo*>(Engine::Get().GetGame())->GetHighScore()) {
high_score_value_ =
static_cast<Demo*>(Engine::Get().GetGame())->GetHighScore();
Engine::Get().RefreshImage("high_score_tex");
high_score_animator_.SetEndCallback(Animator::kBlending, [&]() -> void {
high_score_animator_.SetBlending(kColorFadeOut, 0.3f);
high_score_animator_.SetTimer(5);
high_score_animator_.Play(Animator::kBlending | Animator::kTimer, true);
});
high_score_animator_.SetEndCallback(Animator::kTimer, [&]() -> void {
high_score_animator_.Play(Animator::kBlending | Animator::kTimer, false);
high_score_animator_.SetEndCallback(Animator::kBlending, [&]() -> void {
high_score_animator_.Stop(Animator::kBlending);
});
high_score_animator_.SetEndCallback(Animator::kTimer, nullptr);
});
}
if (high_score_value_ > 0) {
high_score_animator_.SetVisible(true);
high_score_animator_.SetBlending(kHighScoreColor, kFadeSpeed);
high_score_animator_.Play(Animator::kBlending, false);
}
version_animator_.SetVisible(true);
version_animator_.SetBlending(kHighScoreColor, kFadeSpeed);
version_animator_.Play(Animator::kBlending, false);
for (int i = 0; i < kOption_Max; ++i) { for (int i = 0; i < kOption_Max; ++i) {
if (items_[i].hide) if (items_[i].hide)
continue; continue;
@ -146,9 +338,54 @@ void Menu::Show() {
items_[i].text_animator.Play(Animator::kBlending, false); items_[i].text_animator.Play(Animator::kBlending, false);
items_[i].text.SetVisible(true); items_[i].text.SetVisible(true);
} }
toggle_audio_.Show();
toggle_music_.Show();
toggle_vibration_.Show();
Demo* game = static_cast<Demo*>(Engine::Get().GetGame());
if (!items_[kNewGame].hide &&
game->saved_data().root().get(kLastWave, Json::Value(1)).asInt() > 3) {
wave_up_.image().SetPosition(items_[1].text.GetPosition());
wave_up_.image().PlaceToRightOf(items_[1].text);
starting_wave_.image().SetPosition(wave_up_.image().GetPosition());
starting_wave_.Show();
wave_up_.Show();
}
} }
void Menu::Hide() { void Menu::Hide(Closure cb) {
for (int i = 0; i < 2; ++i) {
logo_animator_[i].Stop(Animator::kAllAnimations | Animator::kTimer);
logo_animator_[i].SetBlending(kColorFadeOut, kFadeSpeed);
logo_animator_[i].SetEndCallback(Animator::kBlending, [&, i, cb]() -> void {
logo_animator_[i].Stop(Animator::kAllAnimations | Animator::kTimer);
logo_animator_[i].SetEndCallback(Animator::kBlending, nullptr);
logo_animator_[i].SetVisible(false);
if (i == 0 && cb)
cb();
});
logo_animator_[i].Play(Animator::kBlending, false);
}
high_score_animator_.Stop(Animator::kAllAnimations | Animator::kTimer);
high_score_animator_.SetEndCallback(Animator::kTimer, nullptr);
high_score_animator_.SetBlending(kColorFadeOut, kFadeSpeed);
high_score_animator_.SetEndCallback(Animator::kBlending, [&]() -> void {
high_score_animator_.SetEndCallback(Animator::kBlending, nullptr);
high_score_animator_.SetVisible(false);
});
high_score_animator_.Play(Animator::kBlending, false);
version_animator_.Stop(Animator::kAllAnimations);
version_animator_.SetBlending(kColorFadeOut, kFadeSpeed);
version_animator_.SetEndCallback(Animator::kBlending, [&]() -> void {
version_animator_.SetEndCallback(Animator::kBlending, nullptr);
version_animator_.SetVisible(false);
});
version_animator_.Play(Animator::kBlending, false);
selected_option_ = kOption_Invalid; selected_option_ = kOption_Invalid;
for (int i = 0; i < kOption_Max; ++i) { for (int i = 0; i < kOption_Max; ++i) {
if (items_[i].hide) if (items_[i].hide)
@ -161,15 +398,64 @@ void Menu::Hide() {
items_[i].text_animator.SetBlending(kColorFadeOut, kFadeSpeed); items_[i].text_animator.SetBlending(kColorFadeOut, kFadeSpeed);
items_[i].text_animator.Play(Animator::kBlending, false); items_[i].text_animator.Play(Animator::kBlending, false);
} }
toggle_audio_.Hide();
toggle_music_.Hide();
toggle_vibration_.Hide();
if (starting_wave_.image().IsVisible()) {
starting_wave_.Hide();
wave_up_.Hide();
}
} }
bool Menu::CreateRenderResources() { bool Menu::CreateRenderResources() {
Engine::Get().SetImageSource("menu_tex", std::bind(&Menu::CreateImage, this)); Engine::Get().SetImageSource("menu_tex",
std::bind(&Menu::CreateMenuImage, this));
Engine::Get().SetImageSource("logo_tex0", "woom_logo_start_frames_01.png");
Engine::Get().SetImageSource("logo_tex1", "woom_logo_start_frames_02-03.png");
Engine::Get().SetImageSource("buttons_tex", "menu_icons.png");
Engine::Get().SetImageSource("high_score_tex",
std::bind(&Menu::CreateHighScoreImage, this));
Engine::Get().SetImageSource("wave_up_tex", []() -> std::unique_ptr<Image> {
const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont();
constexpr char btn_text[] = "[ ]";
int w, h;
font.CalculateBoundingBox(btn_text, w, h);
auto image = std::make_unique<Image>();
image->Create(w, h);
image->Clear({1, 1, 1, 0});
font.Print(0, 0, btn_text, image->GetBuffer(), image->GetWidth());
image->Compress();
return image;
});
Engine::Get().SetImageSource("version_tex", []() -> std::unique_ptr<Image> {
const Font* font = Engine::Get().GetSystemFont();
int w, h;
font->CalculateBoundingBox(kVersionStr, w, h);
auto image = std::make_unique<Image>();
image->Create(w, font->GetLineHeight());
image->Clear({1, 1, 1, 0});
font->Print(0, 0, kVersionStr, image->GetBuffer(), image->GetWidth());
image->Compress();
return image;
});
return true; return true;
} }
std::unique_ptr<Image> Menu::CreateImage() { std::unique_ptr<Image> Menu::CreateMenuImage() {
const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont(); const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont();
int line_height = font.GetLineHeight() + 1; int line_height = font.GetLineHeight() + 1;
@ -177,7 +463,8 @@ std::unique_ptr<Image> Menu::CreateImage() {
image->Create(max_text_width_, line_height * kOption_Max); image->Create(max_text_width_, line_height * kOption_Max);
// Fill the area of each menu item with gradient. // Fill the area of each menu item with gradient.
image->GradientV({1.0f, 1.0f, 1.0f, 0}, {.0f, .0f, 1.0f, 0}, line_height); image->GradientV({0.80f, 0.87f, 0.93f, 0},
kColorSwitch[0] * Vector4f(1, 1, 1, 0), line_height);
for (int i = 0; i < kOption_Max; ++i) { for (int i = 0; i < kOption_Max; ++i) {
int w, h; int w, h;
@ -187,6 +474,23 @@ std::unique_ptr<Image> Menu::CreateImage() {
font.Print(x, y, kMenuOption[i], image->GetBuffer(), image->GetWidth()); font.Print(x, y, kMenuOption[i], image->GetBuffer(), image->GetWidth());
} }
image->Compress();
return image;
}
std::unique_ptr<Image> Menu::CreateHighScoreImage() {
std::string text = "High Score: "s + std::to_string(high_score_value_);
const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont();
int width, height;
font.CalculateBoundingBox(text, width, height);
auto image = std::make_unique<Image>();
image->Create(width, height);
image->Clear({1, 1, 1, 0});
font.Print(0, 0, text, image->GetBuffer(), image->GetWidth());
image->Compress();
return image; return image;
} }
@ -197,3 +501,141 @@ bool Menu::IsAnimating() {
} }
return false; return false;
} }
//
// Menu::Button implementation
//
void Menu::Button::Create(const std::string& asset_name,
std::array<int, 2> num_frames,
int frame1,
int frame2,
Closure pressed_cb,
bool switch_control,
bool enabled) {
frame1_ = frame1;
frame2_ = frame2;
pressed_cb_ = std::move(pressed_cb);
switch_control_ = switch_control;
enabled_ = enabled;
image_.Create(asset_name, num_frames);
image_.SetFrame(enabled ? frame1 : frame2);
image_.SetColor(kColorFadeOut);
image_.SetZOrder(41);
image_.SetVisible(false);
animator_.Attach(&image_);
}
bool Menu::Button::OnInputEvent(eng::InputEvent* event) {
if (event->GetType() == InputEvent::kDragStart) {
tap_pos_[0] = event->GetVector();
tap_pos_[1] = event->GetVector();
} else if (event->GetType() == InputEvent::kDrag) {
tap_pos_[1] = event->GetVector();
}
if (event->GetType() != InputEvent::kDragEnd)
return false;
if (!Intersection(image_.GetPosition(), image_.GetSize() * Vector2f(1.2f, 2),
tap_pos_[0]))
return false;
if (!Intersection(image_.GetPosition(), image_.GetSize() * Vector2f(1.2f, 2),
tap_pos_[1]))
return false;
SetEnabled(!enabled_);
pressed_cb_();
return true;
}
void Menu::Button::Show() {
animator_.SetVisible(true);
animator_.SetBlending(enabled_ ? kColorSwitch[0] : kColorSwitch[1],
kBlendingSpeed);
animator_.Play(Animator::kBlending, false);
animator_.SetEndCallback(Animator::kBlending, nullptr);
}
void Menu::Button::Hide() {
animator_.SetBlending(kColorFadeOut, kBlendingSpeed);
animator_.Play(Animator::kBlending, false);
animator_.SetEndCallback(Animator::kBlending,
[&]() -> void { animator_.SetVisible(false); });
}
void Menu::Button::SetEnabled(bool enable) {
if (switch_control_) {
enabled_ = enable;
image_.SetFrame(enabled_ ? frame1_ : frame2_);
image_.SetColor(enabled_ ? kColorSwitch[0] : kColorSwitch[1]);
}
}
//
// Menu::Radio implementation
//
void Menu::Radio::Create(const std::string& asset_name) {
Engine::Get().SetImageSource(asset_name,
std::bind(&Radio::CreateImage, this));
options_.Create(asset_name, {1, (kMaxStartWave + 2) / 3});
options_.SetZOrder(41);
options_.SetColor(kColorFadeOut);
options_.SetFrame(0);
options_.SetVisible(false);
animator_.Attach(&options_);
}
bool Menu::Radio::OnInputEvent(eng::InputEvent* event) {
return false;
}
void Menu::Radio::Show() {
animator_.SetVisible(true);
animator_.SetBlending(kHighScoreColor, kBlendingSpeed);
animator_.Play(Animator::kBlending, false);
animator_.SetEndCallback(Animator::kBlending, nullptr);
}
void Menu::Radio::Hide() {
animator_.SetBlending(kColorFadeOut, kBlendingSpeed);
animator_.Play(Animator::kBlending, false);
animator_.SetEndCallback(Animator::kBlending,
[&]() -> void { animator_.SetVisible(false); });
}
std::unique_ptr<eng::Image> Menu::Radio::CreateImage() {
const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont();
int max_width = 0;
for (int i = 1; i <= kMaxStartWave; i += 3) {
int w, h;
font.CalculateBoundingBox(std::to_string(i), w, h);
if (w > max_width)
max_width = w;
}
int line_height = font.GetLineHeight() + 1;
auto image = std::make_unique<Image>();
image->Create(max_width, line_height * ((kMaxStartWave + 2) / 3));
image->Clear({1, 1, 1, 0});
for (int i = 1, j = 0; i <= kMaxStartWave; i += 3) {
int w, h;
font.CalculateBoundingBox(std::to_string(i), w, h);
float x = (image->GetWidth() - w) / 2;
float y = line_height * j++;
font.Print(x, y, std::to_string(i), image->GetBuffer(), image->GetWidth());
}
image->Compress();
return image;
}

View File

@ -8,10 +8,12 @@
#include "../base/vecmath.h" #include "../base/vecmath.h"
#include "../engine/animator.h" #include "../engine/animator.h"
#include "../engine/image_quad.h" #include "../engine/image_quad.h"
#include "../engine/sound_player.h"
namespace eng { namespace eng {
class Image; class Image;
class InputEvent; class InputEvent;
class Sound;
} // namespace eng } // namespace eng
class Menu { class Menu {
@ -35,11 +37,70 @@ class Menu {
void SetOptionEnabled(Option o, bool enable); void SetOptionEnabled(Option o, bool enable);
void Show(); void Show();
void Hide(); void Hide(base::Closure cb = nullptr);
Option selected_option() const { return selected_option_; } Option selected_option() const { return selected_option_; }
int start_from_wave() { return start_from_wave_; }
private: private:
class Button {
public:
Button() = default;
~Button() = default;
void Create(const std::string& asset_name,
std::array<int, 2> num_frames,
int frame1,
int frame2,
base::Closure pressed_cb,
bool switch_control,
bool enabled);
bool OnInputEvent(eng::InputEvent* event);
void Show();
void Hide();
eng::ImageQuad& image() { return image_; };
bool enabled() const { return enabled_; }
private:
eng::ImageQuad image_;
eng::Animator animator_;
int frame1_ = 0;
int frame2_ = 0;
base::Closure pressed_cb_;
bool switch_control_ = false;
bool enabled_ = false;
base::Vector2f tap_pos_[2] = {{0, 0}, {0, 0}};
void SetEnabled(bool enable);
};
class Radio {
public:
Radio() = default;
~Radio() = default;
void Create(const std::string& asset_name);
bool OnInputEvent(eng::InputEvent* event);
void Show();
void Hide();
eng::ImageQuad& image() { return options_; };
private:
eng::ImageQuad options_;
eng::Animator animator_;
std::unique_ptr<eng::Image> CreateImage();
};
struct Item { struct Item {
eng::ImageQuad text; eng::ImageQuad text;
eng::Animator text_animator; eng::Animator text_animator;
@ -47,6 +108,13 @@ class Menu {
bool hide = false; bool hide = false;
}; };
eng::ImageQuad logo_[2];
eng::Animator logo_animator_[2];
std::shared_ptr<eng::Sound> click_sound_;
eng::SoundPlayer click_;
Item items_[kOption_Max]; Item items_[kOption_Max];
int max_text_width_ = 0; int max_text_width_ = 0;
@ -55,9 +123,28 @@ class Menu {
base::Vector2f tap_pos_[2] = {{0, 0}, {0, 0}}; base::Vector2f tap_pos_[2] = {{0, 0}, {0, 0}};
Button toggle_audio_;
Button toggle_music_;
Button toggle_vibration_;
int high_score_value_ = 0;
eng::ImageQuad high_score_;
eng::Animator high_score_animator_;
eng::ImageQuad version_;
eng::Animator version_animator_;
int start_from_wave_ = 1;
Radio starting_wave_;
Button wave_up_;
Button wave_down_;
bool CreateRenderResources(); bool CreateRenderResources();
std::unique_ptr<eng::Image> CreateImage(); std::unique_ptr<eng::Image> CreateMenuImage();
std::unique_ptr<eng::Image> CreateHighScoreImage();
bool IsAnimating(); bool IsAnimating();
}; };

View File

@ -1,9 +1,11 @@
#include "player.h" #include "player.h"
#include "../base/interpolation.h"
#include "../base/log.h" #include "../base/log.h"
#include "../engine/engine.h" #include "../engine/engine.h"
#include "../engine/image.h" #include "../engine/font.h"
#include "../engine/input_event.h" #include "../engine/input_event.h"
#include "../engine/sound.h"
#include "demo.h" #include "demo.h"
using namespace base; using namespace base;
@ -17,6 +19,9 @@ constexpr int wepon_cooldown_frame[] = {5, 13};
constexpr int wepon_cooldown_frame_count = 3; constexpr int wepon_cooldown_frame_count = 3;
constexpr int wepon_anim_speed = 48; constexpr int wepon_anim_speed = 48;
const Vector4f kNukeColor[2] = {{0.16f, 0.46f, 0.93f, 0},
{0.93f, 0.35f, 0.15f, 1}};
} // namespace } // namespace
Player::Player() = default; Player::Player() = default;
@ -26,13 +31,78 @@ Player::~Player() = default;
bool Player::Initialize() { bool Player::Initialize() {
if (!CreateRenderResources()) if (!CreateRenderResources())
return false; return false;
laser_shot_sound_ = std::make_shared<Sound>();
if (!laser_shot_sound_->Load("laser.mp3", false))
return false;
nuke_explosion_sound_ = std::make_shared<Sound>();
if (!nuke_explosion_sound_->Load("nuke.mp3", false))
return false;
no_nuke_sound_ = std::make_shared<Sound>();
if (!no_nuke_sound_->Load("no_nuke.mp3", false))
return false;
SetupWeapons(); SetupWeapons();
Vector2f hb_pos = Engine::Get().GetScreenSize() / Vector2f(2, -2) +
Vector2f(0, weapon_[0].GetSize().y * 0.3f);
for (int i = 0; i < 3; ++i) {
health_bead_[i].Create("health_bead", {1, 2});
health_bead_[i].SetZOrder(25);
health_bead_[i].Translate(hb_pos * Vector2f(0, 1));
health_bead_[i].SetVisible(true);
}
health_bead_[0].PlaceToLeftOf(health_bead_[1]);
health_bead_[0].Translate(health_bead_[1].GetSize() * Vector2f(-0.1f, 0));
health_bead_[2].PlaceToRightOf(health_bead_[1]);
health_bead_[2].Translate(health_bead_[1].GetSize() * Vector2f(0.1f, 0));
nuke_symbol_.Create("nuke_symbol_tex", {5, 1});
nuke_symbol_.SetZOrder(29);
nuke_symbol_.SetPosition({0, weapon_[0].GetPosition().y});
nuke_symbol_.SetFrame(4);
nuke_symbol_.SetVisible(true);
nuke_.SetZOrder(20);
nuke_.SetSize(Engine::Get().GetScreenSize());
nuke_.SetColor(kNukeColor[0]);
nuke_animator_.Attach(&nuke_);
nuke_symbol_animator_.Attach(&nuke_symbol_);
nuke_explosion_.SetSound(nuke_explosion_sound_);
nuke_explosion_.SetVariate(false);
nuke_explosion_.SetSimulateStereo(false);
nuke_explosion_.SetMaxAplitude(0.8f);
no_nuke_.SetSound(no_nuke_sound_);
return true; return true;
} }
void Player::Update(float delta_time) { void Player::Update(float delta_time) {
if (active_weapon_ != kDamageType_Invalid) for (int i = 0; i < 2; ++i) {
UpdateTarget(); if (drag_weapon_[i] != kDamageType_Invalid)
UpdateTarget(drag_weapon_[i]);
}
#if defined(LOAD_TEST)
Enemy& enemy = static_cast<Demo*>(Engine::Get().GetGame())->GetEnemy();
if (enemy.num_enemies_killed_in_current_wave() == 40)
Nuke();
DamageType type =
(DamageType)(Engine::Get().GetRandomGenerator().Roll(2) - 1);
if (!IsFiring(type)) {
DragStart(type, GetWeaponPos(type));
Drag(type, {0, 0});
DragEnd(type);
}
#endif
} }
void Player::Pause(bool pause) { void Player::Pause(bool pause) {
@ -42,19 +112,59 @@ void Player::Pause(bool pause) {
beam_animator_[i].PauseOrResumeAll(pause); beam_animator_[i].PauseOrResumeAll(pause);
spark_animator_[i].PauseOrResumeAll(pause); spark_animator_[i].PauseOrResumeAll(pause);
} }
nuke_animator_.PauseOrResumeAll(pause);
nuke_symbol_animator_.PauseOrResumeAll(pause);
} }
void Player::OnInputEvent(std::unique_ptr<InputEvent> event) { void Player::OnInputEvent(std::unique_ptr<InputEvent> event) {
if (event->GetType() == InputEvent::kNavigateBack) if (event->GetType() == InputEvent::kNavigateBack)
NavigateBack(); NavigateBack();
else if (event->GetType() == InputEvent::kDragStart) else if (event->GetType() == InputEvent::kDragStart)
DragStart(event->GetVector()); DragStart(event->GetPointerId(), event->GetVector());
else if (event->GetType() == InputEvent::kDrag) else if (event->GetType() == InputEvent::kDrag)
Drag(event->GetVector()); Drag(event->GetPointerId(), event->GetVector());
else if (event->GetType() == InputEvent::kDragEnd) else if (event->GetType() == InputEvent::kDragEnd)
DragEnd(); DragEnd(event->GetPointerId());
else if (event->GetType() == InputEvent::kDragCancel) else if (event->GetType() == InputEvent::kDragCancel)
DragCancel(); DragCancel(event->GetPointerId());
}
void Player::TakeDamage(int damage) {
if (damage > 0)
Engine::Get().Vibrate(250);
hit_points_ = std::min(total_health_, std::max(0, hit_points_ - damage));
for (int i = 0; i < 3; ++i)
health_bead_[i].SetFrame(hit_points_ > i ? 0 : 1);
if (hit_points_ == 0)
static_cast<Demo*>(Engine::Get().GetGame())->EnterGameOverState();
}
void Player::AddNuke(int n) {
int new_nuke_count = std::max(std::min(nuke_count_ + n, 3), 0);
if (new_nuke_count == nuke_count_)
return;
nuke_count_ = new_nuke_count;
nuke_symbol_.SetFrame(4 - nuke_count_);
if (!nuke_symbol_animator_.IsPlaying(Animator::kRotation)) {
nuke_symbol_animator_.SetRotation(
M_PI * 5, 2, std::bind(SmootherStep, std::placeholders::_1));
nuke_symbol_animator_.Play(Animator::kRotation, false);
}
}
void Player::Reset() {
DragCancel(0);
DragCancel(1);
TakeDamage(-total_health_);
nuke_count_ = 1;
nuke_symbol_.SetFrame(3);
} }
Vector2f Player::GetWeaponPos(DamageType type) const { Vector2f Player::GetWeaponPos(DamageType type) const {
@ -79,7 +189,7 @@ DamageType Player::GetWeaponType(const Vector2f& pos) {
} }
DCHECK(closest_weapon != kDamageType_Invalid); DCHECK(closest_weapon != kDamageType_Invalid);
if (closest_dist < weapon_[closest_weapon].GetSize().x * 0.9f) if (closest_dist < weapon_[closest_weapon].GetSize().x * 0.5f)
return closest_weapon; return closest_weapon;
return kDamageType_Invalid; return kDamageType_Invalid;
} }
@ -98,6 +208,9 @@ void Player::Fire(DamageType type, Vector2f dir) {
Engine& engine = Engine::Get(); Engine& engine = Engine::Get();
Enemy& enemy = static_cast<Demo*>(engine.GetGame())->GetEnemy(); Enemy& enemy = static_cast<Demo*>(engine.GetGame())->GetEnemy();
float max_beam_length = engine.GetScreenSize().y * 1.3f * 0.85f;
constexpr float max_beam_duration = 0.259198f;
if (enemy.HasTarget(type)) if (enemy.HasTarget(type))
dir = weapon_[type].GetPosition() - enemy.GetTargetPos(type); dir = weapon_[type].GetPosition() - enemy.GetTargetPos(type);
else else
@ -119,12 +232,15 @@ void Player::Fire(DamageType type, Vector2f dir) {
beam_spark_[type].SetVisible(true); beam_spark_[type].SetVisible(true);
spark_animator_[type].Stop(Animator::kMovement); spark_animator_[type].Stop(Animator::kMovement);
float length = beam_[type].GetSize().x * 0.9f; float length = beam_[type].GetSize().x * 0.9f;
Vector2f movement = dir * -length; Vector2f movement = dir * -length;
// Convert from units per second to duration. float duration = (length * max_beam_duration) / max_beam_length;
float speed = 1.0f / (18.0f / length);
spark_animator_[type].SetMovement(movement, speed); spark_animator_[type].SetMovement(movement, duration);
spark_animator_[type].Play(Animator::kMovement, false); spark_animator_[type].Play(Animator::kMovement, false);
laser_shot_[type].Play(false);
} }
bool Player::IsFiring(DamageType type) { bool Player::IsFiring(DamageType type) {
@ -189,69 +305,121 @@ void Player::SetupWeapons() {
Animator::kBlending, [&, i]() -> void { beam_[i].SetVisible(false); }); Animator::kBlending, [&, i]() -> void { beam_[i].SetVisible(false); });
beam_animator_[i].SetBlending({1, 1, 1, 0}, 0.16f); beam_animator_[i].SetBlending({1, 1, 1, 0}, 0.16f);
beam_animator_[i].Attach(&beam_[i]); beam_animator_[i].Attach(&beam_[i]);
laser_shot_[i].SetSound(laser_shot_sound_);
laser_shot_[i].SetVariate(true);
laser_shot_[i].SetSimulateStereo(false);
laser_shot_[i].SetMaxAplitude(0.4f);
} }
} }
void Player::UpdateTarget() { void Player::UpdateTarget(DamageType weapon) {
if (IsFiring(active_weapon_)) if (IsFiring(weapon))
return; return;
Engine& engine = Engine::Get(); Engine& engine = Engine::Get();
Demo* game = static_cast<Demo*>(engine.GetGame()); Demo* game = static_cast<Demo*>(engine.GetGame());
if (drag_valid_) { int i = weapon_drag_ind[weapon];
Vector2f dir = (drag_end_ - drag_start_).Normalize();
game->GetEnemy().SelectTarget(active_weapon_, drag_start_, dir, 1.2f); if (drag_valid_[i]) {
if (!game->GetEnemy().HasTarget(active_weapon_)) Vector2f origin = weapon_[weapon].GetPosition();
game->GetEnemy().SelectTarget(active_weapon_, drag_start_, dir, 2); Vector2f dir = (drag_end_[i] - drag_start_[i]).Normalize();
game->GetEnemy().SelectTarget(weapon, origin, dir);
} else { } else {
game->GetEnemy().DeselectTarget(active_weapon_); game->GetEnemy().DeselectTarget(weapon);
} }
} }
void Player::DragStart(const Vector2f& pos) { void Player::Nuke() {
active_weapon_ = GetWeaponType(pos); if (nuke_animator_.IsPlaying(Animator::kBlending))
if (active_weapon_ == kDamageType_Invalid)
return; return;
drag_start_ = pos; if (nuke_count_ <= 0) {
drag_end_ = pos; no_nuke_.Play(false);
return;
drag_sign_[active_weapon_].SetPosition(drag_start_);
drag_sign_[active_weapon_].SetVisible(true);
} }
void Player::Drag(const Vector2f& pos) { Engine& engine = Engine::Get();
if (active_weapon_ == kDamageType_Invalid) Demo* game = static_cast<Demo*>(engine.GetGame());
AddNuke(-1);
nuke_animator_.SetEndCallback(Animator::kBlending, [&, game]() -> void {
nuke_animator_.SetEndCallback(Animator::kBlending,
[&]() -> void { nuke_.SetVisible(false); });
nuke_animator_.SetBlending(
kNukeColor[0], 2, std::bind(Acceleration, std::placeholders::_1, -1));
nuke_animator_.SetEndCallback(Animator::kTimer, [&, game]() -> void {
game->GetEnemy().KillAllEnemyUnits(false);
game->GetEnemy().ResumeProgress();
});
nuke_animator_.SetTimer(0.5f);
nuke_animator_.Play(Animator::kBlending | Animator::kTimer, false);
});
nuke_animator_.SetBlending(kNukeColor[1], 0.1f,
std::bind(Acceleration, std::placeholders::_1, 1));
nuke_animator_.Play(Animator::kBlending, false);
nuke_.SetVisible(true);
game->GetEnemy().PauseProgress();
game->GetEnemy().StopAllEnemyUnits(true);
nuke_explosion_.Play(false);
}
void Player::DragStart(int i, const Vector2f& pos) {
drag_weapon_[i] = GetWeaponType(pos);
if (drag_weapon_[i] == kDamageType_Invalid) {
float dist = (pos - nuke_symbol_.GetPosition()).Length();
drag_nuke_[i] = dist <= nuke_symbol_.GetSize().x * 0.7f;
return;
}
weapon_drag_ind[drag_weapon_[i]] = i;
drag_start_[i] = pos;
drag_end_[i] = pos;
drag_sign_[drag_weapon_[i]].SetPosition(pos);
drag_sign_[drag_weapon_[i]].SetVisible(true);
}
void Player::Drag(int i, const Vector2f& pos) {
if (drag_weapon_[i] == kDamageType_Invalid)
return; return;
drag_end_ = pos; drag_end_[i] = pos;
drag_sign_[active_weapon_].SetPosition(drag_end_); drag_sign_[drag_weapon_[i]].SetPosition(pos);
if (ValidateDrag()) { if (ValidateDrag(i)) {
if (!drag_valid_ && !IsFiring(active_weapon_)) if (!drag_valid_[i] && !IsFiring(drag_weapon_[i]))
WarmupWeapon(active_weapon_); WarmupWeapon(drag_weapon_[i]);
drag_valid_ = true; drag_valid_[i] = true;
} else { } else {
if (drag_valid_ && !IsFiring(active_weapon_)) if (drag_valid_[i] && !IsFiring(drag_weapon_[i]))
CooldownWeapon(active_weapon_); CooldownWeapon(drag_weapon_[i]);
drag_valid_ = false; drag_valid_[i] = false;
} }
} }
void Player::DragEnd() { void Player::DragEnd(int i) {
if (active_weapon_ == kDamageType_Invalid) if (drag_weapon_[i] == kDamageType_Invalid) {
if (drag_nuke_[i]) {
drag_nuke_[i] = false;
Nuke();
}
return; return;
}
UpdateTarget(); UpdateTarget(drag_weapon_[i]);
DamageType type = active_weapon_; DamageType type = drag_weapon_[i];
active_weapon_ = kDamageType_Invalid; drag_weapon_[i] = kDamageType_Invalid;
drag_sign_[type].SetVisible(false); drag_sign_[type].SetVisible(false);
Vector2f fire_dir = (drag_start_ - drag_end_).Normalize(); Vector2f fire_dir = (drag_start_[i] - drag_end_[i]).Normalize();
if (drag_valid_ && !IsFiring(type)) { if (drag_valid_[i] && !IsFiring(type)) {
if (warmup_animator_[type].IsPlaying(Animator::kFrames)) { if (warmup_animator_[type].IsPlaying(Animator::kFrames)) {
warmup_animator_[type].SetEndCallback( warmup_animator_[type].SetEndCallback(
Animator::kFrames, [&, type, fire_dir]() -> void { Animator::kFrames, [&, type, fire_dir]() -> void {
@ -265,20 +433,25 @@ void Player::DragEnd() {
} }
} }
drag_valid_ = false; drag_valid_[i] = false;
drag_start_ = {0, 0}; drag_start_[i] = {0, 0};
drag_end_ = {0, 0}; drag_end_[i] = {0, 0};
} }
void Player::DragCancel() { void Player::DragCancel(int i) {
if (active_weapon_ == kDamageType_Invalid) if (drag_weapon_[i] == kDamageType_Invalid)
return; return;
DamageType type = active_weapon_; Engine& engine = Engine::Get();
active_weapon_ = kDamageType_Invalid; Demo* game = static_cast<Demo*>(engine.GetGame());
game->GetEnemy().DeselectTarget(drag_weapon_[i]);
DamageType type = drag_weapon_[i];
drag_weapon_[i] = kDamageType_Invalid;
drag_sign_[type].SetVisible(false); drag_sign_[type].SetVisible(false);
if (drag_valid_ && !IsFiring(type)) { if (drag_valid_[i] && !IsFiring(type)) {
if (warmup_animator_[type].IsPlaying(Animator::kFrames)) { if (warmup_animator_[type].IsPlaying(Animator::kFrames)) {
warmup_animator_[type].SetEndCallback( warmup_animator_[type].SetEndCallback(
Animator::kFrames, [&, type]() -> void { Animator::kFrames, [&, type]() -> void {
@ -290,16 +463,16 @@ void Player::DragCancel() {
} }
} }
drag_valid_ = false; drag_valid_[i] = false;
drag_start_ = {0, 0}; drag_start_[i] = {0, 0};
drag_end_ = {0, 0}; drag_end_[i] = {0, 0};
} }
bool Player::ValidateDrag() { bool Player::ValidateDrag(int i) {
Vector2f dir = drag_end_ - drag_start_; Vector2f dir = drag_end_[i] - drag_start_[i];
float len = dir.Length(); float len = dir.Length();
dir.Normalize(); dir.Normalize();
if (len < weapon_[active_weapon_].GetSize().y / 4) if (len < weapon_[0].GetSize().y / 3)
return false; return false;
if (dir.DotProduct(Vector2f(0, 1)) < 0) if (dir.DotProduct(Vector2f(0, 1)) < 0)
return false; return false;
@ -307,7 +480,8 @@ bool Player::ValidateDrag() {
} }
void Player::NavigateBack() { void Player::NavigateBack() {
DragCancel(); DragCancel(0);
DragCancel(1);
Engine& engine = Engine::Get(); Engine& engine = Engine::Get();
static_cast<Demo*>(engine.GetGame())->EnterMenuState(); static_cast<Demo*>(engine.GetGame())->EnterMenuState();
} }
@ -315,6 +489,8 @@ void Player::NavigateBack() {
bool Player::CreateRenderResources() { bool Player::CreateRenderResources() {
Engine::Get().SetImageSource("weapon_tex", "enemy_anims_flare_ok.png", true); Engine::Get().SetImageSource("weapon_tex", "enemy_anims_flare_ok.png", true);
Engine::Get().SetImageSource("beam_tex", "enemy_ray_ok.png", true); Engine::Get().SetImageSource("beam_tex", "enemy_ray_ok.png", true);
Engine::Get().SetImageSource("nuke_symbol_tex", "nuke_frames.png", true);
Engine::Get().SetImageSource("health_bead", "bead.png", true);
return true; return true;
} }

View File

@ -6,10 +6,13 @@
#include "../base/vecmath.h" #include "../base/vecmath.h"
#include "../engine/animator.h" #include "../engine/animator.h"
#include "../engine/image_quad.h" #include "../engine/image_quad.h"
#include "../engine/solid_quad.h"
#include "../engine/sound_player.h"
#include "damage_type.h" #include "damage_type.h"
namespace eng { namespace eng {
class InputEvent; class InputEvent;
class Sound;
} // namespace eng } // namespace eng
class Player { class Player {
@ -25,25 +28,56 @@ class Player {
void OnInputEvent(std::unique_ptr<eng::InputEvent> event); void OnInputEvent(std::unique_ptr<eng::InputEvent> event);
void TakeDamage(int damage);
void AddNuke(int n);
void Reset();
base::Vector2f GetWeaponPos(DamageType type) const; base::Vector2f GetWeaponPos(DamageType type) const;
base::Vector2f GetWeaponScale() const; base::Vector2f GetWeaponScale() const;
int nuke_count() { return nuke_count_; }
private: private:
std::shared_ptr<eng::Sound> nuke_explosion_sound_;
std::shared_ptr<eng::Sound> no_nuke_sound_;
std::shared_ptr<eng::Sound> laser_shot_sound_;
eng::ImageQuad drag_sign_[2]; eng::ImageQuad drag_sign_[2];
eng::ImageQuad weapon_[2]; eng::ImageQuad weapon_[2];
eng::ImageQuad beam_[2]; eng::ImageQuad beam_[2];
eng::ImageQuad beam_spark_[2]; eng::ImageQuad beam_spark_[2];
eng::SoundPlayer laser_shot_[2];
eng::Animator warmup_animator_[2]; eng::Animator warmup_animator_[2];
eng::Animator cooldown_animator_[2]; eng::Animator cooldown_animator_[2];
eng::Animator beam_animator_[2]; eng::Animator beam_animator_[2];
eng::Animator spark_animator_[2]; eng::Animator spark_animator_[2];
DamageType active_weapon_ = kDamageType_Invalid; eng::ImageQuad health_bead_[3];
base::Vector2f drag_start_ = {0, 0}; eng::SolidQuad nuke_;
base::Vector2f drag_end_ = {0, 0}; eng::Animator nuke_animator_;
bool drag_valid_ = false; eng::SoundPlayer nuke_explosion_;
eng::SoundPlayer no_nuke_;
eng::ImageQuad nuke_symbol_;
eng::Animator nuke_symbol_animator_;
int nuke_count_ = 0;
int total_health_ = 3;
int hit_points_ = 0;
base::Vector2f drag_start_[2] = {{0, 0}, {0, 0}};
base::Vector2f drag_end_[2] = {{0, 0}, {0, 0}};
DamageType drag_weapon_[2] = {kDamageType_Invalid, kDamageType_Invalid};
bool drag_valid_[2] = {false, false};
int weapon_drag_ind[2] = {0, 0};
bool drag_nuke_[2] = {false, false};
DamageType GetWeaponType(const base::Vector2f& pos); DamageType GetWeaponType(const base::Vector2f& pos);
@ -55,13 +89,15 @@ class Player {
void SetupWeapons(); void SetupWeapons();
void UpdateTarget(); void UpdateTarget(DamageType weapon);
void DragStart(const base::Vector2f& pos); void Nuke();
void Drag(const base::Vector2f& pos);
void DragEnd(); void DragStart(int i, const base::Vector2f& pos);
void DragCancel(); void Drag(int i, const base::Vector2f& pos);
bool ValidateDrag(); void DragEnd(int i);
void DragCancel(int i);
bool ValidateDrag(int i);
void NavigateBack(); void NavigateBack();

View File

@ -20,16 +20,12 @@ SkyQuad::SkyQuad()
SkyQuad::~SkyQuad() = default; SkyQuad::~SkyQuad() = default;
bool SkyQuad::Create() { bool SkyQuad::Create(bool without_nebula) {
Engine& engine = Engine::Get(); without_nebula_ = without_nebula;
if (!CreateShaders())
auto source = std::make_unique<ShaderSource>();
if (!source->Load("sky.glsl"))
return false; return false;
shader_->Create(std::move(source), engine.GetQuad()->vertex_description(),
Engine::Get().GetQuad()->primitive(), false);
scale_ = engine.GetScreenSize(); scale_ = Engine::Get().GetScreenSize();
color_animator_.Attach(this); color_animator_.Attach(this);
@ -50,6 +46,7 @@ void SkyQuad::Draw(float frame_frac) {
shader_->SetUniform("scale", scale_); shader_->SetUniform("scale", scale_);
shader_->SetUniform("projection", Engine::Get().GetProjectionMatrix()); shader_->SetUniform("projection", Engine::Get().GetProjectionMatrix());
shader_->SetUniform("sky_offset", sky_offset); shader_->SetUniform("sky_offset", sky_offset);
if (!without_nebula_)
shader_->SetUniform("nebula_color", shader_->SetUniform("nebula_color",
{nebula_color_.x, nebula_color_.y, nebula_color_.z}); {nebula_color_.x, nebula_color_.y, nebula_color_.z});
shader_->UploadUniforms(); shader_->UploadUniforms();
@ -58,15 +55,28 @@ void SkyQuad::Draw(float frame_frac) {
} }
void SkyQuad::ContextLost() { void SkyQuad::ContextLost() {
Create(); CreateShaders();
} }
void SkyQuad::SwitchColor(const Vector4f& color) { void SkyQuad::SwitchColor(const Vector4f& color) {
color_animator_.Pause(Animator::kBlending);
color_animator_.SetTime(Animator::kBlending, 0);
color_animator_.SetBlending(color, 5, color_animator_.SetBlending(color, 5,
std::bind(SmoothStep, std::placeholders::_1)); std::bind(SmoothStep, std::placeholders::_1));
color_animator_.Play(Animator::kBlending, false); color_animator_.Play(Animator::kBlending, false);
} }
bool SkyQuad::CreateShaders() {
Engine& engine = Engine::Get();
auto source = std::make_unique<ShaderSource>();
if (!source->Load(without_nebula_ ? "sky_without_nebula.glsl" : "sky.glsl"))
return false;
shader_->Create(std::move(source), engine.GetQuad()->vertex_description(),
Engine::Get().GetQuad()->primitive(), false);
return true;
}
void SkyQuad::SetSpeed(float speed) { void SkyQuad::SetSpeed(float speed) {
speed_ = speed; speed_ = speed;
} }

View File

@ -22,7 +22,7 @@ class SkyQuad : public eng::Animatable {
SkyQuad(const SkyQuad&) = delete; SkyQuad(const SkyQuad&) = delete;
SkyQuad& operator=(const SkyQuad&) = delete; SkyQuad& operator=(const SkyQuad&) = delete;
bool Create(); bool Create(bool without_nebula);
void Update(float delta_time); void Update(float delta_time);
@ -42,6 +42,8 @@ class SkyQuad : public eng::Animatable {
void SetSpeed(float speed); void SetSpeed(float speed);
const base::Vector4f& nebula_color() { return nebula_color_; }
private: private:
std::unique_ptr<eng::Shader> shader_; std::unique_ptr<eng::Shader> shader_;
@ -53,6 +55,10 @@ class SkyQuad : public eng::Animatable {
eng::Animator color_animator_; eng::Animator color_animator_;
float speed_ = 0; float speed_ = 0;
bool without_nebula_ = false;
bool CreateShaders();
}; };
#endif // SKY_QUAD_H #endif // SKY_QUAD_H