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/app/.cxx
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.
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)
based on this engine. The demo included in this repository is an early prototype
of the game.
based on this engine. Full game code and assets are included in this repository.
#### Building the demo
Linux:
```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'
defaultConfig {
applicationId = 'com.kaliber.demo'
applicationId = 'com.woom.game'
minSdkVersion 21
targetSdkVersion 29
externalNativeBuild {

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.kaliber.demo"
android:versionCode="1"
android:versionName="1.0">
package="com.woom.game"
android:versionCode="16"
android:versionName="1.0.1">
<application
android:allowBackup="false"
@ -36,7 +36,7 @@
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.codepath.fileprovider"
android:authorities="com.woom.game.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<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.AdRequest;
import com.google.android.gms.ads.InterstitialAd;
import com.kaliber.demo.R;
import com.woom.game.R;
import java.io.File;
@ -107,7 +107,7 @@ public class KaliberActivity extends NativeActivity {
File dir = getExternalFilesDir(null);
File file = new File(dir, fileName);
Uri uri = FileProvider.getUriForFile(KaliberActivity.this,
"com.codepath.fileprovider", file);
"com.woom.game.fileprovider", file);
Intent emailIntent = new Intent();
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"?>
<resources>
<string name="app_name">demo</string>
<string name="interstitial_ad_unit_id">ca-app-pub-3940256099942544/1033173712</string>
<string name="app_name">woom</string>
<string name="interstitial_ad_unit_id">ca-app-pub-1321063817979967/8373182022</string>
</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 {
constexpr char kCreditsLines[Credits::kNumLines][15] = {
"Credits", "Code:", "Attila Uygun", "Graphics:", "Erkan Ertürk"};
constexpr char kCreditsLines[Credits::kNumLines][40] = {
"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};
constexpr float kFadeSpeed = 0.2f;

View File

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

View File

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

View File

@ -1,27 +1,59 @@
#include "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);
if (!font_.Load("PixelCaps!.ttf"))
return false;
if (!sky_.Create()) {
if (!sky_.Create(false)) {
LOG << "Could not create the sky.";
return false;
}
@ -51,6 +83,39 @@ bool Demo::Initialize() {
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;
@ -59,68 +124,101 @@ bool Demo::Initialize() {
void Demo::Update(float delta_time) {
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()) {
#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
else if (state_ != kGameOver)
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)
UpdateMenuState(delta_time);
else if (state_ == kGame)
else if (state_ == kGame || state_ == kGameOver)
UpdateGameState(delta_time);
}
void Demo::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)
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) {
add_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) {
sky_.SetSpeed(0);
hud_.Pause(true);
player_.Pause(true);
enemy_.Pause(true);
}
if (wave_ == 0) {
if (state_ == kState_Invalid || state_ == kGameOver) {
menu_.SetOptionEnabled(Menu::kContinue, false);
} else {
menu_.SetOptionEnabled(Menu::kNewGame, true);
} else if (state_ == kGame) {
menu_.SetOptionEnabled(Menu::kContinue, true);
menu_.SetOptionEnabled(Menu::kNewGame, false);
}
@ -131,6 +229,7 @@ void Demo::EnterMenuState() {
void Demo::EnterCreditsState() {
if (state_ == kCredits)
return;
credits_.Show();
state_ = kCredits;
}
@ -139,13 +238,50 @@ 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;
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) {
switch (menu_.selected_option()) {
case Menu::kOption_Invalid:
@ -155,8 +291,12 @@ void Demo::UpdateMenuState(float delta_time) {
Continue();
break;
case Menu::kNewGame:
menu_.Hide();
menu_.Hide([&]() {
if (saved_data_.root().get(kLaunchCount, Json::Value(0)).asInt() >
kLaunchCountBeforeAd)
Engine::Get().ShowInterstitialAd();
StartNewGame();
});
break;
case Menu::kCredits:
menu_.Hide();
@ -171,20 +311,131 @@ void Demo::UpdateMenuState(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_)
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();
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;
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();
int dominant_channel = rnd.Roll(3) - 1;
if (dominant_channel == last_dominant_channel_)
@ -202,39 +453,56 @@ void Demo::UpdateGameState(float delta_time) {
sky_.SwitchColor(c);
++wave_;
hud_.SetScore(score_, true);
hud_.SetWave(wave_, true);
hud_.Show();
hud_.SetProgress(1);
float factor = 3 * (log10(5 * (float)wave_) / log10(1.2f)) - 25;
total_enemies_ = (int)(6 * factor);
last_num_enemies_killed_ = 0;
DLOG << "wave: " << wave_ << " total_enemies_: " << total_enemies_;
if (boss_fight_)
player_.TakeDamage(-3);
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;
});
} 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() {
EnterGameState();
}
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();
size_t Demo::GetHighScore() const {
return saved_data_.root().get(kHightScore, Json::Value(0)).asUInt64();
}
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_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
#include "../base/closure.h"
#include "../engine/animator.h"
#include "../engine/font.h"
#include "../engine/game.h"
#include "../engine/persistent_data.h"
#include "../engine/solid_quad.h"
#include "../engine/sound_player.h"
#include "credits.h"
#include "enemy.h"
#include "hud.h"
@ -11,10 +15,12 @@
#include "player.h"
#include "sky_quad.h"
// #define LOAD_TEST
class Demo : public eng::Game {
public:
Demo() = default;
~Demo() override = default;
Demo();
~Demo() override;
bool Initialize() override;
@ -26,21 +32,38 @@ class Demo : public eng::Game {
void GainedFocus(bool from_interstitial_ad) override;
void AddScore(int score);
void AddScore(size_t score);
void SetEnableMusic(bool enable);
void EnterMenuState();
void EnterCreditsState();
void EnterGameState();
void EnterGameOverState();
const eng::Font& GetFont() { return font_; }
Player& GetPlayer() { return player_; }
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:
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;
@ -55,8 +78,9 @@ class Demo : public eng::Game {
eng::Font font_;
int score_ = 0;
int add_score_ = 0;
size_t wave_score_ = 0;
size_t total_score_ = 0;
size_t delta_score_ = 0;
int wave_ = 0;
@ -65,16 +89,42 @@ class Demo : public eng::Game {
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;
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 UpdateGameState(float delta_time);
void Continue();
void StartNewGame();
void StartNextStage(bool boss);
void Win();
void Dimmer(bool enable);
void SetDelayedWork(float seconds, base::Closure cb);
void BenchmarkResult(int avarage_fps);
};
#endif // DEMO_H

File diff suppressed because it is too large Load Diff

View File

@ -14,6 +14,7 @@
namespace eng {
class Image;
class Shader;
class Sound;
} // namespace eng
@ -28,21 +29,34 @@ class Enemy {
void Pause(bool pause);
void ContextLost();
bool HasTarget(DamageType damage_type);
base::Vector2f GetTargetPos(DamageType damage_type);
void SelectTarget(DamageType damage_type,
const base::Vector2f& origin,
const base::Vector2f& dir,
float snap_factor);
const base::Vector2f& dir);
void DeselectTarget(DamageType damage_type);
void HitTarget(DamageType damage_type);
void OnWaveFinished();
void OnWaveStarted(int wave);
bool IsBossAlive() const;
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_;
}
@ -50,15 +64,28 @@ class Enemy {
struct EnemyUnit {
EnemyType enemy_type = kEnemyType_Invalid;
DamageType damage_type = kDamageType_Invalid;
SpeedType speed_type = kSpeedType_Invalid;
bool marked_for_removal = false;
DamageType targetted_by_weapon_ = kDamageType_Invalid;
bool targetted_by_weapon_[2] = {false, false};
int total_health = 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 target;
eng::ImageQuad blast;
eng::ImageQuad shield;
eng::ImageQuad score;
eng::SolidQuad health_base;
eng::SolidQuad health_bar;
@ -67,38 +94,72 @@ class Enemy {
eng::Animator sprite_animator;
eng::Animator target_animator;
eng::Animator blast_animator;
eng::Animator shield_animator;
eng::Animator health_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> 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_;
int num_enemies_killed_in_current_wave_ = 0;
std::array<float, kEnemyType_Max> seconds_since_last_spawn_ = {0, 0, 0};
std::array<float, kEnemyType_Max> seconds_to_next_spawn_ = {0, 0, 0};
std::array<float, kEnemyType_Unit_Last + 1> seconds_since_last_spawn_ = {
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_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;
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 SpawnNextEnemy();
void Spawn(EnemyType enemy_type,
DamageType damage_type,
const base::Vector2f& pos,
float speed);
void UpdateWave(float delta_time);
void UpdateBoss(float delta_time);
EnemyUnit* GetTarget(DamageType damage_type);
@ -107,6 +168,9 @@ class Enemy {
std::unique_ptr<eng::Image> GetScoreImage(EnemyType enemy_type);
bool CreateRenderResources();
bool CreateShaders();
void TranslateEnemyUnit(EnemyUnit& e, const base::Vector2f& delta);
};
#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}};
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
Hud::Hud() = default;
@ -38,6 +49,10 @@ bool Hud::Initialize() {
Engine::Get().SetImageSource("text0",
std::bind(&Hud::CreateScoreImage, 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) {
text_[i].Create("text"s + std::to_string(i));
@ -71,22 +86,81 @@ bool Hud::Initialize() {
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;
}
void Hud::Pause(bool pause) {
message_animator_.PauseOrResumeAll(pause);
bonus_animator_.PauseOrResumeAll(pause);
}
void Hud::Show() {
if (text_[0].IsVisible())
if (text_[0].IsVisible() && text_[1].IsVisible() &&
progress_bar_[0].IsVisible() && progress_bar_[1].IsVisible())
return;
for (int i = 0; i < 2; ++i) {
progress_bar_[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);
}
}
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;
Engine::Get().RefreshImage("text0");
@ -104,7 +178,8 @@ void Hud::SetWave(int wave, bool flash) {
if (flash) {
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);
}
}
@ -118,6 +193,34 @@ void Hud::SetProgress(float progress) {
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() {
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_));
}
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) {
const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont();

View File

@ -20,28 +20,46 @@ class Hud {
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 SetProgress(float progress);
void ShowMessage(const std::string& text, float duration);
void ShowBonus(size_t bonus);
private:
eng::SolidQuad progress_bar_[2];
eng::ImageQuad text_[2];
eng::ImageQuad message_;
eng::ImageQuad bonus_;
eng::Animator progress_bar_animator_[2];
eng::Animator text_animator_[2];
eng::Animator message_animator_;
base::Closure text_animator_cb_[2];
eng::Animator bonus_animator_;
int max_text_width_ = 0;
int last_score_ = 0;
size_t last_score_ = 0;
int last_wave_ = 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> 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);

View File

@ -1,6 +1,7 @@
#include "menu.h"
#include <cmath>
#include <string>
#include <vector>
#include "../base/collusion_test.h"
@ -10,25 +11,39 @@
#include "../engine/font.h"
#include "../engine/image.h"
#include "../engine/input_event.h"
#include "../engine/sound.h"
#include "demo.h"
using namespace std::string_literals;
using namespace base;
using namespace eng;
namespace {
constexpr char kVersionStr[] = "Version 1.0.1";
constexpr char kMenuOption[Menu::kOption_Max][10] = {"continue", "start",
"credits", "exit"};
constexpr float kMenuOptionSpace = 1.5f;
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;
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};
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
Menu::Menu() = default;
@ -36,7 +51,13 @@ Menu::Menu() = default;
Menu::~Menu() = default;
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;
for (int i = 0; i < kOption_Max; ++i) {
@ -51,31 +72,166 @@ bool Menu::Initialize() {
for (int i = 0; i < kOption_Max; ++i) {
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.SetColor(kColorFadeOut);
items_[i].text.SetVisible(false);
items_[i].text.SetFrame(i);
items_[i].select_item_cb_ = [&, i]() -> void {
items_[i].text_animator.SetEndCallback(
Animator::kBlending, [&, i]() -> void {
items_[i].text_animator.SetEndCallback(Animator::kBlending,
nullptr);
items_[i].text_animator.SetEndCallback(Animator::kBlending, nullptr);
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);
}
// Get the item positions calculated.
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;
}
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) {
tap_pos_[0] = 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].text_animator.SetBlending(kColorHighlight, kBlendingSpeed);
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() {
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) {
if (items_[i].hide)
continue;
@ -146,9 +338,54 @@ void Menu::Show() {
items_[i].text_animator.Play(Animator::kBlending, false);
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;
for (int i = 0; i < kOption_Max; ++i) {
if (items_[i].hide)
@ -161,15 +398,64 @@ void Menu::Hide() {
items_[i].text_animator.SetBlending(kColorFadeOut, kFadeSpeed);
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() {
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;
}
std::unique_ptr<Image> Menu::CreateImage() {
std::unique_ptr<Image> Menu::CreateMenuImage() {
const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont();
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);
// 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) {
int w, h;
@ -187,6 +474,23 @@ std::unique_ptr<Image> Menu::CreateImage() {
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;
}
@ -197,3 +501,141 @@ bool Menu::IsAnimating() {
}
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 "../engine/animator.h"
#include "../engine/image_quad.h"
#include "../engine/sound_player.h"
namespace eng {
class Image;
class InputEvent;
class Sound;
} // namespace eng
class Menu {
@ -35,11 +37,70 @@ class Menu {
void SetOptionEnabled(Option o, bool enable);
void Show();
void Hide();
void Hide(base::Closure cb = nullptr);
Option selected_option() const { return selected_option_; }
int start_from_wave() { return start_from_wave_; }
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 {
eng::ImageQuad text;
eng::Animator text_animator;
@ -47,6 +108,13 @@ class Menu {
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];
int max_text_width_ = 0;
@ -55,9 +123,28 @@ class Menu {
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();
std::unique_ptr<eng::Image> CreateImage();
std::unique_ptr<eng::Image> CreateMenuImage();
std::unique_ptr<eng::Image> CreateHighScoreImage();
bool IsAnimating();
};

View File

@ -1,9 +1,11 @@
#include "player.h"
#include "../base/interpolation.h"
#include "../base/log.h"
#include "../engine/engine.h"
#include "../engine/image.h"
#include "../engine/font.h"
#include "../engine/input_event.h"
#include "../engine/sound.h"
#include "demo.h"
using namespace base;
@ -17,6 +19,9 @@ constexpr int wepon_cooldown_frame[] = {5, 13};
constexpr int wepon_cooldown_frame_count = 3;
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
Player::Player() = default;
@ -26,13 +31,78 @@ Player::~Player() = default;
bool Player::Initialize() {
if (!CreateRenderResources())
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();
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;
}
void Player::Update(float delta_time) {
if (active_weapon_ != kDamageType_Invalid)
UpdateTarget();
for (int i = 0; i < 2; ++i) {
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) {
@ -42,19 +112,59 @@ void Player::Pause(bool pause) {
beam_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) {
if (event->GetType() == InputEvent::kNavigateBack)
NavigateBack();
else if (event->GetType() == InputEvent::kDragStart)
DragStart(event->GetVector());
DragStart(event->GetPointerId(), event->GetVector());
else if (event->GetType() == InputEvent::kDrag)
Drag(event->GetVector());
Drag(event->GetPointerId(), event->GetVector());
else if (event->GetType() == InputEvent::kDragEnd)
DragEnd();
DragEnd(event->GetPointerId());
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 {
@ -79,7 +189,7 @@ DamageType Player::GetWeaponType(const Vector2f& pos) {
}
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 kDamageType_Invalid;
}
@ -98,6 +208,9 @@ void Player::Fire(DamageType type, Vector2f dir) {
Engine& engine = Engine::Get();
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))
dir = weapon_[type].GetPosition() - enemy.GetTargetPos(type);
else
@ -119,12 +232,15 @@ void Player::Fire(DamageType type, Vector2f dir) {
beam_spark_[type].SetVisible(true);
spark_animator_[type].Stop(Animator::kMovement);
float length = beam_[type].GetSize().x * 0.9f;
Vector2f movement = dir * -length;
// Convert from units per second to duration.
float speed = 1.0f / (18.0f / length);
spark_animator_[type].SetMovement(movement, speed);
float duration = (length * max_beam_duration) / max_beam_length;
spark_animator_[type].SetMovement(movement, duration);
spark_animator_[type].Play(Animator::kMovement, false);
laser_shot_[type].Play(false);
}
bool Player::IsFiring(DamageType type) {
@ -189,69 +305,121 @@ void Player::SetupWeapons() {
Animator::kBlending, [&, i]() -> void { beam_[i].SetVisible(false); });
beam_animator_[i].SetBlending({1, 1, 1, 0}, 0.16f);
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() {
if (IsFiring(active_weapon_))
void Player::UpdateTarget(DamageType weapon) {
if (IsFiring(weapon))
return;
Engine& engine = Engine::Get();
Demo* game = static_cast<Demo*>(engine.GetGame());
if (drag_valid_) {
Vector2f dir = (drag_end_ - drag_start_).Normalize();
game->GetEnemy().SelectTarget(active_weapon_, drag_start_, dir, 1.2f);
if (!game->GetEnemy().HasTarget(active_weapon_))
game->GetEnemy().SelectTarget(active_weapon_, drag_start_, dir, 2);
int i = weapon_drag_ind[weapon];
if (drag_valid_[i]) {
Vector2f origin = weapon_[weapon].GetPosition();
Vector2f dir = (drag_end_[i] - drag_start_[i]).Normalize();
game->GetEnemy().SelectTarget(weapon, origin, dir);
} else {
game->GetEnemy().DeselectTarget(active_weapon_);
game->GetEnemy().DeselectTarget(weapon);
}
}
void Player::DragStart(const Vector2f& pos) {
active_weapon_ = GetWeaponType(pos);
if (active_weapon_ == kDamageType_Invalid)
void Player::Nuke() {
if (nuke_animator_.IsPlaying(Animator::kBlending))
return;
drag_start_ = pos;
drag_end_ = pos;
drag_sign_[active_weapon_].SetPosition(drag_start_);
drag_sign_[active_weapon_].SetVisible(true);
if (nuke_count_ <= 0) {
no_nuke_.Play(false);
return;
}
void Player::Drag(const Vector2f& pos) {
if (active_weapon_ == kDamageType_Invalid)
Engine& engine = Engine::Get();
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;
drag_end_ = pos;
drag_sign_[active_weapon_].SetPosition(drag_end_);
drag_end_[i] = pos;
drag_sign_[drag_weapon_[i]].SetPosition(pos);
if (ValidateDrag()) {
if (!drag_valid_ && !IsFiring(active_weapon_))
WarmupWeapon(active_weapon_);
drag_valid_ = true;
if (ValidateDrag(i)) {
if (!drag_valid_[i] && !IsFiring(drag_weapon_[i]))
WarmupWeapon(drag_weapon_[i]);
drag_valid_[i] = true;
} else {
if (drag_valid_ && !IsFiring(active_weapon_))
CooldownWeapon(active_weapon_);
drag_valid_ = false;
if (drag_valid_[i] && !IsFiring(drag_weapon_[i]))
CooldownWeapon(drag_weapon_[i]);
drag_valid_[i] = false;
}
}
void Player::DragEnd() {
if (active_weapon_ == kDamageType_Invalid)
void Player::DragEnd(int i) {
if (drag_weapon_[i] == kDamageType_Invalid) {
if (drag_nuke_[i]) {
drag_nuke_[i] = false;
Nuke();
}
return;
}
UpdateTarget();
UpdateTarget(drag_weapon_[i]);
DamageType type = active_weapon_;
active_weapon_ = kDamageType_Invalid;
DamageType type = drag_weapon_[i];
drag_weapon_[i] = kDamageType_Invalid;
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)) {
warmup_animator_[type].SetEndCallback(
Animator::kFrames, [&, type, fire_dir]() -> void {
@ -265,20 +433,25 @@ void Player::DragEnd() {
}
}
drag_valid_ = false;
drag_start_ = {0, 0};
drag_end_ = {0, 0};
drag_valid_[i] = false;
drag_start_[i] = {0, 0};
drag_end_[i] = {0, 0};
}
void Player::DragCancel() {
if (active_weapon_ == kDamageType_Invalid)
void Player::DragCancel(int i) {
if (drag_weapon_[i] == kDamageType_Invalid)
return;
DamageType type = active_weapon_;
active_weapon_ = kDamageType_Invalid;
Engine& engine = Engine::Get();
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);
if (drag_valid_ && !IsFiring(type)) {
if (drag_valid_[i] && !IsFiring(type)) {
if (warmup_animator_[type].IsPlaying(Animator::kFrames)) {
warmup_animator_[type].SetEndCallback(
Animator::kFrames, [&, type]() -> void {
@ -290,16 +463,16 @@ void Player::DragCancel() {
}
}
drag_valid_ = false;
drag_start_ = {0, 0};
drag_end_ = {0, 0};
drag_valid_[i] = false;
drag_start_[i] = {0, 0};
drag_end_[i] = {0, 0};
}
bool Player::ValidateDrag() {
Vector2f dir = drag_end_ - drag_start_;
bool Player::ValidateDrag(int i) {
Vector2f dir = drag_end_[i] - drag_start_[i];
float len = dir.Length();
dir.Normalize();
if (len < weapon_[active_weapon_].GetSize().y / 4)
if (len < weapon_[0].GetSize().y / 3)
return false;
if (dir.DotProduct(Vector2f(0, 1)) < 0)
return false;
@ -307,7 +480,8 @@ bool Player::ValidateDrag() {
}
void Player::NavigateBack() {
DragCancel();
DragCancel(0);
DragCancel(1);
Engine& engine = Engine::Get();
static_cast<Demo*>(engine.GetGame())->EnterMenuState();
}
@ -315,6 +489,8 @@ void Player::NavigateBack() {
bool Player::CreateRenderResources() {
Engine::Get().SetImageSource("weapon_tex", "enemy_anims_flare_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;
}

View File

@ -6,10 +6,13 @@
#include "../base/vecmath.h"
#include "../engine/animator.h"
#include "../engine/image_quad.h"
#include "../engine/solid_quad.h"
#include "../engine/sound_player.h"
#include "damage_type.h"
namespace eng {
class InputEvent;
class Sound;
} // namespace eng
class Player {
@ -25,25 +28,56 @@ class Player {
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 GetWeaponScale() const;
int nuke_count() { return nuke_count_; }
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 weapon_[2];
eng::ImageQuad beam_[2];
eng::ImageQuad beam_spark_[2];
eng::SoundPlayer laser_shot_[2];
eng::Animator warmup_animator_[2];
eng::Animator cooldown_animator_[2];
eng::Animator beam_animator_[2];
eng::Animator spark_animator_[2];
DamageType active_weapon_ = kDamageType_Invalid;
eng::ImageQuad health_bead_[3];
base::Vector2f drag_start_ = {0, 0};
base::Vector2f drag_end_ = {0, 0};
bool drag_valid_ = false;
eng::SolidQuad nuke_;
eng::Animator nuke_animator_;
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);
@ -55,13 +89,15 @@ class Player {
void SetupWeapons();
void UpdateTarget();
void UpdateTarget(DamageType weapon);
void DragStart(const base::Vector2f& pos);
void Drag(const base::Vector2f& pos);
void DragEnd();
void DragCancel();
bool ValidateDrag();
void Nuke();
void DragStart(int i, const base::Vector2f& pos);
void Drag(int i, const base::Vector2f& pos);
void DragEnd(int i);
void DragCancel(int i);
bool ValidateDrag(int i);
void NavigateBack();

View File

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

View File

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