raylib/games/wave_collector/screens/screen_gameplay.c

500 lines
20 KiB
C
Raw Normal View History

2017-01-22 17:31:56 +03:00
/**********************************************************************************************
*
* raylib - Advance Game template
*
* Gameplay Screen Functions Definitions (Init, Update, Draw, Unload)
*
* Copyright (c) 2014 Ramon Santamaria (Ray San - raysan@raysanweb.com)
*
* This software is provided "as-is", without any express or implied warranty. In no event
* will the authors be held liable for any damages arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose, including commercial
* applications, and to alter it and redistribute it freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not claim that you
* wrote the original software. If you use this software in a product, an acknowledgment
* in the product documentation would be appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and must not be misrepresented
* as being the original software.
*
* 3. This notice may not be removed or altered from any source distribution.
*
**********************************************************************************************/
#include "raylib.h"
#include "screens.h"
#include <stdlib.h> // Required for: malloc(), free()
#include <math.h> // Required for: sqrtf(), asinf()
#define MAX_SAMPLES_SPEED 7 // Max speed for samples movement
#define MIN_SAMPLES_SPEED 3 // Min speed for samples movement
#define SAMPLES_SPACING 100 // Separation between samples in pixels
#define SAMPLES_MULTIPLIER 700 // Defines sample data scaling value (it would be adjusted to MAX_GAME_HEIGHT)
2017-01-22 17:31:56 +03:00
#define MAX_GAME_HEIGHT 400 // Defines max possible amplitude between samples (game area)
//----------------------------------------------------------------------------------
// Types and Structures Definition
//----------------------------------------------------------------------------------
typedef struct Player {
Vector2 position;
Vector2 speed;
int width;
int height;
Color color;
} Player;
typedef struct Sample {
Vector2 position;
float value; // Raw audio sample value (normalized)
2017-01-22 17:31:56 +03:00
int radius;
bool active; // Define if sample is active (can be collected)
bool collected; // Define if sample has been collected
bool renderable; // Define if sample should be rendered
Color color;
} Sample;
//----------------------------------------------------------------------------------
// Global Variables Definition (local to this module)
//----------------------------------------------------------------------------------
// Gameplay screen global variables
static int framesCounter;
static int finishScreen;
static bool pause;
// Player variables
static Player player;
static Rectangle playerArea; // Define player movement area (and sample collection limits)
static float warpCounter; // Time warp counter
static float synchro; // Calculates collected samples relation [0..1]
static int combo;
static int maxCombo;
2017-01-22 17:31:56 +03:00
static Rectangle waveRec;
// Samples variables
static Sample *samples; // Game samples
static int totalSamples = 0; // Total game samples (proportional to waveData num samples)
static int collectedSamples; // Samples collected by player
static int currentSample; // Last sample to go through player collect area
static float samplesSpeed; // All samples move at the same speed
static float waveTime; // Total sample time in ms
// Resources variables
static Texture2D texBackground;
static Texture2D texPlayer;
static Texture2D texSampleSmall;
static Texture2D texSampleMid;
static Texture2D texSampleBig;
static Texture2D texLine;
static RenderTexture2D waveTarget;
2017-01-22 17:31:56 +03:00
static Sound fxSampleOn; // Collected sample sound
static Sound fxSampleOff; // Miss sample sound
static Sound fxPause; // Pause sound
// Debug variables
//------------------------------------------------------------------------------------
// Module Functions Declaration (local)
//------------------------------------------------------------------------------------
static void DrawSamples(Sample *samples, int sampleCount, int playedSamples, Rectangle bounds, Color color);
//----------------------------------------------------------------------------------
// Gameplay Screen Functions Definition
//----------------------------------------------------------------------------------
// Gameplay Screen Initialization logic
void InitGameplayScreen(void)
{
framesCounter = 0;
finishScreen = 0;
pause = false;
endingStatus = 0;
// Textures loading
texBackground = LoadTexture("resources/textures/background_gameplay.png");
texPlayer = LoadTexture("resources/textures/player.png");
texSampleSmall = LoadTexture("resources/textures/sample_small.png");
texSampleMid = LoadTexture("resources/textures/sample_mid.png");
texSampleBig = LoadTexture("resources/textures/sample_big.png");
texLine = LoadTexture("resources/textures/line.png");
waveRec = (Rectangle){ 32, 32, 1280 - 64, 105 };
waveTarget = LoadRenderTexture(waveRec.width, waveRec.height);
2017-01-22 17:31:56 +03:00
// Sound loading
fxSampleOn = LoadSound("resources/audio/sample_on.wav");
fxSampleOff = LoadSound("resources/audio/sample_off.wav");
fxPause = LoadSound("resources/audio/pause.wav");
SetSoundVolume(fxSampleOn, 0.6f);
SetSoundVolume(fxPause, 0.5f);
// Initialize player data
playerArea = (Rectangle){ 200, 160, 80, 400 };
player.width = 20;
player.height = 60;
player.speed = (Vector2){ 15, 15 };
player.color = GOLD;
player.position = (Vector2){ playerArea.x + playerArea.width/2 - texPlayer.width/2,
playerArea.y + playerArea.height/2 - texPlayer.height/2 };
warpCounter = 395;
synchro = 0.2f;
combo = 0;
maxCombo = 0;
2017-01-22 17:31:56 +03:00
// Initialize wave and samples data
Wave wave = LoadWave("resources/audio/wave.ogg");
float *waveData = GetWaveData(wave); // TODO: Be careful with channels!
2017-01-22 17:31:56 +03:00
// We calculate the required parameters to adjust audio time to gameplay time
// that way samples collected correspond to audio playing
// Synchonization is not perfect due to possible rounding issues (float to int)
waveTime = wave.sampleCount/wave.sampleRate; // Total sample time in seconds
float requiredSamples = (MAX_SAMPLES_SPEED*waveTime*60 - 1000)/SAMPLES_SPACING;
int samplesDivision = (int)(wave.sampleCount/requiredSamples);
totalSamples = wave.sampleCount/samplesDivision;
// We don't need wave any more (already got waveData)
2017-01-22 17:31:56 +03:00
UnloadWave(wave);
collectedSamples = 0;
// Init samples
samples = (Sample *)malloc(totalSamples*sizeof(Sample));
// Normalize wave data (min vs max values) to scale properly
float minSampleValue = 0.0f;
float maxSampleValue = 0.0f;
for (int i = 0; i < totalSamples; i++)
{
if (waveData[i*samplesDivision] < minSampleValue) minSampleValue = waveData[i*samplesDivision];
if (waveData[i*samplesDivision] > maxSampleValue) maxSampleValue = waveData[i*samplesDivision];
}
float sampleScaleFactor = 1.0f/(maxSampleValue - minSampleValue); // 400 pixels maximum size
// Initialize samples
for (int i = 0; i < totalSamples; i++)
{
samples[i].value = waveData[i*samplesDivision]*sampleScaleFactor; // Normalized value [-1.0..1.0]
samples[i].position.x = player.position.x + 1000 + i*SAMPLES_SPACING;
samples[i].position.y = GetScreenHeight()/2 + samples[i].value*SAMPLES_MULTIPLIER;
if (samples[i].position.y > GetScreenHeight()/2 + MAX_GAME_HEIGHT/2) samples[i].position.y = GetScreenHeight()/2 - MAX_GAME_HEIGHT/2;
else if (samples[i].position.y < GetScreenHeight()/2 - MAX_GAME_HEIGHT/2) samples[i].position.y = GetScreenHeight()/2 + MAX_GAME_HEIGHT/2;
samples[i].radius = 6;
samples[i].active = true;
samples[i].collected = false;
samples[i].color = RED;
}
samplesSpeed = MAX_SAMPLES_SPEED;
currentSample = 0;
// We already saved the samples we needed for the game, we can free waveData
free(waveData);
// Load and start playing music
// NOTE: Music is loaded in main code base
PlayMusicStream(music);
}
// Gameplay Screen Update logic
void UpdateGameplayScreen(void)
{
if (IsKeyPressed('P'))
{
PlaySound(fxPause);
pause = !pause;
if (pause) PauseMusicStream(music);
else ResumeMusicStream(music);
}
if (!pause)
{
framesCounter++; // Time starts counting to awake enemies
// Player movement logic (mouse)
player.position.y = GetMousePosition().y;
// Player movement logic (keyboard)
if (IsKeyDown(KEY_W)) player.position.y -= player.speed.y;
else if (IsKeyDown(KEY_S)) player.position.y += player.speed.y;
// Player movement logic (gamepad)
/*
if (IsGamepadAvailable(GAMEPAD_PLAYER1))
{
Vector2 movement = { 0.0f };
movement.x = GetGamepadAxisMovement(GAMEPAD_PLAYER1, GAMEPAD_PS3_AXIS_LEFT_X);
movement.y = GetGamepadAxisMovement(GAMEPAD_PLAYER1, GAMEPAD_PS3_AXIS_LEFT_Y);
player.position.x += movement.x*0.1f; // Scale gamepad movement value
player.position.y += movement.y*0.1f; // Scale gamepad movement value
}
*/
// Player logic: check player area limits
if (player.position.x < playerArea.x) player.position.x = playerArea.x;
else if ((player.position.x + player.width) > (playerArea.x + playerArea.width)) player.position.x = playerArea.x + playerArea.width - player.width;
if (player.position.y < playerArea.y) player.position.y = playerArea.y;
else if ((player.position.y + player.height) > (playerArea.y + playerArea.height)) player.position.y = playerArea.y + playerArea.height - player.height;
// Samples logic
for (int i = 0; i < totalSamples; i++)
{
// Samples movement logic
samples[i].position.x -= samplesSpeed;
if (((samples[i].position.x + samples[i].radius) > -SAMPLES_SPACING) &&
((samples[i].position.x - samples[i].radius) < GetScreenWidth())) samples[i].renderable = true;
else samples[i].renderable = false;
// Samples catch logic
if (!samples[i].collected && CheckCollisionCircleRec(samples[i].position, samples[i].radius, (Rectangle){ (int)player.position.x, (int)player.position.y, player.width, player.height }))
{
samples[i].collected = true;
collectedSamples++;
synchro += 0.02;
combo++;
if (combo > maxCombo) maxCombo = combo;
2017-01-22 17:31:56 +03:00
if (synchro >= 1.0f) synchro = 1.0f;
// Set sound pitch depending on sample position (base pitch: 1.0f)
// NOTE: waveData[i*WAVE_SAMPLES_DIV] is scaled to [0.3..1.7]
SetSoundPitch(fxSampleOn, samples[i].value*1.4f + 0.7f);
PlaySound(fxSampleOn);
}
if ((samples[i].position.x - samples[i].radius) < player.position.x)
{
currentSample = i; // Register last sample going out range
if (samples[i].active)
{
samples[i].active = false;
if (!samples[i].collected)
{
synchro -= 0.05f;
PlaySound(fxSampleOff);
combo = 0;
}
2017-01-22 17:31:56 +03:00
}
}
}
if (IsKeyDown(KEY_SPACE) && (warpCounter > 0))
{
warpCounter--;
if (warpCounter < 0) warpCounter = 0;
samplesSpeed -= 0.1f;
if (samplesSpeed <= MIN_SAMPLES_SPEED) samplesSpeed = MIN_SAMPLES_SPEED;
SetMusicPitch(music, samplesSpeed/MAX_SAMPLES_SPEED);
}
else
{
warpCounter++;
if (warpCounter > 395) warpCounter = 395;
samplesSpeed += 0.1f;
if (samplesSpeed >= MAX_SAMPLES_SPEED) samplesSpeed = MAX_SAMPLES_SPEED;
SetMusicPitch(music, samplesSpeed/MAX_SAMPLES_SPEED);
}
// Check ending conditions
if (currentSample >= totalSamples - 1)
2017-01-22 17:31:56 +03:00
{
StopMusicStream(music);
endingStatus = 1; // Win
finishScreen = 1;
}
if (synchro <= 0.0f)
{
synchro = 0.0f;
StopMusicStream(music);
endingStatus = 2; // Loose
finishScreen = 1;
}
}
}
// Gameplay Screen Draw logic
void DrawGameplayScreen(void)
{
// Draw background
DrawTexture(texBackground, 0, 0, WHITE);
// Screen elements drawing
//DrawRectangleLines(playerArea.x, playerArea.y, playerArea.width, playerArea.height, BLUE);
DrawRectangle(0, GetScreenHeight()/2 - 1, GetScreenWidth(), 2, Fade(BLUE, 0.3f));
//DrawRectangleLines(0, GetScreenHeight()/2 - MAX_GAME_HEIGHT/2, GetScreenWidth(), MAX_GAME_HEIGHT, GRAY);
// Draw samples
for (int i = 0; i < totalSamples - 1; i++)
{
if (samples[i].renderable)
{
Color col = samples[i].color;
if (i < (currentSample + 1)) col = Fade(DARKGRAY, 0.5f);
else col = WHITE;
//DrawCircleV(samples[i].position, samples[i].radius, col);
if (!samples[i].collected)
{
if (combo > 50) DrawTexture(texSampleSmall, samples[i].position.x - texSampleSmall.width/2, samples[i].position.y - texSampleSmall.height/2, col);
else if (combo > 25) DrawTexture(texSampleMid, samples[i].position.x - texSampleMid.width/2, samples[i].position.y - texSampleMid.height/2, col);
else DrawTexture(texSampleBig, samples[i].position.x - texSampleSmall.width/2, samples[i].position.y - texSampleBig.height/2, col);
2017-01-22 17:31:56 +03:00
}
if (i < (currentSample + 1)) col = Fade(GRAY, 0.3f);
else col = Fade(WHITE, 0.5f);
// Draw line between samples
//DrawLine(samples[i].position.x, samples[i].position.y, samples[i + 1].position.x, samples[i + 1].position.y, col);
float dx = samples[i + 1].position.x - samples[i].position.x;
float dy = samples[i + 1].position.y - samples[i].position.y;
float d = sqrtf(dx*dx + dy*dy);
float angle = asinf(dy/d);
// Draw lines using textures
2017-01-22 17:31:56 +03:00
//DrawTextureEx(texLine, (Vector2){ samples[i].position.x - 2, samples[i].position.y - 2 }, -RAD2DEG*angle, d/SAMPLES_SPACING, col);
// Draw lines using textures - IMPROVED
// TODO: Further improving to draw lines properly
2017-01-22 17:31:56 +03:00
DrawTexturePro(texLine, (Rectangle){ 0, 0, texLine.width, texLine.height },
(Rectangle){ samples[i].position.x, samples[i].position.y, (float)texLine.width*d/SAMPLES_SPACING, texLine.height },
(Vector2){ 0, (float)texLine.height/2 }, -RAD2DEG*angle, col);
}
}
// Draw player
//DrawRectangle((int)player.position.x, (int)player.position.y, player.width, player.height, player.color);
DrawTexture(texPlayer, player.position.x - 32, player.position.y - 24, WHITE);
// Draw pause message
2017-03-12 14:37:43 +03:00
if (pause) DrawTextEx(font, "WAVE PAUSED", (Vector2){ 235, 400 }, font.baseSize*2, 0, WHITE);
2017-01-22 17:31:56 +03:00
// Draw number of samples
//DrawText(FormatText("%05i", collectedSamples), 900, 200, 40, GRAY);
//DrawText(FormatText("%05i", totalSamples), 900, 250, 40, GRAY);
2017-03-12 14:37:43 +03:00
DrawTextEx(font, FormatText("%05i / %05i", collectedSamples, totalSamples), (Vector2){810, 170}, font.baseSize, -2, SKYBLUE);
// Draw combo
2017-03-12 14:37:43 +03:00
DrawTextEx(font, FormatText("Combo: %02i [max: %02i]", combo, maxCombo), (Vector2){200, 170}, font.baseSize/2, -2, SKYBLUE);
2017-01-22 17:31:56 +03:00
// Draw synchonicity level
DrawRectangle(99, 622, 395, 32, Fade(RAYWHITE, 0.8f));
if (synchro <= 0.3f) DrawRectangle(99, 622, synchro*395, 32, Fade(RED, 0.8f));
else if (synchro <= 0.8f) DrawRectangle(99, 622, synchro*395, 32, Fade(ORANGE,0.8f));
else if (synchro < 1.0f) DrawRectangle(99, 622, synchro*395, 32, Fade(LIME,0.8f));
else DrawRectangle(99, 622, synchro*395, 32, Fade(GREEN, 0.9f));
DrawRectangleLines(99, 622, 395, 32, MAROON);
2017-03-12 14:37:43 +03:00
if (synchro == 1.0f) DrawTextEx(font, FormatText("%02i%%", (int)(synchro*100)), (Vector2){99 + 390, 600}, font.baseSize, -2, GREEN);
else DrawTextEx(font, FormatText("%02i%%", (int)(synchro*100)), (Vector2){99 + 390, 600}, font.baseSize, -2, SKYBLUE);
2017-01-22 17:31:56 +03:00
// Draw time warp coool-down bar
DrawRectangle(754, 622, 395, 32, Fade(RAYWHITE, 0.8f));
DrawRectangle(754, 622, warpCounter, 32, Fade(SKYBLUE, 0.8f));
DrawRectangleLines(754, 622, 395, 32, DARKGRAY);
//DrawText(FormatText("%02i%%", (int)(synchro*100)), 754 + 410, 628, 20, DARKGRAY);
2017-03-12 14:37:43 +03:00
DrawTextEx(font, FormatText("%02i%%", (int)((float)warpCounter/395.0f*100.0f)), (Vector2){754 + 390, 600}, font.baseSize, -2, SKYBLUE);
2017-01-22 17:31:56 +03:00
// Draw wave
// NOTE: Old drawing method, replaced by rendertarget
//DrawSamples(samples, totalSamples, currentSample, waveRec, MAROON);
//DrawRectangle(waveRec.x + (int)currentSample*1240/totalSamples, waveRec.y, 2, 99, DARKGRAY);
2017-01-22 17:31:56 +03:00
//DrawRectangleLines(20, 20, 1240, 140, DARKGRAY);
//DrawRectangle(20, 150, (float)currentSample/totalSamples*1240, 10, GRAY);
// Draw wave using render target
ClearBackground(BLANK);
2017-01-22 17:31:56 +03:00
BeginTextureMode(waveTarget);
DrawSamples(samples, totalSamples, currentSample, (Rectangle){ 0, 0, waveTarget.texture.width, waveTarget.texture.height }, MAROON);
EndTextureMode();
// TODO: Apply antialiasing shader
2017-01-22 17:31:56 +03:00
DrawTextureEx(waveTarget.texture, (Vector2){ waveRec.x, waveRec.y }, 0.0f, 1.0f, WHITE);
DrawRectangle(waveRec.x + (int)currentSample*1215/totalSamples, waveRec.y, 2, 99, DARKGRAY);
2017-01-22 17:31:56 +03:00
}
// Gameplay Screen Unload logic
void UnloadGameplayScreen(void)
{
// Unload textures
UnloadTexture(texBackground);
UnloadTexture(texPlayer);
UnloadTexture(texSampleSmall);
UnloadTexture(texSampleMid);
UnloadTexture(texSampleBig);
UnloadTexture(texLine);
UnloadRenderTexture(waveTarget);
2017-01-22 17:31:56 +03:00
// Unload sounds
UnloadSound(fxSampleOn);
UnloadSound(fxSampleOff);
UnloadSound(fxPause);
free(samples); // Unload game samples (crashes game)
2017-01-22 17:31:56 +03:00
}
// Gameplay Screen should finish?
int FinishGameplayScreen(void)
{
return finishScreen;
}
//------------------------------------------------------------------------------------
// Module Functions Definitions (local)
//------------------------------------------------------------------------------------
// Draw samples in wave form (including already played samples in a different color!)
// NOTE: For proper visualization, MSAA x4 is recommended, alternatively
// it should be rendered to a bigger texture and then scaled down with
// bilinear/trilinear texture filtering
static void DrawSamples(Sample *samples, int sampleCount, int playedSamples, Rectangle bounds, Color color)
{
// NOTE: We just pick a sample to draw every increment
float sampleIncrementX = (float)bounds.width/sampleCount;
Color col = color;
for (int i = 0; i < sampleCount - 1; i++)
{
if (i < playedSamples) col = GRAY;
else col = color;
DrawLineV((Vector2){ (float)bounds.x + (float)i*sampleIncrementX, (float)(bounds.y + bounds.height/2) + samples[i].value*bounds.height },
(Vector2){ (float)bounds.x + (float)(i + 1)*sampleIncrementX, (float)(bounds.y + bounds.height/2) + + samples[i + 1].value*bounds.height }, col);
}
}