Add full game code and assets.
|
@ -1,4 +1,3 @@
|
|||
.vscode
|
||||
build/android/.gradle
|
||||
build/android/app/.cxx
|
||||
build/android/app/build
|
||||
|
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
|
|
After Width: | Height: | Size: 106 KiB |
After Width: | Height: | Size: 103 KiB |
After Width: | Height: | Size: 105 KiB |
After Width: | Height: | Size: 507 B |
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
After Width: | Height: | Size: 8.0 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 26 KiB |
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
After Width: | Height: | Size: 102 KiB |
After Width: | Height: | Size: 143 KiB |
After Width: | Height: | Size: 255 KiB |
|
@ -5,7 +5,7 @@ android {
|
|||
ndkVersion '21.3.6528147'
|
||||
|
||||
defaultConfig {
|
||||
applicationId = 'com.kaliber.demo'
|
||||
applicationId = 'com.woom.game'
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
externalNativeBuild {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 4.1 KiB |
|
@ -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>
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
@ -14,7 +14,7 @@ class InputEvent;
|
|||
|
||||
class Credits {
|
||||
public:
|
||||
static constexpr int kNumLines = 5;
|
||||
static constexpr int kNumLines = 10;
|
||||
|
||||
Credits();
|
||||
~Credits();
|
||||
|
|
|
@ -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
|
||||
|
|
388
src/demo/demo.cc
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
1121
src/demo/enemy.cc
100
src/demo/enemy.h
|
@ -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
|
||||
|
|
150
src/demo/hud.cc
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
470
src/demo/menu.cc
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
|
|