feat: add wake, rose, terrain, pathfinding, gameover, clouds, enemy shooting, variable bird speed refactor: some things

This commit is contained in:
2026-09-19 13:17:46 +02:00
parent c80b4887e9
commit a2ffb45fc5
49 changed files with 3002 additions and 1178 deletions
+69
View File
@@ -0,0 +1,69 @@
#version 330
in vec2 fragWorldPos;
out vec4 finalColor;
uniform float time;
uniform float cloudScale = 0.018;
uniform vec2 driftDirection = vec2(1.0, 0.35);
uniform float driftSpeed = 4.0;
uniform float coverage = 0.99;
uniform float softness = 0.12;
uniform vec4 cloudColor = vec4(1.0, 1.0, 1.0, 0.8);
uniform bool isShadowPass = false;
uniform vec2 shadowWorldOffset = vec2(48.0, 72.0);
uniform vec4 shadowColor = vec4(0.0, 0.05, 0.1, 0.35);
vec2 pixelizeCoordinates(vec2 coordinates)
{
return floor(coordinates);
}
float hash(vec2 p)
{
p = fract(p * vec2(123.34, 456.21));
p += dot(p, p + 45.32);
return fract(p.x * p.y);
}
float valueNoise(vec2 p)
{
vec2 i = floor(p);
vec2 f = fract(p);
float a = hash(i);
float b = hash(i + vec2(1.0, 0.0));
float c = hash(i + vec2(0.0, 1.0));
float d = hash(i + vec2(1.0, 1.0));
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
}
float cloudField(vec2 p)
{
float value = 0.0;
float amplitude = 0.5;
for (int i = 0; i < 4; i++) {
value += amplitude * valueNoise(p);
p *= 2.0;
amplitude *= 0.5;
}
return value;
}
void main()
{
vec2 worldPos = fragWorldPos;
if (isShadowPass) {
worldPos -= shadowWorldOffset;
}
vec2 pixelized = pixelizeCoordinates(worldPos);
vec2 drift = normalize(driftDirection) * time * driftSpeed;
float density = cloudField((pixelized + drift) * cloudScale);
float mask = smoothstep(coverage - softness, coverage + softness, density);
vec4 layerColor = isShadowPass ? shadowColor : cloudColor;
finalColor = vec4(layerColor.rgb, layerColor.a * mask);
}
+14
View File
@@ -0,0 +1,14 @@
#version 330
in vec3 vertexPosition;
in vec2 vertexTexCoord;
in vec4 vertexColor;
out vec2 fragWorldPos;
uniform mat4 mvp;
void main()
{
fragWorldPos = vertexPosition.xy;
gl_Position = mvp * vec4(vertexPosition, 1.0);
}
+45
View File
@@ -0,0 +1,45 @@
#version 330
uniform sampler2D texture0;
uniform vec2 resolution;
uniform float time;
// Screen-space UV of the sun — kept off in a corner like a low sun rather
// than dead center, so the rays read as directional light rather than a
// starburst sitting on top of the view. This shader samples the already-
// rendered scene texture (RenderPostFX draws it via a source rect with a
// negative height to correct raylib's render-texture V-flip for texture0
// itself), but fragTexCoord's V still runs top-of-screen=0 in that same
// flipped sense — so a Y near 0 here actually lands near the BOTTOM of
// what's on screen; 0.88 is what puts the sun near the top.
uniform vec2 sunScreenPos = vec2(0.82, 0.88);
uniform float rayCount = 10.0;
uniform float raySharpness = 3.0;
uniform float rayStrength = 0.35;
uniform float raySpeed = 0.03;
in vec2 fragTexCoord;
out vec4 finalColor;
void main()
{
vec2 uv = fragTexCoord;
vec4 baseColor = texture(texture0, uv);
// Cheap screen-space light shafts: no scene depth/occlusion to sample,
// so this fakes them as angular streaks radiating from the sun position
// that fade out with distance, rather than true raymarched shafts.
vec2 aspectFix = vec2(resolution.x / resolution.y, 1.0);
vec2 toSun = (uv - sunScreenPos) * aspectFix;
float dist = length(toSun);
float angle = atan(toSun.y, toSun.x);
float streaks = sin(angle * rayCount + time * raySpeed) * 0.5 + 0.5;
streaks = pow(streaks, raySharpness);
float falloff = clamp(1.0 - dist * 0.9, 0.0, 1.0);
falloff *= falloff;
vec3 rayColor = vec3(1.0, 0.92, 0.75) * streaks * falloff * rayStrength;
finalColor = vec4(baseColor.rgb + rayColor, baseColor.a);
}
+53
View File
@@ -0,0 +1,53 @@
#version 330
in vec2 fragTexCoord;
in vec2 fragWorldPos;
out vec4 finalColor;
uniform sampler2D texture0;
uniform float time;
uniform vec4 foamColor = vec4(0.96, 0.99, 1.0, 1.0);
uniform float foamAnimSpeed = 1.6;
uniform float foamNoiseScale = 0.55;
float hash(vec2 p)
{
p = fract(p * vec2(123.34, 456.21));
p += dot(p, p + 45.32);
return fract(p.x * p.y);
}
float valueNoise(vec2 p)
{
vec2 i = floor(p);
vec2 f = fract(p);
float a = hash(i);
float b = hash(i + vec2(1.0, 0.0));
float c = hash(i + vec2(0.0, 1.0));
float d = hash(i + vec2(1.0, 1.0));
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
}
void main()
{
vec4 tex = texture(texture0, fragTexCoord);
if (tex.a >= 0.999) {
finalColor = tex;
return;
}
if (tex.a <= 0.001) {
discard;
}
vec2 pixelized = floor(fragWorldPos);
float n1 = valueNoise(pixelized * foamNoiseScale + vec2(time * foamAnimSpeed, 0.0));
float n2 = valueNoise(pixelized * foamNoiseScale * 2.1 - vec2(0.0, time * foamAnimSpeed * 0.7));
float churn = n1 * 0.6 + n2 * 0.4;
float mask = tex.a * smoothstep(0.2, 0.7, churn + tex.a * 0.6);
finalColor = vec4(foamColor.rgb, foamColor.a * mask);
}
+16
View File
@@ -0,0 +1,16 @@
#version 330
in vec3 vertexPosition;
in vec2 vertexTexCoord;
in vec4 vertexColor;
out vec2 fragTexCoord;
out vec2 fragWorldPos;
uniform mat4 mvp;
void main()
{
fragTexCoord = vertexTexCoord;
fragWorldPos = vertexPosition.xy;
gl_Position = mvp * vec4(vertexPosition, 1.0);
}
+79
View File
@@ -0,0 +1,79 @@
#version 330
in vec2 fragWorldPos;
out vec4 finalColor;
uniform float time;
#define TRAIL_POINTS 12
uniform vec2 trailPoints[TRAIL_POINTS];
uniform float trailAges[TRAIL_POINTS];
uniform int trailCount;
uniform float wakeIntensity;
uniform vec4 foamColor = vec4(0.96, 0.99, 1.0, 1.0);
uniform float wakeHalfWidthStart = 2.0;
uniform float wakeHalfWidthEnd = 7.0;
uniform float foamNoiseScale = 0.5;
uniform float foamAnimSpeed = 2.4;
float hash(vec2 p)
{
p = fract(p * vec2(123.34, 456.21));
p += dot(p, p + 45.32);
return fract(p.x * p.y);
}
float valueNoise(vec2 p)
{
vec2 i = floor(p);
vec2 f = fract(p);
float a = hash(i);
float b = hash(i + vec2(1.0, 0.0));
float c = hash(i + vec2(0.0, 1.0));
float d = hash(i + vec2(1.0, 1.0));
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
}
float segmentDistance(vec2 p, vec2 a, vec2 b, out float segT)
{
vec2 ab = b - a;
float lenSq = dot(ab, ab);
segT = lenSq > 0.0001 ? clamp(dot(p - a, ab) / lenSq, 0.0, 1.0) : 0.0;
vec2 closest = a + ab * segT;
return length(p - closest);
}
void main()
{
if (trailCount < 2) discard;
float bestDist = 1e9;
float bestAge = 0.0;
for (int i = 0; i < TRAIL_POINTS - 1; i++) {
if (i >= trailCount - 1) break;
float segT;
float d = segmentDistance(fragWorldPos, trailPoints[i], trailPoints[i + 1], segT);
if (d < bestDist) {
bestDist = d;
bestAge = mix(trailAges[i], trailAges[i + 1], segT);
}
}
float halfWidth = mix(wakeHalfWidthStart, wakeHalfWidthEnd, bestAge);
if (bestDist > halfWidth) discard;
float edgeFade = 1.0 - smoothstep(halfWidth * 0.55, halfWidth, bestDist);
float ageFade = 1.0 - bestAge;
float envelope = edgeFade * ageFade;
vec2 pixelized = floor(fragWorldPos);
float n = valueNoise(pixelized * foamNoiseScale + vec2(time * foamAnimSpeed, 0.0));
float churn = mix(0.55, 1.0, n);
float speedScale = mix(0.65, 1.0, wakeIntensity);
float mask = envelope * churn * speedScale;
finalColor = vec4(foamColor.rgb, foamColor.a * mask);
}
+14
View File
@@ -0,0 +1,14 @@
#version 330
in vec3 vertexPosition;
in vec2 vertexTexCoord;
in vec4 vertexColor;
out vec2 fragWorldPos;
uniform mat4 mvp;
void main()
{
fragWorldPos = vertexPosition.xy;
gl_Position = mvp * vec4(vertexPosition, 1.0);
}
+4 -18
View File
@@ -1,17 +1,12 @@
#version 330 #version 330
// -----------------------------
// Inputs
in vec2 fragTexCoord; in vec2 fragTexCoord;
in vec4 fragColor; in vec4 fragColor;
in vec2 fragWorldPos;
// -----------------------------
// Outputs
out vec4 finalColor; out vec4 finalColor;
// ----------------------------- uniform sampler2D texture0;
// Uniforms
uniform sampler2D texture0; // main texture
uniform sampler2D waterDepthGradient; uniform sampler2D waterDepthGradient;
uniform sampler2D causticTexture; uniform sampler2D causticTexture;
uniform sampler2D causticHighlightTexture; uniform sampler2D causticHighlightTexture;
@@ -24,6 +19,7 @@ uniform sampler2D foamTexture;
uniform float aspectRatio = 1.0; uniform float aspectRatio = 1.0;
uniform float pixelization = 2048.0; uniform float pixelization = 2048.0;
uniform float noiseCoordinateScale = 2048.0;
uniform vec4 causticColor = vec4(0.455, 0.773, 0.765, 1.0); uniform vec4 causticColor = vec4(0.455, 0.773, 0.765, 1.0);
uniform vec4 causticHighlightColor = vec4(0.741, 0.894, 0.898, 1.0); uniform vec4 causticHighlightColor = vec4(0.741, 0.894, 0.898, 1.0);
@@ -46,9 +42,6 @@ uniform float generalTransparency = 1.0;
uniform float time; uniform float time;
// -----------------------------
// Helper functions
vec2 blendSubtract_vec2(vec2 base, vec2 blend, float opacity) vec2 blendSubtract_vec2(vec2 base, vec2 blend, float opacity)
{ {
vec2 result = base - blend; vec2 result = base - blend;
@@ -80,9 +73,6 @@ vec2 applyAspectRatio(vec2 coordinates)
return vec2(coordinates.x, coordinates.y * aspectRatio); return vec2(coordinates.x, coordinates.y * aspectRatio);
} }
// -----------------------------
// Shader layers
vec4 caustics(vec2 pixelizedCoordinates) vec4 caustics(vec2 pixelizedCoordinates)
{ {
vec4 causticNoise = texture(causticNoiseTexture, time * causticSpeed + pixelizedCoordinates); vec4 causticNoise = texture(causticNoiseTexture, time * causticSpeed + pixelizedCoordinates);
@@ -111,12 +101,9 @@ vec4 foam(vec2 pixelizedCoordinates, vec4 mainTexColor)
return vec4(colorizedFoam.rgb, colorizedFoam.a * intensity); return vec4(colorizedFoam.rgb, colorizedFoam.a * intensity);
} }
// -----------------------------
// Main fragment
void main() void main()
{ {
vec2 pixelizedCoordinates = pixelizeCoordinates(applyAspectRatio(fragTexCoord)); vec2 pixelizedCoordinates = pixelizeCoordinates(fragWorldPos / noiseCoordinateScale);
vec4 mainTex = texture(texture0, fragTexCoord); vec4 mainTex = texture(texture0, fragTexCoord);
vec4 depthBasedWaterColor = texture(waterDepthGradient, vec2(1.0 - mainTex.b, 1.0)); vec4 depthBasedWaterColor = texture(waterDepthGradient, vec2(1.0 - mainTex.b, 1.0));
@@ -134,4 +121,3 @@ void main()
vec4 finalRGBColor = mix(waterWithCausticAndSpecularAndFoamLayer, finalOutlineColor, outline); vec4 finalRGBColor = mix(waterWithCausticAndSpecularAndFoamLayer, finalOutlineColor, outline);
finalColor = vec4(finalRGBColor.rgb, mainTex.b * generalTransparency); finalColor = vec4(finalRGBColor.rgb, mainTex.b * generalTransparency);
} }
+2 -1
View File
@@ -5,6 +5,7 @@ in vec4 vertexColor;
out vec2 fragTexCoord; out vec2 fragTexCoord;
out vec4 fragColor; out vec4 fragColor;
out vec2 fragWorldPos;
uniform mat4 mvp; uniform mat4 mvp;
@@ -12,6 +13,6 @@ void main()
{ {
fragTexCoord = vertexTexCoord; fragTexCoord = vertexTexCoord;
fragColor = vertexColor; fragColor = vertexColor;
fragWorldPos = vertexPosition.xy;
gl_Position = mvp * vec4(vertexPosition, 1.0); gl_Position = mvp * vec4(vertexPosition, 1.0);
} }
+13 -7
View File
@@ -16,10 +16,10 @@ void InitApp(App *app) {
InitLogo(&app->logo); InitLogo(&app->logo);
InitMenu(&app->menu); InitMenu(&app->menu);
InitGame(app); InitGame(app);
InitGameOver(&app->gameOver);
} }
void UpdateApp(App *app) { void UpdateApp(App *app) {
app->state.running = true; app->state.running = true;
app->state.gameStarted = false; app->state.gameStarted = false;
@@ -28,24 +28,30 @@ void UpdateApp(App *app) {
case LOGO: { case LOGO: {
UpdateLogo(&app->logo, &app->state.currentView, &app->state.running); UpdateLogo(&app->logo, &app->state.currentView, &app->state.running);
RenderLogo(&app->logo, &app->flags); RenderLogo(&app->logo, &app->flags);
} break; break;
}
case MENU: { case MENU: {
UpdateMenu(&app->menu, &app->state, &app->flags); UpdateMenu(&app->menu, &app->state, &app->flags);
RenderMenu(&app->menu, &app->font, app->window.title, &app->flags); RenderMenu(&app->menu, &app->font, app->window.title, &app->flags, &app->state);
} break; break;
}
case GAMEPLAY: { case GAMEPLAY: {
UpdateGame(app); UpdateGame(app);
RenderGame(app); RenderGame(app);
} break;
default:
break; break;
} }
case GAMEOVER: {
UpdateGameOver(app);
RenderGameOver(app);
break;
}
}
} }
} }
void CleanupApp(App *app) { void CleanupApp(App *app) {
CleanupMenu(&app->menu);
CleanupGame(app); CleanupGame(app);
CleanupMenu(&app->menu);
CleanupFont(&app->font); CleanupFont(&app->font);
CleanUpConfig(&app->config); CleanUpConfig(&app->config);
} }
+99 -55
View File
@@ -1,15 +1,18 @@
#include <time.h>
#include "app.h" #include "app.h"
#include "enginePostfx.h"
#include "game.h"
#include "raylib.h"
const ShaderAsset shaderAssets[SHADER_COUNT] = {{"shaders/postfx.fs"}}; const ShaderAsset shaderAssets[SHADER_COUNT] = {{"shaders/postfx.fs"}};
const SoundtrackAsset soundtrackAssets[SOUNDTRACK_COUNT] = { const SoundtrackAsset soundtrackAssets[SOUNDTRACK_COUNT] = {{"assets/gameplay_theme.mp3"},
{"assets/gameplay_theme.mp3"}, {"assets/sea.wav"}}; {"assets/sea.wav"}};
void InitGame(App *app) { void InitGame(App *app) {
SetRandomSeed(GetRandomValue(0, 10000000));
SetRandomSeed(1234);
GameState *g = &app->game; GameState *g = &app->game;
g->assets = (Assets){.shaderAssets = shaderAssets, g->assets = (Assets){.shaderAssets = shaderAssets, .soundtrackAssets = soundtrackAssets};
.soundtrackAssets = soundtrackAssets};
for (int i = 0; i < SHADER_COUNT; i++) { for (int i = 0; i < SHADER_COUNT; i++) {
g->postFX[i] = CreatePostFX(g->assets.shaderAssets[i].path); g->postFX[i] = CreatePostFX(g->assets.shaderAssets[i].path);
@@ -20,68 +23,116 @@ void InitGame(App *app) {
} }
g->target = LoadRenderTexture(app->window.width, app->window.height); g->target = LoadRenderTexture(app->window.width, app->window.height);
g->postFXTarget = LoadRenderTexture(app->window.width, app->window.height);
CreatePlayer(); g->camera = CreateCamera((Vector2){0.0f, 0.0f}, 10.0f);
Player *player = GetPlayer(); g->terrain = CreateTerrain(&g->camera);
CreateCamera(player->physics.position, 10.0f); g->player = CreatePlayer(&g->terrain);
g->birds = CreateBirds(); g->camera.target = g->player.physics.position;
g->enemies = CreateEnemies(); g->birds = CreateBirds(&g->camera);
g->islands = CreateIslands(); g->enemies = CreateEnemies(&g->camera, &g->terrain);
g->floor = CreateFloor(); g->floor = CreateFloor();
g->clouds = CreateClouds();
g->wake = CreateWake();
g->projectiles = CreateProjectiles(); g->projectiles = CreateProjectiles();
g->overlay = CreateOverlay();
Texture2D mouseTexture = LoadTexture("assets/mouse.png"); Texture2D mouseTexture = LoadTextureCached("assets/mouse.png");
g->mouse = CreateMouse(0.10f, 5.0f, 10.0f, &mouseTexture); g->mouse = CreateMouse(0.10f, 5.0f, 10.0f, &mouseTexture);
} }
void StartGame(App *app) { static void StartGame(App *app) {
if (!app->state.gameStarted) {
app->state.gameStarted = true; app->state.gameStarted = true;
}
} }
void UpdateGame(App *app) { void UpdateGame(App *app) {
GameState *g = &app->game; GameState *g = &app->game;
if (app->flags.limitFPS) { SetTargetFPS(app->flags.limitFPS ? 60 : 0);
SetTargetFPS(60);
} else {
SetTargetFPS(0);
}
StartGame(app); StartGame(app);
OnEscapeChangeView(app, MENU); OnEscapeChangeView(app, MENU);
OnWindowClosedStoppApp(app); OnWindowClosedStoppApp(app);
OnBoolChangeView(g->player.health <= 0, app, GAMEOVER);
for (int i = 0; i < SHADER_COUNT; i++) { UpdateShaders(g->postFX, SHADER_COUNT);
UpdatePostFX(&g->postFX[i]); UpdateMouse(&g->mouse, &g->camera);
} UpdateCamera2D(&g->camera, &g->player.physics.position, &g->mouse);
UpdateFloor(&g->floor, &g->camera);
UpdateMouse(&g->mouse); UpdateClouds(&g->clouds, GetFrameTime());
UpdateWake(&g->wake, GetFrameTime());
Player *player = GetPlayer(); UpdatePlayer(&g->player);
UpdateCamera2D(&player->physics.position, &g->mouse); UpdateBirds(&g->birds, &g->camera);
UpdateFloor(&g->floor); UpdateTerrain(&g->terrain, &g->camera);
UpdatePlayer(); UpdateEnemies(&g->enemies, &g->camera, &g->projectiles, &g->player, &g->terrain);
UpdateBirds(&g->birds);
UpdateEnemies(&g->enemies);
UpdateIslands(&g->islands);
UpdateSoundtrack(&g->soundtracks[AMBIENT], app->flags.soundtrackOn); UpdateSoundtrack(&g->soundtracks[AMBIENT], app->flags.soundtrackOn);
UpdateProjectiles(&g->projectiles, &g->enemies); UpdateProjectiles(&g->projectiles, &g->player);
CheckPlayerCollisions(GetPlayer(), &app->game.enemies);
CheckEnemyCollisions(&app->game.enemies); UpdateOverlay(&g->overlay, &g->player);
CheckProjectileCollisions(&app->game.projectiles, &app->game.enemies,
GetPlayer()); CheckPlayerCollisions(&g->player, &g->enemies);
CheckEnemyCollisions(&g->enemies);
CheckProjectileCollisions(&g->projectiles, &g->enemies, &g->player);
CheckTerrainCollisions(&g->player, &g->enemies, &g->terrain);
}
void ResetGame(App *app) {
GameState *g = &app->game;
SetRandomSeed(GetRandomValue(0, 10000000));
ResetPlayer(&g->player, &g->terrain);
g->camera = CreateCamera(g->player.physics.position, 10.0f);
g->enemies = CreateEnemies(&g->camera, &g->terrain);
ResetProjectiles(&g->projectiles);
}
static void RenderShipWakes(App *app) {
GameState *g = &app->game;
RenderShipWake(&g->wake, &g->player.wakeTrail, GetActualSpeed(&g->player.physics));
for (int i = 0; i < g->enemies.count; i++) {
if (!g->enemies.alive[i])
continue;
RenderShipWake(&g->wake, &g->enemies.wakeTrail[i], GetActualSpeed(&g->enemies.physics[i]));
}
} }
void RenderComponents(App *app) { void RenderComponents(App *app) {
GameState *g = &app->game; GameState *g = &app->game;
RenderFloor(&g->floor); RenderFloor(&g->floor);
RenderPlayer(); RenderTerrain(&g->terrain);
RenderCloudShadows(&g->clouds, &g->camera);
RenderShipWakes(app);
RenderPlayer(&g->player);
RenderEnemies(&g->enemies); RenderEnemies(&g->enemies);
RenderBirds(&g->birds); RenderBirds(&g->birds);
RenderIslands(&g->islands);
RenderProjectiles(&g->projectiles); RenderProjectiles(&g->projectiles);
RenderClouds(&g->clouds, &g->camera);
}
static void RenderPostFXChain(App *app, RenderTexture2D *sceneTarget) {
GameState *g = &app->game;
RenderTexture2D *buffers[2] = {sceneTarget, &g->postFXTarget};
int srcIndex = 0;
for (int i = 0; i < SHADER_COUNT; i++) {
bool isLast = (i == SHADER_COUNT - 1);
RenderTexture2D *source = buffers[srcIndex];
int dstIndex = 1 - srcIndex;
if (isLast) {
BeginDrawing();
ClearBackground(BLACK);
RenderPostFX(&g->postFX[i], source);
if (app->flags.showFPS) {
DrawFPS(10, 10);
}
EndDrawing();
} else {
BeginTextureMode(*buffers[dstIndex]);
RenderPostFX(&g->postFX[i], source);
EndTextureMode();
srcIndex = dstIndex;
}
}
} }
void RenderGame(App *app) { void RenderGame(App *app) {
@@ -89,24 +140,13 @@ void RenderGame(App *app) {
BeginTextureMode(g->target); BeginTextureMode(g->target);
ClearBackground(BLACK); ClearBackground(BLACK);
Camera2D *camera = GetCamera(); BeginMode2D(g->camera);
BeginMode2D(*camera);
RenderComponents(app); RenderComponents(app);
EndMode2D(); EndMode2D();
RenderOverlay(&g->mouse); RenderOverlay(&g->mouse, &g->player, &g->overlay.rose);
EndTextureMode(); EndTextureMode();
BeginDrawing(); RenderPostFXChain(app, &g->target);
ClearBackground(BLACK);
for (int i = 0; i < SHADER_COUNT; i++) {
RenderPostFX(&g->postFX[i], &g->target);
}
if (app->flags.showFPS) {
DrawFPS(10, 10);
}
EndDrawing();
} }
void CleanupGame(App *app) { void CleanupGame(App *app) {
@@ -117,5 +157,9 @@ void CleanupGame(App *app) {
for (int i = 0; i < SOUNDTRACK_COUNT; i++) { for (int i = 0; i < SOUNDTRACK_COUNT; i++) {
UnloadMusicStream(g->soundtracks[i].music); UnloadMusicStream(g->soundtracks[i].music);
} }
UnloadTerrain(&g->terrain);
UnloadClouds(&g->clouds);
UnloadWake(&g->wake);
UnloadRenderTexture(g->target); UnloadRenderTexture(g->target);
UnloadRenderTexture(g->postFXTarget);
} }
+56 -49
View File
@@ -1,88 +1,95 @@
#include "app.h" #include <raylib.h>
#include "birds.h"
#include "engineAssets.h"
#include "engineCamera.h"
#include "enginePhysics.h"
#include "engineRectangle.h"
#include "engineVector.h"
const TextureAsset birdsTextureAssets[BIRDS_TEX_COUNT] = { const TextureAsset birdsTextureAssets[BIRDS_TEX_COUNT] = {
{"assets/birds.png", 16.0f, 16.0f, 45.0f} {"assets/birds.png", 16.0f, 16.0f, 45.0f}};
};
Birds CreateBirds(void){ static void InitBirdSprite(Birds *birds, int i, Texture2D texture, float frameWidth,
float rotation) {
birds->sprite[i] = (Sprite){.texture = texture,
.frameSize = {frameWidth, frameWidth},
.sourceRec = {0.0f, 0.0f, frameWidth, frameWidth},
.destRec = {GetScreenWidth() / 2, 0.0f, frameWidth, frameWidth},
.origin = {frameWidth / 2, frameWidth / 2},
.rotation = rotation,
.color = WHITE};
}
static float InitBirdAnimation(Birds *birds, int i) {
float speedFactor = GetRandomValue(50, 180) / 100.0f;
int framesSpeed = (int)(2.0f * speedFactor + 0.5f);
if (framesSpeed < 1)
framesSpeed = 1;
int maxFrame = 2;
birds->animation[i] =
(Animation){.state = 1,
.currentFrame = GetRandomValue(0, maxFrame - 1),
.maxFrame = maxFrame,
.framesCounter = 0,
.framesSpeed = framesSpeed,
.animTimer = GetRandomValue(0, 99) / 100.0f * (1.0f / (float)framesSpeed)};
return speedFactor;
}
Birds CreateBirds(Camera2D *camera) {
Birds birds = {.count = MAX_BIRDS}; Birds birds = {.count = MAX_BIRDS};
TextureAsset flyingAsset = birdsTextureAssets[FLYING]; TextureAsset flyingAsset = birdsTextureAssets[FLYING];
Texture2D texture = LoadTexture(flyingAsset.path); Texture2D texture = LoadTextureCached(flyingAsset.path);
float frameWidth = flyingAsset.frameWidth; float frameWidth = flyingAsset.frameWidth;
float rotation = flyingAsset.rotation; float rotation = flyingAsset.rotation;
for (int i = 0; i < birds.count; i++) for (int i = 0; i < birds.count; i++) {
{ InitBirdSprite(&birds, i, texture, frameWidth, rotation);
birds.sprite[i] = (Sprite){ float speedFactor = InitBirdAnimation(&birds, i);
.texture = texture,
.frameSize = {frameWidth, frameWidth},
.sourceRec = {0.0f, 0.0f, frameWidth, frameWidth},
.destRec = {GetScreenWidth()/2, 0.0f, frameWidth, frameWidth},
.origin = {frameWidth / 2, frameWidth / 2},
.rotation = rotation,
.color = WHITE
};
birds.animation[i] = (Animation){ Rectangle cameraRec = GetCameraRectangle(camera);
.state = 1, birds.physics[i] = (Physics){.position = GetPositionOutsideRectangle(cameraRec, 100, 1000),
.currentFrame = 0,
.maxFrame = 2,
.framesCounter = 0,
.framesSpeed = 2.0f,
.animTimer = 0.0f
};
Rectangle cameraRec = GetCameraRectangle();
birds.physics[i] = (Physics){
.position = GetPositionOutsideRectangle(cameraRec, 100, 1000),
.direction = (Vector2){0.0f, 0.0f}, .direction = (Vector2){0.0f, 0.0f},
.speed = 5.0f .speed = 5.0f * speedFactor};
};
birds.arrived[i] = true; birds.arrived[i] = true;
birds.destPos[i] = (Vector2){0.0f, 0.0f}; birds.destPos[i] = (Vector2){0.0f, 0.0f};
} }
return birds; return birds;
} }
void UpdateBirds(Birds *birds){ void UpdateBirds(Birds *birds, Camera2D *camera) {
Camera2D *camera = GetCamera(); Rectangle cameraRec = GetCameraRectangle(camera);
Rectangle cameraRec = GetCameraRectangle();
for (int i = 0; i < birds->count; i++) for (int i = 0; i < birds->count; i++) {
{ if (birds->arrived[i]) {
if(birds->arrived[i])
{
birds->destPos[i] = GetPositionOutsideRectangle(cameraRec, 100, 1000); birds->destPos[i] = GetPositionOutsideRectangle(cameraRec, 100, 1000);
birds->arrived[i] = false; birds->arrived[i] = false;
} }
if(CheckCollisionPointCircle(birds->physics[i].position, birds->destPos[i], 10.0F)) if (CheckCollisionPointCircle(birds->physics[i].position, birds->destPos[i], 10.0f)) {
{
birds->arrived[i] = true; birds->arrived[i] = true;
} }
if(GetDistanceBetweenTwoVectors(birds->physics[i].position, camera->target) > 1000){ if (GetDistanceBetweenTwoVectors(birds->physics[i].position, camera->target) > 1000) {
birds->physics[i].position = GetPositionOutsideRectangle(cameraRec, 100, 1000); birds->physics[i].position = GetPositionOutsideRectangle(cameraRec, 100, 1000);
} }
Vector2 dir = GetDirectionBetweenTwoVectors(birds->physics[i].position, birds->destPos[i]); Vector2 dir = GetDirectionBetweenTwoVectors(birds->physics[i].position, birds->destPos[i]);
UpdatePhysics(&birds->physics[i], dir); UpdatePhysics(&birds->physics[i], dir);
UpdateSpriteDestRec(&birds->sprite[i], &birds->physics[i].position); UpdateSpriteDestRec(&birds->sprite[i], &birds->physics[i].position);
float rotation = Vector2ToRotation(dir); float rotation = Vector2ToRotation(dir);
UpdateSpriteRotation(&birds->sprite[i], rotation + 45.0f); UpdateSpriteRotation(&birds->sprite[i], rotation + 45.0f);
UpdateAnimation(&birds->animation[i], GetFrameTime()); UpdateAnimation(&birds->animation[i], GetFrameTime());
UpdateSpriteSourceRec(&birds->sprite[i], &(Vector2){birds->sprite[i].frameSize.x * birds->animation[i].currentFrame, birds->sprite[i].frameSize.y}); UpdateSpriteSourceRec(&birds->sprite[i], &(Vector2){birds->sprite[i].frameSize.x *
birds->animation[i].currentFrame,
birds->sprite[i].frameSize.y});
} }
} }
void RenderBirds(Birds *birds){ void RenderBirds(Birds *birds) {
for (int i = 0; i < birds->count; i++) {
for (int i = 0; i < birds->count; i++)
{
RenderSprite(&birds->sprite[i]); RenderSprite(&birds->sprite[i]);
} }
} }
+63
View File
@@ -0,0 +1,63 @@
#include "clouds.h"
#include "engineCamera.h"
#include "raylib.h"
#include "math.h"
Clouds CreateClouds(void) {
Clouds clouds = {0};
clouds.shader = LoadShader("shaders/clouds.vs", "shaders/clouds.fs");
clouds.time = 0.0f;
clouds.coverage = 1.0f;
return clouds;
}
void UpdateClouds(Clouds *clouds, float deltaTime) {
clouds->time += deltaTime;
int timeLoc = GetShaderLocation(clouds->shader, "time");
SetShaderValue(clouds->shader, timeLoc, &clouds->time, SHADER_UNIFORM_FLOAT);
float clearCoverage = 1.1f;
float overcastCoverage = 0.55f;
float percent = 0.5f + 0.5f * sinf(clouds->time * 0.05f);
clouds->coverage = clearCoverage - percent * (clearCoverage - overcastCoverage);
int coverageLoc = GetShaderLocation(clouds->shader, "coverage");
SetShaderValue(clouds->shader, coverageLoc, &clouds->coverage, SHADER_UNIFORM_FLOAT);
}
static Rectangle GetCloudCoverageRect(Camera2D *camera) {
Rectangle rec = GetCameraRectangle(camera);
float margin = 128.0f;
rec.x -= margin;
rec.y -= margin;
rec.width += margin * 2.0f;
rec.height += margin * 2.0f;
return rec;
}
void RenderCloudShadows(Clouds *clouds, Camera2D *camera) {
int isShadowLoc = GetShaderLocation(clouds->shader, "isShadowPass");
int isShadow = 1;
SetShaderValue(clouds->shader, isShadowLoc, &isShadow, SHADER_UNIFORM_INT);
Rectangle rec = GetCloudCoverageRect(camera);
BeginShaderMode(clouds->shader);
BeginBlendMode(BLEND_MULTIPLIED);
DrawRectangleRec(rec, WHITE);
EndBlendMode();
EndShaderMode();
}
void RenderClouds(Clouds *clouds, Camera2D *camera) {
int isShadowLoc = GetShaderLocation(clouds->shader, "isShadowPass");
int isShadow = 0;
SetShaderValue(clouds->shader, isShadowLoc, &isShadow, SHADER_UNIFORM_INT);
Rectangle rec = GetCloudCoverageRect(camera);
BeginShaderMode(clouds->shader);
DrawRectangleRec(rec, WHITE);
EndShaderMode();
}
void UnloadClouds(Clouds *clouds) {
UnloadShader(clouds->shader);
}
+121 -47
View File
@@ -1,102 +1,176 @@
#include "app.h" #include <math.h>
#include <raylib.h>
#include "collisions.h"
#include "enginePhysics.h"
#include "engineVector.h"
float playerCollisionRadius = 8.0f; float playerCollisionRadius = 8.0f;
float enemiesCollisionRadius = 8.0f; float enemiesCollisionRadius = 8.0f;
float projectilesCollisionRadius = 1.0f; float projectilesCollisionRadius = 1.0f;
static void PlayImpactSound(Sound sound) {
SetSoundPitch(sound, (float)GetRandomValue(90, 110) / 100.0f);
SetSoundVolume(sound, (float)GetRandomValue(85, 100) / 100.0f);
PlaySound(sound);
}
void CheckPlayerCollisions(Player *player, Enemies *enemies) { void CheckPlayerCollisions(Player *player, Enemies *enemies) {
for (int i = 0; i < enemies->count; i++) { for (int i = 0; i < enemies->count; i++) {
if (!enemies->alive[i])
continue;
Vector2 playerPos = player->physics.position; Vector2 playerPos = player->physics.position;
Vector2 enemiesPos = enemies->physics[i].position; Vector2 enemiesPos = enemies->physics[i].position;
float minDist = playerCollisionRadius + enemiesCollisionRadius; CircleCollision collision =
CheckCircleCollision(playerPos, playerCollisionRadius, enemiesPos, enemiesCollisionRadius);
if (CheckCollisionCircles(playerPos, playerCollisionRadius, enemiesPos, if (!collision.overlapping)
enemiesCollisionRadius)) { continue;
player->health -= 1; player->health -= 1;
if (player->health < 0)
player->health = 0;
float dist = GetDistanceBetweenTwoVectors(playerPos, enemiesPos); if (collision.hasDirection) {
float overlap = minDist - dist; player->physics.position.x += collision.direction.x * collision.overlap;
player->physics.position.y += collision.direction.y * collision.overlap;
if (dist > 0.0f) {
Vector2 collisionVector = GetDirectionBetweenTwoVectors(
enemies->physics[i].position, player->physics.position);
player->physics.position.x += collisionVector.x * overlap;
player->physics.position.y += collisionVector.y * overlap;
Vector2 forward = RotationToVector2(player->rotation - 90.0f); Vector2 forward = RotationToVector2(player->rotation - 90.0f);
float side = Cross2D(forward, collisionVector); float side = Cross2D(forward, collision.direction);
player->rotation += side * 120.0f * GetFrameTime(); ApplyCollisionSteer(&player->rotation, side, GetFrameTime());
}
} }
} }
} }
void CheckEnemyCollisions(Enemies *enemies) { void CheckEnemyCollisions(Enemies *enemies) {
for (int a = 0; a < enemies->count; a++) { for (int a = 0; a < enemies->count; a++) {
if (!enemies->alive[a])
continue;
for (int b = a + 1; b < enemies->count; b++) { for (int b = a + 1; b < enemies->count; b++) {
if (!enemies->alive[b])
continue;
Vector2 posA = enemies->physics[a].position; Vector2 posA = enemies->physics[a].position;
Vector2 posB = enemies->physics[b].position; Vector2 posB = enemies->physics[b].position;
float minDist = enemiesCollisionRadius * 2; CircleCollision collision =
CheckCircleCollision(posA, enemiesCollisionRadius, posB, enemiesCollisionRadius);
if (CheckCollisionCircles(posA, enemiesCollisionRadius, posB, if (!collision.overlapping || !collision.hasDirection)
enemiesCollisionRadius)) { continue;
Vector2 collisionVectorA = GetDirectionBetweenTwoVectors(posB, posA); enemies->physics[a].position.x += collision.direction.x * collision.overlap * 0.5f;
Vector2 collisionVectorB = GetDirectionBetweenTwoVectors(posA, posB); enemies->physics[a].position.y += collision.direction.y * collision.overlap * 0.5f;
float dist = GetDistanceBetweenTwoVectors(posA, posB);
float overlap = minDist - dist;
if (dist > 0.0f) { enemies->physics[b].position.x -= collision.direction.x * collision.overlap * 0.5f;
enemies->physics[a].position.x += collisionVectorA.x * overlap * 0.5f; enemies->physics[b].position.y -= collision.direction.y * collision.overlap * 0.5f;
enemies->physics[a].position.y += collisionVectorA.y * overlap * 0.5f;
enemies->physics[b].position.x += collisionVectorB.x * overlap * 0.5f; Vector2 forwardA = RotationToVector2(enemies->sprite[a].rotation - 90.0f);
enemies->physics[b].position.y += collisionVectorB.y * overlap * 0.5f; Vector2 forwardB = RotationToVector2(enemies->sprite[b].rotation - 90.0f);
Vector2 collisionVectorB = {-collision.direction.x, -collision.direction.y};
Vector2 forwardA = float sideA = Cross2D(forwardA, collision.direction);
RotationToVector2(enemies->sprite[a].rotation - 90.0f);
Vector2 forwardB =
RotationToVector2(enemies->sprite[b].rotation - 90.0f);
float sideA = Cross2D(forwardA, collisionVectorA);
float sideB = Cross2D(forwardB, collisionVectorB); float sideB = Cross2D(forwardB, collisionVectorB);
enemies->sprite[a].rotation += sideA * 120.0f * GetFrameTime(); ApplyCollisionSteer(&enemies->sprite[a].rotation, sideA, GetFrameTime());
enemies->sprite[b].rotation += sideB * 120.0f * GetFrameTime(); ApplyCollisionSteer(&enemies->sprite[b].rotation, sideB, GetFrameTime());
}
}
} }
} }
} }
void CheckProjectileCollisions(Projectiles *projectiles, Enemies *enemies, void CheckProjectileCollisions(Projectiles *projectiles, Enemies *enemies, Player *player) {
Player *player) {
for (int i = 0; i < projectiles->count; i++) { for (int i = 0; i < projectiles->count; i++) {
if (!projectiles->active[i]) if (!projectiles->active[i])
continue; continue;
Vector2 projectilePos = projectiles->physics[i].position; Vector2 projectilePos = projectiles->physics[i].position;
for (int j = 0; j < enemies->count; j++) { for (int j = 0; j < enemies->count; j++) {
if (!enemies->alive[j])
continue;
Vector2 enemyPos = enemies->physics[j].position; Vector2 enemyPos = enemies->physics[j].position;
if (CheckCollisionCircles(projectilePos, projectilesCollisionRadius, if (CheckCollisionCircles(projectilePos, projectilesCollisionRadius, enemyPos,
enemyPos, enemiesCollisionRadius)) { enemiesCollisionRadius)) {
PlayImpactSound(projectiles->soundImpact[i]);
enemies->alive[j] = false;
projectiles->active[i] = false;
if (projectiles->friendly[i]) {
player->gold += 20; player->gold += 20;
Rectangle cameraRec = GetCameraRectangle(); SavePlayer(player);
enemies->physics[j].position = }
GetPositionOutsideRectangle(cameraRec, 100, 1000); break;
} }
} }
if (!projectiles->active[i]) if (!projectiles->active[i])
continue; continue;
if (!projectiles->friendly[i]) {
Vector2 playerPos = player->physics.position; Vector2 playerPos = player->physics.position;
if (CheckCollisionCircles(projectilePos, projectilesCollisionRadius, if (CheckCollisionCircles(projectilePos, projectilesCollisionRadius, playerPos,
playerPos, playerCollisionRadius)) { playerCollisionRadius)) {
PlayImpactSound(projectiles->soundImpact[i]);
player->health -= 20; player->health -= 20;
if (player->health < 0)
player->health = 0;
projectiles->active[i] = false;
}
}
}
}
#define TERRAIN_COLLISION_SAMPLES 16
static bool ResolveTerrainCollision(Terrain *terrain, Vector2 *position, float radius,
Vector2 *outPushDirection) {
Vector2 pushSum = {0.0f, 0.0f};
int hits = 0;
for (int i = 0; i < TERRAIN_COLLISION_SAMPLES; i++) {
float angle = (2.0f * PI * (float)i) / (float)TERRAIN_COLLISION_SAMPLES;
Vector2 direction = {cosf(angle), sinf(angle)};
Vector2 samplePos = {position->x + direction.x * radius, position->y + direction.y * radius};
if (IsTerrainLand(terrain, samplePos)) {
pushSum.x -= direction.x;
pushSum.y -= direction.y;
hits++;
}
}
if (hits == 0)
return false;
float length = sqrtf(pushSum.x * pushSum.x + pushSum.y * pushSum.y);
if (length < 0.0001f)
return false;
Vector2 pushDirection = {pushSum.x / length, pushSum.y / length};
float penetration = radius * ((float)hits / (float)TERRAIN_COLLISION_SAMPLES);
position->x += pushDirection.x * penetration;
position->y += pushDirection.y * penetration;
*outPushDirection = pushDirection;
return true;
}
void CheckTerrainCollisions(Player *player, Enemies *enemies, Terrain *terrain) {
Vector2 pushDirection;
if (ResolveTerrainCollision(terrain, &player->physics.position, playerCollisionRadius,
&pushDirection)) {
Vector2 forward = RotationToVector2(player->rotation - 90.0f);
float side = Cross2D(forward, pushDirection);
ApplyCollisionSteer(&player->rotation, side, GetFrameTime());
}
for (int j = 0; j < enemies->count; j++) {
if (!enemies->alive[j])
continue;
if (ResolveTerrainCollision(terrain, &enemies->physics[j].position, enemiesCollisionRadius,
&pushDirection)) {
Vector2 forward = RotationToVector2(enemies->sprite[j].rotation - 90.0f);
float side = Cross2D(forward, pushDirection);
ApplyCollisionSteer(&enemies->sprite[j].rotation, side, GetFrameTime());
} }
} }
} }
+249 -41
View File
@@ -1,18 +1,242 @@
#include "app.h" #include <raylib.h>
#include <raymath.h>
#include "enemies.h"
#include "engineAssets.h"
#include "engineCamera.h"
#include "enginePhysics.h"
#include "engineRectangle.h"
#include "engineVector.h"
#include "pathfinding.h"
const TextureAsset enemiesTextureAssets[ENEMIES_TEX_COUNT] = { const TextureAsset enemiesTextureAssets[ENEMIES_TEX_COUNT] = {
{"assets/ship_start.png", 16.0f, 16.0f, 90.0f}}; {"assets/ship_start.png", 16.0f, 16.0f, 90.0f}};
Enemies CreateEnemies(void) { #define ENEMY_DETECTION_RANGE 70.0f
#define ENEMY_ROTATION_SPEED 50.0f
#define ENEMY_AGGRO_RANGE 250.0f
#define ENEMY_CHASE_REPATH_INTERVAL 1.0f
#define ENEMY_HIT_TOLERANCE 16.0f
#define ENEMY_RELOAD_TIME 1.5f
#define ENEMY_MUZZLE_OFFSET 12.0f
#define WATER_POSITION_MAX_ATTEMPTS 20
#define ENEMY_TERRAIN_CLEARANCE 40.0f
#define ENEMY_WATER_SEARCH_MAX_RADIUS 800.0f
static Vector2 GetRandomClearWaterPosition(Terrain *terrain, Rectangle cameraRec,
Vector2 fallbackOrigin, int min, int max,
float clearance) {
for (int attempt = 0; attempt < WATER_POSITION_MAX_ATTEMPTS; attempt++) {
Vector2 candidate = GetPositionOutsideRectangle(cameraRec, min, max);
if (IsWaterWithClearance(terrain, candidate, clearance))
return candidate;
}
return GetRandomWaterPositionNear(terrain, fallbackOrigin, ENEMY_WATER_SEARCH_MAX_RADIUS,
clearance);
}
#define ENEMY_MIN_SPAWN_SEPARATION 24.0f
#define SPAWN_SEPARATION_MAX_ATTEMPTS 80
static bool IsFarFromOtherEnemies(Enemies *enemies, int selfIndex, Vector2 pos) {
for (int k = 0; k < enemies->count; k++) {
if (k == selfIndex || !enemies->alive[k])
continue;
if (GetDistanceBetweenTwoVectors(pos, enemies->physics[k].position) <
ENEMY_MIN_SPAWN_SEPARATION) {
return false;
}
}
return true;
}
static Vector2 GetSpawnPosition(Terrain *terrain, Rectangle cameraRec, Vector2 cameraTarget,
Enemies *enemies, int selfIndex) {
Vector2 candidate = {0};
for (int attempt = 0; attempt < SPAWN_SEPARATION_MAX_ATTEMPTS; attempt++) {
candidate = GetRandomClearWaterPosition(terrain, cameraRec, cameraTarget, 100, 700,
ENEMY_TERRAIN_CLEARANCE);
if (IsFarFromOtherEnemies(enemies, selfIndex, candidate))
return candidate;
}
return candidate;
}
#define DESTINATION_MAX_ATTEMPTS 5
static void CommitPath(Enemies *enemies, int i, Terrain *terrain, Path path, Vector2 origin) {
if (path.count == 0) {
Rectangle localRec = {origin.x, origin.y, 0.0f, 0.0f};
Vector2 destination =
GetRandomClearWaterPosition(terrain, localRec, origin, 20, 80, ENEMY_TERRAIN_CLEARANCE);
path = FindWaterPath(terrain, origin, destination, ENEMY_TERRAIN_CLEARANCE);
}
if (path.count == 0) {
path.waypoints[0] = origin;
path.count = 1;
enemies->destinationCooldown[i] = 2.0f;
}
for (int w = 0; w < path.count; w++) {
enemies->path[i][w] = path.waypoints[w];
}
enemies->pathLength[i] = path.count;
enemies->pathIndex[i] = 0;
enemies->destPos[i] = enemies->path[i][0];
enemies->arrived[i] = false;
}
static void AssignNewDestination(Enemies *enemies, int i, Terrain *terrain, Rectangle cameraRec,
Vector2 cameraTarget) {
Vector2 origin = enemies->physics[i].position;
Path path = {0};
for (int attempt = 0; attempt < DESTINATION_MAX_ATTEMPTS && path.count == 0; attempt++) {
Vector2 destination = GetRandomClearWaterPosition(terrain, cameraRec, cameraTarget, 100, 500,
ENEMY_TERRAIN_CLEARANCE);
path = FindWaterPath(terrain, origin, destination, ENEMY_TERRAIN_CLEARANCE);
}
CommitPath(enemies, i, terrain, path, origin);
}
static void AssignChaseDestination(Enemies *enemies, int i, Terrain *terrain,
Vector2 playerPosition) {
Vector2 origin = enemies->physics[i].position;
Path path = FindWaterPath(terrain, origin, playerPosition, ENEMY_TERRAIN_CLEARANCE);
CommitPath(enemies, i, terrain, path, origin);
}
static void RespawnDeadEnemy(Enemies *enemies, int i, Terrain *terrain, Rectangle cameraRec,
Camera2D *camera) {
if (enemies->alive[i])
return;
enemies->physics[i].position = GetSpawnPosition(terrain, cameraRec, camera->target, enemies, i);
enemies->arrived[i] = true;
enemies->alive[i] = true;
enemies->chasing[i] = false;
enemies->reloadTimer[i] = ENEMY_RELOAD_TIME;
ResetWakeTrail(&enemies->wakeTrail[i]);
}
static void UpdateEnemyTargeting(Enemies *enemies, int i, Terrain *terrain, Rectangle cameraRec,
Camera2D *camera, Vector2 playerPos, float distanceToPlayer) {
if (enemies->destinationCooldown[i] > 0.0f) {
enemies->destinationCooldown[i] -= GetFrameTime();
}
if (enemies->chaseRepathTimer[i] > 0.0f) {
enemies->chaseRepathTimer[i] -= GetFrameTime();
}
bool shouldChase = distanceToPlayer <= ENEMY_AGGRO_RANGE;
if (shouldChase) {
if (!enemies->chasing[i] || enemies->arrived[i] || enemies->chaseRepathTimer[i] <= 0.0f) {
AssignChaseDestination(enemies, i, terrain, playerPos);
enemies->chasing[i] = true;
enemies->chaseRepathTimer[i] = ENEMY_CHASE_REPATH_INTERVAL;
}
} else if (enemies->chasing[i]) {
enemies->chasing[i] = false;
enemies->arrived[i] = true;
}
if (!enemies->chasing[i] && enemies->arrived[i] && enemies->destinationCooldown[i] <= 0.0f) {
AssignNewDestination(enemies, i, terrain, cameraRec, camera->target);
}
}
static void AdvanceEnemyAlongPath(Enemies *enemies, int i) {
if (!CheckCollisionPointCircle(enemies->physics[i].position, enemies->destPos[i], 10.0f)) {
return;
}
enemies->pathIndex[i]++;
if (enemies->pathIndex[i] >= enemies->pathLength[i]) {
enemies->arrived[i] = true;
} else {
enemies->destPos[i] = enemies->path[i][enemies->pathIndex[i]];
}
}
static void RecycleFarEnemy(Enemies *enemies, int i, Terrain *terrain, Rectangle cameraRec,
Camera2D *camera) {
if (GetDistanceBetweenTwoVectors(enemies->physics[i].position, camera->target) <= 1000)
return;
enemies->physics[i].position = GetSpawnPosition(terrain, cameraRec, camera->target, enemies, i);
enemies->arrived[i] = true;
enemies->chasing[i] = false;
ResetWakeTrail(&enemies->wakeTrail[i]);
}
static void SteerAndMoveEnemy(Enemies *enemies, int i) {
Vector2 idealDir =
GetDirectionBetweenTwoVectors(enemies->physics[i].position, enemies->destPos[i]);
float targetRotation = Vector2ToRotation(idealDir) + 90.0f;
UpdateSpriteRotationTowards(&enemies->sprite[i], targetRotation, ENEMY_ROTATION_SPEED,
GetFrameTime());
Vector2 actualDir = RotationToVector2(enemies->sprite[i].rotation - 90.0f);
UpdatePhysics(&enemies->physics[i], actualDir);
UpdateSpriteDestRec(&enemies->sprite[i], &enemies->physics[i].position);
UpdateWakeTrail(&enemies->wakeTrail[i], enemies->physics[i].position);
}
static void UpdateEnemyAnimationFrame(Enemies *enemies, int i) {
int speedFrame = (enemies->physics[i].speed > 0.0f) ? 1 : 0;
UpdateSpriteSourceRec(&enemies->sprite[i], &(Vector2){enemies->sprite[i].frameSize.x * speedFrame,
enemies->sprite[i].frameSize.y});
}
static void TryFireAtPlayer(Enemies *enemies, int i, Projectiles *projectiles, Player *player) {
if (enemies->reloadTimer[i] > 0.0f) {
enemies->reloadTimer[i] -= GetFrameTime();
}
Vector2 enemyPos = enemies->physics[i].position;
Vector2 playerPos = player->physics.position;
Vector2 toPlayer = Vector2Subtract(playerPos, enemyPos);
float distanceToPlayer = Vector2Length(toPlayer);
if (enemies->reloadTimer[i] > 0.0f || distanceToPlayer > ENEMY_DETECTION_RANGE)
return;
Vector2 portDirection = Vector2Rotate(enemies->physics[i].direction, -90.0f * DEG2RAD);
Vector2 starboardDirection = Vector2Rotate(enemies->physics[i].direction, 90.0f * DEG2RAD);
Vector2 aimDirection =
(Vector2DotProduct(toPlayer, portDirection) > 0.0f) ? portDirection : starboardDirection;
float travelDistance = Vector2DotProduct(toPlayer, aimDirection);
if (travelDistance <= 0.0f)
return;
float travelTime = travelDistance / PROJECTILE_SPEED;
Vector2 closestPointOnShot = Vector2Add(enemyPos, Vector2Scale(aimDirection, travelDistance));
Vector2 predictedPlayerPos = Vector2Add(
playerPos, Vector2Scale(player->physics.direction, player->physics.speed * travelTime));
float missDistance = Vector2Distance(closestPointOnShot, predictedPlayerPos);
if (missDistance > ENEMY_HIT_TOLERANCE)
return;
Vector2 firePosition = Vector2Add(enemyPos, Vector2Scale(aimDirection, ENEMY_MUZZLE_OFFSET));
if (FireProjectile(projectiles, firePosition, aimDirection, false)) {
enemies->reloadTimer[i] = ENEMY_RELOAD_TIME;
}
}
Enemies CreateEnemies(Camera2D *camera, Terrain *terrain) {
Enemies enemies = {.count = MAX_ENEMIES}; Enemies enemies = {.count = MAX_ENEMIES};
TextureAsset textureAsset = enemiesTextureAssets[BOAT]; TextureAsset textureAsset = enemiesTextureAssets[BOAT];
Texture2D texture = LoadTexture(textureAsset.path); Texture2D texture = LoadTextureCached(textureAsset.path);
float frameWidth = textureAsset.frameWidth; float frameWidth = textureAsset.frameWidth;
float rotation = textureAsset.rotation; float rotation = textureAsset.rotation;
for (int i = 0; i < enemies.count; i++) { for (int i = 0; i < enemies.count; i++) {
enemies.sprite[i] = (Sprite){ enemies.sprite[i] = (Sprite){.texture = texture,
.texture = texture,
.frameSize = {frameWidth, frameWidth}, .frameSize = {frameWidth, frameWidth},
.sourceRec = {0.0f, 0.0f, frameWidth, frameWidth}, .sourceRec = {0.0f, 0.0f, frameWidth, frameWidth},
.destRec = {GetScreenWidth() / 2, 0.0f, frameWidth, frameWidth}, .destRec = {GetScreenWidth() / 2, 0.0f, frameWidth, frameWidth},
@@ -27,60 +251,44 @@ Enemies CreateEnemies(void) {
.framesSpeed = 2.0f, .framesSpeed = 2.0f,
.animTimer = 0.0f}; .animTimer = 0.0f};
Rectangle cameraRec = GetCameraRectangle(); Rectangle cameraRec = GetCameraRectangle(camera);
enemies.physics[i] = enemies.physics[i] =
(Physics){.position = GetPositionOutsideRectangle(cameraRec, 100, 1000), (Physics){.position = GetSpawnPosition(terrain, cameraRec, camera->target, &enemies, i),
.direction = (Vector2){0.0f, 0.0f}, .direction = (Vector2){0.0f, 0.0f},
.speed = 5.0f, .speed = 5.0f,
.weight = 5.0f}; .weight = 5.0f};
enemies.arrived[i] = true; enemies.arrived[i] = true;
enemies.destPos[i] = (Vector2){0.0f, 0.0f}; enemies.destPos[i] = (Vector2){0.0f, 0.0f};
enemies.pathLength[i] = 0;
enemies.pathIndex[i] = 0;
enemies.destinationCooldown[i] = 0.0f;
enemies.chasing[i] = false;
enemies.chaseRepathTimer[i] = 0.0f;
enemies.alive[i] = true; enemies.alive[i] = true;
enemies.reloadTimer[i] = GetRandomValue(0, 100) / 100.0f * ENEMY_RELOAD_TIME;
} }
return enemies; return enemies;
} }
void UpdateEnemies(Enemies *enemies) { void UpdateEnemies(Enemies *enemies, Camera2D *camera, Projectiles *projectiles, Player *player,
Camera2D *camera = GetCamera(); Terrain *terrain) {
Rectangle cameraRec = GetCameraRectangle(); Rectangle cameraRec = GetCameraRectangle(camera);
for (int i = 0; i < enemies->count; i++) { for (int i = 0; i < enemies->count; i++) {
if (!enemies->alive[i]) RespawnDeadEnemy(enemies, i, terrain, cameraRec, camera);
continue;
if (enemies->arrived[i]) { Vector2 playerPos = player->physics.position;
enemies->destPos[i] = GetPositionOutsideRectangle(cameraRec, 100, 1000); float distanceToPlayer = GetDistanceBetweenTwoVectors(enemies->physics[i].position, playerPos);
enemies->arrived[i] = false;
}
if (CheckCollisionPointCircle(enemies->physics[i].position, UpdateEnemyTargeting(enemies, i, terrain, cameraRec, camera, playerPos, distanceToPlayer);
enemies->destPos[i], 10.0F)) { AdvanceEnemyAlongPath(enemies, i);
enemies->arrived[i] = true; RecycleFarEnemy(enemies, i, terrain, cameraRec, camera);
} SteerAndMoveEnemy(enemies, i);
UpdateEnemyAnimationFrame(enemies, i);
if (GetDistanceBetweenTwoVectors(enemies->physics[i].position, TryFireAtPlayer(enemies, i, projectiles, player);
camera->target) > 1000) {
enemies->physics[i].position =
GetPositionOutsideRectangle(cameraRec, 100, 1000);
}
Vector2 dir = GetDirectionBetweenTwoVectors(enemies->physics[i].position,
enemies->destPos[i]);
UpdatePhysics(&enemies->physics[i], dir);
UpdateSpriteDestRec(&enemies->sprite[i], &enemies->physics[i].position);
float rotation = Vector2ToRotation(dir);
UpdateSpriteRotation(&enemies->sprite[i], rotation + 90.0f);
UpdateAnimation(&enemies->animation[i], GetFrameTime());
UpdateSpriteSourceRec(&enemies->sprite[i],
&(Vector2){enemies->sprite[i].frameSize.x *
enemies->animation[i].currentFrame,
enemies->sprite[i].frameSize.y});
} }
} }
+95 -45
View File
@@ -1,20 +1,66 @@
#include "app.h" #include <raylib.h>
#include <rlgl.h>
Floor CreateFloor(void) #include "engineAssets.h"
{ #include "floor.h"
Texture2D base = LoadTexture("assets/water_base.png"); static void BindWaterTexture(Shader shader, const char *uniformName, Texture2D texture, int slot) {
Texture2D caustic = LoadTexture("assets/water_caustics.png"); int loc = GetShaderLocation(shader, uniformName);
Texture2D causticHighlight = LoadTexture("assets/water_caustics_highlights.png"); if (loc < 0)
Texture2D noise = LoadTexture("assets/water_noise.png"); return;
Texture2D cloud = LoadTexture("assets/water_cloudnoise.png");
Texture2D voronoise = LoadTexture("assets/water_voronoise.png"); rlActiveTextureSlot(slot);
rlEnableTexture(texture.id);
SetShaderValue(shader, loc, &slot, SHADER_UNIFORM_INT);
}
static void PrepareTiledTexture(Texture2D texture) {
SetTextureWrap(texture, TEXTURE_WRAP_REPEAT);
GenTextureMipmaps(&texture);
SetTextureFilter(texture, TEXTURE_FILTER_TRILINEAR);
}
static Texture2D CreateDepthGradientTexture(void) {
Image gradientImage =
GenImageGradientLinear(64, 64, 90, (Color){12, 44, 84, 255}, (Color){64, 168, 186, 255});
Texture2D depthGradient = LoadTextureFromImage(gradientImage);
UnloadImage(gradientImage);
SetTextureWrap(depthGradient, TEXTURE_WRAP_CLAMP);
return depthGradient;
}
static void ConfigureFloorShaderUniforms(Shader shader, Texture2D base, float worldTileSize,
float textureSize) {
float aspect = (float)GetScreenHeight() / (float)GetScreenWidth();
float pixelization = 2048.0f;
float noiseCoordinateScale = worldTileSize * ((float)base.width / textureSize);
int aspectLoc = GetShaderLocation(shader, "aspectRatio");
int pixelLoc = GetShaderLocation(shader, "pixelization");
int noiseCoordinateScaleLoc = GetShaderLocation(shader, "noiseCoordinateScale");
SetShaderValue(shader, aspectLoc, &aspect, SHADER_UNIFORM_FLOAT);
SetShaderValue(shader, pixelLoc, &pixelization, SHADER_UNIFORM_FLOAT);
SetShaderValue(shader, noiseCoordinateScaleLoc, &noiseCoordinateScale, SHADER_UNIFORM_FLOAT);
}
Floor CreateFloor(void) {
Texture2D base = LoadTextureCached("assets/water_base.png");
Texture2D caustic = LoadTextureCached("assets/water_caustics.png");
Texture2D causticHighlight = LoadTextureCached("assets/water_caustics_highlights.png");
Texture2D noise = LoadTextureCached("assets/water_noise.png");
Texture2D cloud = LoadTextureCached("assets/water_cloudnoise.png");
Texture2D voronoise = LoadTextureCached("assets/water_voronoise.png");
PrepareTiledTexture(caustic);
PrepareTiledTexture(causticHighlight);
PrepareTiledTexture(noise);
PrepareTiledTexture(cloud);
PrepareTiledTexture(voronoise);
Texture2D depthGradient = CreateDepthGradientTexture();
float textureWidth = 64.0f; float textureWidth = 64.0f;
Floor floor = {.sprite = {.texture = base,
Floor floor = {
.sprite = {
.texture = base,
.frameSize = {textureWidth, textureWidth}, .frameSize = {textureWidth, textureWidth},
.sourceRec = {0.0f, 0.0f, textureWidth, textureWidth}, .sourceRec = {0.0f, 0.0f, textureWidth, textureWidth},
.destRec = {0.0f, 0.0f, textureWidth, textureWidth}, .destRec = {0.0f, 0.0f, textureWidth, textureWidth},
@@ -23,8 +69,7 @@ Floor CreateFloor(void)
.color = WHITE}, .color = WHITE},
.bounds = {0}, .bounds = {0},
.shader = LoadShader("shaders/water.vs", "shaders/water.fs"), .shader = LoadShader("shaders/water.vs", "shaders/water.fs"),
.time = 0.0f .time = 0.0f};
};
float worldTileSize = 128.0f; float worldTileSize = 128.0f;
float textureSize = 32.0f; float textureSize = 32.0f;
@@ -34,47 +79,53 @@ Floor CreateFloor(void)
floor.sprite.destRec = (Rectangle){0, 0, worldTileSize, worldTileSize}; floor.sprite.destRec = (Rectangle){0, 0, worldTileSize, worldTileSize};
floor.sprite.origin = (Vector2){0, 0}; floor.sprite.origin = (Vector2){0, 0};
SetShaderValueTexture(floor.shader, GetShaderLocation(floor.shader, "causticTexture"), caustic); ConfigureFloorShaderUniforms(floor.shader, base, worldTileSize, textureSize);
SetShaderValueTexture(floor.shader, GetShaderLocation(floor.shader, "causticHighlightTexture"), causticHighlight);
SetShaderValueTexture(floor.shader, GetShaderLocation(floor.shader, "causticNoiseTexture"), noise);
SetShaderValueTexture(floor.shader, GetShaderLocation(floor.shader, "causticFadeNoiseTexture"), cloud);
SetShaderValueTexture(floor.shader, GetShaderLocation(floor.shader, "specularNoiseTexture"), voronoise);
SetShaderValueTexture(floor.shader, GetShaderLocation(floor.shader, "specularMovementLeftNoiseTexture"), voronoise);
SetShaderValueTexture(floor.shader, GetShaderLocation(floor.shader, "specularMovementRightNoiseTexture"), cloud);
float aspect = (float)GetScreenHeight() / (float)GetScreenWidth();
float pixelization = 2048.0f;
int aspectLoc = GetShaderLocation(floor.shader, "aspectRatio");
int pixelLoc = GetShaderLocation(floor.shader, "pixelization");
SetShaderValue(floor.shader, aspectLoc, &aspect, SHADER_UNIFORM_FLOAT);
SetShaderValue(floor.shader, pixelLoc, &pixelization, SHADER_UNIFORM_FLOAT);
floor.sprite.texture = base; floor.sprite.texture = base;
floor.causticTexture = caustic;
floor.causticHighlightTexture = causticHighlight;
floor.noiseTexture = noise;
floor.cloudTexture = cloud;
floor.voronoiseTexture = voronoise;
floor.depthGradientTexture = depthGradient;
return floor; return floor;
} }
void UpdateFloor(Floor *floor) void UpdateFloor(Floor *floor, Camera2D *camera) {
{ floor->bounds.x = (int)((camera->target.x - (GetScreenWidth() / 2.0f / camera->zoom)) /
Camera2D *camera = GetCamera(); floor->sprite.frameSize.x) -
floor->bounds.x = (int)((camera->target.x - (GetScreenWidth() / 2.0f / camera->zoom)) / floor->sprite.frameSize.x) - 1; 1;
floor->bounds.width = (int)((camera->target.x + (GetScreenWidth() / 2.0f / camera->zoom)) / floor->sprite.frameSize.x) + 1; floor->bounds.width = (int)((camera->target.x + (GetScreenWidth() / 2.0f / camera->zoom)) /
floor->bounds.y = (int)((camera->target.y - (GetScreenHeight() / 2.0f / camera->zoom)) / floor->sprite.frameSize.y) - 1; floor->sprite.frameSize.x) +
floor->bounds.height = (int)((camera->target.y + (GetScreenHeight() / 2.0f / camera->zoom)) / floor->sprite.frameSize.y) + 1; 1;
floor->bounds.y = (int)((camera->target.y - (GetScreenHeight() / 2.0f / camera->zoom)) /
floor->sprite.frameSize.y) -
1;
floor->bounds.height = (int)((camera->target.y + (GetScreenHeight() / 2.0f / camera->zoom)) /
floor->sprite.frameSize.y) +
1;
floor->time += GetFrameTime(); floor->time += GetFrameTime();
int timeLoc = GetShaderLocation(floor->shader, "time"); int timeLoc = GetShaderLocation(floor->shader, "time");
SetShaderValue(floor->shader, timeLoc, &floor->time, SHADER_UNIFORM_FLOAT); SetShaderValue(floor->shader, timeLoc, &floor->time, SHADER_UNIFORM_FLOAT);
} }
void RenderFloor(Floor *floor) void RenderFloor(Floor *floor) {
{
BeginShaderMode(floor->shader); BeginShaderMode(floor->shader);
for (int y = (int)floor->bounds.y; y <= (int)floor->bounds.height; y++) BindWaterTexture(floor->shader, "causticTexture", floor->causticTexture, 1);
{ BindWaterTexture(floor->shader, "causticHighlightTexture", floor->causticHighlightTexture, 2);
for (int x = (int)floor->bounds.x; x <= (int)floor->bounds.width; x++) BindWaterTexture(floor->shader, "causticNoiseTexture", floor->noiseTexture, 3);
{ BindWaterTexture(floor->shader, "causticFadeNoiseTexture", floor->cloudTexture, 4);
BindWaterTexture(floor->shader, "specularNoiseTexture", floor->voronoiseTexture, 5);
BindWaterTexture(floor->shader, "specularMovementLeftNoiseTexture", floor->voronoiseTexture, 6);
BindWaterTexture(floor->shader, "specularMovementRightNoiseTexture", floor->cloudTexture, 7);
BindWaterTexture(floor->shader, "foamTexture", floor->voronoiseTexture, 8);
BindWaterTexture(floor->shader, "waterDepthGradient", floor->depthGradientTexture, 9);
for (int y = (int)floor->bounds.y; y <= (int)floor->bounds.height; y++) {
for (int x = (int)floor->bounds.x; x <= (int)floor->bounds.width; x++) {
Vector2 floorPosition = {x * floor->sprite.frameSize.x, y * floor->sprite.frameSize.y}; Vector2 floorPosition = {x * floor->sprite.frameSize.x, y * floor->sprite.frameSize.y};
UpdateSpriteDestRec(&floor->sprite, &floorPosition); UpdateSpriteDestRec(&floor->sprite, &floorPosition);
RenderSprite(&floor->sprite); RenderSprite(&floor->sprite);
@@ -84,8 +135,7 @@ void RenderFloor(Floor *floor)
EndShaderMode(); EndShaderMode();
} }
void UnloadFloor(Floor *floor) void UnloadFloor(Floor *floor) {
{
UnloadShader(floor->shader); UnloadShader(floor->shader);
UnloadTexture(floor->depthGradientTexture);
} }
+189 -29
View File
@@ -1,39 +1,199 @@
#include "app.h" #include "islands.h"
#include "engineCamera.h"
#include "enginePhysics.h"
#include "engineRectangle.h"
#include "engineVector.h"
#include <math.h>
#include <raylib.h>
#include <raymath.h>
const TextureAsset islandsTextureAssets[ISLANDS_TEX_COUNT] = { // How far an island can drift from the camera before it's considered "left
{"assets/island.png", 16.0f, 16.0f, 0.0f}}; // behind" and gets regenerated fresh outside the current view, the same
// recycling idea used for enemies/birds so the world stays populated as the
// player explores.
#define ISLAND_RECYCLE_DISTANCE 800.0f
#define ISLAND_MIN_SPAWN_DISTANCE 80
#define ISLAND_MAX_SPAWN_DISTANCE 600
// Concentric elevation bands, each a fraction of the coastline radius at
// that angle — same idea as a real elevation-banded map (shore/lowland/
// highland rings), just three tiers instead of five since this renders at
// pixel-art scale, not map scale.
#define ISLAND_GRASS_SCALE 0.72f
#define ISLAND_HILL_SCALE 0.35f
// Native resolution the shape is painted at. Higher than the 16x16 ship/bird
// sprites — islands are the biggest thing on screen now, and matching their
// texel density would make each "pixel" a huge, coarse block; this keeps
// the pixel-art look while the individual pixels stay small relative to the
// island's size.
#define ISLAND_IMAGE_SIZE 128
Islands CreateIslands(void) { // A coastline is two overlapping cosine lobes (a broad one for bays/
TextureAsset islandAsset = islandsTextureAssets[ISLAND]; // peninsulas, a finer one for coastal roughness) plus light jitter, instead
Texture2D texture = LoadTexture(islandAsset.path); // of a single jittered circle — the same kind of shape irregularity a real
float frameWidth = islandAsset.frameWidth; // procedural terrain generator uses, just far cheaper.
float rotation = islandAsset.rotation; static void GenerateCoastline(float shoreRadiusPx[ISLAND_SHAPE_POINTS], float baseRadius) {
float amp1 = (float)GetRandomValue(15, 30) / 100.0f;
float freq1 = (float)GetRandomValue(2, 3);
float phase1 = (float)GetRandomValue(0, 628) / 100.0f;
float amp2 = (float)GetRandomValue(5, 15) / 100.0f;
float freq2 = (float)GetRandomValue(5, 8);
float phase2 = (float)GetRandomValue(0, 628) / 100.0f;
Sprite islandSprite = { for (int i = 0; i < ISLAND_SHAPE_POINTS; i++) {
float angle = (2.0f * PI * (float)i) / (float)ISLAND_SHAPE_POINTS;
float shapeFactor = 1.0f
+ amp1 * cosf(freq1 * angle + phase1)
+ amp2 * cosf(freq2 * angle + phase2);
if (shapeFactor < 0.5f) shapeFactor = 0.5f;
float jitter = (float)GetRandomValue(90, 110) / 100.0f;
shoreRadiusPx[i] = baseRadius * shapeFactor * jitter;
}
// Average each radius with its neighbours a couple of times so the
// outline reads as an irregular but rounded coastline instead of a spiky
// star.
for (int pass = 0; pass < 2; pass++) {
float smoothed[ISLAND_SHAPE_POINTS];
for (int i = 0; i < ISLAND_SHAPE_POINTS; i++) {
int prev = (i - 1 + ISLAND_SHAPE_POINTS) % ISLAND_SHAPE_POINTS;
int next = (i + 1) % ISLAND_SHAPE_POINTS;
smoothed[i] = (shoreRadiusPx[prev] + shoreRadiusPx[i] + shoreRadiusPx[next]) / 3.0f;
}
for (int i = 0; i < ISLAND_SHAPE_POINTS; i++) shoreRadiusPx[i] = smoothed[i];
}
}
static float SampleRadius(const float shoreRadiusPx[ISLAND_SHAPE_POINTS], float angle) {
float t = angle / (2.0f * PI) * ISLAND_SHAPE_POINTS;
int i0 = ((int)t) % ISLAND_SHAPE_POINTS;
int i1 = (i0 + 1) % ISLAND_SHAPE_POINTS;
float frac = t - (int)t;
return shoreRadiusPx[i0] * (1.0f - frac) + shoreRadiusPx[i1] * frac;
}
static Texture2D GenerateIslandTexture(const float shoreRadiusPx[ISLAND_SHAPE_POINTS],
Color shoreColor, Color grassColor, Color hillColor) {
Image image = GenImageColor(ISLAND_IMAGE_SIZE, ISLAND_IMAGE_SIZE, BLANK);
float center = ISLAND_IMAGE_SIZE / 2.0f;
for (int y = 0; y < ISLAND_IMAGE_SIZE; y++) {
for (int x = 0; x < ISLAND_IMAGE_SIZE; x++) {
float dx = (x + 0.5f) - center;
float dy = (y + 0.5f) - center;
float dist = sqrtf(dx * dx + dy * dy);
float angle = atan2f(dy, dx);
if (angle < 0.0f) angle += 2.0f * PI;
float shoreRadius = SampleRadius(shoreRadiusPx, angle);
if (dist <= shoreRadius * ISLAND_HILL_SCALE) {
ImageDrawPixel(&image, x, y, hillColor);
} else if (dist <= shoreRadius * ISLAND_GRASS_SCALE) {
ImageDrawPixel(&image, x, y, grassColor);
} else if (dist <= shoreRadius) {
ImageDrawPixel(&image, x, y, shoreColor);
}
}
}
Texture2D texture = LoadTextureFromImage(image);
UnloadImage(image);
SetTextureFilter(texture, TEXTURE_FILTER_POINT);
return texture;
}
static void GenerateIsland(Island *island, Vector2 position) {
if (island->sprite.texture.id != 0) {
UnloadTexture(island->sprite.texture);
}
island->position = position;
float worldDiameter = (float)GetRandomValue(220, 380);
float basePixelRadius = (float)GetRandomValue(30, 44);
float shoreRadiusPx[ISLAND_SHAPE_POINTS];
GenerateCoastline(shoreRadiusPx, basePixelRadius);
// The collision radius has to track the coastline's actual average
// extent, not a fraction of the destRec/canvas size — the canvas has a
// fair amount of open water margin around the shore (needed so the
// harmonic wobble and jitter don't clip off-canvas), so anything based on
// worldDiameter directly ends up much bigger than the visible island,
// making the ship "bump" into open water well before the shore.
float avgRadiusPx = 0.0f;
for (int i = 0; i < ISLAND_SHAPE_POINTS; i++) avgRadiusPx += shoreRadiusPx[i];
avgRadiusPx /= ISLAND_SHAPE_POINTS;
float pixelToWorld = worldDiameter / (float)ISLAND_IMAGE_SIZE;
// Slightly over the average so the collision circle covers the coastline
// at most angles instead of sitting exactly on it — the shape bulges out
// past the average at peninsulas, and a ship visually overlapping the
// shore reads worse than the collision boundary having a little slack.
island->radius = avgRadiusPx * pixelToWorld * 1.05f;
// Slight per-island tint variation so a cluster of islands doesn't look
// like the same stamp copy-pasted around.
Color shoreColor = (Color){
(unsigned char)Clamp(233 + GetRandomValue(-10, 10), 0, 255),
(unsigned char)Clamp(220 + GetRandomValue(-10, 10), 0, 255),
(unsigned char)Clamp(178 + GetRandomValue(-10, 10), 0, 255),
255};
Color grassColor = (Color){
(unsigned char)Clamp(80 + GetRandomValue(-15, 15), 0, 255),
(unsigned char)Clamp(168 + GetRandomValue(-15, 15), 0, 255),
(unsigned char)Clamp(84 + GetRandomValue(-15, 15), 0, 255),
255};
Color hillColor = (Color){
(unsigned char)Clamp(52 + GetRandomValue(-12, 12), 0, 255),
(unsigned char)Clamp(120 + GetRandomValue(-12, 15), 0, 255),
(unsigned char)Clamp(64 + GetRandomValue(-12, 12), 0, 255),
255};
Texture2D texture = GenerateIslandTexture(shoreRadiusPx, shoreColor, grassColor, hillColor);
island->sprite = (Sprite){
.texture = texture, .texture = texture,
.frameSize = {frameWidth, frameWidth}, .frameSize = {ISLAND_IMAGE_SIZE, ISLAND_IMAGE_SIZE},
.sourceRec = {0.0f, 0.0f, frameWidth, frameWidth}, .sourceRec = {0.0f, 0.0f, ISLAND_IMAGE_SIZE, ISLAND_IMAGE_SIZE},
.destRec = {GetScreenWidth() / 2, 0.0f, frameWidth, frameWidth}, .destRec = {position.x, position.y, worldDiameter, worldDiameter},
.origin = {frameWidth / 2, frameWidth / 2}, .origin = {worldDiameter / 2.0f, worldDiameter / 2.0f},
.rotation = rotation, .rotation = 0.0f,
.color = WHITE}; .color = WHITE};
Physics islandPhysics = {.position = (Vector2){-100.0f, 0.0f},
.direction = (Vector2){0.0f, 0.0f},
.speed = 10.0f};
return (Islands){.sprite = islandSprite, .physics = islandPhysics};
} }
void UpdateIslands(Islands *islands) { Islands CreateIslands(Camera2D *camera) {
Islands islands = {.count = MAX_ISLANDS};
Rectangle cameraRec = GetCameraRectangle(camera);
UpdatePhysics(&islands->physics, (Vector2){0.0f, 0.0f}); for (int i = 0; i < islands.count; i++) {
UpdateSpriteDestRec(&islands->sprite, &islands->physics.position); GenerateIsland(&islands.island[i],
UpdateAnimation(&islands->animation, GetFrameTime()); GetPositionOutsideRectangle(cameraRec, ISLAND_MIN_SPAWN_DISTANCE, ISLAND_MAX_SPAWN_DISTANCE));
UpdateSpriteSourceRec( }
&islands->sprite,
&(Vector2){islands->sprite.frameSize.x * islands->animation.currentFrame, return islands;
islands->sprite.frameSize.y});
} }
void RenderIslands(Islands *islands) { RenderSprite(&islands->sprite); } void UpdateIslands(Islands *islands, Camera2D *camera) {
Rectangle cameraRec = GetCameraRectangle(camera);
for (int i = 0; i < islands->count; i++) {
if (GetDistanceBetweenTwoVectors(islands->island[i].position, camera->target) > ISLAND_RECYCLE_DISTANCE) {
GenerateIsland(&islands->island[i],
GetPositionOutsideRectangle(cameraRec, ISLAND_MIN_SPAWN_DISTANCE, ISLAND_MAX_SPAWN_DISTANCE));
}
}
}
void RenderIslands(Islands *islands) {
for (int i = 0; i < islands->count; i++) {
RenderSprite(&islands->island[i].sprite);
}
}
void UnloadIslands(Islands *islands) {
for (int i = 0; i < islands->count; i++) {
UnloadTexture(islands->island[i].sprite.texture);
}
}
+60
View File
@@ -0,0 +1,60 @@
#include <raylib.h>
#include "app.h"
#include "gameover.h"
void InitGameOver(GameOverState *g) {
int windowWidth = GetScreenWidth();
int windowHeight = GetScreenHeight();
Vector2 buttonDimensions = {200, 60};
Vector2 buttonPos = {windowWidth / 2.0f, windowHeight / 2.0f + 80.0f};
g->restartButton = initButton("Restart", buttonDimensions, buttonPos);
}
void UpdateGameOver(App *app) {
if (isButtonClicked(&app->gameOver.restartButton)) {
ResetGame(app);
app->state.currentView = GAMEPLAY;
}
UpdateMouseScreen(&app->game.mouse);
OnWindowClosedStoppApp(app);
}
void RenderGameOver(App *app) {
BeginDrawing();
ClearBackground(BLACK);
int windowWidth = GetScreenWidth();
int windowHeight = GetScreenHeight();
const char *title = "GAME OVER";
int titleSize = app->font.baseSize * 2;
Vector2 titleDim = MeasureTextEx(app->font, title, titleSize, 2);
DrawTextEx(
app->font, title,
(Vector2){windowWidth / 2.0f - titleDim.x / 2.0f, windowHeight / 2.0f - titleDim.y - 40.0f},
titleSize, 2, TEXT_COLOR_LIGHT);
const char *goldText = TextFormat("Gold collected: %i", app->game.player.gold);
int goldSize = app->font.baseSize;
Vector2 goldDim = MeasureTextEx(app->font, goldText, goldSize, 2);
DrawTextEx(
app->font, goldText,
(Vector2){windowWidth / 2.0f - goldDim.x / 2.0f, windowHeight / 2.0f - goldDim.y / 2.0f},
goldSize, 2, TEXT_COLOR_LIGHT);
renderButton(&app->gameOver.restartButton);
Mouse *mouse = &app->game.mouse;
Rectangle mouseRectangle =
(Rectangle){0, 0, mouse->cursorTexture.width, mouse->cursorTexture.height};
DrawTextureRec(mouse->cursorTexture, mouseRectangle, mouse->screenPosition, WHITE);
if (app->flags.showFPS) {
DrawFPS(10, 10);
}
EndDrawing();
}
+20 -11
View File
@@ -1,19 +1,28 @@
#include "app.h"
#include "raylib.h"
#include <limits.h> #include <limits.h>
#include <raylib.h>
void RenderOverlay(Mouse *mouse) { #include "engineInput.h"
#include "overlay.h"
#include "player.h"
#include "rose.h"
// Health Overlay CreateOverlay(void) {
Player *player = GetPlayer(); return (Overlay){.rose = CreateRose()};
const char *text = TextFormat("Health: %i\n" }
void UpdateOverlay(Overlay *overlay, Player *player) {
UpdateRose(&overlay->rose, player);
}
void RenderOverlay(Mouse *mouse, Player *player, Rose *rose) {
const char *text = TextFormat(
"Health: %i\n"
"Gold: %i\n", "Gold: %i\n",
player->health, player->gold); player->health, player->gold);
DrawText(text, 0, 0, 25, WHITE); DrawText(text, 0, 0, 25, WHITE);
// Mouse Rectangle mouseRectangle =
Rectangle mouseRectangle = (Rectangle){0, 0, mouse->cursorTexture.width, (Rectangle){0, 0, mouse->cursorTexture.width, mouse->cursorTexture.height};
mouse->cursorTexture.height}; DrawTextureRec(mouse->cursorTexture, mouseRectangle, mouse->screenPosition, WHITE);
DrawTextureRec(mouse->cursorTexture, mouseRectangle, mouse->screenPosition, RenderRose(rose);
WHITE);
} }
+273
View File
@@ -0,0 +1,273 @@
#include "pathfinding.h"
#include <math.h>
#include <raymath.h>
#include <stdbool.h>
#include <stdlib.h>
#define PATH_GRID_MAX_DIM 24
#define PATH_MIN_CELL_SIZE 16.0f
#define PATH_GRID_MAX_NODES ((PATH_GRID_MAX_DIM + 2) * (PATH_GRID_MAX_DIM + 2))
#define PATH_LINE_SAMPLE_STEP 8.0f
#define CLEARANCE_GRID_STEP 8.0f
#define PATH_GRID_PADDING 256.0f
typedef struct {
float minX, minY;
float cellSize;
int cols, rows, nodeCount;
int startIdx, goalIdx;
} PathGrid;
static bool IsKnownWater(Terrain *terrain, Vector2 pos) {
return IsTerrainLoaded(terrain, pos) && !IsTerrainLand(terrain, pos);
}
bool IsWaterWithClearance(Terrain *terrain, Vector2 pos, float clearance) {
if (!IsKnownWater(terrain, pos))
return false;
int radiusSteps = (int)(clearance / CLEARANCE_GRID_STEP) + 1;
float clearanceSquared = clearance * clearance;
for (int gy = -radiusSteps; gy <= radiusSteps; gy++) {
for (int gx = -radiusSteps; gx <= radiusSteps; gx++) {
if (gx == 0 && gy == 0)
continue;
float ox = (float)gx * CLEARANCE_GRID_STEP;
float oy = (float)gy * CLEARANCE_GRID_STEP;
if (ox * ox + oy * oy > clearanceSquared)
continue;
if (!IsKnownWater(terrain, (Vector2){pos.x + ox, pos.y + oy}))
return false;
}
}
return true;
}
Vector2 GetRandomWaterPositionNear(Terrain *terrain, Vector2 origin, float maxRadius,
float clearance) {
float startAngle = (float)GetRandomValue(0, 359);
for (float r = 16.0f; r < maxRadius; r += 16.0f) {
for (float aStep = 0.0f; aStep < 360.0f; aStep += 30.0f) {
float a = startAngle + aStep;
Vector2 p = {origin.x + cosf(a * DEG2RAD) * r, origin.y + sinf(a * DEG2RAD) * r};
if (IsWaterWithClearance(terrain, p, clearance))
return p;
}
}
return origin;
}
static bool IsLineClear(Terrain *terrain, Vector2 a, Vector2 b, float clearance) {
float dist = Vector2Distance(a, b);
int steps = (int)(dist / PATH_LINE_SAMPLE_STEP) + 1;
for (int i = 0; i <= steps; i++) {
Vector2 p = Vector2Lerp(a, b, (float)i / (float)steps);
if (!IsWaterWithClearance(terrain, p, clearance))
return false;
}
return true;
}
static float Heuristic(int c1, int r1, int c2, int r2, float cellSize) {
int dx = abs(c1 - c2);
int dy = abs(r1 - r2);
int mn = dx < dy ? dx : dy;
int mx = dx < dy ? dy : dx;
return cellSize * (1.41421356f * (float)mn + (float)(mx - mn));
}
static int ClampToGrid(int value, int size) {
if (value < 0)
return 0;
if (value >= size)
return size - 1;
return value;
}
static PathGrid BuildPathGrid(Vector2 start, Vector2 goal) {
PathGrid grid = {0};
float minX = fminf(start.x, goal.x) - PATH_GRID_PADDING;
float maxX = fmaxf(start.x, goal.x) + PATH_GRID_PADDING;
float minY = fminf(start.y, goal.y) - PATH_GRID_PADDING;
float maxY = fmaxf(start.y, goal.y) + PATH_GRID_PADDING;
float spanX = maxX - minX;
float spanY = maxY - minY;
float cellSize = fmaxf(PATH_MIN_CELL_SIZE, fmaxf(spanX, spanY) / (float)PATH_GRID_MAX_DIM);
grid.minX = minX;
grid.minY = minY;
grid.cellSize = cellSize;
grid.cols = (int)(spanX / cellSize) + 1;
grid.rows = (int)(spanY / cellSize) + 1;
grid.nodeCount = grid.cols * grid.rows;
if (grid.nodeCount > PATH_GRID_MAX_NODES || grid.nodeCount <= 0) {
grid.nodeCount = 0;
return grid;
}
int startCol = ClampToGrid((int)((start.x - minX) / cellSize), grid.cols);
int startRow = ClampToGrid((int)((start.y - minY) / cellSize), grid.rows);
int goalCol = ClampToGrid((int)((goal.x - minX) / cellSize), grid.cols);
int goalRow = ClampToGrid((int)((goal.y - minY) / cellSize), grid.rows);
grid.startIdx = startRow * grid.cols + startCol;
grid.goalIdx = goalRow * grid.cols + goalCol;
return grid;
}
static bool RunAStar(Terrain *terrain, PathGrid grid, float clearance, int *cameFrom) {
static float gScore[PATH_GRID_MAX_NODES];
static float fScore[PATH_GRID_MAX_NODES];
static bool inOpen[PATH_GRID_MAX_NODES];
static bool inClosed[PATH_GRID_MAX_NODES];
int cols = grid.cols, rows = grid.rows;
int goalCol = grid.goalIdx % cols, goalRow = grid.goalIdx / cols;
for (int i = 0; i < grid.nodeCount; i++) {
gScore[i] = INFINITY;
cameFrom[i] = -1;
inOpen[i] = false;
inClosed[i] = false;
}
gScore[grid.startIdx] = 0.0f;
fScore[grid.startIdx] =
Heuristic(grid.startIdx % cols, grid.startIdx / cols, goalCol, goalRow, grid.cellSize);
inOpen[grid.startIdx] = true;
int openCount = 1;
static const int neighborCol[8] = {-1, 1, 0, 0, -1, -1, 1, 1};
static const int neighborRow[8] = {0, 0, -1, 1, -1, 1, -1, 1};
while (openCount > 0) {
int current = -1;
float best = INFINITY;
for (int i = 0; i < grid.nodeCount; i++) {
if (inOpen[i] && fScore[i] < best) {
best = fScore[i];
current = i;
}
}
if (current == -1)
break;
if (current == grid.goalIdx)
return true;
inOpen[current] = false;
openCount--;
inClosed[current] = true;
int curCol = current % cols;
int curRow = current / cols;
for (int n = 0; n < 8; n++) {
int nc = curCol + neighborCol[n];
int nr = curRow + neighborRow[n];
if (nc < 0 || nc >= cols || nr < 0 || nr >= rows)
continue;
int nIdx = nr * cols + nc;
if (inClosed[nIdx])
continue;
Vector2 cellCenter = {grid.minX + ((float)nc + 0.5f) * grid.cellSize,
grid.minY + ((float)nr + 0.5f) * grid.cellSize};
if (!IsWaterWithClearance(terrain, cellCenter, clearance))
continue;
if (neighborCol[n] != 0 && neighborRow[n] != 0) {
Vector2 side1 = {grid.minX + ((float)(curCol + neighborCol[n]) + 0.5f) * grid.cellSize,
grid.minY + ((float)curRow + 0.5f) * grid.cellSize};
Vector2 side2 = {grid.minX + ((float)curCol + 0.5f) * grid.cellSize,
grid.minY + ((float)(curRow + neighborRow[n]) + 0.5f) * grid.cellSize};
if (!IsWaterWithClearance(terrain, side1, clearance) ||
!IsWaterWithClearance(terrain, side2, clearance))
continue;
}
float moveCost = (neighborCol[n] != 0 && neighborRow[n] != 0) ? grid.cellSize * 1.41421356f
: grid.cellSize;
float tentativeG = gScore[current] + moveCost;
if (tentativeG < gScore[nIdx]) {
cameFrom[nIdx] = current;
gScore[nIdx] = tentativeG;
fScore[nIdx] = tentativeG + Heuristic(nc, nr, goalCol, goalRow, grid.cellSize);
if (!inOpen[nIdx]) {
inOpen[nIdx] = true;
openCount++;
}
}
}
}
return false;
}
static int ReconstructRawPath(PathGrid grid, const int *cameFrom, Vector2 goal,
Vector2 *rawPoints) {
int rawCount = 0;
for (int cur = grid.goalIdx; cur != -1; cur = cameFrom[cur]) {
int c = cur % grid.cols;
int r = cur / grid.cols;
rawPoints[rawCount++] = (Vector2){grid.minX + ((float)c + 0.5f) * grid.cellSize,
grid.minY + ((float)r + 0.5f) * grid.cellSize};
}
for (int i = 0; i < rawCount / 2; i++) {
Vector2 tmp = rawPoints[i];
rawPoints[i] = rawPoints[rawCount - 1 - i];
rawPoints[rawCount - 1 - i] = tmp;
}
rawPoints[rawCount - 1] = goal;
return rawCount;
}
static int SimplifyPathWithStringPulling(Terrain *terrain, Vector2 start, const Vector2 *rawPoints,
int rawCount, float clearance, Vector2 *outWaypoints) {
int count = 0;
int i = 0;
Vector2 anchor = start;
while (i < rawCount - 1 && count < PATH_MAX_WAYPOINTS) {
int farthest = i + 1;
for (int j = rawCount - 1; j > i; j--) {
if (IsLineClear(terrain, anchor, rawPoints[j], clearance)) {
farthest = j;
break;
}
}
anchor = rawPoints[farthest];
outWaypoints[count++] = anchor;
i = farthest;
}
return count;
}
Path FindWaterPath(Terrain *terrain, Vector2 start, Vector2 goal, float clearance) {
Path path = {0};
if (IsLineClear(terrain, start, goal, clearance)) {
path.waypoints[0] = goal;
path.count = 1;
return path;
}
PathGrid grid = BuildPathGrid(start, goal);
if (grid.nodeCount == 0)
return path;
static int cameFrom[PATH_GRID_MAX_NODES];
if (!RunAStar(terrain, grid, clearance, cameFrom))
return path;
static Vector2 rawPoints[PATH_GRID_MAX_NODES];
int rawCount = ReconstructRawPath(grid, cameFrom, goal, rawPoints);
path.count =
SimplifyPathWithStringPulling(terrain, start, rawPoints, rawCount, clearance, path.waypoints);
return path;
}
+91 -55
View File
@@ -1,8 +1,27 @@
#include "app.h" #include <raylib.h>
#include "engine.h"
#include "appCore.h"
#include "engineAssets.h"
#include "engineConfig.h"
#include "enginePhysics.h"
#include "engineSprite.h"
#include "engineStorage.h"
#include "engineVector.h"
#include "pathfinding.h"
#include "player.h"
typedef enum { FLAPPING, SOUND_COUNT } SoundID; typedef enum { FLAPPING, SOUND_COUNT } SoundID;
#define PLAYER_SAVE_VERSION 1
typedef struct {
int version;
int gold;
float posX;
float posY;
float rotation;
} PlayerSaveData;
const SoundAsset playerSoundAssets[SOUND_COUNT] = {{"assets/flapping.wav"}}; const SoundAsset playerSoundAssets[SOUND_COUNT] = {{"assets/flapping.wav"}};
const TextureAsset playerTextureAssets[PLAYER_TEX_COUNT] = { const TextureAsset playerTextureAssets[PLAYER_TEX_COUNT] = {
@@ -10,78 +29,94 @@ const TextureAsset playerTextureAssets[PLAYER_TEX_COUNT] = {
float playerSpeeds[SPEED_COUNT] = {0.0f, 5.0f, 10.0f}; float playerSpeeds[SPEED_COUNT] = {0.0f, 5.0f, 10.0f};
Config config; #define PLAYER_TERRAIN_CLEARANCE 40.0f
#define PLAYER_SPAWN_MAX_RADIUS 400.0f
playerSpeedID GetPlayerSpeedID(float speed) { PlayerSpeedID GetPlayerSpeedID(float speed) {
for (int i = 0; i < SPEED_COUNT; i++) { for (int i = 0; i < SPEED_COUNT; i++) {
if (playerSpeeds[i] == speed) { if (playerSpeeds[i] == speed) {
return (playerSpeedID)i; return (PlayerSpeedID)i;
} }
} }
return ANCHOR; return ANCHOR;
} }
Player *GetPlayer(void) { static void RandomizeSpawn(Player *player, Terrain *terrain) {
static Player player; player->physics.position = GetRandomWaterPositionNear(
return &player; terrain, (Vector2){0.0f, 0.0f}, PLAYER_SPAWN_MAX_RADIUS, PLAYER_TERRAIN_CLEARANCE);
player->physics.direction = (Vector2){0.0f, 0.0f};
player->physics.speed = playerSpeeds[ANCHOR];
player->rotation = (float)GetRandomValue(0, 359);
player->sprite.rotation = player->rotation;
} }
void CreatePlayer(void) { Player CreatePlayer(Terrain *terrain) {
InitConfig(&config, "config/player.ini"); Player player = {0};
InitConfig(&player.config, "config/player.ini");
int PLAYER_HEALTH_MAX = GetConfigInt(&config, "HEALTH_MAX"); int PLAYER_HEALTH_MAX = GetConfigInt(&player.config, "HEALTH_MAX");
int START_GOLD = GetConfigInt(&config, "START_GOLD"); int START_GOLD = GetConfigInt(&player.config, "START_GOLD");
TextureAsset startShipAsset = playerTextureAssets[START_SHIP]; TextureAsset startShipAsset = playerTextureAssets[START_SHIP];
Texture2D texture = LoadTextureCached(startShipAsset.path);
Texture2D texture = LoadTexture(startShipAsset.path);
float frameWidth = startShipAsset.frameWidth; float frameWidth = startShipAsset.frameWidth;
float rotation =
(float)LoadStorageValue(STORAGE_DATA_FILE, STORAGE_POSITION_ROTATION);
Sprite playerSprite = {.texture = texture, PlayerSaveData saveData = {0};
LoadStorageData(STORAGE_DATA_FILE, PLAYER_SAVE_VERSION, &saveData, sizeof(saveData));
player.sprite = (Sprite){.texture = texture,
.frameSize = {frameWidth, frameWidth}, .frameSize = {frameWidth, frameWidth},
.sourceRec = {0.0f, 0.0f, frameWidth, frameWidth}, .sourceRec = {0.0f, 0.0f, frameWidth, frameWidth},
.destRec = {0.0f, 0.0f, frameWidth, frameWidth}, .destRec = {0.0f, 0.0f, frameWidth, frameWidth},
.origin = {frameWidth / 2, frameWidth / 2}, .origin = {frameWidth / 2, frameWidth / 2},
.rotation = rotation, .rotation = 0.0f,
.color = WHITE}; .color = WHITE};
Animation playerAnimation = {.state = 1, player.animation = (Animation){.state = 1,
.currentFrame = 0, .currentFrame = 0,
.maxFrame = 3, .maxFrame = 3,
.framesCounter = 0, .framesCounter = 0,
.framesSpeed = 0.0f, .framesSpeed = 0.0f,
.animTimer = 0.0f}; .animTimer = 0.0f};
Physics playerPhysics = { player.physics = (Physics){.position = {0.0f, 0.0f},
.position = {(float)LoadStorageValue(STORAGE_DATA_FILE,
STORAGE_POSITION_POSX),
(float)LoadStorageValue(STORAGE_DATA_FILE,
STORAGE_POSITION_POSY)},
.direction = {0.0f, 0.0f}, .direction = {0.0f, 0.0f},
.speed = playerSpeeds[ANCHOR], .speed = playerSpeeds[ANCHOR],
.weight = 10.0f}; .weight = 10.0f};
Player *player = GetPlayer(); player.rotation = 0.0f;
player.soundFlapping = LoadSoundCached(playerSoundAssets[FLAPPING].path);
player.health = PLAYER_HEALTH_MAX;
player.gold = saveData.gold;
*player = (Player){ if (player.gold == 0) {
.sprite = playerSprite, player.gold = START_GOLD;
.animation = playerAnimation,
.physics = playerPhysics,
.rotation = rotation,
.soundFlapping = LoadSound(playerSoundAssets[FLAPPING].path),
.health = PLAYER_HEALTH_MAX,
.gold = LoadStorageValue(STORAGE_DATA_FILE, STORAGE_POSITION_GOLD)};
if (player->gold == 0) {
player->gold = START_GOLD;
} }
RandomizeSpawn(&player, terrain);
return player;
} }
void UpdatePlayer(void) { void ResetPlayer(Player *player, Terrain *terrain) {
Player *player = GetPlayer(); int PLAYER_HEALTH_MAX = GetConfigInt(&player->config, "HEALTH_MAX");
if (IsKeyPressed(KEY_W) || IsKeyPressed(KEY_UP)) { player->health = PLAYER_HEALTH_MAX;
RandomizeSpawn(player, terrain);
ResetWakeTrail(&player->wakeTrail);
SavePlayer(player);
}
void SavePlayer(Player *player) {
PlayerSaveData saveData = {.version = PLAYER_SAVE_VERSION,
.gold = player->gold,
.posX = player->physics.position.x,
.posY = player->physics.position.y,
.rotation = player->rotation};
SaveStorageData(STORAGE_DATA_FILE, PLAYER_SAVE_VERSION, &saveData, sizeof(saveData));
}
static void UpdatePlayerSpeedFromInput(Player *player) {
if (IsGameActionPressed(ACTION_FORWARD)) {
if (player->physics.speed < playerSpeeds[FAST_AHEAD]) { if (player->physics.speed < playerSpeeds[FAST_AHEAD]) {
if (player->physics.speed < playerSpeeds[SLOW_AHEAD]) { if (player->physics.speed < playerSpeeds[SLOW_AHEAD]) {
player->physics.speed = playerSpeeds[SLOW_AHEAD]; player->physics.speed = playerSpeeds[SLOW_AHEAD];
@@ -91,7 +126,7 @@ void UpdatePlayer(void) {
} }
} }
if (IsKeyPressed(KEY_S) || IsKeyPressed(KEY_DOWN)) { if (IsGameActionPressed(ACTION_BACKWARD)) {
if (player->physics.speed > playerSpeeds[ANCHOR]) { if (player->physics.speed > playerSpeeds[ANCHOR]) {
if (player->physics.speed > playerSpeeds[SLOW_AHEAD]) { if (player->physics.speed > playerSpeeds[SLOW_AHEAD]) {
player->physics.speed = playerSpeeds[SLOW_AHEAD]; player->physics.speed = playerSpeeds[SLOW_AHEAD];
@@ -100,35 +135,36 @@ void UpdatePlayer(void) {
} }
} }
} }
}
player->sprite.sourceRec.x = 16.0f * GetPlayerSpeedID(player->physics.speed); static void UpdatePlayerRotationFromInput(Player *player) {
float rotationSpeed = 50.0f; float rotationSpeed = 50.0f;
if (player->physics.speed <= playerSpeeds[ANCHOR])
return;
if (player->physics.speed > playerSpeeds[ANCHOR]) { if (IsGameActionDown(ACTION_TURN_LEFT)) {
if (IsKeyDown(KEY_A) || IsKeyDown(KEY_LEFT)) {
player->rotation -= rotationSpeed * GetFrameTime(); player->rotation -= rotationSpeed * GetFrameTime();
} }
if (IsKeyDown(KEY_D) || IsKeyDown(KEY_RIGHT)) { if (IsGameActionDown(ACTION_TURN_RIGHT)) {
player->rotation += rotationSpeed * GetFrameTime(); player->rotation += rotationSpeed * GetFrameTime();
} }
} }
void UpdatePlayer(Player *player) {
UpdatePlayerSpeedFromInput(player);
player->sprite.sourceRec.x = 16.0f * GetPlayerSpeedID(player->physics.speed);
UpdatePlayerRotationFromInput(player);
player->sprite.rotation = player->rotation; player->sprite.rotation = player->rotation;
Vector2 dir = RotationToVector2(player->rotation - 90.0f); Vector2 dir = RotationToVector2(player->rotation - 90.0f);
UpdatePhysics(&player->physics, dir); UpdatePhysics(&player->physics, dir);
UpdateSpriteDestRec(&player->sprite, &player->physics.position); UpdateSpriteDestRec(&player->sprite, &player->physics.position);
SaveStorageValue(STORAGE_DATA_FILE, STORAGE_POSITION_POSX, UpdateWakeTrail(&player->wakeTrail, player->physics.position);
(int)player->physics.position.x); SavePlayer(player);
SaveStorageValue(STORAGE_DATA_FILE, STORAGE_POSITION_POSY,
(int)player->physics.position.y);
SaveStorageValue(STORAGE_DATA_FILE, STORAGE_POSITION_ROTATION,
(int)player->rotation);
} }
void RenderPlayer(void) { void RenderPlayer(Player *player) {
Player *player = GetPlayer();
RenderSprite(&player->sprite); RenderSprite(&player->sprite);
} }
+60 -55
View File
@@ -1,94 +1,99 @@
#include "app.h"
#include <raylib.h> #include <raylib.h>
#include <raymath.h> #include <raymath.h>
#include "appCore.h"
#include "engineAssets.h"
#include "enginePhysics.h"
#include "projectiles.h"
typedef enum { CANNONBALL_SHOOTING, CANNONBALL_IMPACT, SOUND_COUNT } SoundID; typedef enum { CANNONBALL_SHOOTING, CANNONBALL_IMPACT, SOUND_COUNT } SoundID;
const SoundAsset soundAssets[SOUND_COUNT] = {{"assets/cannonball_shooting.mp3"}, const SoundAsset soundAssets[SOUND_COUNT] = {{"assets/cannonball_shooting.mp3"},
{"assets/cannonball_impact.mp3"}}; {"assets/cannonball_impact.mp3"}};
#define PROJECTILE_RELOAD_TIME 1.0f
#define PROJECTILE_MUZZLE_OFFSET 12.0f
Projectiles CreateProjectiles(void) { Projectiles CreateProjectiles(void) {
Projectiles projectiles = {.count = MAX_PROJECTILES}; Projectiles projectiles = {.count = MAX_PROJECTILES};
Sound shootingSound = LoadSoundCached(soundAssets[CANNONBALL_SHOOTING].path);
Sound impactSound = LoadSoundCached(soundAssets[CANNONBALL_IMPACT].path);
for (int i = 0; i < projectiles.count; i++) { for (int i = 0; i < projectiles.count; i++) {
projectiles.physics[i] = projectiles.physics[i] = (Physics){(Vector2){0.0f, 0.0f}, (Vector2){0.0f, 0.0f},
(Physics){(Vector2){0.0f, 0.0f}, (Vector2){0.0f, 0.0f}, (Vector2){0.0f, 0.0f}, PROJECTILE_SPEED};
(Vector2){0.0f, 0.0f}, 50.0f};
projectiles.radius[i] = 1.0f; projectiles.radius[i] = 1.0f;
projectiles.active[i] = false; projectiles.active[i] = false;
projectiles.friendly[i] = true;
projectiles.color[i] = GRAY; projectiles.color[i] = GRAY;
projectiles.soundShooting[i] = projectiles.soundShooting[i] = LoadSoundAlias(shootingSound);
LoadSound(soundAssets[CANNONBALL_SHOOTING].path); projectiles.soundImpact[i] = LoadSoundAlias(impactSound);
projectiles.soundImpact[i] = LoadSound(soundAssets[CANNONBALL_IMPACT].path);
} }
return projectiles; return projectiles;
} }
void UpdateProjectiles(Projectiles *projectiles, Enemies *enemies) { bool FireProjectile(Projectiles *projectiles, Vector2 position, Vector2 direction, bool friendly) {
for (int i = 0; i < projectiles->count; i++) {
if (projectiles->active[i])
continue;
projectiles->active[i] = true;
projectiles->friendly[i] = friendly;
projectiles->physics[i].position = position;
projectiles->physics[i].direction = direction;
projectiles->color[i] = friendly ? GRAY : RED;
SetSoundPitch(projectiles->soundShooting[i], (float)GetRandomValue(90, 110) / 100.0f);
SetSoundVolume(projectiles->soundShooting[i], (float)GetRandomValue(85, 100) / 100.0f);
PlaySound(projectiles->soundShooting[i]);
return true;
}
return false;
}
static bool TryFirePlayerBroadside(Projectiles *projectiles, Player *player, GameAction action,
float sideDegrees, float currentTime, float *lastShotTime) {
if (!IsGameActionDown(action) || (currentTime - *lastShotTime < PROJECTILE_RELOAD_TIME)) {
return false;
}
Vector2 direction = Vector2Rotate(player->physics.direction, sideDegrees * DEG2RAD);
Vector2 firePosition =
Vector2Add(player->physics.position, Vector2Scale(direction, PROJECTILE_MUZZLE_OFFSET));
if (FireProjectile(projectiles, firePosition, direction, true)) {
*lastShotTime = currentTime;
return true;
}
return false;
}
void UpdateProjectiles(Projectiles *projectiles, Player *player) {
static float lastShotTime = 0.0f; static float lastShotTime = 0.0f;
const float reloadTime = 1.0f;
float currentTime = GetTime(); float currentTime = GetTime();
if (IsKeyDown(KEY_Q) && (currentTime - lastShotTime >= reloadTime)) { TryFirePlayerBroadside(projectiles, player, ACTION_FIRE_PORT, -90.0f, currentTime, &lastShotTime);
for (int i = 0; i < projectiles->count; i++) { TryFirePlayerBroadside(projectiles, player, ACTION_FIRE_STARBOARD, 90.0f, currentTime,
if (!projectiles->active[i]) { &lastShotTime);
projectiles->active[i] = true;
Player *player = GetPlayer();
projectiles->physics[i].position = player->physics.position;
projectiles->physics[i].direction =
Vector2Rotate(player->physics.direction, -90.0f * DEG2RAD);
PlaySound(projectiles->soundShooting[i]);
lastShotTime = currentTime;
break;
}
}
}
if (IsKeyDown(KEY_E) && (currentTime - lastShotTime >= reloadTime)) {
for (int i = 0; i < projectiles->count; i++) {
if (!projectiles->active[i]) {
projectiles->active[i] = true;
Player *player = GetPlayer();
projectiles->physics[i].position = player->physics.position;
projectiles->physics[i].direction =
Vector2Rotate(player->physics.direction, 90.0f * DEG2RAD);
PlaySound(projectiles->soundShooting[i]);
lastShotTime = currentTime;
break;
}
}
}
for (int i = 0; i < projectiles->count; i++) { for (int i = 0; i < projectiles->count; i++) {
if (!projectiles->active[i]) if (!projectiles->active[i])
continue; continue;
for (int j = 0; j < enemies->count; j++) {
if (CheckCollisionCircleRec(projectiles->physics[i].position,
projectiles->radius[i],
enemies->sprite[j].destRec)) {
PlaySound(projectiles->soundImpact[i]);
Player *player = GetPlayer();
player->gold += 1;
SaveStorageValue(STORAGE_DATA_FILE, STORAGE_POSITION_GOLD,
player->gold);
projectiles->active[i] = false;
}
}
UpdatePhysics(&projectiles->physics[i], projectiles->physics[i].direction); UpdatePhysics(&projectiles->physics[i], projectiles->physics[i].direction);
} }
} }
void ResetProjectiles(Projectiles *projectiles) {
for (int i = 0; i < projectiles->count; i++) {
projectiles->active[i] = false;
}
}
void RenderProjectiles(Projectiles *projectiles) { void RenderProjectiles(Projectiles *projectiles) {
for (int i = 0; i < projectiles->count; i++) { for (int i = 0; i < projectiles->count; i++) {
if (projectiles->active[i] == true) { if (projectiles->active[i] == true) {
DrawCircle(projectiles->physics[i].position.x, DrawCircleV(projectiles->physics[i].position, projectiles->radius[i], projectiles->color[i]);
projectiles->physics[i].position.y, projectiles->radius[i],
projectiles->color[i]);
} }
} }
} }
+59
View File
@@ -0,0 +1,59 @@
#include <raylib.h>
#include "appCore.h"
#include "engineAssets.h"
#include "rose.h"
#include "engineSprite.h"
const TextureAsset roseTextureAssets[ROSE_TEX_COUNT] = {{
"assets/rose.png",
2560.0f,
2560.0f,
0.0f
}};
Rose CreateRose(void){
TextureAsset roseAsset = roseTextureAssets[ROSE];
Texture2D texture = LoadTextureCached(roseAsset.path);
float frameWidth = roseAsset.frameWidth;
Vector2 frameSize = {frameWidth, frameWidth};
float rotation = roseAsset.rotation;
Rectangle sourceRec = {0.0f, 0.0f, frameWidth, frameWidth};
float destSize = GetScreenWidth() * ROSE_SCREEN_WIDTH_FRACTION;
Rectangle destRec = {GetScreenWidth() / 2, GetScreenHeight(), destSize, destSize};
Vector2 origin = {destSize / 2, destSize / 2};
Color color = WHITE;
Sprite sprite = {
.texture = texture,
.frameSize = frameSize,
.sourceRec = sourceRec,
.destRec = destRec,
.origin = origin,
.rotation = rotation,
.color = color
};
Rose rose = {
.sprite = sprite
};
return rose;
}
void UpdateRose(Rose *rose, Player *player){
float rotationSpeed = player->rotation;
if (IsGameActionDown(ACTION_TURN_LEFT)) {
rose->sprite.rotation -= rotationSpeed * GetFrameTime();
}
if (IsGameActionDown(ACTION_TURN_RIGHT)) {
rose->sprite.rotation += rotationSpeed * GetFrameTime();
}
}
void RenderRose(Rose *rose){
RenderSprite(&rose->sprite);
}
+286
View File
@@ -0,0 +1,286 @@
#include <math.h>
#include <raylib.h>
#include <stdlib.h>
#include "terrain.h"
#define TERRAIN_BASE_SCALE 2.2f
#define TERRAIN_DETAIL_SCALE 9.0f
#define TERRAIN_DETAIL_WEIGHT 0.22f
#define TERRAIN_WATER_LEVEL 0.62f
#define TERRAIN_SAND_BAND 0.035f
#define TERRAIN_GRASS_BAND 0.12f
#define TERRAIN_SHORE_BAND_RADIUS 3
#define TERRAIN_STREAM_BUDGET 2
static int WorldToChunkCoord(float worldCoord) {
return (int)floorf(worldCoord / TERRAIN_CHUNK_WORLD_SIZE);
}
static void GenerateChunkHeight(int chunkX, int chunkY, int padding, int paddedResolution,
float *outHeight) {
int offsetX = chunkX * TERRAIN_CHUNK_RESOLUTION - padding;
int offsetY = chunkY * TERRAIN_CHUNK_RESOLUTION - padding;
Image base =
GenImagePerlinNoise(paddedResolution, paddedResolution, offsetX, offsetY, TERRAIN_BASE_SCALE);
Image detail = GenImagePerlinNoise(paddedResolution, paddedResolution, offsetX, offsetY,
TERRAIN_DETAIL_SCALE);
for (int y = 0; y < paddedResolution; y++) {
for (int x = 0; x < paddedResolution; x++) {
float baseValue = GetImageColor(base, x, y).r / 255.0f;
float detailValue = GetImageColor(detail, x, y).r / 255.0f;
outHeight[y * paddedResolution + x] =
baseValue * (1.0f - TERRAIN_DETAIL_WEIGHT) + detailValue * TERRAIN_DETAIL_WEIGHT;
}
}
UnloadImage(base);
UnloadImage(detail);
}
static Color GetLandColor(float height) {
Color sandColor = {233, 220, 178, 255};
Color grassColor = {80, 168, 84, 255};
Color hillColor = {52, 120, 64, 255};
if (height < TERRAIN_WATER_LEVEL + TERRAIN_SAND_BAND)
return sandColor;
if (height < TERRAIN_WATER_LEVEL + TERRAIN_GRASS_BAND)
return grassColor;
return hillColor;
}
static Color GetShoreAlphaColor(const float *height, int paddedResolution, int px, int py) {
float nearestDistSq = 1e9f;
int r = TERRAIN_SHORE_BAND_RADIUS;
for (int dy = -r; dy <= r; dy++) {
for (int dx = -r; dx <= r; dx++) {
if (height[(py + dy) * paddedResolution + (px + dx)] >= TERRAIN_WATER_LEVEL) {
float distSq = (float)(dx * dx + dy * dy);
if (distSq < nearestDistSq)
nearestDistSq = distSq;
}
}
}
if (nearestDistSq > (float)(r * r))
return BLANK;
float t = 1.0f - sqrtf(nearestDistSq) / (float)r;
float alphaValue = t * 254.0f;
if (alphaValue < 1.0f)
alphaValue = 1.0f;
if (alphaValue > 254.0f)
alphaValue = 254.0f;
return (Color){255, 255, 255, (unsigned char)alphaValue};
}
static void PaintChunkPixel(Image *image, bool *landMask, const float *height, int paddedResolution,
int padding, int x, int y) {
int px = x + padding, py = y + padding;
float h = height[py * paddedResolution + px];
bool isLand = h >= TERRAIN_WATER_LEVEL;
landMask[y * TERRAIN_CHUNK_RESOLUTION + x] = isLand;
if (isLand) {
ImageDrawPixel(image, x, y, GetLandColor(h));
return;
}
Color shoreColor = GetShoreAlphaColor(height, paddedResolution, px, py);
if (shoreColor.a > 0) {
ImageDrawPixel(image, x, y, shoreColor);
}
}
static void GenerateChunk(TerrainChunk *chunk, int chunkX, int chunkY) {
if (chunk->active) {
UnloadTexture(chunk->sprite.texture);
free(chunk->landMask);
}
chunk->landMask = malloc(TERRAIN_CHUNK_RESOLUTION * TERRAIN_CHUNK_RESOLUTION * sizeof(bool));
int padding = TERRAIN_SHORE_BAND_RADIUS;
int paddedResolution = TERRAIN_CHUNK_RESOLUTION + padding * 2;
float *height = malloc((size_t)paddedResolution * (size_t)paddedResolution * sizeof(float));
GenerateChunkHeight(chunkX, chunkY, padding, paddedResolution, height);
Image image = GenImageColor(TERRAIN_CHUNK_RESOLUTION, TERRAIN_CHUNK_RESOLUTION, BLANK);
for (int y = 0; y < TERRAIN_CHUNK_RESOLUTION; y++) {
for (int x = 0; x < TERRAIN_CHUNK_RESOLUTION; x++) {
PaintChunkPixel(&image, chunk->landMask, height, paddedResolution, padding, x, y);
}
}
free(height);
Texture2D texture = LoadTextureFromImage(image);
UnloadImage(image);
SetTextureFilter(texture, TEXTURE_FILTER_POINT);
chunk->active = true;
chunk->chunkX = chunkX;
chunk->chunkY = chunkY;
chunk->sprite =
(Sprite){.texture = texture,
.frameSize = {TERRAIN_CHUNK_RESOLUTION, TERRAIN_CHUNK_RESOLUTION},
.sourceRec = {0.0f, 0.0f, TERRAIN_CHUNK_RESOLUTION, TERRAIN_CHUNK_RESOLUTION},
.destRec = {chunkX * TERRAIN_CHUNK_WORLD_SIZE, chunkY * TERRAIN_CHUNK_WORLD_SIZE,
TERRAIN_CHUNK_WORLD_SIZE, TERRAIN_CHUNK_WORLD_SIZE},
.origin = {0.0f, 0.0f},
.rotation = 0.0f,
.color = WHITE};
}
static bool IsChunkLoaded(Terrain *terrain, int chunkX, int chunkY) {
for (int i = 0; i < TERRAIN_MAX_CHUNKS; i++) {
if (terrain->chunks[i].active && terrain->chunks[i].chunkX == chunkX &&
terrain->chunks[i].chunkY == chunkY) {
return true;
}
}
return false;
}
Terrain CreateTerrain(Camera2D *camera) {
Terrain terrain = {0};
terrain.shader = LoadShader("shaders/terrain.vs", "shaders/terrain.fs");
terrain.time = 0.0f;
int centerX = WorldToChunkCoord(camera->target.x);
int centerY = WorldToChunkCoord(camera->target.y);
int slot = 0;
for (int cy = centerY - TERRAIN_LOAD_RADIUS; cy <= centerY + TERRAIN_LOAD_RADIUS; cy++) {
for (int cx = centerX - TERRAIN_LOAD_RADIUS; cx <= centerX + TERRAIN_LOAD_RADIUS; cx++) {
GenerateChunk(&terrain.chunks[slot], cx, cy);
slot++;
}
}
return terrain;
}
static void UnloadChunk(TerrainChunk *chunk) {
UnloadTexture(chunk->sprite.texture);
free(chunk->landMask);
chunk->landMask = NULL;
chunk->active = false;
}
void UpdateTerrain(Terrain *terrain, Camera2D *camera) {
int centerX = WorldToChunkCoord(camera->target.x);
int centerY = WorldToChunkCoord(camera->target.y);
for (int i = 0; i < TERRAIN_MAX_CHUNKS; i++) {
if (!terrain->chunks[i].active)
continue;
int dx = terrain->chunks[i].chunkX - centerX;
int dy = terrain->chunks[i].chunkY - centerY;
if (dx < -TERRAIN_LOAD_RADIUS || dx > TERRAIN_LOAD_RADIUS || dy < -TERRAIN_LOAD_RADIUS ||
dy > TERRAIN_LOAD_RADIUS) {
UnloadChunk(&terrain->chunks[i]);
}
}
int budget = TERRAIN_STREAM_BUDGET;
for (int cy = centerY - TERRAIN_LOAD_RADIUS; cy <= centerY + TERRAIN_LOAD_RADIUS && budget > 0;
cy++) {
for (int cx = centerX - TERRAIN_LOAD_RADIUS; cx <= centerX + TERRAIN_LOAD_RADIUS && budget > 0;
cx++) {
if (IsChunkLoaded(terrain, cx, cy))
continue;
for (int i = 0; i < TERRAIN_MAX_CHUNKS; i++) {
if (!terrain->chunks[i].active) {
GenerateChunk(&terrain->chunks[i], cx, cy);
budget--;
break;
}
}
}
}
terrain->time += GetFrameTime();
int timeLoc = GetShaderLocation(terrain->shader, "time");
SetShaderValue(terrain->shader, timeLoc, &terrain->time, SHADER_UNIFORM_FLOAT);
}
void RenderTerrain(Terrain *terrain) {
BeginShaderMode(terrain->shader);
for (int i = 0; i < TERRAIN_MAX_CHUNKS; i++) {
if (terrain->chunks[i].active) {
RenderSprite(&terrain->chunks[i].sprite);
}
}
EndShaderMode();
}
void UnloadTerrain(Terrain *terrain) {
UnloadShader(terrain->shader);
for (int i = 0; i < TERRAIN_MAX_CHUNKS; i++) {
if (terrain->chunks[i].active) {
UnloadChunk(&terrain->chunks[i]);
}
}
}
static TerrainChunk *GetChunkAt(Terrain *terrain, Vector2 worldPos, int *outPx, int *outPy) {
int cx = WorldToChunkCoord(worldPos.x);
int cy = WorldToChunkCoord(worldPos.y);
for (int i = 0; i < TERRAIN_MAX_CHUNKS; i++) {
TerrainChunk *chunk = &terrain->chunks[i];
if (!chunk->active || chunk->chunkX != cx || chunk->chunkY != cy)
continue;
float localX = worldPos.x - (float)cx * TERRAIN_CHUNK_WORLD_SIZE;
float localY = worldPos.y - (float)cy * TERRAIN_CHUNK_WORLD_SIZE;
int px = (int)(localX / TERRAIN_CHUNK_WORLD_SIZE * TERRAIN_CHUNK_RESOLUTION);
int py = (int)(localY / TERRAIN_CHUNK_WORLD_SIZE * TERRAIN_CHUNK_RESOLUTION);
if (px < 0)
px = 0;
if (px >= TERRAIN_CHUNK_RESOLUTION)
px = TERRAIN_CHUNK_RESOLUTION - 1;
if (py < 0)
py = 0;
if (py >= TERRAIN_CHUNK_RESOLUTION)
py = TERRAIN_CHUNK_RESOLUTION - 1;
*outPx = px;
*outPy = py;
return chunk;
}
return NULL;
}
bool IsTerrainLand(Terrain *terrain, Vector2 worldPos) {
int px, py;
TerrainChunk *chunk = GetChunkAt(terrain, worldPos, &px, &py);
if (!chunk)
return false;
return chunk->landMask[py * TERRAIN_CHUNK_RESOLUTION + px];
}
bool IsTerrainLoaded(Terrain *terrain, Vector2 worldPos) {
int cx = WorldToChunkCoord(worldPos.x);
int cy = WorldToChunkCoord(worldPos.y);
for (int i = 0; i < TERRAIN_MAX_CHUNKS; i++) {
TerrainChunk *chunk = &terrain->chunks[i];
if (chunk->active && chunk->chunkX == cx && chunk->chunkY == cy)
return true;
}
return false;
}
+120
View File
@@ -0,0 +1,120 @@
#include <math.h>
#include "wake.h"
#define WAKE_TRAIL_MIN_SAMPLE_DIST 2.5f
#define WAKE_TRAIL_MAX_AGE 1.8
#define WAKE_MIN_SPEED 0.5f
#define WAKE_MAX_SPEED 10.0f
static void ExpireOldWakePoints(WakeTrail *trail, double now) {
while (trail->count > 0 && now - trail->timestamps[trail->count - 1] > WAKE_TRAIL_MAX_AGE) {
trail->count--;
}
}
static bool HasTraveledFarEnough(WakeTrail *trail, Vector2 position) {
if (trail->count == 0)
return true;
float dx = position.x - trail->points[0].x;
float dy = position.y - trail->points[0].y;
return dx * dx + dy * dy >= WAKE_TRAIL_MIN_SAMPLE_DIST * WAKE_TRAIL_MIN_SAMPLE_DIST;
}
void UpdateWakeTrail(WakeTrail *trail, Vector2 position) {
double now = GetTime();
ExpireOldWakePoints(trail, now);
if (!HasTraveledFarEnough(trail, position))
return;
for (int i = WAKE_TRAIL_POINTS - 1; i > 0; i--) {
trail->points[i] = trail->points[i - 1];
trail->timestamps[i] = trail->timestamps[i - 1];
}
trail->points[0] = position;
trail->timestamps[0] = now;
if (trail->count < WAKE_TRAIL_POINTS)
trail->count++;
}
void ResetWakeTrail(WakeTrail *trail) {
trail->count = 0;
}
Wake CreateWake(void) {
Wake wake = {0};
wake.shader = LoadShader("shaders/wake.vs", "shaders/wake.fs");
wake.time = 0.0f;
return wake;
}
void UpdateWake(Wake *wake, float deltaTime) {
wake->time += deltaTime;
int timeLoc = GetShaderLocation(wake->shader, "time");
SetShaderValue(wake->shader, timeLoc, &wake->time, SHADER_UNIFORM_FLOAT);
}
static void ComputeWakeAges(WakeTrail *trail, double now, float *outAges) {
for (int i = 0; i < WAKE_TRAIL_POINTS; i++) {
if (i >= trail->count) {
outAges[i] = 1.0f;
continue;
}
float age = (float)((now - trail->timestamps[i]) / WAKE_TRAIL_MAX_AGE);
outAges[i] = age < 0.0f ? 0.0f : (age > 1.0f ? 1.0f : age);
}
}
static Rectangle ComputeWakeBoundingBox(WakeTrail *trail) {
float minX = trail->points[0].x, maxX = trail->points[0].x;
float minY = trail->points[0].y, maxY = trail->points[0].y;
for (int i = 1; i < trail->count; i++) {
if (trail->points[i].x < minX)
minX = trail->points[i].x;
if (trail->points[i].x > maxX)
maxX = trail->points[i].x;
if (trail->points[i].y < minY)
minY = trail->points[i].y;
if (trail->points[i].y > maxY)
maxY = trail->points[i].y;
}
float margin = 10.0f;
return (Rectangle){minX - margin, minY - margin, (maxX - minX) + margin * 2.0f,
(maxY - minY) + margin * 2.0f};
}
void RenderShipWake(Wake *wake, WakeTrail *trail, float speed) {
if (trail->count < 2)
return;
float t = (fabsf(speed) - WAKE_MIN_SPEED) / (WAKE_MAX_SPEED - WAKE_MIN_SPEED);
if (t < 0.0f)
t = 0.0f;
if (t > 1.0f)
t = 1.0f;
float ages[WAKE_TRAIL_POINTS];
ComputeWakeAges(trail, GetTime(), ages);
int pointsLoc = GetShaderLocation(wake->shader, "trailPoints");
int agesLoc = GetShaderLocation(wake->shader, "trailAges");
int countLoc = GetShaderLocation(wake->shader, "trailCount");
int intensityLoc = GetShaderLocation(wake->shader, "wakeIntensity");
SetShaderValueV(wake->shader, pointsLoc, trail->points, SHADER_UNIFORM_VEC2, WAKE_TRAIL_POINTS);
SetShaderValueV(wake->shader, agesLoc, ages, SHADER_UNIFORM_FLOAT, WAKE_TRAIL_POINTS);
SetShaderValue(wake->shader, countLoc, &trail->count, SHADER_UNIFORM_INT);
SetShaderValue(wake->shader, intensityLoc, &t, SHADER_UNIFORM_FLOAT);
Rectangle box = ComputeWakeBoundingBox(trail);
BeginShaderMode(wake->shader);
DrawRectangleRec(box, WHITE);
EndShaderMode();
}
void UnloadWake(Wake *wake) {
UnloadShader(wake->shader);
}
+28
View File
@@ -0,0 +1,28 @@
#include "appCore.h"
#include <raylib.h>
typedef struct {
int keys[2];
} ActionBinding;
static const ActionBinding actionBindings[ACTION_COUNT] = {
[ACTION_FORWARD] = {{KEY_W, KEY_UP}},
[ACTION_BACKWARD] = {{KEY_S, KEY_DOWN}},
[ACTION_TURN_LEFT] = {{KEY_A, KEY_LEFT}},
[ACTION_TURN_RIGHT] = {{KEY_D, KEY_RIGHT}},
[ACTION_FIRE_PORT] = {{KEY_Q, KEY_NULL}},
[ACTION_FIRE_STARBOARD] = {{KEY_E, KEY_NULL}},
[ACTION_BACK] = {{KEY_ESCAPE, KEY_NULL}},
};
bool IsGameActionPressed(GameAction action) {
const ActionBinding *binding = &actionBindings[action];
return IsKeyPressed(binding->keys[0]) ||
(binding->keys[1] != KEY_NULL && IsKeyPressed(binding->keys[1]));
}
bool IsGameActionDown(GameAction action) {
const ActionBinding *binding = &actionBindings[action];
return IsKeyDown(binding->keys[0]) ||
(binding->keys[1] != KEY_NULL && IsKeyDown(binding->keys[1]));
}
+3 -2
View File
@@ -1,4 +1,5 @@
#include "app.h" #include "logo.h"
#include <raylib.h>
void InitLogo(LogoState *l) { void InitLogo(LogoState *l) {
SetTargetFPS(60); SetTargetFPS(60);
@@ -16,7 +17,7 @@ void InitLogo(LogoState *l) {
void UpdateLogo(LogoState *l, View *currentView, bool *running) { void UpdateLogo(LogoState *l, View *currentView, bool *running) {
if (WindowShouldClose() && !IsKeyPressed(KEY_ESCAPE)) { if (WindowShouldClose() && !IsGameActionPressed(ACTION_BACK)) {
*running = false; *running = false;
} }
+19 -26
View File
@@ -1,49 +1,42 @@
#include "app.h" #include "menu.h"
void InitMenu(MenuState *m) void InitMenu(MenuState *m) {
{
m->menuView = MAIN; m->menuView = MAIN;
InitMenuSettings(&m->menuSettings); InitMenuSettings(&m->menuSettings);
InitMenuMain(&m->menuMain); InitMenuMain(&m->menuMain);
} }
void UpdateMenu(MenuState *m, State *state, Flags *flags) void UpdateMenu(MenuState *m, State *state, Flags *flags) {
{ switch (m->menuView) {
switch (m->menuView)
{
case MAIN: case MAIN:
{
UpdateMenuMain(&m->menuMain, state, flags, &m->menuView); UpdateMenuMain(&m->menuMain, state, flags, &m->menuView);
}break; break;
case SETTINGS: case SETTINGS:
{
UpdateMenuSettings(&m->menuSettings, &m->menuView, state, flags); UpdateMenuSettings(&m->menuSettings, &m->menuView, state, flags);
}break; break;
default: break;
}
default:
break;
}
} }
void RenderMenu(MenuState *m, Font *font, const char *title, Flags *flags) void RenderMenu(MenuState *m, Font *font, const char *title, Flags *flags, State *state) {
{ switch (m->menuView) {
switch (m->menuView)
{
case MAIN: case MAIN:
{ RenderMenuMain(&m->menuMain, font, title, flags, state->gameStarted);
RenderMenuMain(&m->menuMain, font, title, flags); break;
}break;
case SETTINGS: case SETTINGS:
{
RenderMenuSettings(&m->menuSettings, flags); RenderMenuSettings(&m->menuSettings, flags);
} break;
default: break;
default:
break;
} }
} }
void CleanupMenu(MenuState *m) void CleanupMenu(MenuState *m) {
{
CleanupMenuMain(&m->menuMain); CleanupMenuMain(&m->menuMain);
CleanupMenuSettings(&m->menuSettings);
} }
+94 -132
View File
@@ -1,166 +1,140 @@
#include "app.h" #include <raylib.h>
#include "engineAssets.h"
#include "menu.h"
const TextureAsset menuTextureAssets[MENU_TEX_COUNT] = { const TextureAsset menuTextureAssets[MENU_TEX_COUNT] = {
{"assets/mouse.png", 16.0f, 16.0f, 0.0f}, {"assets/mouse.png", 16.0f, 16.0f, 0.0f},
{"assets/buttons.png", 288.0f/3, 96.0f/3, 0.0f}, {"assets/buttons.png", 288.0f / 3, 96.0f / 3, 0.0f},
{"assets/menu_board.png", 128.0f, 144.0f, 0.0f}, {"assets/menu_board.png", 128.0f, 144.0f, 0.0f},
{"assets/cloud_background.png", 576.0f, 324.0f, 0.0f}, {"assets/cloud_background.png", 576.0f, 324.0f, 0.0f},
{"assets/cloud_midground.png", 576.0f, 324.0f, 0.0f}, {"assets/cloud_midground.png", 576.0f, 324.0f, 0.0f},
{"assets/cloud_foreground.png", 576.0f, 324.0f, 0.0f}}; {"assets/cloud_foreground.png", 576.0f, 324.0f, 0.0f}};
const SoundtrackAsset menuSoundtrackAssets[MENU_SOUNDTRACK_COUNT] = {{"assets/theme.mp3"}};
const SoundtrackAsset menuSoundtrackAssets[MENU_SOUNDTRACK_COUNT] = { const SoundAsset menuSoundAssets[MENU_SOUND_COUNT] = {{"assets/button_hover.wav"},
{"assets/theme.mp3"}
};
const SoundAsset menuSoundAssets[MENU_SOUND_COUNT] = {
{"assets/button_hover.wav"},
{"assets/button_click.wav"}}; {"assets/button_click.wav"}};
static void InitMenuMainAssets(MenuMain *m) {
void InitMenuMain(MenuMain *m){ m->assets = (Assets){.textureAssets = menuTextureAssets,
m->assets = (Assets){
.textureAssets = menuTextureAssets,
.soundAssets = menuSoundAssets, .soundAssets = menuSoundAssets,
.soundtrackAssets = menuSoundtrackAssets}; .soundtrackAssets = menuSoundtrackAssets};
for (int i = 0; i < MENU_TEX_COUNT; i++) for (int i = 0; i < MENU_TEX_COUNT; i++) {
{ m->textures[i] = LoadTextureCached(m->assets.textureAssets[i].path);
m->textures[i] = LoadTexture(m->assets.textureAssets[i].path);
} }
for (int i = 0; i < MENU_SOUND_COUNT; i++) for (int i = 0; i < MENU_SOUND_COUNT; i++) {
{ m->sounds[i] = LoadSoundCached(m->assets.soundAssets[i].path);
m->sounds[i] = LoadSound(m->assets.soundAssets[i].path);
} }
for (int i = 0; i < MENU_SOUNDTRACK_COUNT; i++) for (int i = 0; i < MENU_SOUNDTRACK_COUNT; i++) {
{
m->soundtracks[i] = LoadSoundtrack(m->assets.soundtrackAssets[i].path); m->soundtracks[i] = LoadSoundtrack(m->assets.soundtrackAssets[i].path);
} }
}
InitBackground(0.1f, &m->background, &m->textures[MENU_CLOUD_BACKGROUND], &m->assets.textureAssets[MENU_CLOUD_BACKGROUND]); static void InitMenuMainBackgrounds(MenuMain *m) {
InitBackground(0.2f, &m->midground, &m->textures[MENU_CLOUD_MIDGROUND], &m->assets.textureAssets[MENU_CLOUD_MIDGROUND]); InitBackground(0.1f, &m->background, &m->textures[MENU_CLOUD_BACKGROUND],
InitBackground(0.3f, &m->foreground, &m->textures[MENU_CLOUD_FOREGROUND], &m->assets.textureAssets[MENU_CLOUD_FOREGROUND]); &m->assets.textureAssets[MENU_CLOUD_BACKGROUND]);
InitBackground(0.2f, &m->midground, &m->textures[MENU_CLOUD_MIDGROUND],
&m->assets.textureAssets[MENU_CLOUD_MIDGROUND]);
InitBackground(0.3f, &m->foreground, &m->textures[MENU_CLOUD_FOREGROUND],
&m->assets.textureAssets[MENU_CLOUD_FOREGROUND]);
}
m->mouse = CreateMouse(0.10f, 5.0f, 10.0f, &m->textures[MENU_CURSOR_TEX]); static void InitMenuMainBoard(MenuMain *m, Vector2 windowCenter) {
int boardWidth = GetScreenWidth() / m->menuScale * 2.5f;
m->menuScale = 4;
int windowWidth = GetScreenWidth();
int windowHeight = GetScreenHeight();
Vector2 windowCenter = {windowWidth / 2, windowHeight / 2};
int boardWidth = windowWidth / m->menuScale * 2.5f;
float boardScaleFactor = (float)boardWidth / m->assets.textureAssets[MENU_BOARD_TEX].frameWidth; float boardScaleFactor = (float)boardWidth / m->assets.textureAssets[MENU_BOARD_TEX].frameWidth;
int boardHeight = (int)(m->assets.textureAssets[MENU_BOARD_TEX].frameHeight * boardScaleFactor); int boardHeight = (int)(m->assets.textureAssets[MENU_BOARD_TEX].frameHeight * boardScaleFactor);
Vector2 boardSource = {128.0f, 0.0f}; Vector2 boardSource = {128.0f, 0.0f};
Rectangle boardDestRec = { Rectangle boardDestRec = {.x = windowCenter.x - (boardWidth / 2),
.x = windowCenter.x - (boardWidth / 2),
.y = windowCenter.y - (boardHeight / 2), .y = windowCenter.y - (boardHeight / 2),
.width = boardWidth, .width = boardWidth,
.height = boardHeight}; .height = boardHeight};
m->board = CreateImageElement(&m->textures[MENU_BOARD_TEX], &m->assets.textureAssets[MENU_BOARD_TEX], &boardSource, &boardDestRec); m->board =
CreateImageElement(&m->textures[MENU_BOARD_TEX], &m->assets.textureAssets[MENU_BOARD_TEX],
int buttonWidth = windowWidth / m->menuScale; &boardSource, &boardDestRec);
float buttonScaleFactor = (float)buttonWidth / m->assets.textureAssets[MENU_BIG_BUTTONS_TEX].frameWidth;
int buttonHeight = (int)(m->assets.textureAssets[MENU_BIG_BUTTONS_TEX].frameHeight * buttonScaleFactor);
int buttonSpacing = buttonHeight;
//Button texture dimensions
float menuButtonWidth = m->assets.textureAssets[MENU_BIG_BUTTONS_TEX].frameWidth;
float menuButtonHeight = m->assets.textureAssets[MENU_BIG_BUTTONS_TEX].frameHeight;
//Quit Button
Vector2 quitButtonNotPressed = {0.0f, 0.0f};
Vector2 quitButtonHover = {menuButtonWidth, 0.0f};
Vector2 quitButtonPressed = {menuButtonWidth * 2, 0.0f};
Rectangle quitButtonDestRec = {
.x = windowCenter.x - (buttonWidth / 2),
.y = windowCenter.y - (buttonHeight / 2) + buttonSpacing,
.width = buttonWidth,
.height = buttonHeight
};
m->quitButton = CreateImageButton(NOT_CLICKED, &m->textures[MENU_BIG_BUTTONS_TEX], &m->assets.textureAssets[MENU_BIG_BUTTONS_TEX], &quitButtonNotPressed, &quitButtonPressed, &quitButtonHover, &quitButtonDestRec);
//Play Button
Vector2 playButtonNotPressed = {0.0f, menuButtonHeight};
Vector2 playButtonHover = {menuButtonWidth, menuButtonHeight};
Vector2 playButtonPressed = {menuButtonWidth * 2, menuButtonHeight};
Rectangle playButtonDestRec = {
.x = windowCenter.x - (buttonWidth / 2),
.y = windowCenter.y - (buttonHeight / 2),
.width = buttonWidth,
.height = buttonHeight
};
m->playButton = CreateImageButton(NOT_CLICKED, &m->textures[MENU_BIG_BUTTONS_TEX], &m->assets.textureAssets[MENU_BIG_BUTTONS_TEX], &playButtonNotPressed, &playButtonPressed, &playButtonHover, &playButtonDestRec);
//Settings Button
Vector2 settingsButtonNotPressed = {0.0f, menuButtonHeight * 2};
Vector2 settingsButtonHover = {menuButtonWidth, menuButtonHeight * 2};
Vector2 settingsButtonPressed = {menuButtonWidth * 2, menuButtonHeight * 2};
Rectangle settingsButtonDestRec = {
.x = windowCenter.x - (buttonWidth / 2),
.y = windowCenter.y - (buttonHeight / 2) + buttonSpacing * 2,
.width = buttonWidth,
.height = buttonHeight
};
m->settingsButton = CreateImageButton(NOT_CLICKED, &m->textures[MENU_BIG_BUTTONS_TEX], &m->assets.textureAssets[MENU_BIG_BUTTONS_TEX], &settingsButtonNotPressed, &settingsButtonPressed, &settingsButtonHover, &settingsButtonDestRec);
} }
void UpdateMenuMain(MenuMain *m, State *state, Flags *flags, MenuView *menuView){ static void InitMenuMainButtons(MenuMain *m, Vector2 windowCenter) {
for (int i = 0; i < MENU_SOUNDTRACK_COUNT; i++) int buttonWidth = GetScreenWidth() / m->menuScale;
{ float buttonScaleFactor =
(float)buttonWidth / m->assets.textureAssets[MENU_BIG_BUTTONS_TEX].frameWidth;
int buttonHeight =
(int)(m->assets.textureAssets[MENU_BIG_BUTTONS_TEX].frameHeight * buttonScaleFactor);
int buttonSpacing = buttonHeight;
m->quitButton = CreateMenuButton(&m->textures[MENU_BIG_BUTTONS_TEX],
&m->assets.textureAssets[MENU_BIG_BUTTONS_TEX], 0, 1,
buttonWidth, buttonHeight, windowCenter, buttonSpacing);
m->playButton = CreateMenuButton(&m->textures[MENU_BIG_BUTTONS_TEX],
&m->assets.textureAssets[MENU_BIG_BUTTONS_TEX], 1, 0,
buttonWidth, buttonHeight, windowCenter, buttonSpacing);
m->settingsButton = CreateMenuButton(&m->textures[MENU_BIG_BUTTONS_TEX],
&m->assets.textureAssets[MENU_BIG_BUTTONS_TEX], 2, 2,
buttonWidth, buttonHeight, windowCenter, buttonSpacing);
Vector2 restartDimensions = {buttonWidth * 0.8f, buttonHeight * 0.4f};
float settingsBottom =
m->settingsButton.sprite.destRec.y + m->settingsButton.sprite.destRec.height;
Vector2 restartCenter = {windowCenter.x, settingsBottom + restartDimensions.y / 2.0f + 16.0f};
m->restartButton = initButton("Restart", restartDimensions, restartCenter);
}
void InitMenuMain(MenuMain *m) {
InitMenuMainAssets(m);
InitMenuMainBackgrounds(m);
m->mouse = CreateMouse(0.10f, 5.0f, 10.0f, &m->textures[MENU_CURSOR_TEX]);
m->menuScale = 4;
Vector2 windowCenter = {GetScreenWidth() / 2, GetScreenHeight() / 2};
InitMenuMainBoard(m, windowCenter);
InitMenuMainButtons(m, windowCenter);
}
void UpdateMenuMain(MenuMain *m, State *state, Flags *flags, MenuView *menuView) {
for (int i = 0; i < MENU_SOUNDTRACK_COUNT; i++) {
UpdateSoundtrack(&m->soundtracks[i], flags->soundtrackOn); UpdateSoundtrack(&m->soundtracks[i], flags->soundtrackOn);
} }
if (GetFPS() >= 60) if (GetFPS() >= 60) {
{
SetTargetFPS(60); SetTargetFPS(60);
} }
if (isImageButtonClicked(&m->playButton, m->sounds[HOVER_SOUND], m->sounds[CLICK_SOUND])) if (isImageButtonClicked(&m->playButton, m->sounds[HOVER_SOUND], m->sounds[CLICK_SOUND])) {
{
state->running = true; state->running = true;
state->currentView = GAMEPLAY; state->currentView = GAMEPLAY;
} }
if (IsKeyPressed(KEY_ESCAPE) && state->gameStarted) if (IsGameActionPressed(ACTION_BACK) && state->gameStarted) {
{
state->currentView = GAMEPLAY; state->currentView = GAMEPLAY;
} }
if (isImageButtonClicked(&m->settingsButton, m->sounds[HOVER_SOUND], m->sounds[CLICK_SOUND])) if (state->gameStarted && isButtonClicked(&m->restartButton)) {
{ state->restartRequested = true;
state->currentView = GAMEPLAY;
}
if (isImageButtonClicked(&m->settingsButton, m->sounds[HOVER_SOUND], m->sounds[CLICK_SOUND])) {
*menuView = SETTINGS; *menuView = SETTINGS;
} }
if (isImageButtonClicked(&m->quitButton, m->sounds[HOVER_SOUND], m->sounds[CLICK_SOUND]) || (WindowShouldClose() && !IsKeyPressed(KEY_ESCAPE))) if (isImageButtonClicked(&m->quitButton, m->sounds[HOVER_SOUND], m->sounds[CLICK_SOUND]) ||
{ (WindowShouldClose() && !IsGameActionPressed(ACTION_BACK))) {
state->running = false; state->running = false;
} }
UpdateBackground(&m->background); UpdateBackground(&m->background);
UpdateBackground(&m->midground); UpdateBackground(&m->midground);
UpdateBackground(&m->foreground); UpdateBackground(&m->foreground);
UpdateMouseScreen(&m->mouse); UpdateMouseScreen(&m->mouse);
} }
void RenderMenuMain(MenuMain *m, Font *font, const char *title, Flags *flags){ void RenderMenuMain(MenuMain *m, Font *font, const char *title, Flags *flags, bool gameStarted) {
BeginDrawing(); BeginDrawing();
ClearBackground(WHITE); ClearBackground(WHITE);
RenderBackground(&m->background); RenderBackground(&m->background);
@@ -170,40 +144,28 @@ void RenderMenuMain(MenuMain *m, Font *font, const char *title, Flags *flags){
renderImageButton(&m->playButton); renderImageButton(&m->playButton);
renderImageButton(&m->quitButton); renderImageButton(&m->quitButton);
renderImageButton(&m->settingsButton); renderImageButton(&m->settingsButton);
int textWidth = MeasureText(title, font->baseSize); if (gameStarted) {
DrawTextEx(*font, title, (Vector2){GetScreenWidth() / 2 - textWidth / 2, textWidth / 4}, (float)font->baseSize, 2, WHITE); renderButton(&m->restartButton);
DrawTexturePro( }
m->mouse.cursorTexture,
(Rectangle){0, 0, m->mouse.cursorTexture.width, m->mouse.cursorTexture.height},
(Rectangle){m->mouse.screenPosition.x, m->mouse.screenPosition.y, m->mouse.cursorTexture.width, m->mouse.cursorTexture.height},
(Vector2){0.0f, 0.0f},
0.0f,
WHITE);
if (flags->showFPS) int textWidth = MeasureText(title, font->baseSize);
{ DrawTextEx(*font, title, (Vector2){GetScreenWidth() / 2 - textWidth / 2, textWidth / 4},
(float)font->baseSize, 2, WHITE);
DrawTexturePro(m->mouse.cursorTexture,
(Rectangle){0, 0, m->mouse.cursorTexture.width, m->mouse.cursorTexture.height},
(Rectangle){m->mouse.screenPosition.x, m->mouse.screenPosition.y,
m->mouse.cursorTexture.width, m->mouse.cursorTexture.height},
(Vector2){0.0f, 0.0f}, 0.0f, WHITE);
if (flags->showFPS) {
DrawFPS(10, 10); DrawFPS(10, 10);
} }
EndDrawing(); EndDrawing();
} }
void CleanupMenuMain(MenuMain *m){ void CleanupMenuMain(MenuMain *m) {
for (int i = 0; i < MENU_SOUND_COUNT; i++) for (int i = 0; i < MENU_SOUNDTRACK_COUNT; i++) {
{
UnloadSound(m->sounds[i]);
}
for (int i = 0; i < MENU_TEX_COUNT; i++)
{
if (m->textures[i].id > 0)
{
UnloadTexture(m->textures[i]);
}
}
for (int i = 0; i < MENU_SOUNDTRACK_COUNT; i++)
{
UnloadMusicStream(m->soundtracks[i].music); UnloadMusicStream(m->soundtracks[i].music);
} }
} }
+25 -38
View File
@@ -1,58 +1,45 @@
#include "app.h" #include <raylib.h>
void InitBackground(float scrollSpeed, Background *bg, Texture2D *texture, const TextureAsset *backgroundAsset){ #include "menu.h"
void InitBackground(float scrollSpeed, Background *bg, Texture2D *texture,
const TextureAsset *backgroundAsset) {
bg->scrolling = 0.0f; bg->scrolling = 0.0f;
bg->scrollSpeed = scrollSpeed; bg->scrollSpeed = scrollSpeed;
bg->scale = 1.0f; bg->scale = 1.0f;
bg->sprite = (Sprite){ bg->sprite = (Sprite){
.texture = *texture, .texture = *texture,
.sourceRec = (Rectangle){0.0f,0.0f,backgroundAsset->frameWidth, backgroundAsset->frameHeight}, .sourceRec =
.destRec = (Rectangle){0.0f,0.0f,(float)GetScreenWidth(),(float)GetScreenHeight()}, (Rectangle){0.0f, 0.0f, backgroundAsset->frameWidth, backgroundAsset->frameHeight},
.origin = (Vector2){0.0f,0.0f}, .destRec = (Rectangle){0.0f, 0.0f, (float)GetScreenWidth(), (float)GetScreenHeight()},
.origin = (Vector2){0.0f, 0.0f},
.rotation = 0.0f, .rotation = 0.0f,
.color = WHITE .color = WHITE};
};
} }
void UpdateBackground(Background *bg){ void UpdateBackground(Background *bg) {
bg->scrolling -= bg->scrollSpeed; bg->scrolling -= bg->scrollSpeed;
if (bg->scrolling <= -bg->sprite.texture.width * bg->scale) { if (bg->scrolling <= -bg->sprite.texture.width * bg->scale) {
bg->scrolling = 0; bg->scrolling = 0;
} }
bg->pos = (Vector2){ bg->scrolling, 0.0f }; bg->pos = (Vector2){bg->scrolling, 0.0f};
UpdateSpriteDestRec(&bg->sprite, &bg->pos); UpdateSpriteDestRec(&bg->sprite, &bg->pos);
} }
void RenderBackground(Background *bg){ static void RenderBackgroundTile(Background *bg, float tileOffset) {
Sprite tile = bg->sprite;
Sprite temp0 = bg->sprite; float x = bg->sprite.destRec.x + bg->sprite.texture.width * bg->scale * tileOffset;
float newX0 = bg->sprite.destRec.x + bg->sprite.texture.width * bg->scale * 2; Vector2 pos = {x, bg->sprite.destRec.y};
Vector2 newPos0 = {newX0, bg->sprite.destRec.y}; UpdateSpriteDestRec(&tile, &pos);
UpdateSpriteDestRec(&temp0, &newPos0); RenderSprite(&tile);
RenderSprite(&temp0);
Sprite temp = bg->sprite;
float newX = bg->sprite.destRec.x + bg->sprite.texture.width * bg->scale;
Vector2 newPos = {newX, bg->sprite.destRec.y};
UpdateSpriteDestRec(&temp, &newPos);
RenderSprite(&temp);
RenderSprite(&bg->sprite);
Sprite temp2 = bg->sprite;
float newX2 = bg->sprite.destRec.x - bg->sprite.texture.width * bg->scale;
Vector2 newPos2 = {newX2, bg->sprite.destRec.y};
UpdateSpriteDestRec(&temp2, &newPos2);
RenderSprite(&temp2);
Sprite temp3 = bg->sprite;
float newX3 = bg->sprite.destRec.x - bg->sprite.texture.width * bg->scale * 2;
Vector2 newPos3 = {newX3, bg->sprite.destRec.y};
UpdateSpriteDestRec(&temp3, &newPos3);
RenderSprite(&temp3);
} }
void RenderBackground(Background *bg) {
RenderBackgroundTile(bg, 2.0f);
RenderBackgroundTile(bg, 1.0f);
RenderSprite(&bg->sprite);
RenderBackgroundTile(bg, -1.0f);
RenderBackgroundTile(bg, -2.0f);
}
+20 -26
View File
@@ -1,51 +1,47 @@
#include "app.h" #include <raylib.h>
void InitMenuSettings(MenuSettings *s) #include "menu.h"
{
int windowWidth = GetScreenWidth(); void InitMenuSettings(MenuSettings *s) {
int windowHeight = GetScreenHeight(); Vector2 windowCenter = {GetScreenWidth() / 2, GetScreenHeight() / 2};
Vector2 windowCenter = {windowWidth / 2, windowHeight / 2};
Vector2 standardButtonDimensions = {100, 50}; Vector2 standardButtonDimensions = {100, 50};
int standardSpacing = standardButtonDimensions.y / 2; int standardSpacing = standardButtonDimensions.y / 2;
Vector2 button1Pos = {windowCenter.x, windowCenter.y}; Vector2 button1Pos = {windowCenter.x, windowCenter.y};
Vector2 button2Pos = {windowCenter.x, windowCenter.y + standardButtonDimensions.y * 1 + standardSpacing * 1}; Vector2 button2Pos = {windowCenter.x,
Vector2 button3Pos = {windowCenter.x, windowCenter.y + standardButtonDimensions.y * 2 + standardSpacing * 2}; windowCenter.y + standardButtonDimensions.y * 1 + standardSpacing * 1};
Vector2 button3Pos = {windowCenter.x,
windowCenter.y + standardButtonDimensions.y * 2 + standardSpacing * 2};
s->showFPS = initButton("showFPS", standardButtonDimensions, button1Pos); s->showFPS = initButton("showFPS", standardButtonDimensions, button1Pos);
s->limitFPS = initButton("limitFPS", standardButtonDimensions, button2Pos); s->limitFPS = initButton("limitFPS", standardButtonDimensions, button2Pos);
s->soundtrackOn = initButton("soundtrackOn", standardButtonDimensions, button3Pos); s->soundtrackOn = initButton("soundtrackOn", standardButtonDimensions, button3Pos);
} }
void UpdateMenuSettings(MenuSettings *s, MenuView *menuView, State* state, Flags *flags) void UpdateMenuSettings(MenuSettings *s, MenuView *menuView, State *state, Flags *flags) {
{ if (isButtonClicked(&s->showFPS)) {
if(isButtonClicked(&s->showFPS)){
flags->showFPS = !flags->showFPS; flags->showFPS = !flags->showFPS;
} }
if(isButtonClicked(&s->limitFPS)){ if (isButtonClicked(&s->limitFPS)) {
flags->limitFPS = !flags->limitFPS; flags->limitFPS = !flags->limitFPS;
} }
if(isButtonClicked(&s->soundtrackOn)){ if (isButtonClicked(&s->soundtrackOn)) {
flags->soundtrackOn = !flags->soundtrackOn; flags->soundtrackOn = !flags->soundtrackOn;
} }
if (IsKeyPressed(KEY_ESCAPE)){ if (IsGameActionPressed(ACTION_BACK)) {
*menuView = MAIN; *menuView = MAIN;
} }
if (WindowShouldClose() && !IsKeyPressed(KEY_ESCAPE)){ if (WindowShouldClose() && !IsGameActionPressed(ACTION_BACK)) {
state->running = false; state->running = false;
} }
} }
void RenderMenuSettings(MenuSettings *s, Flags *flags) void RenderMenuSettings(MenuSettings *s, Flags *flags) {
{
BeginDrawing(); BeginDrawing();
ClearBackground(BLACK); ClearBackground(BLACK);
@@ -53,15 +49,13 @@ void RenderMenuSettings(MenuSettings *s, Flags *flags)
renderButton(&s->limitFPS); renderButton(&s->limitFPS);
renderButton(&s->soundtrackOn); renderButton(&s->soundtrackOn);
if (flags->showFPS){ if (flags->showFPS) {
DrawFPS(10,10); DrawFPS(10, 10);
} }
EndDrawing(); EndDrawing();
} }
void ClenupMenuSettings(MenuSettings *s){ void CleanupMenuSettings(MenuSettings *s) {
(void)s;
} }
+10 -2
View File
@@ -2,7 +2,7 @@
void OnEscapeChangeView(App *app, View view) void OnEscapeChangeView(App *app, View view)
{ {
if (IsKeyPressed(KEY_ESCAPE)) if (IsGameActionPressed(ACTION_BACK))
{ {
app->state.currentView = view; app->state.currentView = view;
} }
@@ -10,8 +10,16 @@ void OnEscapeChangeView(App *app, View view)
void OnWindowClosedStoppApp(App *app) void OnWindowClosedStoppApp(App *app)
{ {
if (WindowShouldClose() && !IsKeyPressed(KEY_ESCAPE)) if (WindowShouldClose() && !IsGameActionPressed(ACTION_BACK))
{ {
app->state.running = false; app->state.running = false;
} }
} }
void OnBoolChangeView(bool flag, App *app, View view)
{
if (flag)
{
app->state.currentView = view;
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
#include "engine.h" #include "engineAnimation.h"
void UpdateAnimation(Animation *animation, float deltaTime) { void UpdateAnimation(Animation *animation, float deltaTime) {
animation->animTimer += deltaTime; animation->animTimer += deltaTime;
+2 -1
View File
@@ -1,4 +1,5 @@
#include "engine.h" #include "engineArray.h"
#include <stdlib.h>
Array* CreateArray(unsigned int initCapacity) Array* CreateArray(unsigned int initCapacity)
{ {
+78
View File
@@ -0,0 +1,78 @@
#include "engineAssets.h"
#include <raylib.h>
#include <string.h>
#define ASSET_CACHE_CAPACITY 64
#define ASSET_PATH_MAX 128
typedef struct {
char path[ASSET_PATH_MAX];
Texture2D texture;
} CachedTexture;
typedef struct {
char path[ASSET_PATH_MAX];
Sound sound;
} CachedSound;
static CachedTexture textureCache[ASSET_CACHE_CAPACITY];
static int textureCacheCount = 0;
static CachedSound soundCache[ASSET_CACHE_CAPACITY];
static int soundCacheCount = 0;
Texture2D LoadTextureCached(const char *path) {
for (int i = 0; i < textureCacheCount; i++) {
if (strcmp(textureCache[i].path, path) == 0) {
return textureCache[i].texture;
}
}
Texture2D texture = LoadTexture(path);
if (textureCacheCount >= ASSET_CACHE_CAPACITY) {
TraceLog(LOG_WARNING, "ASSETS: Texture cache full, not caching %s", path);
return texture;
}
CachedTexture *entry = &textureCache[textureCacheCount++];
strncpy(entry->path, path, ASSET_PATH_MAX - 1);
entry->path[ASSET_PATH_MAX - 1] = '\0';
entry->texture = texture;
return texture;
}
Sound LoadSoundCached(const char *path) {
for (int i = 0; i < soundCacheCount; i++) {
if (strcmp(soundCache[i].path, path) == 0) {
return soundCache[i].sound;
}
}
Sound sound = LoadSound(path);
if (soundCacheCount >= ASSET_CACHE_CAPACITY) {
TraceLog(LOG_WARNING, "ASSETS: Sound cache full, not caching %s", path);
return sound;
}
CachedSound *entry = &soundCache[soundCacheCount++];
strncpy(entry->path, path, ASSET_PATH_MAX - 1);
entry->path[ASSET_PATH_MAX - 1] = '\0';
entry->sound = sound;
return sound;
}
void UnloadCachedAssets(void) {
for (int i = 0; i < textureCacheCount; i++) {
UnloadTexture(textureCache[i].texture);
}
textureCacheCount = 0;
for (int i = 0; i < soundCacheCount; i++) {
UnloadSound(soundCache[i].sound);
}
soundCacheCount = 0;
}
+10 -18
View File
@@ -1,13 +1,8 @@
#include "engine.h" #include "engineCamera.h"
#include <raymath.h>
Camera2D* GetCamera(void){ Rectangle GetCameraRectangle(Camera2D *camera)
static Camera2D camera;
return &camera;
}
Rectangle GetCameraRectangle(void)
{ {
Camera2D *camera = GetCamera();
float screenWidth = GetScreenWidth(); float screenWidth = GetScreenWidth();
float screenHeight = GetScreenHeight(); float screenHeight = GetScreenHeight();
@@ -38,11 +33,10 @@ Rectangle GetCameraRectangle(void)
}; };
} }
void CreateCamera(Vector2 playerPosition, float initZoom){ Camera2D CreateCamera(Vector2 playerPosition, float initZoom){
Camera2D* camera = GetCamera();
int windowWidth = GetScreenWidth(); int windowWidth = GetScreenWidth();
int windowHeight = GetScreenHeight(); int windowHeight = GetScreenHeight();
*camera = (Camera2D){ return (Camera2D){
.offset = (Vector2){windowWidth / 2.0f, windowHeight / 2.0f}, .offset = (Vector2){windowWidth / 2.0f, windowHeight / 2.0f},
.target = playerPosition, .target = playerPosition,
.rotation = 0.0f, .rotation = 0.0f,
@@ -50,19 +44,17 @@ void CreateCamera(Vector2 playerPosition, float initZoom){
}; };
} }
void UpdateCameraTarget(Vector2 *position, const float smoothness){ void UpdateCameraTarget(Camera2D *camera, Vector2 *position, const float smoothness){
Camera2D* camera = GetCamera();
camera->target = Vector2Lerp(camera->target, *position, smoothness * GetFrameTime()); camera->target = Vector2Lerp(camera->target, *position, smoothness * GetFrameTime());
} }
void UpdateCameraZoom(const float *scaleFactor, const float *minZoom, const float *maxZoom){ void UpdateCameraZoom(Camera2D *camera, const float *scaleFactor, const float *minZoom, const float *maxZoom){
Camera2D *camera = GetCamera();
float targetZoom = Clamp(camera->zoom * *scaleFactor, *minZoom, *maxZoom); float targetZoom = Clamp(camera->zoom * *scaleFactor, *minZoom, *maxZoom);
camera->zoom = Lerp(camera->zoom, targetZoom, 0.1f); camera->zoom = Lerp(camera->zoom, targetZoom, 0.1f);
} }
void UpdateCamera2D(Vector2* position, Mouse* mouse){ void UpdateCamera2D(Camera2D *camera, Vector2* position, Mouse* mouse){
UpdateCameraTarget(position, 5.0f); UpdateCameraTarget(camera, position, 5.0f);
UpdateCameraZoom(&mouse->scaleFactor, &mouse->minZoom, &mouse->maxZoom); UpdateCameraZoom(camera, &mouse->scaleFactor, &mouse->minZoom, &mouse->maxZoom);
} }
+19 -18
View File
@@ -1,56 +1,57 @@
#define RINI_IMPLEMENTATION #define RINI_IMPLEMENTATION
#define RINI_VALUE_DELIMITER '=' #define RINI_VALUE_DELIMITER '='
#include "engine.h" #include "engineConfig.h"
void InitConfig(Config *config, const char* path) { #include <stdio.h>
config->rini = rini_load_config(path);
void InitConfig(Config *config, const char *path) {
config->rini = rini_load(path);
if (config->rini.count == 0) { if (config->rini.count == 0) {
printf("Warning: Loaded config has no entries. Path: %s\n", config->path); printf("Warning: Loaded config has no entries. Path: %s\n", config->path);
} }
} }
int GetConfigInt(Config *config, const char *key) { int GetConfigInt(Config *config, const char *key) {
return rini_get_config_value_fallback(config->rini, key, 0); return rini_get_value_fallback(config->rini, key, 0);
} }
int GetConfigIntOrDefault(Config *config, const char *key, int default_value) { int GetConfigIntOrDefault(Config *config, const char *key, int default_value) {
return rini_get_config_value_fallback(config->rini, key, default_value); return rini_get_value_fallback(config->rini, key, default_value);
} }
const char* GetConfigString(Config *config, const char *key) { const char *GetConfigString(Config *config, const char *key) {
return rini_get_config_value_text(config->rini, key); return rini_get_value_text(config->rini, key);
} }
const char* GetConfigStringOrDefault(Config *config, const char *key, const char *default_value) { const char *GetConfigStringOrDefault(Config *config, const char *key, const char *default_value) {
return rini_get_config_value_text_fallback(config->rini, key, default_value); return rini_get_value_text_fallback(config->rini, key, default_value);
} }
const char* GetConfigDescription(Config *config, const char *key) { const char *GetConfigDescription(Config *config, const char *key) {
return rini_get_config_value_description(config->rini, key); return rini_get_value_description(config->rini, key);
} }
int SetConfigInt(Config *config, const char *key, int value, const char *desc) { int SetConfigInt(Config *config, const char *key, int value, const char *desc) {
return rini_set_config_value(&config->rini, key, value, desc); return rini_set_value(&config->rini, key, value, desc);
} }
int SetConfigString(Config *config, const char *key, const char *text, const char *desc) { int SetConfigString(Config *config, const char *key, const char *text, const char *desc) {
return rini_set_config_value_text(&config->rini, key, text, desc); return rini_set_value_text(&config->rini, key, text, desc);
} }
int SetConfigDescription(Config *config, const char *key, const char *desc) { int SetConfigDescription(Config *config, const char *key, const char *desc) {
return rini_set_config_value_description(&config->rini, key, desc); return rini_set_value_description(&config->rini, key, desc);
} }
int AddCommentLine(Config *config, const char *comment) { int AddCommentLine(Config *config, const char *comment) {
return rini_set_config_comment_line(&config->rini, comment); return rini_set_comment_line(&config->rini, comment);
} }
void SaveConfig(Config *config) { void SaveConfig(Config *config) {
rini_save_config(config->rini, config->path); rini_save(config->rini, config->path);
} }
void CleanUpConfig(Config *config) { void CleanUpConfig(Config *config) {
rini_unload_config(&config->rini); rini_unload(&config->rini);
} }
+3 -4
View File
@@ -1,11 +1,10 @@
#include "engine.h" #include "engineFont.h"
void InitFont(Font *font, const char* path){ void InitFont(Font *font, const char *path) {
*font = LoadFont(path); *font = LoadFont(path);
SetTextureFilter(font->texture, TEXTURE_FILTER_POINT); SetTextureFilter(font->texture, TEXTURE_FILTER_POINT);
} }
void CleanupFont(Font *font){ void CleanupFont(Font *font) {
UnloadFont(*font); UnloadFont(*font);
} }
-72
View File
@@ -1,72 +0,0 @@
#include "engine.h"
ImageButton CreateImageButton(ButtonState state, Texture2D *texture, const TextureAsset *asset, Vector2 *source, Vector2 *clicked, Vector2 *hover, Rectangle *destRec){
return (ImageButton){
.sprite = {
.texture = *texture,
.sourceRec = (Rectangle){ source->x, source->y, asset->frameWidth, asset->frameHeight},
.destRec = *destRec,
.frameSize = (Vector2){asset->frameWidth, asset->frameHeight},
.origin = (Vector2){0.0f, 0.0f},
.rotation = 0.0f,
.color = WHITE
},
.notClicked = (Vector2){source->x, source->y},
.hover = *hover,
.clicked = *clicked
};
}
ImageElement CreateImageElement(Texture2D *texture, const TextureAsset *asset, Vector2 *source, Rectangle *destRec){
return (ImageElement){
.sprite = {
.texture = *texture,
.sourceRec = (Rectangle){ source->x, source->y, asset->frameWidth, asset->frameHeight},
.destRec = *destRec,
.frameSize = (Vector2){asset->frameWidth, asset->frameHeight},
.origin = (Vector2){0.0f, 0.0f},
.rotation = 0.0f,
.color = WHITE
}
};
}
int isImageButtonClicked(ImageButton *b, Sound soundHover, Sound soundClick){
if (CheckCollisionPointRec(GetMousePosition(), b->sprite.destRec)) {
if (!b->isHovered) {
PlaySound(soundHover);
b->isHovered = true;
UpdateSpriteSourceRec(&b->sprite, &b->hover);
}
if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)){
if (!b->isClicked) {
PlaySound(soundClick);
b->isClicked = true;
UpdateSpriteSourceRec(&b->sprite, &b->clicked);
}
}
if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON))
{
b->isClicked = false;
b->isHovered = false;
UpdateSpriteSourceRec(&b->sprite, &b->notClicked);
return true;
}
} else {
UpdateSpriteSourceRec(&b->sprite, &b->notClicked);
b->isClicked = false;
b->isHovered = false;
}
return false;
}
void renderImageButton(ImageButton *b) {
RenderSprite(&b->sprite);
}
void renderImageElement(ImageElement *e){
RenderSprite(&e->sprite);
}
+92 -11
View File
@@ -1,23 +1,104 @@
#define RAYGUI_IMPLEMENTATION #define RAYGUI_IMPLEMENTATION
#include "engine.h" #include "engineGui.h"
Button initButton(char *text, Vector2 dimensions, Vector2 position) #include <raygui.h>
{
ImageButton CreateImageButton(Texture2D *texture, const TextureAsset *asset, Vector2 *source,
Vector2 *clicked, Vector2 *hover, Rectangle *destRec) {
return (ImageButton){.sprite = {.texture = *texture,
.sourceRec = (Rectangle){source->x, source->y, asset->frameWidth,
asset->frameHeight},
.destRec = *destRec,
.frameSize = (Vector2){asset->frameWidth, asset->frameHeight},
.origin = (Vector2){0.0f, 0.0f},
.rotation = 0.0f,
.color = WHITE},
.notClicked = (Vector2){source->x, source->y},
.hover = *hover,
.clicked = *clicked};
}
ImageElement CreateImageElement(Texture2D *texture, const TextureAsset *asset, Vector2 *source,
Rectangle *destRec) {
return (ImageElement){.sprite = {.texture = *texture,
.sourceRec = (Rectangle){source->x, source->y, asset->frameWidth,
asset->frameHeight},
.destRec = *destRec,
.frameSize = (Vector2){asset->frameWidth, asset->frameHeight},
.origin = (Vector2){0.0f, 0.0f},
.rotation = 0.0f,
.color = WHITE}};
}
int isImageButtonClicked(ImageButton *b, Sound soundHover, Sound soundClick) {
if (CheckCollisionPointRec(GetMousePosition(), b->sprite.destRec)) {
if (!b->isHovered) {
PlaySound(soundHover);
b->isHovered = true;
UpdateSpriteSourceRec(&b->sprite, &b->hover);
}
if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) {
if (!b->isClicked) {
PlaySound(soundClick);
b->isClicked = true;
UpdateSpriteSourceRec(&b->sprite, &b->clicked);
}
}
if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) {
b->isClicked = false;
b->isHovered = false;
UpdateSpriteSourceRec(&b->sprite, &b->notClicked);
return true;
}
} else {
UpdateSpriteSourceRec(&b->sprite, &b->notClicked);
b->isClicked = false;
b->isHovered = false;
}
return false;
}
ImageButton CreateMenuButton(Texture2D *texture, const TextureAsset *asset, int textureRow,
int screenRank, int buttonWidth, int buttonHeight,
Vector2 windowCenter, int buttonSpacing) {
float menuButtonWidth = asset->frameWidth;
float menuButtonHeight = asset->frameHeight;
Vector2 notClicked = {0.0f, menuButtonHeight * textureRow};
Vector2 hover = {menuButtonWidth, menuButtonHeight * textureRow};
Vector2 clicked = {menuButtonWidth * 2, menuButtonHeight * textureRow};
Rectangle destRec = {.x = windowCenter.x - (buttonWidth / 2),
.y = windowCenter.y - (buttonHeight / 2) + buttonSpacing * screenRank,
.width = buttonWidth,
.height = buttonHeight};
return CreateImageButton(texture, asset, &notClicked, &clicked, &hover, &destRec);
}
void renderImageButton(ImageButton *b) {
RenderSprite(&b->sprite);
}
void renderImageElement(ImageElement *e) {
RenderSprite(&e->sprite);
}
Button initButton(char *text, Vector2 dimensions, Vector2 position) {
Button b; Button b;
b.rec = (Rectangle){position.x - dimensions.x / 2, position.y - dimensions.y / 2, dimensions.x,
b.rec = (Rectangle){position.x - dimensions.x / 2, position.y - dimensions.y / 2, dimensions.x, dimensions.y}; dimensions.y};
b.text = text; b.text = text;
return b; return b;
} }
int isButtonClicked(Button *b) int isButtonClicked(Button *b) {
{ return (CheckCollisionPointRec(GetMousePosition(), b->rec) &&
return (CheckCollisionPointRec(GetMousePosition(), b->rec) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)); IsMouseButtonPressed(MOUSE_LEFT_BUTTON));
} }
void renderButton(Button *b) void renderButton(Button *b) {
{
GuiButton(b->rec, b->text); GuiButton(b->rec, b->text);
} }
+4 -3
View File
@@ -1,4 +1,6 @@
#include "engine.h" #include "engineInput.h"
#include <math.h>
#include <raymath.h>
Mouse CreateMouse(float zoomSpeed, float minZoom, float maxZoom, Texture2D *texture){ Mouse CreateMouse(float zoomSpeed, float minZoom, float maxZoom, Texture2D *texture){
HideCursor(); HideCursor();
@@ -15,8 +17,7 @@ void UpdateMouseScreen(Mouse *mouse){
} }
void UpdateMouse(Mouse *mouse) { void UpdateMouse(Mouse *mouse, Camera2D *camera) {
Camera2D* camera = GetCamera();
Vector2 pos = GetMousePosition(); Vector2 pos = GetMousePosition();
mouse->screenPosition = pos; mouse->screenPosition = pos;
mouse->worldPosition = GetScreenToWorld2D(pos, *camera); mouse->worldPosition = GetScreenToWorld2D(pos, *camera);
+7 -1
View File
@@ -1,4 +1,10 @@
#include "engine.h" #include "engineArray.h"
#include "enginePhysics.h"
#include "engineRectangle.h"
#include "engineVector.h"
#include <raymath.h>
#include <stdio.h>
#include <stdlib.h>
ObjectArray CreateObjectArray(const Object *prototype, const size_t count) { ObjectArray CreateObjectArray(const Object *prototype, const size_t count) {
ObjectArray objects = {0}; ObjectArray objects = {0};
+29 -23
View File
@@ -1,43 +1,49 @@
#include "engine.h" #include "enginePhysics.h"
Vector2 GetDirectionBetweenTwoVectors(Vector2 vec1, Vector2 vec2) #include <raymath.h>
{
Vector2 dir = {vec2.x - vec1.x, vec2.y - vec1.y};
dir = Vector2Normalize(dir);
return dir;
}
float GetDistanceBetweenTwoVectors(Vector2 vec1, Vector2 vec2) #include "engineVector.h"
{
float dx = vec1.x - vec2.x;
float dy = vec1.y - vec2.y;
return sqrtf(dx * dx + dy * dy);
}
void UpdatePhysicsDirection(Vector2 *direction, Vector2 vector) void UpdatePhysicsDirection(Vector2 *direction, Vector2 vector) {
{
*direction = Vector2Normalize(vector); *direction = Vector2Normalize(vector);
} }
void UpdatePhysicsVelocity(Vector2 *velocity, Vector2 *direction, float *speed) void UpdatePhysicsVelocity(Vector2 *velocity, Vector2 *direction, float *speed) {
{
*velocity = Vector2Scale(*direction, *speed * GetFrameTime()); *velocity = Vector2Scale(*direction, *speed * GetFrameTime());
} }
void UpdatePhysicsPosition(Vector2 *position, Vector2 *velocity) void UpdatePhysicsPosition(Vector2 *position, Vector2 *velocity) {
{
*position = Vector2Add(*position, *velocity); *position = Vector2Add(*position, *velocity);
} }
void UpdatePhysicsSpeed(float *speed, float *newSpeed) void UpdatePhysicsSpeed(float *speed, float *newSpeed) {
{
*speed = *newSpeed; *speed = *newSpeed;
} }
void UpdatePhysics(Physics *physics, Vector2 direction) void UpdatePhysics(Physics *physics, Vector2 direction) {
{
UpdatePhysicsDirection(&physics->direction, direction); UpdatePhysicsDirection(&physics->direction, direction);
UpdatePhysicsVelocity(&physics->velocity, &physics->direction, &physics->speed); UpdatePhysicsVelocity(&physics->velocity, &physics->direction, &physics->speed);
UpdatePhysicsPosition(&physics->position, &physics->velocity); UpdatePhysicsPosition(&physics->position, &physics->velocity);
} }
float GetActualSpeed(Physics *physics) {
return physics->speed;
}
CircleCollision CheckCircleCollision(Vector2 posA, float radiusA, Vector2 posB, float radiusB) {
CircleCollision result = {0};
result.overlapping = CheckCollisionCircles(posA, radiusA, posB, radiusB);
if (!result.overlapping) {
return result;
}
float dist = GetDistanceBetweenTwoVectors(posA, posB);
result.hasDirection = dist > 0.0f;
if (result.hasDirection) {
result.direction = GetDirectionBetweenTwoVectors(posB, posA);
result.overlap = (radiusA + radiusB) - dist;
}
return result;
}
+27 -10
View File
@@ -1,6 +1,8 @@
#include "engine.h" #include "enginePostfx.h"
PostProcessing CreatePostFX(const char* shaderPath){ #include <stddef.h>
PostProcessing CreatePostFX(const char *shaderPath) {
PostProcessing postFX = {0}; PostProcessing postFX = {0};
postFX.shader = LoadShader(NULL, shaderPath); postFX.shader = LoadShader(NULL, shaderPath);
@@ -10,27 +12,42 @@ PostProcessing CreatePostFX(const char* shaderPath){
} }
postFX.strength = 0.0f; postFX.strength = 0.0f;
postFX.time = 0.0f;
postFX.resolutionLoc = GetShaderLocation(postFX.shader, "resolution"); postFX.resolutionLoc = GetShaderLocation(postFX.shader, "resolution");
postFX.strengthLoc = GetShaderLocation(postFX.shader, "strength"); postFX.strengthLoc = GetShaderLocation(postFX.shader, "strength");
Vector2 resolution = { (float)GetScreenWidth(), (float)GetScreenHeight() }; postFX.timeLoc = GetShaderLocation(postFX.shader, "time");
Vector2 resolution = {(float)GetScreenWidth(), (float)GetScreenHeight()};
SetShaderValue(postFX.shader, postFX.resolutionLoc, &resolution, SHADER_UNIFORM_VEC2); SetShaderValue(postFX.shader, postFX.resolutionLoc, &resolution, SHADER_UNIFORM_VEC2);
return postFX; return postFX;
} }
void UpdatePostFX(PostProcessing *postFX){ void UpdatePostFX(PostProcessing *postFX) {
if (postFX->shader.id == 0) return; if (postFX->shader.id == 0)
return;
SetShaderValue(postFX->shader, postFX->strengthLoc, &postFX->strength, SHADER_UNIFORM_FLOAT); SetShaderValue(postFX->shader, postFX->strengthLoc, &postFX->strength, SHADER_UNIFORM_FLOAT);
Vector2 resolution = { (float)GetScreenWidth(), (float)GetScreenHeight() }; Vector2 resolution = {(float)GetScreenWidth(), (float)GetScreenHeight()};
SetShaderValue(postFX->shader, postFX->resolutionLoc, &resolution, SHADER_UNIFORM_VEC2); SetShaderValue(postFX->shader, postFX->resolutionLoc, &resolution, SHADER_UNIFORM_VEC2);
if (postFX->timeLoc != -1) {
postFX->time += GetFrameTime();
SetShaderValue(postFX->shader, postFX->timeLoc, &postFX->time, SHADER_UNIFORM_FLOAT);
}
} }
void RenderPostFX(PostProcessing *postFX, RenderTexture2D *target){ void UpdateShaders(PostProcessing *postFX, int count) {
for (int i = 0; i < count; i++) {
UpdatePostFX(&postFX[i]);
}
}
void RenderPostFX(PostProcessing *postFX, RenderTexture2D *target) {
BeginShaderMode(postFX->shader); BeginShaderMode(postFX->shader);
DrawTextureRec(target->texture, (Rectangle){ 0, 0, target->texture.width, -target->texture.height }, (Vector2){ 0, 0 }, WHITE); DrawTextureRec(target->texture, (Rectangle){0, 0, target->texture.width, -target->texture.height},
(Vector2){0, 0}, WHITE);
EndShaderMode(); EndShaderMode();
} }
void UnloadPostFX(PostProcessing *postFX){ void UnloadPostFX(PostProcessing *postFX) {
if (postFX->shader.id == 0) return; if (postFX->shader.id == 0)
return;
UnloadShader(postFX->shader); UnloadShader(postFX->shader);
} }
+9 -13
View File
@@ -1,36 +1,32 @@
#include "engine.h" #include "engineRectangle.h"
Vector2 GetPositionInsideRectangle(Rectangle rec) Vector2 GetPositionInsideRectangle(Rectangle rec) {
{
int randX = GetRandomValue(rec.x, rec.width); int randX = GetRandomValue(rec.x, rec.width);
int randY = GetRandomValue(rec.y, rec.height); int randY = GetRandomValue(rec.y, rec.height);
Vector2 randVector = {randX, randY}; return (Vector2){randX, randY};
return randVector;
} }
Vector2 GetPositionOutsideRectangle(Rectangle rec, int min, int max) Vector2 GetPositionOutsideRectangle(Rectangle rec, int min, int max) {
{
int side = GetRandomValue(0, 3); int side = GetRandomValue(0, 3);
Vector2 pos = {0}; Vector2 pos = {0};
switch (side) switch (side) {
{ case 0:
case 0: // left
pos.x = rec.x - GetRandomValue(min, max); pos.x = rec.x - GetRandomValue(min, max);
pos.y = GetRandomValue((int)rec.y, (int)(rec.y + rec.height)); pos.y = GetRandomValue((int)rec.y, (int)(rec.y + rec.height));
break; break;
case 1: // right case 1:
pos.x = rec.x + rec.width + GetRandomValue(min, max); pos.x = rec.x + rec.width + GetRandomValue(min, max);
pos.y = GetRandomValue((int)rec.y, (int)(rec.y + rec.height)); pos.y = GetRandomValue((int)rec.y, (int)(rec.y + rec.height));
break; break;
case 2: // top case 2:
pos.x = GetRandomValue((int)rec.x, (int)(rec.x + rec.width)); pos.x = GetRandomValue((int)rec.x, (int)(rec.x + rec.width));
pos.y = rec.y - GetRandomValue(min, max); pos.y = rec.y - GetRandomValue(min, max);
break; break;
case 3: // bottom case 3:
pos.x = GetRandomValue((int)rec.x, (int)(rec.x + rec.width)); pos.x = GetRandomValue((int)rec.x, (int)(rec.x + rec.width));
pos.y = rec.y + rec.height + GetRandomValue(min, max); pos.y = rec.y + rec.height + GetRandomValue(min, max);
break; break;
+1 -1
View File
@@ -1,4 +1,4 @@
#include "engine.h" #include "engineSoundtrack.h"
Soundtrack LoadSoundtrack(const char* musicPath){ Soundtrack LoadSoundtrack(const char* musicPath){
Soundtrack soundtrack = { Soundtrack soundtrack = {
+30 -27
View File
@@ -1,42 +1,45 @@
#include "engine.h" #include "engineSprite.h"
#include <math.h>
void UpdateSpriteSourceRec(Sprite *sprite, const Vector2 *vector) void UpdateSpriteSourceRec(Sprite *sprite, const Vector2 *vector) {
{
sprite->sourceRec.x = vector->x; sprite->sourceRec.x = vector->x;
sprite->sourceRec.y = vector->y; sprite->sourceRec.y = vector->y;
} }
void UpdateSpriteDestRec(Sprite *sprite, const Vector2 *vector) void UpdateSpriteDestRec(Sprite *sprite, const Vector2 *vector) {
{
sprite->destRec.x = vector->x; sprite->destRec.x = vector->x;
sprite->destRec.y = vector->y; sprite->destRec.y = vector->y;
} }
void UpdateSpriteRotation(Sprite *sprite, float rotation) static float ShortestAngleDifference(float from, float to) {
{ float diff = fmodf(to - from, 360.0f);
sprite->rotation = Lerp(sprite->rotation, rotation, 0.1f); if (diff < -180.0f)
diff += 360.0f;
else if (diff > 180.0f)
diff -= 360.0f;
return diff;
} }
void RenderSprite(const Sprite *sprite) void UpdateSpriteRotation(Sprite *sprite, float rotation) {
{ float diff = ShortestAngleDifference(sprite->rotation, rotation);
DrawTexturePro( sprite->rotation += diff * 0.1f;
sprite->texture,
sprite->sourceRec,
sprite->destRec,
sprite->origin,
sprite->rotation,
sprite->color);
} }
void PrintSprite(const Sprite *sprite) void UpdateSpriteRotationTowards(Sprite *sprite, float rotation, float degreesPerSecond,
{ float deltaTime) {
printf("Sprite:\n"); float diff = ShortestAngleDifference(sprite->rotation, rotation);
printf(" Sprite.texture: id = %d\n", sprite->texture.id);
printf(" Sprite.sourceRec: x = %f, y = %f, width = %f, height = %f\n", sprite->sourceRec.x, sprite->sourceRec.y, sprite->sourceRec.width, sprite->sourceRec.height); float maxStep = degreesPerSecond * deltaTime;
printf(" Sprite.destRec: x = %f, y = %f, width = %f, height = %f\n", sprite->destRec.x, sprite->destRec.y, sprite->destRec.width, sprite->destRec.height); if (diff > maxStep)
printf(" Sprite.frameSize: x = %f, y = %f\n", sprite->frameSize.x, sprite->frameSize.y); diff = maxStep;
printf(" Sprite.origin: x = %f, y = %f\n", sprite->origin.x, sprite->origin.y); else if (diff < -maxStep)
printf(" Sprite.rotation: %f\n", sprite->rotation); diff = -maxStep;
printf(" Sprite.color: r = %d, g = %d, b = %d, a = %d\n", sprite->color.r, sprite->color.g, sprite->color.b, sprite->color.a);
sprite->rotation += diff;
}
void RenderSprite(const Sprite *sprite) {
DrawTexturePro(sprite->texture, sprite->sourceRec, sprite->destRec, sprite->origin,
sprite->rotation, sprite->color);
} }
+41 -62
View File
@@ -1,71 +1,50 @@
#include "engine.h" #include "engineStorage.h"
bool SaveStorageValue(const char* fileName, unsigned int position, int value){ #include <raylib.h>
bool success = false; #include <stdlib.h>
int dataSize = 0; #include <string.h>
unsigned int newDataSize = 0;
unsigned char *fileData = LoadFileData(fileName, &dataSize);
unsigned char *newFileData = NULL;
if (fileData != NULL) bool SaveStorageData(const char *fileName, int version, const void *data, unsigned int dataSize) {
{ unsigned int totalSize = sizeof(int) + dataSize;
if (dataSize <= (position*sizeof(int))) unsigned char *buffer = (unsigned char *)RL_MALLOC(totalSize);
{ if (buffer == NULL)
newDataSize = (position + 1)*sizeof(int); return false;
newFileData = (unsigned char *)RL_REALLOC(fileData, newDataSize);
if (newFileData != NULL)
{
int *dataPtr = (int *)newFileData;
dataPtr[position]=value;
}
else
{
TraceLog(LOG_WARNING, "FLIEIO: [%s] Failed to realloc data (%u), position in bytes (%u) bigger than actual file size", fileName, dataSize, position*sizeof(int));
newFileData = fileData;
newDataSize = dataSize;
}
}
else
{
newFileData = fileData;
newDataSize = dataSize;
int *dataPtr = (int *)newFileData;
dataPtr[position] = value;
}
success = SaveFileData(fileName, newFileData, newDataSize); memcpy(buffer, &version, sizeof(int));
RL_FREE(newFileData); memcpy(buffer + sizeof(int), data, dataSize);
TraceLog(LOG_INFO, "FILEIO: [%s] Saved storage value: %i", fileName, value);
} bool success = SaveFileData(fileName, buffer, totalSize);
else RL_FREE(buffer);
{
TraceLog(LOG_INFO, "FILEIO: [%s] File created successfully", fileName); TraceLog(LOG_INFO, "FILEIO: [%s] Saved versioned storage data (version %i)", fileName, version);
dataSize = (position + 1)*sizeof(int);
fileData = (unsigned char *)RL_MALLOC(dataSize);
int *dataPtr = (int *)fileData;
dataPtr[position] = value;
success = SaveFileData(fileName, fileData, dataSize);
UnloadFileData(fileData);
TraceLog(LOG_INFO, "FILEIO: [%s] Saved storage value: %i", fileName, value);
}
return success; return success;
} }
int LoadStorageValue(const char * fileName, unsigned int position) bool LoadStorageData(const char *fileName, int expectedVersion, void *data, unsigned int dataSize) {
{ int fileSize = 0;
int value = 0; unsigned char *fileData = LoadFileData(fileName, &fileSize);
int dataSize = 0; if (fileData == NULL)
unsigned char *fileData = LoadFileData(fileName, &dataSize); return false;
if (fileData != NULL)
{ bool success = false;
if (dataSize < ((int)(position*4))) TraceLog(LOG_WARNING, "FILEIO: [%s] Failed to find storage position: %i", fileName, position); unsigned int expectedSize = sizeof(int) + dataSize;
else if ((unsigned int)fileSize == expectedSize) {
{ int fileVersion = 0;
int *dataPtr = (int *)fileData; memcpy(&fileVersion, fileData, sizeof(int));
value = dataPtr[position]; if (fileVersion == expectedVersion) {
memcpy(data, fileData + sizeof(int), dataSize);
success = true;
} }
}
UnloadFileData(fileData); UnloadFileData(fileData);
TraceLog(LOG_INFO, "FILEIO: [%s] Loaded storage value: %i", fileName, value);
} if (success)
return value; TraceLog(LOG_INFO, "FILEIO: [%s] Loaded versioned storage data (version %i)", fileName,
expectedVersion);
else
TraceLog(LOG_INFO, "FILEIO: [%s] No matching versioned storage data (expected version %i)",
fileName, expectedVersion);
return success;
} }
+34 -8
View File
@@ -1,4 +1,19 @@
#include "engine.h" #include "engineVector.h"
#include <math.h>
#include <raylib.h>
#include <raymath.h>
Vector2 GetDirectionBetweenTwoVectors(Vector2 vec1, Vector2 vec2) {
Vector2 dir = {vec2.x - vec1.x, vec2.y - vec1.y};
return Vector2Normalize(dir);
}
float GetDistanceBetweenTwoVectors(Vector2 vec1, Vector2 vec2) {
float dx = vec1.x - vec2.x;
float dy = vec1.y - vec2.y;
return sqrtf(dx * dx + dy * dy);
}
bool IsVectorZero(Vector2 v) { bool IsVectorZero(Vector2 v) {
float epsilon = 0.01f; float epsilon = 0.01f;
@@ -6,21 +21,32 @@ bool IsVectorZero(Vector2 v) {
} }
Vector2 GetRandomVector(const Rectangle bounds) { Vector2 GetRandomVector(const Rectangle bounds) {
return (Vector2){ return (Vector2){bounds.x + (float)GetRandomValue(0, 10000) / 10000.0f * bounds.width,
bounds.x + (float)GetRandomValue(0, 10000) / 10000.0f * bounds.width,
bounds.y + (float)GetRandomValue(0, 10000) / 10000.0f * bounds.height}; bounds.y + (float)GetRandomValue(0, 10000) / 10000.0f * bounds.height};
} }
Vector2 RotationToVector2(float rotation) { Vector2 RotationToVector2(float rotation) {
float rad = rotation * (PI / 180.0f); float rad = rotation * (PI / 180.0f);
Vector2 vec = {cosf(rad), sinf(rad)}; return (Vector2){cosf(rad), sinf(rad)};
return vec;
} }
float Vector2ToRotation(Vector2 vec) { float Vector2ToRotation(Vector2 vec) {
float rad = atan2f(vec.y, vec.x); float rad = atan2f(vec.y, vec.x);
float deg = rad * (180.0f / PI); return rad * (180.0f / PI);
return deg;
} }
float Cross2D(Vector2 a, Vector2 b) { return a.x * b.y - a.y * b.x; } float Cross2D(Vector2 a, Vector2 b) {
return a.x * b.y - a.y * b.x;
}
#define COLLISION_STEER_RATE 120.0f
#define COLLISION_STEER_MAX_STEP 4.0f
void ApplyCollisionSteer(float *rotation, float side, float deltaTime) {
float delta = side * COLLISION_STEER_RATE * deltaTime;
if (delta > COLLISION_STEER_MAX_STEP)
delta = COLLISION_STEER_MAX_STEP;
else if (delta < -COLLISION_STEER_MAX_STEP)
delta = -COLLISION_STEER_MAX_STEP;
*rotation += delta;
}