feat: add wake, rose, terrain, pathfinding, gameover, clouds, enemy shooting, variable bird speed refactor: some things
This commit is contained in:
+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] = {
|
||||
{"assets/island.png", 16.0f, 16.0f, 0.0f}};
|
||||
// How far an island can drift from the camera before it's considered "left
|
||||
// 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) {
|
||||
TextureAsset islandAsset = islandsTextureAssets[ISLAND];
|
||||
Texture2D texture = LoadTexture(islandAsset.path);
|
||||
float frameWidth = islandAsset.frameWidth;
|
||||
float rotation = islandAsset.rotation;
|
||||
// A coastline is two overlapping cosine lobes (a broad one for bays/
|
||||
// peninsulas, a finer one for coastal roughness) plus light jitter, instead
|
||||
// of a single jittered circle — the same kind of shape irregularity a real
|
||||
// procedural terrain generator uses, just far cheaper.
|
||||
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,
|
||||
.frameSize = {frameWidth, frameWidth},
|
||||
.sourceRec = {0.0f, 0.0f, frameWidth, frameWidth},
|
||||
.destRec = {GetScreenWidth() / 2, 0.0f, frameWidth, frameWidth},
|
||||
.origin = {frameWidth / 2, frameWidth / 2},
|
||||
.rotation = rotation,
|
||||
.frameSize = {ISLAND_IMAGE_SIZE, ISLAND_IMAGE_SIZE},
|
||||
.sourceRec = {0.0f, 0.0f, ISLAND_IMAGE_SIZE, ISLAND_IMAGE_SIZE},
|
||||
.destRec = {position.x, position.y, worldDiameter, worldDiameter},
|
||||
.origin = {worldDiameter / 2.0f, worldDiameter / 2.0f},
|
||||
.rotation = 0.0f,
|
||||
.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});
|
||||
UpdateSpriteDestRec(&islands->sprite, &islands->physics.position);
|
||||
UpdateAnimation(&islands->animation, GetFrameTime());
|
||||
UpdateSpriteSourceRec(
|
||||
&islands->sprite,
|
||||
&(Vector2){islands->sprite.frameSize.x * islands->animation.currentFrame,
|
||||
islands->sprite.frameSize.y});
|
||||
for (int i = 0; i < islands.count; i++) {
|
||||
GenerateIsland(&islands.island[i],
|
||||
GetPositionOutsideRectangle(cameraRec, ISLAND_MIN_SPAWN_DISTANCE, ISLAND_MAX_SPAWN_DISTANCE));
|
||||
}
|
||||
|
||||
return islands;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user