feat: add wake, rose, terrain, pathfinding, gameover, clouds, enemy shooting, variable bird speed refactor: some things
This commit is contained in:
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
+9
-23
@@ -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,27 +101,23 @@ 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));
|
||||||
|
|
||||||
vec4 finalCaustics = caustics(pixelizedCoordinates);
|
vec4 finalCaustics = caustics(pixelizedCoordinates);
|
||||||
vec4 finalSpecular = specular(pixelizedCoordinates);
|
vec4 finalSpecular = specular(pixelizedCoordinates);
|
||||||
vec4 finalFoam = foam(pixelizedCoordinates, mainTex);
|
vec4 finalFoam = foam(pixelizedCoordinates, mainTex);
|
||||||
|
|
||||||
vec4 waterWithCausticLayer = mix(depthBasedWaterColor, finalCaustics, finalCaustics.a);
|
vec4 waterWithCausticLayer = mix(depthBasedWaterColor, finalCaustics, finalCaustics.a);
|
||||||
vec4 waterWithCausticAndSpecularLayer = mix(waterWithCausticLayer, finalSpecular, ceil(finalCaustics.a) * finalSpecular.a);
|
vec4 waterWithCausticAndSpecularLayer = mix(waterWithCausticLayer, finalSpecular, ceil(finalCaustics.a) * finalSpecular.a);
|
||||||
vec4 waterWithCausticAndSpecularAndFoamLayer = mix(waterWithCausticAndSpecularLayer, finalFoam, finalFoam.a);
|
vec4 waterWithCausticAndSpecularAndFoamLayer = mix(waterWithCausticAndSpecularLayer, finalFoam, finalFoam.a);
|
||||||
|
|
||||||
float outline = mainTex.a * mainTex.r;
|
float outline = mainTex.a * mainTex.r;
|
||||||
vec4 finalOutlineColor = outline * outlineColor;
|
vec4 finalOutlineColor = outline * outlineColor;
|
||||||
|
|
||||||
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
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,36 +16,42 @@ 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;
|
||||||
|
|
||||||
while (app->state.running) {
|
while (app->state.running) {
|
||||||
switch (app->state.currentView) {
|
switch (app->state.currentView) {
|
||||||
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: {
|
}
|
||||||
UpdateMenu(&app->menu, &app->state, &app->flags);
|
case MENU: {
|
||||||
RenderMenu(&app->menu, &app->font, app->window.title, &app->flags);
|
UpdateMenu(&app->menu, &app->state, &app->flags);
|
||||||
} break;
|
RenderMenu(&app->menu, &app->font, app->window.title, &app->flags, &app->state);
|
||||||
case GAMEPLAY: {
|
break;
|
||||||
UpdateGame(app);
|
}
|
||||||
RenderGame(app);
|
case GAMEPLAY: {
|
||||||
} break;
|
UpdateGame(app);
|
||||||
default:
|
RenderGame(app);
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|||||||
+100
-56
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
+82
-75
@@ -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,
|
||||||
Birds birds = {.count = MAX_BIRDS};
|
float rotation) {
|
||||||
TextureAsset flyingAsset = birdsTextureAssets[FLYING];
|
birds->sprite[i] = (Sprite){.texture = texture,
|
||||||
Texture2D texture = LoadTexture(flyingAsset.path);
|
.frameSize = {frameWidth, frameWidth},
|
||||||
float frameWidth = flyingAsset.frameWidth;
|
.sourceRec = {0.0f, 0.0f, frameWidth, frameWidth},
|
||||||
float rotation = flyingAsset.rotation;
|
.destRec = {GetScreenWidth() / 2, 0.0f, frameWidth, frameWidth},
|
||||||
|
.origin = {frameWidth / 2, frameWidth / 2},
|
||||||
for (int i = 0; i < birds.count; i++)
|
.rotation = rotation,
|
||||||
{
|
.color = WHITE};
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
birds.animation[i] = (Animation){
|
|
||||||
.state = 1,
|
|
||||||
.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},
|
|
||||||
.speed = 5.0f
|
|
||||||
};
|
|
||||||
|
|
||||||
birds.arrived[i] = true;
|
|
||||||
birds.destPos[i] = (Vector2){0.0f, 0.0f};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return birds;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdateBirds(Birds *birds){
|
static float InitBirdAnimation(Birds *birds, int i) {
|
||||||
Camera2D *camera = GetCamera();
|
float speedFactor = GetRandomValue(50, 180) / 100.0f;
|
||||||
Rectangle cameraRec = GetCameraRectangle();
|
int framesSpeed = (int)(2.0f * speedFactor + 0.5f);
|
||||||
|
if (framesSpeed < 1)
|
||||||
|
framesSpeed = 1;
|
||||||
|
int maxFrame = 2;
|
||||||
|
|
||||||
for (int i = 0; i < birds->count; i++)
|
birds->animation[i] =
|
||||||
{
|
(Animation){.state = 1,
|
||||||
if(birds->arrived[i])
|
.currentFrame = GetRandomValue(0, maxFrame - 1),
|
||||||
{
|
.maxFrame = maxFrame,
|
||||||
birds->destPos[i] = GetPositionOutsideRectangle(cameraRec, 100, 1000);
|
.framesCounter = 0,
|
||||||
birds->arrived[i] = false;
|
.framesSpeed = framesSpeed,
|
||||||
}
|
.animTimer = GetRandomValue(0, 99) / 100.0f * (1.0f / (float)framesSpeed)};
|
||||||
if(CheckCollisionPointCircle(birds->physics[i].position, birds->destPos[i], 10.0F))
|
return speedFactor;
|
||||||
{
|
|
||||||
birds->arrived[i] = true;
|
|
||||||
}
|
|
||||||
if(GetDistanceBetweenTwoVectors(birds->physics[i].position, camera->target) > 1000){
|
|
||||||
birds->physics[i].position = GetPositionOutsideRectangle(cameraRec, 100, 1000);
|
|
||||||
}
|
|
||||||
Vector2 dir = GetDirectionBetweenTwoVectors(birds->physics[i].position, birds->destPos[i]);
|
|
||||||
UpdatePhysics(&birds->physics[i], dir);
|
|
||||||
UpdateSpriteDestRec(&birds->sprite[i], &birds->physics[i].position);
|
|
||||||
float rotation = Vector2ToRotation(dir);
|
|
||||||
UpdateSpriteRotation(&birds->sprite[i], rotation + 45.0f);
|
|
||||||
UpdateAnimation(&birds->animation[i], GetFrameTime());
|
|
||||||
UpdateSpriteSourceRec(&birds->sprite[i], &(Vector2){birds->sprite[i].frameSize.x * birds->animation[i].currentFrame, birds->sprite[i].frameSize.y});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderBirds(Birds *birds){
|
Birds CreateBirds(Camera2D *camera) {
|
||||||
|
Birds birds = {.count = MAX_BIRDS};
|
||||||
|
TextureAsset flyingAsset = birdsTextureAssets[FLYING];
|
||||||
|
Texture2D texture = LoadTextureCached(flyingAsset.path);
|
||||||
|
float frameWidth = flyingAsset.frameWidth;
|
||||||
|
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);
|
||||||
RenderSprite(&birds->sprite[i]);
|
float speedFactor = InitBirdAnimation(&birds, i);
|
||||||
}
|
|
||||||
|
|
||||||
|
Rectangle cameraRec = GetCameraRectangle(camera);
|
||||||
|
birds.physics[i] = (Physics){.position = GetPositionOutsideRectangle(cameraRec, 100, 1000),
|
||||||
|
.direction = (Vector2){0.0f, 0.0f},
|
||||||
|
.speed = 5.0f * speedFactor};
|
||||||
|
|
||||||
|
birds.arrived[i] = true;
|
||||||
|
birds.destPos[i] = (Vector2){0.0f, 0.0f};
|
||||||
|
}
|
||||||
|
|
||||||
|
return birds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UpdateBirds(Birds *birds, Camera2D *camera) {
|
||||||
|
Rectangle cameraRec = GetCameraRectangle(camera);
|
||||||
|
|
||||||
|
for (int i = 0; i < birds->count; i++) {
|
||||||
|
if (birds->arrived[i]) {
|
||||||
|
birds->destPos[i] = GetPositionOutsideRectangle(cameraRec, 100, 1000);
|
||||||
|
birds->arrived[i] = false;
|
||||||
|
}
|
||||||
|
if (CheckCollisionPointCircle(birds->physics[i].position, birds->destPos[i], 10.0f)) {
|
||||||
|
birds->arrived[i] = true;
|
||||||
|
}
|
||||||
|
if (GetDistanceBetweenTwoVectors(birds->physics[i].position, camera->target) > 1000) {
|
||||||
|
birds->physics[i].position = GetPositionOutsideRectangle(cameraRec, 100, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2 dir = GetDirectionBetweenTwoVectors(birds->physics[i].position, birds->destPos[i]);
|
||||||
|
UpdatePhysics(&birds->physics[i], dir);
|
||||||
|
UpdateSpriteDestRec(&birds->sprite[i], &birds->physics[i].position);
|
||||||
|
float rotation = Vector2ToRotation(dir);
|
||||||
|
UpdateSpriteRotation(&birds->sprite[i], rotation + 45.0f);
|
||||||
|
UpdateAnimation(&birds->animation[i], GetFrameTime());
|
||||||
|
UpdateSpriteSourceRec(&birds->sprite[i], &(Vector2){birds->sprite[i].frameSize.x *
|
||||||
|
birds->animation[i].currentFrame,
|
||||||
|
birds->sprite[i].frameSize.y});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderBirds(Birds *birds) {
|
||||||
|
for (int i = 0; i < birds->count; i++) {
|
||||||
|
RenderSprite(&birds->sprite[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
+126
-52
@@ -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 forward = RotationToVector2(player->rotation - 90.0f);
|
||||||
Vector2 collisionVector = GetDirectionBetweenTwoVectors(
|
float side = Cross2D(forward, collision.direction);
|
||||||
enemies->physics[i].position, player->physics.position);
|
ApplyCollisionSteer(&player->rotation, side, GetFrameTime());
|
||||||
player->physics.position.x += collisionVector.x * overlap;
|
|
||||||
player->physics.position.y += collisionVector.y * overlap;
|
|
||||||
Vector2 forward = RotationToVector2(player->rotation - 90.0f);
|
|
||||||
float side = Cross2D(forward, collisionVector);
|
|
||||||
player->rotation += side * 120.0f * 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);
|
float sideB = Cross2D(forwardB, collisionVectorB);
|
||||||
Vector2 forwardB =
|
ApplyCollisionSteer(&enemies->sprite[a].rotation, sideA, GetFrameTime());
|
||||||
RotationToVector2(enemies->sprite[b].rotation - 90.0f);
|
ApplyCollisionSteer(&enemies->sprite[b].rotation, sideB, GetFrameTime());
|
||||||
float sideA = Cross2D(forwardA, collisionVectorA);
|
|
||||||
float sideB = Cross2D(forwardB, collisionVectorB);
|
|
||||||
enemies->sprite[a].rotation += sideA * 120.0f * GetFrameTime();
|
|
||||||
enemies->sprite[b].rotation += sideB * 120.0f * 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)) {
|
||||||
player->gold += 20;
|
PlayImpactSound(projectiles->soundImpact[i]);
|
||||||
Rectangle cameraRec = GetCameraRectangle();
|
enemies->alive[j] = false;
|
||||||
enemies->physics[j].position =
|
projectiles->active[i] = false;
|
||||||
GetPositionOutsideRectangle(cameraRec, 100, 1000);
|
if (projectiles->friendly[i]) {
|
||||||
|
player->gold += 20;
|
||||||
|
SavePlayer(player);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!projectiles->active[i])
|
if (!projectiles->active[i])
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
Vector2 playerPos = player->physics.position;
|
if (!projectiles->friendly[i]) {
|
||||||
if (CheckCollisionCircles(projectilePos, projectilesCollisionRadius,
|
Vector2 playerPos = player->physics.position;
|
||||||
playerPos, playerCollisionRadius)) {
|
if (CheckCollisionCircles(projectilePos, projectilesCollisionRadius, playerPos,
|
||||||
|
playerCollisionRadius)) {
|
||||||
|
PlayImpactSound(projectiles->soundImpact[i]);
|
||||||
|
player->health -= 20;
|
||||||
|
if (player->health < 0)
|
||||||
|
player->health = 0;
|
||||||
|
projectiles->active[i] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
player->health -= 20;
|
#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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+255
-47
@@ -1,24 +1,248 @@
|
|||||||
#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},
|
.origin = {frameWidth / 2, frameWidth / 2},
|
||||||
.origin = {frameWidth / 2, frameWidth / 2},
|
.rotation = rotation,
|
||||||
.rotation = rotation,
|
.color = WHITE};
|
||||||
.color = WHITE};
|
|
||||||
|
|
||||||
enemies.animation[i] = (Animation){.state = 1,
|
enemies.animation[i] = (Animation){.state = 1,
|
||||||
.currentFrame = 0,
|
.currentFrame = 0,
|
||||||
@@ -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});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+128
-78
@@ -1,91 +1,141 @@
|
|||||||
#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");
|
|
||||||
|
|
||||||
float textureWidth = 64.0f;
|
rlActiveTextureSlot(slot);
|
||||||
|
rlEnableTexture(texture.id);
|
||||||
Floor floor = {
|
SetShaderValue(shader, loc, &slot, SHADER_UNIFORM_INT);
|
||||||
.sprite = {
|
|
||||||
.texture = base,
|
|
||||||
.frameSize = {textureWidth, textureWidth},
|
|
||||||
.sourceRec = {0.0f, 0.0f, textureWidth, textureWidth},
|
|
||||||
.destRec = {0.0f, 0.0f, textureWidth, textureWidth},
|
|
||||||
.origin = {textureWidth / 2, textureWidth / 2},
|
|
||||||
.rotation = 0.0f,
|
|
||||||
.color = WHITE},
|
|
||||||
.bounds = {0},
|
|
||||||
.shader = LoadShader("shaders/water.vs", "shaders/water.fs"),
|
|
||||||
.time = 0.0f
|
|
||||||
};
|
|
||||||
|
|
||||||
float worldTileSize = 128.0f;
|
|
||||||
float textureSize = 32.0f;
|
|
||||||
|
|
||||||
floor.sprite.frameSize = (Vector2){worldTileSize, worldTileSize};
|
|
||||||
floor.sprite.sourceRec = (Rectangle){0, 0, textureSize, textureSize};
|
|
||||||
floor.sprite.destRec = (Rectangle){0, 0, worldTileSize, worldTileSize};
|
|
||||||
floor.sprite.origin = (Vector2){0, 0};
|
|
||||||
|
|
||||||
SetShaderValueTexture(floor.shader, GetShaderLocation(floor.shader, "causticTexture"), caustic);
|
|
||||||
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;
|
|
||||||
|
|
||||||
return floor;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdateFloor(Floor *floor)
|
static void PrepareTiledTexture(Texture2D texture) {
|
||||||
{
|
SetTextureWrap(texture, TEXTURE_WRAP_REPEAT);
|
||||||
Camera2D *camera = GetCamera();
|
GenTextureMipmaps(&texture);
|
||||||
floor->bounds.x = (int)((camera->target.x - (GetScreenWidth() / 2.0f / camera->zoom)) / floor->sprite.frameSize.x) - 1;
|
SetTextureFilter(texture, TEXTURE_FILTER_TRILINEAR);
|
||||||
floor->bounds.width = (int)((camera->target.x + (GetScreenWidth() / 2.0f / camera->zoom)) / floor->sprite.frameSize.x) + 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();
|
|
||||||
int timeLoc = GetShaderLocation(floor->shader, "time");
|
|
||||||
SetShaderValue(floor->shader, timeLoc, &floor->time, SHADER_UNIFORM_FLOAT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderFloor(Floor *floor)
|
static Texture2D CreateDepthGradientTexture(void) {
|
||||||
{
|
Image gradientImage =
|
||||||
BeginShaderMode(floor->shader);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
for (int y = (int)floor->bounds.y; y <= (int)floor->bounds.height; y++)
|
static void ConfigureFloorShaderUniforms(Shader shader, Texture2D base, float worldTileSize,
|
||||||
{
|
float textureSize) {
|
||||||
for (int x = (int)floor->bounds.x; x <= (int)floor->bounds.width; x++)
|
float aspect = (float)GetScreenHeight() / (float)GetScreenWidth();
|
||||||
{
|
float pixelization = 2048.0f;
|
||||||
Vector2 floorPosition = {x * floor->sprite.frameSize.x, y * floor->sprite.frameSize.y};
|
float noiseCoordinateScale = worldTileSize * ((float)base.width / textureSize);
|
||||||
UpdateSpriteDestRec(&floor->sprite, &floorPosition);
|
|
||||||
RenderSprite(&floor->sprite);
|
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;
|
||||||
|
Floor floor = {.sprite = {.texture = base,
|
||||||
|
.frameSize = {textureWidth, textureWidth},
|
||||||
|
.sourceRec = {0.0f, 0.0f, textureWidth, textureWidth},
|
||||||
|
.destRec = {0.0f, 0.0f, textureWidth, textureWidth},
|
||||||
|
.origin = {textureWidth / 2, textureWidth / 2},
|
||||||
|
.rotation = 0.0f,
|
||||||
|
.color = WHITE},
|
||||||
|
.bounds = {0},
|
||||||
|
.shader = LoadShader("shaders/water.vs", "shaders/water.fs"),
|
||||||
|
.time = 0.0f};
|
||||||
|
|
||||||
|
float worldTileSize = 128.0f;
|
||||||
|
float textureSize = 32.0f;
|
||||||
|
|
||||||
|
floor.sprite.frameSize = (Vector2){worldTileSize, worldTileSize};
|
||||||
|
floor.sprite.sourceRec = (Rectangle){0, 0, textureSize, textureSize};
|
||||||
|
floor.sprite.destRec = (Rectangle){0, 0, worldTileSize, worldTileSize};
|
||||||
|
floor.sprite.origin = (Vector2){0, 0};
|
||||||
|
|
||||||
|
ConfigureFloorShaderUniforms(floor.shader, base, worldTileSize, textureSize);
|
||||||
|
|
||||||
|
floor.sprite.texture = base;
|
||||||
|
floor.causticTexture = caustic;
|
||||||
|
floor.causticHighlightTexture = causticHighlight;
|
||||||
|
floor.noiseTexture = noise;
|
||||||
|
floor.cloudTexture = cloud;
|
||||||
|
floor.voronoiseTexture = voronoise;
|
||||||
|
floor.depthGradientTexture = depthGradient;
|
||||||
|
|
||||||
|
return floor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateFloor(Floor *floor, Camera2D *camera) {
|
||||||
|
floor->bounds.x = (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->sprite.frameSize.x) +
|
||||||
|
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();
|
||||||
|
int timeLoc = GetShaderLocation(floor->shader, "time");
|
||||||
|
SetShaderValue(floor->shader, timeLoc, &floor->time, SHADER_UNIFORM_FLOAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderFloor(Floor *floor) {
|
||||||
|
BeginShaderMode(floor->shader);
|
||||||
|
|
||||||
|
BindWaterTexture(floor->shader, "causticTexture", floor->causticTexture, 1);
|
||||||
|
BindWaterTexture(floor->shader, "causticHighlightTexture", floor->causticHighlightTexture, 2);
|
||||||
|
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};
|
||||||
|
UpdateSpriteDestRec(&floor->sprite, &floorPosition);
|
||||||
|
RenderSprite(&floor->sprite);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
EndShaderMode();
|
EndShaderMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
void UnloadFloor(Floor *floor)
|
void UnloadFloor(Floor *floor) {
|
||||||
{
|
UnloadShader(floor->shader);
|
||||||
UnloadShader(floor->shader);
|
UnloadTexture(floor->depthGradientTexture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+189
-29
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
+22
-13
@@ -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"
|
}
|
||||||
"Gold: %i\n",
|
|
||||||
player->health, player->gold);
|
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",
|
||||||
|
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);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
+108
-72
@@ -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};
|
||||||
.frameSize = {frameWidth, frameWidth},
|
LoadStorageData(STORAGE_DATA_FILE, PLAYER_SAVE_VERSION, &saveData, sizeof(saveData));
|
||||||
.sourceRec = {0.0f, 0.0f, frameWidth, frameWidth},
|
|
||||||
.destRec = {0.0f, 0.0f, frameWidth, frameWidth},
|
|
||||||
.origin = {frameWidth / 2, frameWidth / 2},
|
|
||||||
.rotation = rotation,
|
|
||||||
.color = WHITE};
|
|
||||||
|
|
||||||
Animation playerAnimation = {.state = 1,
|
player.sprite = (Sprite){.texture = texture,
|
||||||
.currentFrame = 0,
|
.frameSize = {frameWidth, frameWidth},
|
||||||
.maxFrame = 3,
|
.sourceRec = {0.0f, 0.0f, frameWidth, frameWidth},
|
||||||
.framesCounter = 0,
|
.destRec = {0.0f, 0.0f, frameWidth, frameWidth},
|
||||||
.framesSpeed = 0.0f,
|
.origin = {frameWidth / 2, frameWidth / 2},
|
||||||
.animTimer = 0.0f};
|
.rotation = 0.0f,
|
||||||
|
.color = WHITE};
|
||||||
|
|
||||||
Physics playerPhysics = {
|
player.animation = (Animation){.state = 1,
|
||||||
.position = {(float)LoadStorageValue(STORAGE_DATA_FILE,
|
.currentFrame = 0,
|
||||||
STORAGE_POSITION_POSX),
|
.maxFrame = 3,
|
||||||
(float)LoadStorageValue(STORAGE_DATA_FILE,
|
.framesCounter = 0,
|
||||||
STORAGE_POSITION_POSY)},
|
.framesSpeed = 0.0f,
|
||||||
.direction = {0.0f, 0.0f},
|
.animTimer = 0.0f};
|
||||||
.speed = playerSpeeds[ANCHOR],
|
|
||||||
.weight = 10.0f};
|
|
||||||
|
|
||||||
Player *player = GetPlayer();
|
player.physics = (Physics){.position = {0.0f, 0.0f},
|
||||||
|
.direction = {0.0f, 0.0f},
|
||||||
|
.speed = playerSpeeds[ANCHOR],
|
||||||
|
.weight = 10.0f};
|
||||||
|
|
||||||
*player = (Player){
|
player.rotation = 0.0f;
|
||||||
.sprite = playerSprite,
|
player.soundFlapping = LoadSoundCached(playerSoundAssets[FLAPPING].path);
|
||||||
.animation = playerAnimation,
|
player.health = PLAYER_HEALTH_MAX;
|
||||||
.physics = playerPhysics,
|
player.gold = saveData.gold;
|
||||||
.rotation = rotation,
|
|
||||||
.soundFlapping = LoadSound(playerSoundAssets[FLAPPING].path),
|
|
||||||
.health = PLAYER_HEALTH_MAX,
|
|
||||||
.gold = LoadStorageValue(STORAGE_DATA_FILE, STORAGE_POSITION_GOLD)};
|
|
||||||
|
|
||||||
if (player->gold == 0) {
|
if (player.gold == 0) {
|
||||||
player->gold = START_GOLD;
|
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) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void UpdatePlayerRotationFromInput(Player *player) {
|
||||||
|
float rotationSpeed = 50.0f;
|
||||||
|
if (player->physics.speed <= playerSpeeds[ANCHOR])
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (IsGameActionDown(ACTION_TURN_LEFT)) {
|
||||||
|
player->rotation -= rotationSpeed * GetFrameTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsGameActionDown(ACTION_TURN_RIGHT)) {
|
||||||
|
player->rotation += rotationSpeed * GetFrameTime();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdatePlayer(Player *player) {
|
||||||
|
UpdatePlayerSpeedFromInput(player);
|
||||||
player->sprite.sourceRec.x = 16.0f * GetPlayerSpeedID(player->physics.speed);
|
player->sprite.sourceRec.x = 16.0f * GetPlayerSpeedID(player->physics.speed);
|
||||||
|
|
||||||
float rotationSpeed = 50.0f;
|
UpdatePlayerRotationFromInput(player);
|
||||||
|
|
||||||
if (player->physics.speed > playerSpeeds[ANCHOR]) {
|
|
||||||
|
|
||||||
if (IsKeyDown(KEY_A) || IsKeyDown(KEY_LEFT)) {
|
|
||||||
player->rotation -= rotationSpeed * GetFrameTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsKeyDown(KEY_D) || IsKeyDown(KEY_RIGHT)) {
|
|
||||||
player->rotation += rotationSpeed * GetFrameTime();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
@@ -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]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+31
-38
@@ -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:
|
||||||
{
|
UpdateMenuMain(&m->menuMain, state, flags, &m->menuView);
|
||||||
case MAIN:
|
break;
|
||||||
{
|
|
||||||
UpdateMenuMain(&m->menuMain, state, flags, &m->menuView);
|
|
||||||
}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:
|
||||||
{
|
RenderMenuMain(&m->menuMain, font, title, flags, state->gameStarted);
|
||||||
case MAIN:
|
break;
|
||||||
{
|
|
||||||
RenderMenuMain(&m->menuMain, font, title, flags);
|
|
||||||
}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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+147
-185
@@ -1,209 +1,171 @@
|
|||||||
#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"}
|
{"assets/button_click.wav"}};
|
||||||
};
|
|
||||||
|
|
||||||
|
static void InitMenuMainAssets(MenuMain *m) {
|
||||||
|
m->assets = (Assets){.textureAssets = menuTextureAssets,
|
||||||
|
.soundAssets = menuSoundAssets,
|
||||||
|
.soundtrackAssets = menuSoundtrackAssets};
|
||||||
|
|
||||||
const SoundAsset menuSoundAssets[MENU_SOUND_COUNT] = {
|
for (int i = 0; i < MENU_TEX_COUNT; i++) {
|
||||||
{"assets/button_hover.wav"},
|
m->textures[i] = LoadTextureCached(m->assets.textureAssets[i].path);
|
||||||
{"assets/button_click.wav"}};
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < MENU_SOUND_COUNT; i++) {
|
||||||
|
m->sounds[i] = LoadSoundCached(m->assets.soundAssets[i].path);
|
||||||
|
}
|
||||||
|
|
||||||
void InitMenuMain(MenuMain *m){
|
for (int i = 0; i < MENU_SOUNDTRACK_COUNT; i++) {
|
||||||
|
m->soundtracks[i] = LoadSoundtrack(m->assets.soundtrackAssets[i].path);
|
||||||
m->assets = (Assets){
|
}
|
||||||
.textureAssets = menuTextureAssets,
|
|
||||||
.soundAssets = menuSoundAssets,
|
|
||||||
.soundtrackAssets = menuSoundtrackAssets};
|
|
||||||
|
|
||||||
for (int i = 0; i < MENU_TEX_COUNT; i++)
|
|
||||||
{
|
|
||||||
m->textures[i] = LoadTexture(m->assets.textureAssets[i].path);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < MENU_SOUND_COUNT; i++)
|
|
||||||
{
|
|
||||||
m->sounds[i] = LoadSound(m->assets.soundAssets[i].path);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < MENU_SOUNDTRACK_COUNT; i++)
|
|
||||||
{
|
|
||||||
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]);
|
|
||||||
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]);
|
|
||||||
|
|
||||||
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;
|
|
||||||
int boardHeight = (int)(m->assets.textureAssets[MENU_BOARD_TEX].frameHeight * boardScaleFactor);
|
|
||||||
|
|
||||||
Vector2 boardSource = {128.0f, 0.0f};
|
|
||||||
Rectangle boardDestRec = {
|
|
||||||
.x = windowCenter.x - (boardWidth / 2),
|
|
||||||
.y = windowCenter.y - (boardHeight / 2),
|
|
||||||
.width = boardWidth,
|
|
||||||
.height = boardHeight};
|
|
||||||
|
|
||||||
m->board = CreateImageElement(&m->textures[MENU_BOARD_TEX], &m->assets.textureAssets[MENU_BOARD_TEX], &boardSource, &boardDestRec);
|
|
||||||
|
|
||||||
int buttonWidth = windowWidth / 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;
|
|
||||||
|
|
||||||
|
|
||||||
//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 InitMenuMainBackgrounds(MenuMain *m) {
|
||||||
for (int i = 0; i < MENU_SOUNDTRACK_COUNT; i++)
|
InitBackground(0.1f, &m->background, &m->textures[MENU_CLOUD_BACKGROUND],
|
||||||
{
|
&m->assets.textureAssets[MENU_CLOUD_BACKGROUND]);
|
||||||
UpdateSoundtrack(&m->soundtracks[i], flags->soundtrackOn);
|
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],
|
||||||
if (GetFPS() >= 60)
|
&m->assets.textureAssets[MENU_CLOUD_FOREGROUND]);
|
||||||
{
|
|
||||||
SetTargetFPS(60);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isImageButtonClicked(&m->playButton, m->sounds[HOVER_SOUND], m->sounds[CLICK_SOUND]))
|
|
||||||
{
|
|
||||||
state->running = true;
|
|
||||||
state->currentView = GAMEPLAY;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsKeyPressed(KEY_ESCAPE) && state->gameStarted)
|
|
||||||
{
|
|
||||||
state->currentView = GAMEPLAY;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isImageButtonClicked(&m->settingsButton, m->sounds[HOVER_SOUND], m->sounds[CLICK_SOUND]))
|
|
||||||
{
|
|
||||||
*menuView = SETTINGS;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isImageButtonClicked(&m->quitButton, m->sounds[HOVER_SOUND], m->sounds[CLICK_SOUND]) || (WindowShouldClose() && !IsKeyPressed(KEY_ESCAPE)))
|
|
||||||
{
|
|
||||||
state->running = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
UpdateBackground(&m->background);
|
|
||||||
UpdateBackground(&m->midground);
|
|
||||||
UpdateBackground(&m->foreground);
|
|
||||||
UpdateMouseScreen(&m->mouse);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderMenuMain(MenuMain *m, Font *font, const char *title, Flags *flags){
|
static void InitMenuMainBoard(MenuMain *m, Vector2 windowCenter) {
|
||||||
BeginDrawing();
|
int boardWidth = GetScreenWidth() / m->menuScale * 2.5f;
|
||||||
ClearBackground(WHITE);
|
float boardScaleFactor = (float)boardWidth / m->assets.textureAssets[MENU_BOARD_TEX].frameWidth;
|
||||||
RenderBackground(&m->background);
|
int boardHeight = (int)(m->assets.textureAssets[MENU_BOARD_TEX].frameHeight * boardScaleFactor);
|
||||||
RenderBackground(&m->midground);
|
|
||||||
RenderBackground(&m->foreground);
|
|
||||||
renderImageElement(&m->board);
|
|
||||||
renderImageButton(&m->playButton);
|
|
||||||
renderImageButton(&m->quitButton);
|
|
||||||
renderImageButton(&m->settingsButton);
|
|
||||||
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)
|
Vector2 boardSource = {128.0f, 0.0f};
|
||||||
{
|
Rectangle boardDestRec = {.x = windowCenter.x - (boardWidth / 2),
|
||||||
DrawFPS(10, 10);
|
.y = windowCenter.y - (boardHeight / 2),
|
||||||
}
|
.width = boardWidth,
|
||||||
|
.height = boardHeight};
|
||||||
EndDrawing();
|
|
||||||
|
|
||||||
|
m->board =
|
||||||
|
CreateImageElement(&m->textures[MENU_BOARD_TEX], &m->assets.textureAssets[MENU_BOARD_TEX],
|
||||||
|
&boardSource, &boardDestRec);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CleanupMenuMain(MenuMain *m){
|
static void InitMenuMainButtons(MenuMain *m, Vector2 windowCenter) {
|
||||||
for (int i = 0; i < MENU_SOUND_COUNT; i++)
|
int buttonWidth = GetScreenWidth() / m->menuScale;
|
||||||
{
|
float buttonScaleFactor =
|
||||||
UnloadSound(m->sounds[i]);
|
(float)buttonWidth / m->assets.textureAssets[MENU_BIG_BUTTONS_TEX].frameWidth;
|
||||||
}
|
int buttonHeight =
|
||||||
for (int i = 0; i < MENU_TEX_COUNT; i++)
|
(int)(m->assets.textureAssets[MENU_BIG_BUTTONS_TEX].frameHeight * buttonScaleFactor);
|
||||||
{
|
int buttonSpacing = buttonHeight;
|
||||||
if (m->textures[i].id > 0)
|
|
||||||
{
|
m->quitButton = CreateMenuButton(&m->textures[MENU_BIG_BUTTONS_TEX],
|
||||||
UnloadTexture(m->textures[i]);
|
&m->assets.textureAssets[MENU_BIG_BUTTONS_TEX], 0, 1,
|
||||||
}
|
buttonWidth, buttonHeight, windowCenter, buttonSpacing);
|
||||||
}
|
m->playButton = CreateMenuButton(&m->textures[MENU_BIG_BUTTONS_TEX],
|
||||||
for (int i = 0; i < MENU_SOUNDTRACK_COUNT; i++)
|
&m->assets.textureAssets[MENU_BIG_BUTTONS_TEX], 1, 0,
|
||||||
{
|
buttonWidth, buttonHeight, windowCenter, buttonSpacing);
|
||||||
UnloadMusicStream(m->soundtracks[i].music);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GetFPS() >= 60) {
|
||||||
|
SetTargetFPS(60);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isImageButtonClicked(&m->playButton, m->sounds[HOVER_SOUND], m->sounds[CLICK_SOUND])) {
|
||||||
|
state->running = true;
|
||||||
|
state->currentView = GAMEPLAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsGameActionPressed(ACTION_BACK) && state->gameStarted) {
|
||||||
|
state->currentView = GAMEPLAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isImageButtonClicked(&m->quitButton, m->sounds[HOVER_SOUND], m->sounds[CLICK_SOUND]) ||
|
||||||
|
(WindowShouldClose() && !IsGameActionPressed(ACTION_BACK))) {
|
||||||
|
state->running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateBackground(&m->background);
|
||||||
|
UpdateBackground(&m->midground);
|
||||||
|
UpdateBackground(&m->foreground);
|
||||||
|
UpdateMouseScreen(&m->mouse);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderMenuMain(MenuMain *m, Font *font, const char *title, Flags *flags, bool gameStarted) {
|
||||||
|
BeginDrawing();
|
||||||
|
ClearBackground(WHITE);
|
||||||
|
RenderBackground(&m->background);
|
||||||
|
RenderBackground(&m->midground);
|
||||||
|
RenderBackground(&m->foreground);
|
||||||
|
renderImageElement(&m->board);
|
||||||
|
renderImageButton(&m->playButton);
|
||||||
|
renderImageButton(&m->quitButton);
|
||||||
|
renderImageButton(&m->settingsButton);
|
||||||
|
if (gameStarted) {
|
||||||
|
renderButton(&m->restartButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
EndDrawing();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CleanupMenuMain(MenuMain *m) {
|
||||||
|
for (int i = 0; i < MENU_SOUNDTRACK_COUNT; i++) {
|
||||||
|
UnloadMusicStream(m->soundtracks[i].music);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+36
-49
@@ -1,58 +1,45 @@
|
|||||||
#include "app.h"
|
#include <raylib.h>
|
||||||
|
|
||||||
void InitBackground(float scrollSpeed, Background *bg, Texture2D *texture, const TextureAsset *backgroundAsset){
|
#include "menu.h"
|
||||||
bg->scrolling = 0.0f;
|
|
||||||
bg->scrollSpeed = scrollSpeed;
|
void InitBackground(float scrollSpeed, Background *bg, Texture2D *texture,
|
||||||
bg->scale = 1.0f;
|
const TextureAsset *backgroundAsset) {
|
||||||
bg->sprite = (Sprite){
|
bg->scrolling = 0.0f;
|
||||||
.texture = *texture,
|
bg->scrollSpeed = scrollSpeed;
|
||||||
.sourceRec = (Rectangle){0.0f,0.0f,backgroundAsset->frameWidth, backgroundAsset->frameHeight},
|
bg->scale = 1.0f;
|
||||||
.destRec = (Rectangle){0.0f,0.0f,(float)GetScreenWidth(),(float)GetScreenHeight()},
|
bg->sprite = (Sprite){
|
||||||
.origin = (Vector2){0.0f,0.0f},
|
.texture = *texture,
|
||||||
.rotation = 0.0f,
|
.sourceRec =
|
||||||
.color = WHITE
|
(Rectangle){0.0f, 0.0f, backgroundAsset->frameWidth, backgroundAsset->frameHeight},
|
||||||
};
|
.destRec = (Rectangle){0.0f, 0.0f, (float)GetScreenWidth(), (float)GetScreenHeight()},
|
||||||
|
.origin = (Vector2){0.0f, 0.0f},
|
||||||
|
.rotation = 0.0f,
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
|||||||
+43
-49
@@ -1,67 +1,61 @@
|
|||||||
#include "app.h"
|
#include <raylib.h>
|
||||||
|
|
||||||
void InitMenuSettings(MenuSettings *s)
|
#include "menu.h"
|
||||||
{
|
|
||||||
int windowWidth = GetScreenWidth();
|
|
||||||
int windowHeight = GetScreenHeight();
|
|
||||||
Vector2 windowCenter = {windowWidth / 2, windowHeight / 2};
|
|
||||||
|
|
||||||
Vector2 standardButtonDimensions = {100, 50};
|
void InitMenuSettings(MenuSettings *s) {
|
||||||
int standardSpacing = standardButtonDimensions.y / 2;
|
Vector2 windowCenter = {GetScreenWidth() / 2, GetScreenHeight() / 2};
|
||||||
|
|
||||||
Vector2 button1Pos = {windowCenter.x, windowCenter.y};
|
Vector2 standardButtonDimensions = {100, 50};
|
||||||
Vector2 button2Pos = {windowCenter.x, windowCenter.y + standardButtonDimensions.y * 1 + standardSpacing * 1};
|
int standardSpacing = standardButtonDimensions.y / 2;
|
||||||
Vector2 button3Pos = {windowCenter.x, windowCenter.y + standardButtonDimensions.y * 2 + standardSpacing * 2};
|
|
||||||
|
|
||||||
s->showFPS = initButton("showFPS", standardButtonDimensions, button1Pos);
|
Vector2 button1Pos = {windowCenter.x, windowCenter.y};
|
||||||
s->limitFPS = initButton("limitFPS", standardButtonDimensions, button2Pos);
|
Vector2 button2Pos = {windowCenter.x,
|
||||||
s->soundtrackOn = initButton("soundtrackOn", standardButtonDimensions, button3Pos);
|
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->limitFPS = initButton("limitFPS", standardButtonDimensions, button2Pos);
|
||||||
|
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)) {
|
||||||
|
flags->showFPS = !flags->showFPS;
|
||||||
|
}
|
||||||
|
|
||||||
if(isButtonClicked(&s->showFPS)){
|
if (isButtonClicked(&s->limitFPS)) {
|
||||||
flags->showFPS = !flags->showFPS;
|
flags->limitFPS = !flags->limitFPS;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isButtonClicked(&s->limitFPS)){
|
if (isButtonClicked(&s->soundtrackOn)) {
|
||||||
flags->limitFPS = !flags->limitFPS;
|
flags->soundtrackOn = !flags->soundtrackOn;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isButtonClicked(&s->soundtrackOn)){
|
if (IsGameActionPressed(ACTION_BACK)) {
|
||||||
flags->soundtrackOn = !flags->soundtrackOn;
|
*menuView = MAIN;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsKeyPressed(KEY_ESCAPE)){
|
if (WindowShouldClose() && !IsGameActionPressed(ACTION_BACK)) {
|
||||||
*menuView = MAIN;
|
state->running = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (WindowShouldClose() && !IsKeyPressed(KEY_ESCAPE)){
|
|
||||||
state->running = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderMenuSettings(MenuSettings *s, Flags *flags)
|
void RenderMenuSettings(MenuSettings *s, Flags *flags) {
|
||||||
{
|
BeginDrawing();
|
||||||
BeginDrawing();
|
ClearBackground(BLACK);
|
||||||
ClearBackground(BLACK);
|
|
||||||
|
|
||||||
renderButton(&s->showFPS);
|
renderButton(&s->showFPS);
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+11
-3
@@ -1,8 +1,8 @@
|
|||||||
#include "app.h"
|
#include "app.h"
|
||||||
|
|
||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+11
-11
@@ -1,17 +1,17 @@
|
|||||||
#include "engine.h"
|
#include "engineAnimation.h"
|
||||||
|
|
||||||
void UpdateAnimation(Animation *animation, float deltaTime) {
|
void UpdateAnimation(Animation *animation, float deltaTime) {
|
||||||
animation->animTimer += deltaTime;
|
animation->animTimer += deltaTime;
|
||||||
float frameTime = 1.0f / animation->framesSpeed;
|
float frameTime = 1.0f / animation->framesSpeed;
|
||||||
|
|
||||||
if (animation->animTimer < frameTime) {
|
if (animation->animTimer < frameTime) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
animation->animTimer -= frameTime;
|
animation->animTimer -= frameTime;
|
||||||
animation->currentFrame++;
|
animation->currentFrame++;
|
||||||
|
|
||||||
if (animation->currentFrame >= animation->maxFrame) {
|
if (animation->currentFrame >= animation->maxFrame) {
|
||||||
animation->currentFrame = 0;
|
animation->currentFrame = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -1,4 +1,5 @@
|
|||||||
#include "engine.h"
|
#include "engineArray.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
Array* CreateArray(unsigned int initCapacity)
|
Array* CreateArray(unsigned int initCapacity)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+22
-21
@@ -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);
|
|
||||||
if (config->rini.count == 0) {
|
void InitConfig(Config *config, const char *path) {
|
||||||
printf("Warning: Loaded config has no entries. Path: %s\n", config->path);
|
config->rini = rini_load(path);
|
||||||
}
|
if (config->rini.count == 0) {
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
-7
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
+94
-13
@@ -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>
|
||||||
{
|
|
||||||
Button b;
|
|
||||||
|
|
||||||
b.rec = (Rectangle){position.x - dimensions.x / 2, position.y - dimensions.y / 2, dimensions.x, dimensions.y};
|
ImageButton CreateImageButton(Texture2D *texture, const TextureAsset *asset, Vector2 *source,
|
||||||
b.text = text;
|
Vector2 *clicked, Vector2 *hover, Rectangle *destRec) {
|
||||||
return b;
|
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};
|
||||||
}
|
}
|
||||||
|
|
||||||
int isButtonClicked(Button *b)
|
ImageElement CreateImageElement(Texture2D *texture, const TextureAsset *asset, Vector2 *source,
|
||||||
{
|
Rectangle *destRec) {
|
||||||
return (CheckCollisionPointRec(GetMousePosition(), b->rec) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON));
|
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}};
|
||||||
}
|
}
|
||||||
|
|
||||||
void renderButton(Button *b)
|
int isImageButtonClicked(ImageButton *b, Sound soundHover, Sound soundClick) {
|
||||||
{
|
if (CheckCollisionPointRec(GetMousePosition(), b->sprite.destRec)) {
|
||||||
GuiButton(b->rec, b->text);
|
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, ¬Clicked, &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;
|
||||||
|
b.rec = (Rectangle){position.x - dimensions.x / 2, position.y - dimensions.y / 2, dimensions.x,
|
||||||
|
dimensions.y};
|
||||||
|
b.text = text;
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
int isButtonClicked(Button *b) {
|
||||||
|
return (CheckCollisionPointRec(GetMousePosition(), b->rec) &&
|
||||||
|
IsMouseButtonPressed(MOUSE_LEFT_BUTTON));
|
||||||
|
}
|
||||||
|
|
||||||
|
void renderButton(Button *b) {
|
||||||
|
GuiButton(b->rec, b->text);
|
||||||
|
}
|
||||||
|
|||||||
+4
-3
@@ -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
@@ -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};
|
||||||
|
|||||||
+35
-29
@@ -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};
|
#include "engineVector.h"
|
||||||
dir = Vector2Normalize(dir);
|
|
||||||
return dir;
|
void UpdatePhysicsDirection(Vector2 *direction, Vector2 vector) {
|
||||||
|
*direction = Vector2Normalize(vector);
|
||||||
}
|
}
|
||||||
|
|
||||||
float GetDistanceBetweenTwoVectors(Vector2 vec1, Vector2 vec2)
|
void UpdatePhysicsVelocity(Vector2 *velocity, Vector2 *direction, float *speed) {
|
||||||
{
|
*velocity = Vector2Scale(*direction, *speed * GetFrameTime());
|
||||||
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 UpdatePhysicsPosition(Vector2 *position, Vector2 *velocity) {
|
||||||
{
|
*position = Vector2Add(*position, *velocity);
|
||||||
*direction = Vector2Normalize(vector);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdatePhysicsVelocity(Vector2 *velocity, Vector2 *direction, float *speed)
|
void UpdatePhysicsSpeed(float *speed, float *newSpeed) {
|
||||||
{
|
*speed = *newSpeed;
|
||||||
*velocity = Vector2Scale(*direction, *speed * GetFrameTime());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdatePhysicsPosition(Vector2 *position, Vector2 *velocity)
|
void UpdatePhysics(Physics *physics, Vector2 direction) {
|
||||||
{
|
UpdatePhysicsDirection(&physics->direction, direction);
|
||||||
*position = Vector2Add(*position, *velocity);
|
UpdatePhysicsVelocity(&physics->velocity, &physics->direction, &physics->speed);
|
||||||
|
UpdatePhysicsPosition(&physics->position, &physics->velocity);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdatePhysicsSpeed(float *speed, float *newSpeed)
|
float GetActualSpeed(Physics *physics) {
|
||||||
{
|
return physics->speed;
|
||||||
*speed = *newSpeed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdatePhysics(Physics *physics, Vector2 direction)
|
CircleCollision CheckCircleCollision(Vector2 posA, float radiusA, Vector2 posB, float radiusB) {
|
||||||
{
|
CircleCollision result = {0};
|
||||||
UpdatePhysicsDirection(&physics->direction, direction);
|
|
||||||
UpdatePhysicsVelocity(&physics->velocity, &physics->direction, &physics->speed);
|
|
||||||
UpdatePhysicsPosition(&physics->position, &physics->velocity);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|||||||
+42
-25
@@ -1,36 +1,53 @@
|
|||||||
#include "engine.h"
|
#include "enginePostfx.h"
|
||||||
|
|
||||||
PostProcessing CreatePostFX(const char* shaderPath){
|
#include <stddef.h>
|
||||||
PostProcessing postFX = {0};
|
|
||||||
postFX.shader = LoadShader(NULL, shaderPath);
|
|
||||||
|
|
||||||
if (postFX.shader.id == 0) {
|
PostProcessing CreatePostFX(const char *shaderPath) {
|
||||||
TraceLog(LOG_ERROR, "Failed to load shader: %s", shaderPath);
|
PostProcessing postFX = {0};
|
||||||
return postFX;
|
postFX.shader = LoadShader(NULL, shaderPath);
|
||||||
}
|
|
||||||
|
|
||||||
postFX.strength = 0.0f;
|
if (postFX.shader.id == 0) {
|
||||||
postFX.resolutionLoc = GetShaderLocation(postFX.shader, "resolution");
|
TraceLog(LOG_ERROR, "Failed to load shader: %s", shaderPath);
|
||||||
postFX.strengthLoc = GetShaderLocation(postFX.shader, "strength");
|
|
||||||
Vector2 resolution = { (float)GetScreenWidth(), (float)GetScreenHeight() };
|
|
||||||
SetShaderValue(postFX.shader, postFX.resolutionLoc, &resolution, SHADER_UNIFORM_VEC2);
|
|
||||||
return postFX;
|
return postFX;
|
||||||
|
}
|
||||||
|
|
||||||
|
postFX.strength = 0.0f;
|
||||||
|
postFX.time = 0.0f;
|
||||||
|
postFX.resolutionLoc = GetShaderLocation(postFX.shader, "resolution");
|
||||||
|
postFX.strengthLoc = GetShaderLocation(postFX.shader, "strength");
|
||||||
|
postFX.timeLoc = GetShaderLocation(postFX.shader, "time");
|
||||||
|
Vector2 resolution = {(float)GetScreenWidth(), (float)GetScreenHeight()};
|
||||||
|
SetShaderValue(postFX.shader, postFX.resolutionLoc, &resolution, SHADER_UNIFORM_VEC2);
|
||||||
|
return postFX;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdatePostFX(PostProcessing *postFX){
|
void UpdatePostFX(PostProcessing *postFX) {
|
||||||
if (postFX->shader.id == 0) return;
|
if (postFX->shader.id == 0)
|
||||||
SetShaderValue(postFX->shader, postFX->strengthLoc, &postFX->strength, SHADER_UNIFORM_FLOAT);
|
return;
|
||||||
Vector2 resolution = { (float)GetScreenWidth(), (float)GetScreenHeight() };
|
SetShaderValue(postFX->shader, postFX->strengthLoc, &postFX->strength, SHADER_UNIFORM_FLOAT);
|
||||||
SetShaderValue(postFX->shader, postFX->resolutionLoc, &resolution, SHADER_UNIFORM_VEC2);
|
Vector2 resolution = {(float)GetScreenWidth(), (float)GetScreenHeight()};
|
||||||
|
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) {
|
||||||
BeginShaderMode(postFX->shader);
|
for (int i = 0; i < count; i++) {
|
||||||
DrawTextureRec(target->texture, (Rectangle){ 0, 0, target->texture.width, -target->texture.height }, (Vector2){ 0, 0 }, WHITE);
|
UpdatePostFX(&postFX[i]);
|
||||||
EndShaderMode();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UnloadPostFX(PostProcessing *postFX){
|
void RenderPostFX(PostProcessing *postFX, RenderTexture2D *target) {
|
||||||
if (postFX->shader.id == 0) return;
|
BeginShaderMode(postFX->shader);
|
||||||
UnloadShader(postFX->shader);
|
DrawTextureRec(target->texture, (Rectangle){0, 0, target->texture.width, -target->texture.height},
|
||||||
|
(Vector2){0, 0}, WHITE);
|
||||||
|
EndShaderMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UnloadPostFX(PostProcessing *postFX) {
|
||||||
|
if (postFX->shader.id == 0)
|
||||||
|
return;
|
||||||
|
UnloadShader(postFX->shader);
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-31
@@ -1,40 +1,36 @@
|
|||||||
#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);
|
return (Vector2){randX, randY};
|
||||||
Vector2 randVector = {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;
|
||||||
}
|
}
|
||||||
|
|
||||||
return pos;
|
return pos;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "engine.h"
|
#include "engineSoundtrack.h"
|
||||||
|
|
||||||
Soundtrack LoadSoundtrack(const char* musicPath){
|
Soundtrack LoadSoundtrack(const char* musicPath){
|
||||||
Soundtrack soundtrack = {
|
Soundtrack soundtrack = {
|
||||||
|
|||||||
+34
-31
@@ -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
@@ -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);
|
|
||||||
TraceLog(LOG_INFO, "FILEIO: [%s] Loaded storage value: %i", fileName, value);
|
|
||||||
}
|
}
|
||||||
return value;
|
|
||||||
|
UnloadFileData(fileData);
|
||||||
|
|
||||||
|
if (success)
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
+35
-9
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user