Compare commits

...
6 Commits
90 changed files with 4433 additions and 1687 deletions
+11
View File
@@ -0,0 +1,11 @@
BasedOnStyle: Google
IndentWidth: 2
ColumnLimit: 100
BreakBeforeBraces: Attach
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
DerivePointerAlignment: false
PointerAlignment: Right
SpaceBeforeParens: ControlStatements
SortIncludes: false
+1
View File
@@ -1,3 +1,4 @@
bin/
build/
compile_commands.json
*.data
+55 -14
View File
@@ -9,6 +9,7 @@ C_FLAG_PEDANTIC = -Wpedantic # Enforce ISO C standard stri
C_FLAG_SHADOW = -Wshadow # Warn if a local variable shadows another
C_FLAG_STRICT_PROTOS = -Wstrict-prototypes # Warn if function declarations are missing parameter types
C_FLAG_NO_UNKNOWN_PRAGMAS = -Wno-unknown-pragmas # Suppress warnings about unknown pragmas
C_FLAG_DEPS = -MMD -MP # Emit per-file header dependency files for incremental rebuilds
C_FLAGS = \
$(C_FLAG_STD_C99) \
@@ -18,7 +19,12 @@ C_FLAGS = \
$(C_FLAG_PEDANTIC) \
$(C_FLAG_SHADOW) \
$(C_FLAG_STRICT_PROTOS) \
$(C_FLAG_NO_UNKNOWN_PRAGMAS)
$(C_FLAG_NO_UNKNOWN_PRAGMAS) \
$(C_FLAG_DEPS)
# BUILD-MODE FLAGS
RELEASE_FLAGS = -O2
DEBUG_FLAGS = -O0 -DDEBUG -fsanitize=address,undefined -fno-omit-frame-pointer
# INCLUDE FLGAS
INCLUDE_FLAGS = \
@@ -36,28 +42,63 @@ EXTERNAL_FLAGS = \
# CROSS PLATTFORM
ifeq ($(OS), Windows_NT)
SRC = $(wildcard src\*.c)
TARGET = bin\win\game.exe
RAYLIB_CFLAGS = -I%RAYLIB%
BIN_DIR = bin\win
RAYLIB_CFLAGS = -I%RAYLIB%
RAYLIB_LIBS = -L%RAYLIB% -lraylib -lopengl32 -lgdi32 -lwinmm
EXE_EXT = .exe
else
SRC = $(wildcard src/*.c)
TARGET = bin/unix/game
BIN_DIR = bin/unix
RAYLIB_CFLAGS = $(shell pkg-config --cflags raylib)
RAYLIB_LIBS = $(shell pkg-config --libs raylib) -lm
EXE_EXT =
endif
# CREATE BIN DIR
$(shell mkdir -p bin/unix bin/win)
TARGET_RELEASE = $(BIN_DIR)/game$(EXE_EXT)
TARGET_DEBUG = $(BIN_DIR)/game-debug$(EXE_EXT)
# PER-FILE OBJECTS (one .o per .c, only stale ones get rebuilt)
OBJ_DIR_RELEASE = build/release
OBJ_DIR_DEBUG = build/debug
OBJS_RELEASE = $(patsubst src/%.c,$(OBJ_DIR_RELEASE)/%.o,$(SRC))
OBJS_DEBUG = $(patsubst src/%.c,$(OBJ_DIR_DEBUG)/%.o,$(SRC))
DEPS_RELEASE = $(OBJS_RELEASE:.o=.d)
DEPS_DEBUG = $(OBJS_DEBUG:.o=.d)
# COMMANDS
build: $(SRC)
$(COMPILER) $(SRC) -o $(TARGET) \
$(C_FLAGS) \
$(INCLUDE_FLAGS) \
$(RAYLIB_LIBS)
build: $(TARGET_RELEASE)
debug: $(TARGET_DEBUG)
$(TARGET_RELEASE): $(OBJS_RELEASE) | $(BIN_DIR)
$(COMPILER) $(OBJS_RELEASE) -o $(TARGET_RELEASE) $(RAYLIB_LIBS)
$(TARGET_DEBUG): $(OBJS_DEBUG) | $(BIN_DIR)
$(COMPILER) $(DEBUG_FLAGS) $(OBJS_DEBUG) -o $(TARGET_DEBUG) $(RAYLIB_LIBS)
$(OBJ_DIR_RELEASE)/%.o: src/%.c | $(OBJ_DIR_RELEASE)
$(COMPILER) $(C_FLAGS) $(RELEASE_FLAGS) $(INCLUDE_FLAGS) -c $< -o $@
$(OBJ_DIR_DEBUG)/%.o: src/%.c | $(OBJ_DIR_DEBUG)
$(COMPILER) $(C_FLAGS) $(DEBUG_FLAGS) $(INCLUDE_FLAGS) -c $< -o $@
# DIRECTORIES ARE ORDER-ONLY PREREQUISITES SO `clean` CAN REMOVE THEM WITHOUT
# RACING A CHAINED `build`/`debug` IN THE SAME INVOCATION (e.g. `make clean build`)
$(OBJ_DIR_RELEASE) $(OBJ_DIR_DEBUG) $(BIN_DIR):
mkdir -p $@
-include $(DEPS_RELEASE)
-include $(DEPS_DEBUG)
clean:
rm -rf $(TARGET)
rm -rf bin build
run: clean build
./$(TARGET)
run: build
./$(TARGET_RELEASE)
run-debug: debug
./$(TARGET_DEBUG)
.PHONY: build debug clean run run-debug
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 558 KiB

+396 -223
View File
File diff suppressed because it is too large Load Diff
+18 -263
View File
@@ -1,202 +1,21 @@
#ifndef APPTYPES_H
#define APPTYPES_H
#ifndef APP_H
#define APP_H
// INCLUDES//
#include <engine.h>
#include <raylib.h>
// DEFINES//
#define TEXT_COLOR_DARK (Color){182, 137, 98, 255}
#define TEXT_COLOR_LIGHT (Color){243, 229, 194, 255}
#define STORAGE_DATA_FILE "save1.data"
#include "appCore.h"
#include "collisions.h"
#include "engineAssets.h"
#include "engineCamera.h"
#include "engineConfig.h"
#include "engineFont.h"
#include "engineWindow.h"
#include "game.h"
#include "gameover.h"
#include "logo.h"
#include "menu.h"
// ENUMS//
typedef enum {
MENU_CURSOR_TEX,
MENU_BIG_BUTTONS_TEX,
MENU_BOARD_TEX,
MENU_CLOUD_BACKGROUND,
MENU_CLOUD_MIDGROUND,
MENU_CLOUD_FOREGROUND,
MENU_TEX_COUNT
} MenuTextureID;
typedef enum { MENU_MUSIC, MENU_SOUNDTRACK_COUNT } MenuSoundtrackID;
typedef enum { HOVER_SOUND, CLICK_SOUND, MENU_SOUND_COUNT } MenuSoundID;
typedef enum MenuView { MAIN = 0, SETTINGS = 1 } MenuView;
typedef enum { START_SHIP, PLAYER_TEX_COUNT } playerTextureID;
typedef enum { ANCHOR, SLOW_AHEAD, FAST_AHEAD, SPEED_COUNT } playerSpeedID;
typedef enum { ISLAND, ISLANDS_TEX_COUNT } islandsTextureID;
typedef enum { POST_FX, SHADER_COUNT } ShaderID;
typedef enum { GAMEPLAY_THEME, AMBIENT, SOUNDTRACK_COUNT } SoundtrackID;
typedef enum View { LOGO = 0, MENU = 1, GAMEPLAY = 2 } View;
typedef enum {
STORAGE_POSITION_GOLD = 0,
STORAGE_POSITION_POSX = 1,
STORAGE_POSITION_POSY = 2,
STORAGE_POSITION_ROTATION = 3
} StorageData;
// STRUCTS//
typedef struct {
int frameCounter;
int positionX;
int positionY;
int lettersCount;
int topSideRecWidth;
int leftSideRecHeight;
int bottomSideRecWidth;
int rightSideRecHeight;
int state;
float alpha;
} LogoState;
typedef struct {
Sprite sprite;
float scrolling;
float scale;
float scrollSpeed;
Vector2 pos;
} Background;
typedef struct {
Assets assets;
Mouse mouse;
Texture2D textures[MENU_TEX_COUNT];
Sound sounds[MENU_SOUND_COUNT];
Soundtrack soundtracks[MENU_SOUNDTRACK_COUNT];
ImageElement board;
ImageButton playButton;
ImageButton quitButton;
ImageButton settingsButton;
int menuScale;
Background background;
Background midground;
Background foreground;
} MenuMain;
typedef struct {
Button showFPS;
Button limitFPS;
Button soundtrackOn;
} MenuSettings;
typedef struct {
MenuView menuView;
MenuMain menuMain;
MenuSettings menuSettings;
} MenuState;
typedef struct {
Sprite sprite;
Rectangle bounds;
Shader shader;
float time;
} Floor;
typedef struct {
Sprite sprite;
Animation animation;
Physics physics;
float rotation;
Sound soundFlapping;
int health;
int gold;
} Player;
typedef enum { BOAT, ENEMIES_TEX_COUNT } enemiesTextureID;
typedef enum { START, SHIP_COUNT } shipID;
typedef struct {
int id;
char *assetPath;
int frameSize;
int speed0;
int speed1;
int speed2;
int rotationSpeed;
} Ship;
#define MAX_ENEMIES 100
typedef struct {
Config config;
int count;
Sprite sprite[MAX_ENEMIES];
Animation animation[MAX_ENEMIES];
Physics physics[MAX_ENEMIES];
bool arrived[MAX_ENEMIES];
Vector2 destPos[MAX_ENEMIES];
bool alive[MAX_ENEMIES];
} Enemies;
typedef enum { FLYING, BIRDS_TEX_COUNT } birdsTextureID;
#define MAX_BIRDS 100
typedef struct {
int count;
Sprite sprite[MAX_BIRDS];
Animation animation[MAX_BIRDS];
Physics physics[MAX_BIRDS];
bool arrived[MAX_BIRDS];
Vector2 destPos[MAX_BIRDS];
} Birds;
typedef struct {
Sprite sprite;
Animation animation;
Physics physics;
} Islands;
#define MAX_PROJECTILES 100
typedef struct {
int count;
Physics physics[MAX_PROJECTILES];
bool active[MAX_PROJECTILES];
float radius[MAX_PROJECTILES];
Color color[MAX_PROJECTILES];
Sound soundShooting[MAX_PROJECTILES];
Sound soundImpact[MAX_PROJECTILES];
} Projectiles;
typedef struct {
Assets assets;
PostProcessing postFX[SHADER_COUNT];
Soundtrack soundtracks[SOUNDTRACK_COUNT];
RenderTexture2D target;
Floor floor;
Birds birds;
Enemies enemies;
Islands islands;
Mouse mouse;
Image image;
Projectiles projectiles;
} GameState;
typedef struct {
View currentView;
bool running;
bool gameStarted;
} State;
typedef struct {
bool showFPS;
bool limitFPS;
bool soundtrackOn;
} Flags;
typedef struct {
struct App {
Config config;
State state;
Flags flags;
@@ -205,75 +24,11 @@ typedef struct {
LogoState logo;
MenuState menu;
GameState game;
} App;
// FUNCTIONS//
void InitLogo(LogoState *l);
void UpdateLogo(LogoState *l, View *currentView, bool *running);
void RenderLogo(const LogoState *l, Flags *flags);
void InitBackground(float scrollSpeed, Background *bg, Texture2D *texture,
const TextureAsset *backgroundAsset);
void UpdateBackground(Background *bg);
void RenderBackground(Background *bg);
void InitMenuMain(MenuMain *m);
void UpdateMenuMain(MenuMain *m, State *state, Flags *flags,
MenuView *menuView);
void RenderMenuMain(MenuMain *m, Font *font, const char *title, Flags *flags);
void CleanupMenuMain(MenuMain *m);
void InitMenuSettings(MenuSettings *s);
void UpdateMenuSettings(MenuSettings *s, MenuView *menuView, State *state,
Flags *flags);
void RenderMenuSettings(MenuSettings *s, Flags *flags);
void CleanupMenuSettings(MenuSettings *s);
void InitMenu(MenuState *m);
void UpdateMenu(MenuState *m, State *state, Flags *flags);
void RenderMenu(MenuState *m, Font *font, const char *title, Flags *flags);
void CleanupMenu(MenuState *m);
Floor CreateFloor(void);
void UpdateFloor(Floor *floor);
void RenderFloor(Floor *floor);
void UnloadFloor(Floor *floor);
playerSpeedID GetPlayerSpeedID(float speed);
Player *GetPlayer(void);
void CreatePlayer(void);
void UpdatePlayer(void);
void RenderPlayer(void);
Enemies CreateEnemies(void);
void UpdateEnemies(Enemies *enemies);
void RenderEnemies(Enemies *enemies);
Birds CreateBirds(void);
void UpdateBirds(Birds *birds);
void RenderBirds(Birds *birds);
Islands CreateIslands(void);
void UpdateIslands(Islands *islands);
void RenderIslands(Islands *islands);
Projectiles CreateProjectiles(void);
void UpdateProjectiles(Projectiles *projectiles, Enemies *enemies);
void RenderProjectiles(Projectiles *projectiles);
void CheckPlayerCollisions(Player *player, Enemies *enemies);
void CheckEnemyCollisions(Enemies *enemies);
void CheckProjectileCollisions(Projectiles *projectiles, Enemies *enemies,
Player *player);
void InitGame(App *app);
void UpdateGame(App *app);
void RenderComponents(App *app);
void RenderOverlay(Mouse *mouse);
void RenderGame(App *app);
void CleanupGame(App *app);
GameOverState gameOver;
};
void OnEscapeChangeView(App *app, View view);
void OnBoolChangeView(bool flag, App *app, View view);
void OnWindowClosedStoppApp(App *app);
void ConfigApp(App *app);
@@ -281,4 +36,4 @@ void InitApp(App *app);
void UpdateApp(App *app);
void CleanupApp(App *app);
#endif // APPTYPES_H
#endif
+42
View File
@@ -0,0 +1,42 @@
#ifndef APP_CORE_H
#define APP_CORE_H
#include <raylib.h>
#include <stdbool.h>
typedef struct App App;
#define TEXT_COLOR_DARK (Color){182, 137, 98, 255}
#define TEXT_COLOR_LIGHT (Color){243, 229, 194, 255}
#define STORAGE_DATA_FILE "save1.data"
typedef enum View { LOGO = 0, MENU = 1, GAMEPLAY = 2, GAMEOVER = 3 } View;
typedef enum {
ACTION_FORWARD,
ACTION_BACKWARD,
ACTION_TURN_LEFT,
ACTION_TURN_RIGHT,
ACTION_FIRE_PORT,
ACTION_FIRE_STARBOARD,
ACTION_BACK,
ACTION_COUNT
} GameAction;
typedef struct {
View currentView;
bool running;
bool gameStarted;
bool restartRequested;
} State;
typedef struct {
bool showFPS;
bool limitFPS;
bool soundtrackOn;
} Flags;
bool IsGameActionPressed(GameAction action);
bool IsGameActionDown(GameAction action);
#endif
+28
View File
@@ -0,0 +1,28 @@
#ifndef BIRDS_H
#define BIRDS_H
#include <raylib.h>
#include <stdbool.h>
#include "engineAnimation.h"
#include "enginePhysics.h"
#include "engineSprite.h"
typedef enum { FLYING, BIRDS_TEX_COUNT } BirdsTextureID;
#define MAX_BIRDS 100
typedef struct {
int count;
Sprite sprite[MAX_BIRDS];
Animation animation[MAX_BIRDS];
Physics physics[MAX_BIRDS];
bool arrived[MAX_BIRDS];
Vector2 destPos[MAX_BIRDS];
} Birds;
Birds CreateBirds(Camera2D *camera);
void UpdateBirds(Birds *birds, Camera2D *camera);
void RenderBirds(Birds *birds);
#endif
+18
View File
@@ -0,0 +1,18 @@
#ifndef CLOUDS_H
#define CLOUDS_H
#include <raylib.h>
typedef struct {
Shader shader;
float time;
float coverage;
} Clouds;
Clouds CreateClouds(void);
void UpdateClouds(Clouds *clouds, float deltaTime);
void RenderCloudShadows(Clouds *clouds, Camera2D *camera);
void RenderClouds(Clouds *clouds, Camera2D *camera);
void UnloadClouds(Clouds *clouds);
#endif
+15
View File
@@ -0,0 +1,15 @@
#ifndef COLLISIONS_H
#define COLLISIONS_H
#include "player.h"
#include "enemies.h"
#include "terrain.h"
#include "projectiles.h"
void CheckPlayerCollisions(Player *player, Enemies *enemies);
void CheckEnemyCollisions(Enemies *enemies);
void CheckProjectileCollisions(Projectiles *projectiles, Enemies *enemies,
Player *player);
void CheckTerrainCollisions(Player *player, Enemies *enemies, Terrain *terrain);
#endif // COLLISIONS_H
+43
View File
@@ -0,0 +1,43 @@
#ifndef ENEMIES_H
#define ENEMIES_H
#include <raylib.h>
#include <stdbool.h>
#include "engineAnimation.h"
#include "enginePhysics.h"
#include "engineSprite.h"
#include "pathfinding.h"
#include "player.h"
#include "projectiles.h"
#include "terrain.h"
#include "wake.h"
typedef enum { BOAT, ENEMIES_TEX_COUNT } EnemiesTextureID;
#define MAX_ENEMIES 100
typedef struct {
int count;
Sprite sprite[MAX_ENEMIES];
Animation animation[MAX_ENEMIES];
Physics physics[MAX_ENEMIES];
bool arrived[MAX_ENEMIES];
Vector2 destPos[MAX_ENEMIES];
Vector2 path[MAX_ENEMIES][PATH_MAX_WAYPOINTS];
int pathLength[MAX_ENEMIES];
int pathIndex[MAX_ENEMIES];
bool alive[MAX_ENEMIES];
float reloadTimer[MAX_ENEMIES];
float destinationCooldown[MAX_ENEMIES];
bool chasing[MAX_ENEMIES];
float chaseRepathTimer[MAX_ENEMIES];
WakeTrail wakeTrail[MAX_ENEMIES];
} Enemies;
Enemies CreateEnemies(Camera2D *camera, Terrain *terrain);
void UpdateEnemies(Enemies *enemies, Camera2D *camera, Projectiles *projectiles, Player *player,
Terrain *terrain);
void RenderEnemies(Enemies *enemies);
#endif
+26 -9
View File
@@ -118,6 +118,13 @@ typedef struct {
float weight;
} Physics;
typedef struct {
bool overlapping;
bool hasDirection; // false when centers coincide, direction/overlap unset
Vector2 direction; // normalized, points from B to A
float overlap;
} CircleCollision;
typedef struct {
Shader shader;
int resolutionLoc;
@@ -152,13 +159,12 @@ void UpdateArray(Array *array, void (*updateFunc)(void *));
void RenderArray(Array *array, void (*renderFunc)(void *));
void FreeArray(Array **array);
Camera2D *GetCamera(void);
Rectangle GetCameraRectangle(void);
void CreateCamera(Vector2 playerPosition, float initZoom);
void UpdateCameraTarget(Vector2 *position, const float smoothness);
void UpdateCameraZoom(const float *scaleFactor, const float *minZoom,
Rectangle GetCameraRectangle(Camera2D *camera);
Camera2D CreateCamera(Vector2 playerPosition, float initZoom);
void UpdateCameraTarget(Camera2D *camera, Vector2 *position, const float smoothness);
void UpdateCameraZoom(Camera2D *camera, const float *scaleFactor, const float *minZoom,
const float *maxZoom);
void UpdateCamera2D(Vector2 *position, Mouse *mouse);
void UpdateCamera2D(Camera2D *camera, Vector2 *position, Mouse *mouse);
void InitConfig(Config *config, const char *path);
int GetConfigInt(Config *config, const char *key);
@@ -182,6 +188,10 @@ ImageButton CreateImageButton(ButtonState state, Texture2D *texture,
const TextureAsset *asset, Vector2 *source,
Vector2 *clicked, Vector2 *hover,
Rectangle *destRec);
ImageButton CreateMenuButton(Texture2D *texture, const TextureAsset *asset,
int textureRow, int screenRank, int buttonWidth,
int buttonHeight, Vector2 windowCenter,
int buttonSpacing);
ImageElement CreateImageElement(Texture2D *texture, const TextureAsset *asset,
Vector2 *source, Rectangle *destRec);
int isImageButtonClicked(ImageButton *b, Sound soundHover, Sound soundClick);
@@ -195,7 +205,7 @@ void renderButton(Button *b);
Mouse CreateMouse(float zoomSpeed, float minZoom, float maxZoom,
Texture2D *texture);
void UpdateMouseScreen(Mouse *mouse);
void UpdateMouse(Mouse *mouse);
void UpdateMouse(Mouse *mouse, Camera2D *camera);
Vector2 GetDirectionVector(void);
ObjectArray CreateObjectArray(const Object *prototype, const size_t count);
@@ -220,6 +230,9 @@ void UpdatePhysicsPosition(Vector2 *position, Vector2 *velocity);
void UpdatePhysicsSpeed(float *speed, float *newSpeed);
void UpdatePhysics(Physics *physics, Vector2 direction);
CircleCollision CheckCircleCollision(Vector2 posA, float radiusA,
Vector2 posB, float radiusB);
PostProcessing CreatePostFX(const char *shaderPath);
void UpdatePostFX(PostProcessing *postFX);
void RenderPostFX(PostProcessing *postFX, RenderTexture2D *target);
@@ -243,7 +256,11 @@ float Cross2D(Vector2 a, Vector2 b);
Vector2 GetPositionInsideRectangle(Rectangle rec);
Vector2 GetPositionOutsideRectangle(Rectangle rec, int min, int max);
bool SaveStorageValue(const char *fileName, unsigned int position, int value);
int LoadStorageValue(const char *fileName, unsigned int position);
bool SaveStorageData(const char *fileName, int version, const void *data, unsigned int dataSize);
bool LoadStorageData(const char *fileName, int expectedVersion, void *data, unsigned int dataSize);
Texture2D LoadTextureCached(const char *path);
Sound LoadSoundCached(const char *path);
void UnloadCachedAssets(void);
#endif // ENGINE_H
+15
View File
@@ -0,0 +1,15 @@
#ifndef ENGINE_ANIMATION_H
#define ENGINE_ANIMATION_H
typedef struct {
int state;
int currentFrame;
int maxFrame;
int framesCounter;
int framesSpeed;
float animTimer;
} Animation;
void UpdateAnimation(Animation *animation, float deltaTime);
#endif
+50
View File
@@ -0,0 +1,50 @@
#ifndef ENGINE_ARRAY_H
#define ENGINE_ARRAY_H
#include "engineSprite.h"
#include "enginePhysics.h"
#include "engineVector.h"
#include <raylib.h>
#include <stddef.h>
typedef struct {
void **elements;
unsigned int count;
unsigned int capacity;
} Array;
typedef struct {
Sprite sprite;
Vector2 position;
} Object;
typedef struct {
Object *data;
const Object *prototype;
size_t count;
} ObjectArray;
Array *CreateArray(unsigned int initCapacity);
int AddElementToArray(Array *array, void *element);
int RemoveElementFromArray(Array *array, unsigned int index);
void *GetRandomElementFromArray(Array *array);
void UpdateArray(Array *array, void (*updateFunc)(void *));
void RenderArray(Array *array, void (*renderFunc)(void *));
void FreeArray(Array **array);
ObjectArray CreateObjectArray(const Object *prototype, const size_t count);
Object *GetRandomObject(ObjectArray *objects);
void AddObjectToArray(ObjectArray *objects, const Object *prototype,
Vector2 position);
void SetPositionForObjectArray(ObjectArray *objects,
const Vector2Array *positions);
void SetRandomPositionForObjectArray(ObjectArray *objects,
const Rectangle bounds);
void SwapAndPopObjectsArrayOnRecCollision(ObjectArray *objects,
const Rectangle rec);
void SwapAndPopObjectsArrayOnPointCollision(ObjectArray *objects,
const Vector2 point);
Vector2 GetDirectionToObject(Object *object, Vector2 position);
void RenderObjectArray(const ObjectArray *objects);
#endif // ENGINE_ARRAY_H
+10
View File
@@ -0,0 +1,10 @@
#ifndef ENGINE_ASSETS_H
#define ENGINE_ASSETS_H
#include <raylib.h>
Texture2D LoadTextureCached(const char *path);
Sound LoadSoundCached(const char *path);
void UnloadCachedAssets(void);
#endif // ENGINE_ASSETS_H
+14
View File
@@ -0,0 +1,14 @@
#ifndef ENGINE_CAMERA_H
#define ENGINE_CAMERA_H
#include "engineInput.h"
#include <raylib.h>
Rectangle GetCameraRectangle(Camera2D *camera);
Camera2D CreateCamera(Vector2 playerPosition, float initZoom);
void UpdateCameraTarget(Camera2D *camera, Vector2 *position, const float smoothness);
void UpdateCameraZoom(Camera2D *camera, const float *scaleFactor, const float *minZoom,
const float *maxZoom);
void UpdateCamera2D(Camera2D *camera, Vector2 *position, Mouse *mouse);
#endif // ENGINE_CAMERA_H
+25
View File
@@ -0,0 +1,25 @@
#ifndef ENGINE_CONFIG_H
#define ENGINE_CONFIG_H
#include <stdbool.h>
#include <rini.h>
typedef struct {
char *path;
rini_data rini;
} Config;
void InitConfig(Config *config, const char *path);
int GetConfigInt(Config *config, const char *key);
int GetConfigIntOrDefault(Config *config, const char *key, int default_value);
const char *GetConfigString(Config *config, const char *key);
const char *GetConfigStringOrDefault(Config *config, const char *key, const char *default_value);
const char *GetConfigDescription(Config *config, const char *key);
int SetConfigInt(Config *config, const char *key, int value, const char *desc);
int SetConfigString(Config *config, const char *key, const char *text, const char *desc);
int SetConfigDescription(Config *config, const char *key, const char *desc);
int AddCommentLine(Config *config, const char *comment);
void SaveConfig(Config *config);
void CleanUpConfig(Config *config);
#endif
+9
View File
@@ -0,0 +1,9 @@
#ifndef ENGINE_FONT_H
#define ENGINE_FONT_H
#include <raylib.h>
void InitFont(Font *font, const char *path);
void CleanupFont(Font *font);
#endif
+42
View File
@@ -0,0 +1,42 @@
#ifndef ENGINE_GUI_H
#define ENGINE_GUI_H
#include <raylib.h>
#include <stdbool.h>
#include "engineSprite.h"
typedef struct {
Sprite sprite;
Vector2 notClicked;
Vector2 hover;
Vector2 clicked;
bool isHovered;
bool isClicked;
} ImageButton;
typedef struct {
Sprite sprite;
} ImageElement;
typedef struct {
Rectangle rec;
char *text;
} Button;
ImageButton CreateImageButton(Texture2D *texture, const TextureAsset *asset, Vector2 *source,
Vector2 *clicked, Vector2 *hover, Rectangle *destRec);
ImageButton CreateMenuButton(Texture2D *texture, const TextureAsset *asset, int textureRow,
int screenRank, int buttonWidth, int buttonHeight,
Vector2 windowCenter, int buttonSpacing);
ImageElement CreateImageElement(Texture2D *texture, const TextureAsset *asset, Vector2 *source,
Rectangle *destRec);
int isImageButtonClicked(ImageButton *b, Sound soundHover, Sound soundClick);
void renderImageButton(ImageButton *b);
void renderImageElement(ImageElement *e);
Button initButton(char *text, Vector2 dimensions, Vector2 position);
int isButtonClicked(Button *b);
void renderButton(Button *b);
#endif
+25
View File
@@ -0,0 +1,25 @@
#ifndef ENGINE_INPUT_H
#define ENGINE_INPUT_H
#include <raylib.h>
typedef struct {
Vector2 screenPosition;
Vector2 worldPosition;
float zoomSpeed;
float minZoom;
float maxZoom;
float scaleFactor;
float wheelMove;
bool leftButton;
bool rightButton;
Texture2D cursorTexture;
} Mouse;
Mouse CreateMouse(float zoomSpeed, float minZoom, float maxZoom,
Texture2D *texture);
void UpdateMouseScreen(Mouse *mouse);
void UpdateMouse(Mouse *mouse, Camera2D *camera);
Vector2 GetDirectionVector(void);
#endif // ENGINE_INPUT_H
+31
View File
@@ -0,0 +1,31 @@
#ifndef ENGINE_PHYSICS_H
#define ENGINE_PHYSICS_H
#include <raylib.h>
#include <stdbool.h>
typedef struct {
Vector2 position;
Vector2 direction;
Vector2 velocity;
float speed;
float weight;
} Physics;
typedef struct {
bool overlapping;
bool hasDirection;
Vector2 direction;
float overlap;
} CircleCollision;
void UpdatePhysicsDirection(Vector2 *direction, Vector2 vector);
void UpdatePhysicsVelocity(Vector2 *velocity, Vector2 *direction, float *speed);
void UpdatePhysicsPosition(Vector2 *position, Vector2 *velocity);
void UpdatePhysicsSpeed(float *speed, float *newSpeed);
void UpdatePhysics(Physics *physics, Vector2 direction);
float GetActualSpeed(Physics *physics);
CircleCollision CheckCircleCollision(Vector2 posA, float radiusA, Vector2 posB, float radiusB);
#endif
+21
View File
@@ -0,0 +1,21 @@
#ifndef ENGINE_POSTFX_H
#define ENGINE_POSTFX_H
#include <raylib.h>
typedef struct {
Shader shader;
int resolutionLoc;
int strengthLoc;
int timeLoc;
float strength;
float time;
} PostProcessing;
PostProcessing CreatePostFX(const char *shaderPath);
void UpdatePostFX(PostProcessing *postFX);
void UpdateShaders(PostProcessing *postFX, int count);
void RenderPostFX(PostProcessing *postFX, RenderTexture2D *target);
void UnloadPostFX(PostProcessing *postFX);
#endif // ENGINE_POSTFX_H
+9
View File
@@ -0,0 +1,9 @@
#ifndef ENGINE_RECTANGLE_H
#define ENGINE_RECTANGLE_H
#include <raylib.h>
Vector2 GetPositionInsideRectangle(Rectangle rec);
Vector2 GetPositionOutsideRectangle(Rectangle rec, int min, int max);
#endif
+16
View File
@@ -0,0 +1,16 @@
#ifndef ENGINE_SOUNDTRACK_H
#define ENGINE_SOUNDTRACK_H
#include <raylib.h>
#include <stdbool.h>
typedef struct {
Music music;
bool playing;
bool paused;
} Soundtrack;
Soundtrack LoadSoundtrack(const char *musicPath);
void UpdateSoundtrack(Soundtrack *soundtrack, bool playing);
#endif // ENGINE_SOUNDTRACK_H
+49
View File
@@ -0,0 +1,49 @@
#ifndef ENGINE_SPRITE_H
#define ENGINE_SPRITE_H
#include <raylib.h>
typedef struct {
Texture2D texture;
Rectangle sourceRec;
Rectangle destRec;
Vector2 frameSize;
Vector2 origin;
float rotation;
Color color;
} Sprite;
typedef struct {
const char *path;
float frameWidth;
float frameHeight;
float rotation;
} TextureAsset;
typedef struct {
const char *path;
} ShaderAsset;
typedef struct {
const char *path;
} SoundtrackAsset;
typedef struct {
const char *path;
} SoundAsset;
typedef struct {
const TextureAsset *textureAssets;
const ShaderAsset *shaderAssets;
const SoundtrackAsset *soundtrackAssets;
const SoundAsset *soundAssets;
} Assets;
void UpdateSpriteSourceRec(Sprite *sprite, const Vector2 *vector);
void UpdateSpriteDestRec(Sprite *sprite, const Vector2 *vector);
void UpdateSpriteRotation(Sprite *sprite, float rotation);
void UpdateSpriteRotationTowards(Sprite *sprite, float rotation, float degreesPerSecond,
float deltaTime);
void RenderSprite(const Sprite *sprite);
#endif
+9
View File
@@ -0,0 +1,9 @@
#ifndef ENGINE_STORAGE_H
#define ENGINE_STORAGE_H
#include <stdbool.h>
bool SaveStorageData(const char *fileName, int version, const void *data, unsigned int dataSize);
bool LoadStorageData(const char *fileName, int expectedVersion, void *data, unsigned int dataSize);
#endif // ENGINE_STORAGE_H
+22
View File
@@ -0,0 +1,22 @@
#ifndef ENGINE_VECTOR_H
#define ENGINE_VECTOR_H
#include <raylib.h>
#include <stdbool.h>
#include <stddef.h>
typedef struct {
Vector2 *data;
size_t count;
} Vector2Array;
Vector2 GetDirectionBetweenTwoVectors(Vector2 vec1, Vector2 vec2);
float GetDistanceBetweenTwoVectors(Vector2 vec1, Vector2 vec2);
bool IsVectorZero(Vector2 v);
Vector2 GetRandomVector(const Rectangle bounds);
Vector2 RotationToVector2(float rotation);
float Vector2ToRotation(Vector2 vec);
float Cross2D(Vector2 a, Vector2 b);
void ApplyCollisionSteer(float *rotation, float side, float deltaTime);
#endif
+10
View File
@@ -0,0 +1,10 @@
#ifndef ENGINE_WINDOW_H
#define ENGINE_WINDOW_H
typedef struct {
int width;
int height;
const char *title;
} Window;
#endif // ENGINE_WINDOW_H
+26
View File
@@ -0,0 +1,26 @@
#ifndef FLOOR_H
#define FLOOR_H
#include <raylib.h>
#include "engineSprite.h"
typedef struct {
Sprite sprite;
Rectangle bounds;
Shader shader;
float time;
Texture2D causticTexture;
Texture2D causticHighlightTexture;
Texture2D noiseTexture;
Texture2D cloudTexture;
Texture2D voronoiseTexture;
Texture2D depthGradientTexture;
} Floor;
Floor CreateFloor(void);
void UpdateFloor(Floor *floor, Camera2D *camera);
void RenderFloor(Floor *floor);
void UnloadFloor(Floor *floor);
#endif
+51
View File
@@ -0,0 +1,51 @@
#ifndef GAME_H
#define GAME_H
#include <raylib.h>
#include "appCore.h"
#include "birds.h"
#include "clouds.h"
#include "enemies.h"
#include "engineInput.h"
#include "enginePostfx.h"
#include "engineSoundtrack.h"
#include "engineSprite.h"
#include "floor.h"
#include "player.h"
#include "projectiles.h"
#include "terrain.h"
#include "wake.h"
#include "overlay.h"
typedef enum { POST_FX, SHADER_COUNT } ShaderID;
typedef enum { GAMEPLAY_THEME, AMBIENT, SOUNDTRACK_COUNT } SoundtrackID;
typedef struct {
Assets assets;
PostProcessing postFX[SHADER_COUNT];
Soundtrack soundtracks[SOUNDTRACK_COUNT];
RenderTexture2D target;
RenderTexture2D postFXTarget;
Camera2D camera;
Player player;
Floor floor;
Clouds clouds;
Wake wake;
Birds birds;
Enemies enemies;
Terrain terrain;
Mouse mouse;
Image image;
Projectiles projectiles;
Overlay overlay;
} GameState;
void InitGame(App *app);
void UpdateGame(App *app);
void RenderComponents(App *app);
void RenderGame(App *app);
void ResetGame(App *app);
void CleanupGame(App *app);
#endif
+15
View File
@@ -0,0 +1,15 @@
#ifndef GAMEOVER_H
#define GAMEOVER_H
#include "appCore.h"
#include "engineGui.h"
typedef struct {
Button restartButton;
} GameOverState;
void InitGameOver(GameOverState *g);
void UpdateGameOver(App *app);
void RenderGameOver(App *app);
#endif
+31
View File
@@ -0,0 +1,31 @@
#ifndef ISLANDS_H
#define ISLANDS_H
#include "engineSprite.h"
#include <raylib.h>
#define MAX_ISLANDS 14
// Perimeter samples used to build each island's procedural shape (see
// GenerateIsland in appGameIslands.c). Higher than you'd need for the old
// small islands since they're now rendered at much higher native resolution
// and a coarser sample count would show as flat facets on the coastline.
#define ISLAND_SHAPE_POINTS 28
typedef struct {
Vector2 position;
float radius; // collision radius (outer shore edge), in world units
Sprite sprite; // a small procedurally-painted texture, point-filtered like
// the rest of the cast, not a shader effect
} Island;
typedef struct {
int count;
Island island[MAX_ISLANDS];
} Islands;
Islands CreateIslands(Camera2D *camera);
void UpdateIslands(Islands *islands, Camera2D *camera);
void RenderIslands(Islands *islands);
void UnloadIslands(Islands *islands);
#endif // ISLANDS_H
+24
View File
@@ -0,0 +1,24 @@
#ifndef LOGO_H
#define LOGO_H
#include "appCore.h"
#include <stdbool.h>
typedef struct {
int frameCounter;
int positionX;
int positionY;
int lettersCount;
int topSideRecWidth;
int leftSideRecHeight;
int bottomSideRecWidth;
int rightSideRecHeight;
int state;
float alpha;
} LogoState;
void InitLogo(LogoState *l);
void UpdateLogo(LogoState *l, View *currentView, bool *running);
void RenderLogo(const LogoState *l, Flags *flags);
#endif // LOGO_H
+85
View File
@@ -0,0 +1,85 @@
#ifndef MENU_H
#define MENU_H
#include <raylib.h>
#include "appCore.h"
#include "engineGui.h"
#include "engineInput.h"
#include "engineSoundtrack.h"
#include "engineSprite.h"
typedef enum {
MENU_CURSOR_TEX,
MENU_BIG_BUTTONS_TEX,
MENU_BOARD_TEX,
MENU_CLOUD_BACKGROUND,
MENU_CLOUD_MIDGROUND,
MENU_CLOUD_FOREGROUND,
MENU_TEX_COUNT
} MenuTextureID;
typedef enum { MENU_MUSIC, MENU_SOUNDTRACK_COUNT } MenuSoundtrackID;
typedef enum { HOVER_SOUND, CLICK_SOUND, MENU_SOUND_COUNT } MenuSoundID;
typedef enum MenuView { MAIN = 0, SETTINGS = 1 } MenuView;
typedef struct {
Sprite sprite;
float scrolling;
float scale;
float scrollSpeed;
Vector2 pos;
} Background;
typedef struct {
Assets assets;
Mouse mouse;
Texture2D textures[MENU_TEX_COUNT];
Sound sounds[MENU_SOUND_COUNT];
Soundtrack soundtracks[MENU_SOUNDTRACK_COUNT];
ImageElement board;
ImageButton playButton;
ImageButton quitButton;
ImageButton settingsButton;
Button restartButton;
int menuScale;
Background background;
Background midground;
Background foreground;
} MenuMain;
typedef struct {
Button showFPS;
Button limitFPS;
Button soundtrackOn;
} MenuSettings;
typedef struct {
MenuView menuView;
MenuMain menuMain;
MenuSettings menuSettings;
} MenuState;
void InitBackground(float scrollSpeed, Background *bg, Texture2D *texture,
const TextureAsset *backgroundAsset);
void UpdateBackground(Background *bg);
void RenderBackground(Background *bg);
void InitMenuMain(MenuMain *m);
void UpdateMenuMain(MenuMain *m, State *state, Flags *flags, MenuView *menuView);
void RenderMenuMain(MenuMain *m, Font *font, const char *title, Flags *flags, bool gameStarted);
void CleanupMenuMain(MenuMain *m);
void InitMenuSettings(MenuSettings *s);
void UpdateMenuSettings(MenuSettings *s, MenuView *menuView, State *state, Flags *flags);
void RenderMenuSettings(MenuSettings *s, Flags *flags);
void CleanupMenuSettings(MenuSettings *s);
void InitMenu(MenuState *m);
void UpdateMenu(MenuState *m, State *state, Flags *flags);
void RenderMenu(MenuState *m, Font *font, const char *title, Flags *flags, State *state);
void CleanupMenu(MenuState *m);
#endif
+20
View File
@@ -0,0 +1,20 @@
#ifndef OVERLAY_H
#define OVERLAY_H
#include <limits.h>
#include <raylib.h>
#include "engineInput.h"
#include "player.h"
#include "rose.h"
#include "rose.h"
typedef struct {
Rose rose;
} Overlay;
Overlay CreateOverlay(void);
void UpdateOverlay(Overlay *overlay, Player *player);
void RenderOverlay(Mouse *mouse, Player *player, Rose *rose);
#endif
+21
View File
@@ -0,0 +1,21 @@
#ifndef PATHFINDING_H
#define PATHFINDING_H
#include <raylib.h>
#include <stdbool.h>
#include "terrain.h"
#define PATH_MAX_WAYPOINTS 16
typedef struct {
Vector2 waypoints[PATH_MAX_WAYPOINTS];
int count;
} Path;
bool IsWaterWithClearance(Terrain *terrain, Vector2 pos, float clearance);
Vector2 GetRandomWaterPositionNear(Terrain *terrain, Vector2 origin, float maxRadius,
float clearance);
Path FindWaterPath(Terrain *terrain, Vector2 start, Vector2 goal, float clearance);
#endif
+35
View File
@@ -0,0 +1,35 @@
#ifndef PLAYER_H
#define PLAYER_H
#include <raylib.h>
#include "engineAnimation.h"
#include "engineConfig.h"
#include "enginePhysics.h"
#include "engineSprite.h"
#include "terrain.h"
#include "wake.h"
typedef enum { START_SHIP, PLAYER_TEX_COUNT } PlayerTextureID;
typedef enum { ANCHOR, SLOW_AHEAD, FAST_AHEAD, SPEED_COUNT } PlayerSpeedID;
typedef struct {
Sprite sprite;
Animation animation;
Physics physics;
float rotation;
Sound soundFlapping;
Config config;
int health;
int gold;
WakeTrail wakeTrail;
} Player;
PlayerSpeedID GetPlayerSpeedID(float speed);
Player CreatePlayer(Terrain *terrain);
void UpdatePlayer(Player *player);
void RenderPlayer(Player *player);
void SavePlayer(Player *player);
void ResetPlayer(Player *player, Terrain *terrain);
#endif
+30
View File
@@ -0,0 +1,30 @@
#ifndef PROJECTILES_H
#define PROJECTILES_H
#include <raylib.h>
#include <stdbool.h>
#include "enginePhysics.h"
#include "player.h"
#define MAX_PROJECTILES 100
#define PROJECTILE_SPEED 50.0f
typedef struct {
int count;
Physics physics[MAX_PROJECTILES];
bool active[MAX_PROJECTILES];
bool friendly[MAX_PROJECTILES];
float radius[MAX_PROJECTILES];
Color color[MAX_PROJECTILES];
Sound soundShooting[MAX_PROJECTILES];
Sound soundImpact[MAX_PROJECTILES];
} Projectiles;
Projectiles CreateProjectiles(void);
bool FireProjectile(Projectiles *projectiles, Vector2 position, Vector2 direction, bool friendly);
void UpdateProjectiles(Projectiles *projectiles, Player *player);
void RenderProjectiles(Projectiles *projectiles);
void ResetProjectiles(Projectiles *projectiles);
#endif
+20
View File
@@ -0,0 +1,20 @@
#ifndef ROSE_H
#define ROSE_H
#include <raylib.h>
#include "engineSprite.h"
#include "player.h"
#define ROSE_SCREEN_WIDTH_FRACTION 0.2f
typedef enum { ROSE, ROSE_TEX_COUNT } RoseTextureID;
typedef struct {
Sprite sprite;
} Rose;
Rose CreateRose(void);
void UpdateRose(Rose *rose, Player *player);
void RenderRose(Rose *rose);
#endif
+36
View File
@@ -0,0 +1,36 @@
#ifndef TERRAIN_H
#define TERRAIN_H
#include <raylib.h>
#include <stdbool.h>
#include "engineSprite.h"
#define TERRAIN_CHUNK_WORLD_SIZE 256.0f
#define TERRAIN_CHUNK_RESOLUTION ((int)TERRAIN_CHUNK_WORLD_SIZE)
#define TERRAIN_LOAD_RADIUS 2
#define TERRAIN_GRID_SIZE (TERRAIN_LOAD_RADIUS * 2 + 1)
#define TERRAIN_MAX_CHUNKS (TERRAIN_GRID_SIZE * TERRAIN_GRID_SIZE)
typedef struct {
bool active;
int chunkX, chunkY;
Sprite sprite;
bool *landMask;
} TerrainChunk;
typedef struct {
TerrainChunk chunks[TERRAIN_MAX_CHUNKS];
Shader shader;
float time;
} Terrain;
Terrain CreateTerrain(Camera2D *camera);
void UpdateTerrain(Terrain *terrain, Camera2D *camera);
void RenderTerrain(Terrain *terrain);
void UnloadTerrain(Terrain *terrain);
bool IsTerrainLand(Terrain *terrain, Vector2 worldPos);
bool IsTerrainLoaded(Terrain *terrain, Vector2 worldPos);
#endif
+27
View File
@@ -0,0 +1,27 @@
#ifndef WAKE_H
#define WAKE_H
#include <raylib.h>
#define WAKE_TRAIL_POINTS 12
typedef struct {
Vector2 points[WAKE_TRAIL_POINTS];
double timestamps[WAKE_TRAIL_POINTS];
int count;
} WakeTrail;
void UpdateWakeTrail(WakeTrail *trail, Vector2 position);
void ResetWakeTrail(WakeTrail *trail);
typedef struct {
Shader shader;
float time;
} Wake;
Wake CreateWake(void);
void UpdateWake(Wake *wake, float deltaTime);
void RenderShipWake(Wake *wake, WakeTrail *trail, float speed);
void UnloadWake(Wake *wake);
#endif
+69
View File
@@ -0,0 +1,69 @@
#version 330
in vec2 fragWorldPos;
out vec4 finalColor;
uniform float time;
uniform float cloudScale = 0.018;
uniform vec2 driftDirection = vec2(1.0, 0.35);
uniform float driftSpeed = 4.0;
uniform float coverage = 0.99;
uniform float softness = 0.12;
uniform vec4 cloudColor = vec4(1.0, 1.0, 1.0, 0.8);
uniform bool isShadowPass = false;
uniform vec2 shadowWorldOffset = vec2(48.0, 72.0);
uniform vec4 shadowColor = vec4(0.0, 0.05, 0.1, 0.35);
vec2 pixelizeCoordinates(vec2 coordinates)
{
return floor(coordinates);
}
float hash(vec2 p)
{
p = fract(p * vec2(123.34, 456.21));
p += dot(p, p + 45.32);
return fract(p.x * p.y);
}
float valueNoise(vec2 p)
{
vec2 i = floor(p);
vec2 f = fract(p);
float a = hash(i);
float b = hash(i + vec2(1.0, 0.0));
float c = hash(i + vec2(0.0, 1.0));
float d = hash(i + vec2(1.0, 1.0));
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
}
float cloudField(vec2 p)
{
float value = 0.0;
float amplitude = 0.5;
for (int i = 0; i < 4; i++) {
value += amplitude * valueNoise(p);
p *= 2.0;
amplitude *= 0.5;
}
return value;
}
void main()
{
vec2 worldPos = fragWorldPos;
if (isShadowPass) {
worldPos -= shadowWorldOffset;
}
vec2 pixelized = pixelizeCoordinates(worldPos);
vec2 drift = normalize(driftDirection) * time * driftSpeed;
float density = cloudField((pixelized + drift) * cloudScale);
float mask = smoothstep(coverage - softness, coverage + softness, density);
vec4 layerColor = isShadowPass ? shadowColor : cloudColor;
finalColor = vec4(layerColor.rgb, layerColor.a * mask);
}
+14
View File
@@ -0,0 +1,14 @@
#version 330
in vec3 vertexPosition;
in vec2 vertexTexCoord;
in vec4 vertexColor;
out vec2 fragWorldPos;
uniform mat4 mvp;
void main()
{
fragWorldPos = vertexPosition.xy;
gl_Position = mvp * vec4(vertexPosition, 1.0);
}
+45
View File
@@ -0,0 +1,45 @@
#version 330
uniform sampler2D texture0;
uniform vec2 resolution;
uniform float time;
// Screen-space UV of the sun — kept off in a corner like a low sun rather
// than dead center, so the rays read as directional light rather than a
// starburst sitting on top of the view. This shader samples the already-
// rendered scene texture (RenderPostFX draws it via a source rect with a
// negative height to correct raylib's render-texture V-flip for texture0
// itself), but fragTexCoord's V still runs top-of-screen=0 in that same
// flipped sense — so a Y near 0 here actually lands near the BOTTOM of
// what's on screen; 0.88 is what puts the sun near the top.
uniform vec2 sunScreenPos = vec2(0.82, 0.88);
uniform float rayCount = 10.0;
uniform float raySharpness = 3.0;
uniform float rayStrength = 0.35;
uniform float raySpeed = 0.03;
in vec2 fragTexCoord;
out vec4 finalColor;
void main()
{
vec2 uv = fragTexCoord;
vec4 baseColor = texture(texture0, uv);
// Cheap screen-space light shafts: no scene depth/occlusion to sample,
// so this fakes them as angular streaks radiating from the sun position
// that fade out with distance, rather than true raymarched shafts.
vec2 aspectFix = vec2(resolution.x / resolution.y, 1.0);
vec2 toSun = (uv - sunScreenPos) * aspectFix;
float dist = length(toSun);
float angle = atan(toSun.y, toSun.x);
float streaks = sin(angle * rayCount + time * raySpeed) * 0.5 + 0.5;
streaks = pow(streaks, raySharpness);
float falloff = clamp(1.0 - dist * 0.9, 0.0, 1.0);
falloff *= falloff;
vec3 rayColor = vec3(1.0, 0.92, 0.75) * streaks * falloff * rayStrength;
finalColor = vec4(baseColor.rgb + rayColor, baseColor.a);
}
+53
View File
@@ -0,0 +1,53 @@
#version 330
in vec2 fragTexCoord;
in vec2 fragWorldPos;
out vec4 finalColor;
uniform sampler2D texture0;
uniform float time;
uniform vec4 foamColor = vec4(0.96, 0.99, 1.0, 1.0);
uniform float foamAnimSpeed = 1.6;
uniform float foamNoiseScale = 0.55;
float hash(vec2 p)
{
p = fract(p * vec2(123.34, 456.21));
p += dot(p, p + 45.32);
return fract(p.x * p.y);
}
float valueNoise(vec2 p)
{
vec2 i = floor(p);
vec2 f = fract(p);
float a = hash(i);
float b = hash(i + vec2(1.0, 0.0));
float c = hash(i + vec2(0.0, 1.0));
float d = hash(i + vec2(1.0, 1.0));
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
}
void main()
{
vec4 tex = texture(texture0, fragTexCoord);
if (tex.a >= 0.999) {
finalColor = tex;
return;
}
if (tex.a <= 0.001) {
discard;
}
vec2 pixelized = floor(fragWorldPos);
float n1 = valueNoise(pixelized * foamNoiseScale + vec2(time * foamAnimSpeed, 0.0));
float n2 = valueNoise(pixelized * foamNoiseScale * 2.1 - vec2(0.0, time * foamAnimSpeed * 0.7));
float churn = n1 * 0.6 + n2 * 0.4;
float mask = tex.a * smoothstep(0.2, 0.7, churn + tex.a * 0.6);
finalColor = vec4(foamColor.rgb, foamColor.a * mask);
}
+16
View File
@@ -0,0 +1,16 @@
#version 330
in vec3 vertexPosition;
in vec2 vertexTexCoord;
in vec4 vertexColor;
out vec2 fragTexCoord;
out vec2 fragWorldPos;
uniform mat4 mvp;
void main()
{
fragTexCoord = vertexTexCoord;
fragWorldPos = vertexPosition.xy;
gl_Position = mvp * vec4(vertexPosition, 1.0);
}
+79
View File
@@ -0,0 +1,79 @@
#version 330
in vec2 fragWorldPos;
out vec4 finalColor;
uniform float time;
#define TRAIL_POINTS 12
uniform vec2 trailPoints[TRAIL_POINTS];
uniform float trailAges[TRAIL_POINTS];
uniform int trailCount;
uniform float wakeIntensity;
uniform vec4 foamColor = vec4(0.96, 0.99, 1.0, 1.0);
uniform float wakeHalfWidthStart = 2.0;
uniform float wakeHalfWidthEnd = 7.0;
uniform float foamNoiseScale = 0.5;
uniform float foamAnimSpeed = 2.4;
float hash(vec2 p)
{
p = fract(p * vec2(123.34, 456.21));
p += dot(p, p + 45.32);
return fract(p.x * p.y);
}
float valueNoise(vec2 p)
{
vec2 i = floor(p);
vec2 f = fract(p);
float a = hash(i);
float b = hash(i + vec2(1.0, 0.0));
float c = hash(i + vec2(0.0, 1.0));
float d = hash(i + vec2(1.0, 1.0));
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
}
float segmentDistance(vec2 p, vec2 a, vec2 b, out float segT)
{
vec2 ab = b - a;
float lenSq = dot(ab, ab);
segT = lenSq > 0.0001 ? clamp(dot(p - a, ab) / lenSq, 0.0, 1.0) : 0.0;
vec2 closest = a + ab * segT;
return length(p - closest);
}
void main()
{
if (trailCount < 2) discard;
float bestDist = 1e9;
float bestAge = 0.0;
for (int i = 0; i < TRAIL_POINTS - 1; i++) {
if (i >= trailCount - 1) break;
float segT;
float d = segmentDistance(fragWorldPos, trailPoints[i], trailPoints[i + 1], segT);
if (d < bestDist) {
bestDist = d;
bestAge = mix(trailAges[i], trailAges[i + 1], segT);
}
}
float halfWidth = mix(wakeHalfWidthStart, wakeHalfWidthEnd, bestAge);
if (bestDist > halfWidth) discard;
float edgeFade = 1.0 - smoothstep(halfWidth * 0.55, halfWidth, bestDist);
float ageFade = 1.0 - bestAge;
float envelope = edgeFade * ageFade;
vec2 pixelized = floor(fragWorldPos);
float n = valueNoise(pixelized * foamNoiseScale + vec2(time * foamAnimSpeed, 0.0));
float churn = mix(0.55, 1.0, n);
float speedScale = mix(0.65, 1.0, wakeIntensity);
float mask = envelope * churn * speedScale;
finalColor = vec4(foamColor.rgb, foamColor.a * mask);
}
+14
View File
@@ -0,0 +1,14 @@
#version 330
in vec3 vertexPosition;
in vec2 vertexTexCoord;
in vec4 vertexColor;
out vec2 fragWorldPos;
uniform mat4 mvp;
void main()
{
fragWorldPos = vertexPosition.xy;
gl_Position = mvp * vec4(vertexPosition, 1.0);
}
+9 -23
View File
@@ -1,17 +1,12 @@
#version 330
// -----------------------------
// Inputs
in vec2 fragTexCoord;
in vec4 fragColor;
in vec2 fragWorldPos;
// -----------------------------
// Outputs
out vec4 finalColor;
// -----------------------------
// Uniforms
uniform sampler2D texture0; // main texture
uniform sampler2D texture0;
uniform sampler2D waterDepthGradient;
uniform sampler2D causticTexture;
uniform sampler2D causticHighlightTexture;
@@ -24,6 +19,7 @@ uniform sampler2D foamTexture;
uniform float aspectRatio = 1.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 causticHighlightColor = vec4(0.741, 0.894, 0.898, 1.0);
@@ -46,9 +42,6 @@ uniform float generalTransparency = 1.0;
uniform float time;
// -----------------------------
// Helper functions
vec2 blendSubtract_vec2(vec2 base, vec2 blend, float opacity)
{
vec2 result = base - blend;
@@ -80,9 +73,6 @@ vec2 applyAspectRatio(vec2 coordinates)
return vec2(coordinates.x, coordinates.y * aspectRatio);
}
// -----------------------------
// Shader layers
vec4 caustics(vec2 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);
}
// -----------------------------
// Main fragment
void main()
{
vec2 pixelizedCoordinates = pixelizeCoordinates(applyAspectRatio(fragTexCoord));
vec2 pixelizedCoordinates = pixelizeCoordinates(fragWorldPos / noiseCoordinateScale);
vec4 mainTex = texture(texture0, fragTexCoord);
vec4 depthBasedWaterColor = texture(waterDepthGradient, vec2(1.0 - mainTex.b, 1.0));
vec4 finalCaustics = caustics(pixelizedCoordinates);
vec4 finalCaustics = caustics(pixelizedCoordinates);
vec4 finalSpecular = specular(pixelizedCoordinates);
vec4 finalFoam = foam(pixelizedCoordinates, mainTex);
vec4 waterWithCausticLayer = mix(depthBasedWaterColor, finalCaustics, finalCaustics.a);
vec4 waterWithCausticAndSpecularLayer = mix(waterWithCausticLayer, finalSpecular, ceil(finalCaustics.a) * finalSpecular.a);
vec4 waterWithCausticAndSpecularAndFoamLayer = mix(waterWithCausticAndSpecularLayer, finalFoam, finalFoam.a);
float outline = mainTex.a * mainTex.r;
vec4 finalOutlineColor = outline * outlineColor;
vec4 finalRGBColor = mix(waterWithCausticAndSpecularAndFoamLayer, finalOutlineColor, outline);
finalColor = vec4(finalRGBColor.rgb, mainTex.b * generalTransparency);
}
+2 -1
View File
@@ -5,6 +5,7 @@ in vec4 vertexColor;
out vec2 fragTexCoord;
out vec4 fragColor;
out vec2 fragWorldPos;
uniform mat4 mvp;
@@ -12,6 +13,6 @@ void main()
{
fragTexCoord = vertexTexCoord;
fragColor = vertexColor;
fragWorldPos = vertexPosition.xy;
gl_Position = mvp * vec4(vertexPosition, 1.0);
}
+22 -16
View File
@@ -16,36 +16,42 @@ void InitApp(App *app) {
InitLogo(&app->logo);
InitMenu(&app->menu);
InitGame(app);
InitGameOver(&app->gameOver);
}
void UpdateApp(App *app) {
app->state.running = true;
app->state.gameStarted = false;
while (app->state.running) {
switch (app->state.currentView) {
case LOGO: {
UpdateLogo(&app->logo, &app->state.currentView, &app->state.running);
RenderLogo(&app->logo, &app->flags);
} break;
case MENU: {
UpdateMenu(&app->menu, &app->state, &app->flags);
RenderMenu(&app->menu, &app->font, app->window.title, &app->flags);
} break;
case GAMEPLAY: {
UpdateGame(app);
RenderGame(app);
} break;
default:
break;
case LOGO: {
UpdateLogo(&app->logo, &app->state.currentView, &app->state.running);
RenderLogo(&app->logo, &app->flags);
break;
}
case MENU: {
UpdateMenu(&app->menu, &app->state, &app->flags);
RenderMenu(&app->menu, &app->font, app->window.title, &app->flags, &app->state);
break;
}
case GAMEPLAY: {
UpdateGame(app);
RenderGame(app);
break;
}
case GAMEOVER: {
UpdateGameOver(app);
RenderGameOver(app);
break;
}
}
}
}
void CleanupApp(App *app) {
CleanupMenu(&app->menu);
CleanupGame(app);
CleanupMenu(&app->menu);
CleanupFont(&app->font);
CleanUpConfig(&app->config);
}
+100 -56
View File
@@ -1,15 +1,18 @@
#include <time.h>
#include "app.h"
#include "enginePostfx.h"
#include "game.h"
#include "raylib.h"
const ShaderAsset shaderAssets[SHADER_COUNT] = {{"shaders/postfx.fs"}};
const SoundtrackAsset soundtrackAssets[SOUNDTRACK_COUNT] = {
{"assets/gameplay_theme.mp3"}, {"assets/sea.wav"}};
const SoundtrackAsset soundtrackAssets[SOUNDTRACK_COUNT] = {{"assets/gameplay_theme.mp3"},
{"assets/sea.wav"}};
void InitGame(App *app) {
SetRandomSeed(1234);
SetRandomSeed(GetRandomValue(0, 10000000));
GameState *g = &app->game;
g->assets = (Assets){.shaderAssets = shaderAssets,
.soundtrackAssets = soundtrackAssets};
g->assets = (Assets){.shaderAssets = shaderAssets, .soundtrackAssets = soundtrackAssets};
for (int i = 0; i < SHADER_COUNT; i++) {
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->postFXTarget = LoadRenderTexture(app->window.width, app->window.height);
CreatePlayer();
Player *player = GetPlayer();
CreateCamera(player->physics.position, 10.0f);
g->birds = CreateBirds();
g->enemies = CreateEnemies();
g->islands = CreateIslands();
g->camera = CreateCamera((Vector2){0.0f, 0.0f}, 10.0f);
g->terrain = CreateTerrain(&g->camera);
g->player = CreatePlayer(&g->terrain);
g->camera.target = g->player.physics.position;
g->birds = CreateBirds(&g->camera);
g->enemies = CreateEnemies(&g->camera, &g->terrain);
g->floor = CreateFloor();
g->clouds = CreateClouds();
g->wake = CreateWake();
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);
}
void StartGame(App *app) {
if (!app->state.gameStarted) {
app->state.gameStarted = true;
}
static void StartGame(App *app) {
app->state.gameStarted = true;
}
void UpdateGame(App *app) {
GameState *g = &app->game;
if (app->flags.limitFPS) {
SetTargetFPS(60);
} else {
SetTargetFPS(0);
}
SetTargetFPS(app->flags.limitFPS ? 60 : 0);
StartGame(app);
OnEscapeChangeView(app, MENU);
OnWindowClosedStoppApp(app);
OnBoolChangeView(g->player.health <= 0, app, GAMEOVER);
for (int i = 0; i < SHADER_COUNT; i++) {
UpdatePostFX(&g->postFX[i]);
}
UpdateMouse(&g->mouse);
Player *player = GetPlayer();
UpdateCamera2D(&player->physics.position, &g->mouse);
UpdateFloor(&g->floor);
UpdatePlayer();
UpdateBirds(&g->birds);
UpdateEnemies(&g->enemies);
UpdateIslands(&g->islands);
UpdateShaders(g->postFX, SHADER_COUNT);
UpdateMouse(&g->mouse, &g->camera);
UpdateCamera2D(&g->camera, &g->player.physics.position, &g->mouse);
UpdateFloor(&g->floor, &g->camera);
UpdateClouds(&g->clouds, GetFrameTime());
UpdateWake(&g->wake, GetFrameTime());
UpdatePlayer(&g->player);
UpdateBirds(&g->birds, &g->camera);
UpdateTerrain(&g->terrain, &g->camera);
UpdateEnemies(&g->enemies, &g->camera, &g->projectiles, &g->player, &g->terrain);
UpdateSoundtrack(&g->soundtracks[AMBIENT], app->flags.soundtrackOn);
UpdateProjectiles(&g->projectiles, &g->enemies);
CheckPlayerCollisions(GetPlayer(), &app->game.enemies);
CheckEnemyCollisions(&app->game.enemies);
CheckProjectileCollisions(&app->game.projectiles, &app->game.enemies,
GetPlayer());
UpdateProjectiles(&g->projectiles, &g->player);
UpdateOverlay(&g->overlay, &g->player);
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) {
GameState *g = &app->game;
RenderFloor(&g->floor);
RenderPlayer();
RenderTerrain(&g->terrain);
RenderCloudShadows(&g->clouds, &g->camera);
RenderShipWakes(app);
RenderPlayer(&g->player);
RenderEnemies(&g->enemies);
RenderBirds(&g->birds);
RenderIslands(&g->islands);
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) {
@@ -89,24 +140,13 @@ void RenderGame(App *app) {
BeginTextureMode(g->target);
ClearBackground(BLACK);
Camera2D *camera = GetCamera();
BeginMode2D(*camera);
BeginMode2D(g->camera);
RenderComponents(app);
EndMode2D();
RenderOverlay(&g->mouse);
RenderOverlay(&g->mouse, &g->player, &g->overlay.rose);
EndTextureMode();
BeginDrawing();
ClearBackground(BLACK);
for (int i = 0; i < SHADER_COUNT; i++) {
RenderPostFX(&g->postFX[i], &g->target);
}
if (app->flags.showFPS) {
DrawFPS(10, 10);
}
EndDrawing();
RenderPostFXChain(app, &g->target);
}
void CleanupGame(App *app) {
@@ -117,5 +157,9 @@ void CleanupGame(App *app) {
for (int i = 0; i < SOUNDTRACK_COUNT; i++) {
UnloadMusicStream(g->soundtracks[i].music);
}
UnloadTerrain(&g->terrain);
UnloadClouds(&g->clouds);
UnloadWake(&g->wake);
UnloadRenderTexture(g->target);
UnloadRenderTexture(g->postFXTarget);
}
+82 -75
View File
@@ -1,88 +1,95 @@
#include "app.h"
#include <raylib.h>
#include "birds.h"
#include "engineAssets.h"
#include "engineCamera.h"
#include "enginePhysics.h"
#include "engineRectangle.h"
#include "engineVector.h"
const TextureAsset birdsTextureAssets[BIRDS_TEX_COUNT] = {
{"assets/birds.png", 16.0f, 16.0f, 45.0f}
};
{"assets/birds.png", 16.0f, 16.0f, 45.0f}};
Birds CreateBirds(void){
Birds birds = {.count = MAX_BIRDS};
TextureAsset flyingAsset = birdsTextureAssets[FLYING];
Texture2D texture = LoadTexture(flyingAsset.path);
float frameWidth = flyingAsset.frameWidth;
float rotation = flyingAsset.rotation;
for (int i = 0; i < birds.count; i++)
{
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;
static void InitBirdSprite(Birds *birds, int i, Texture2D texture, float frameWidth,
float rotation) {
birds->sprite[i] = (Sprite){.texture = texture,
.frameSize = {frameWidth, frameWidth},
.sourceRec = {0.0f, 0.0f, frameWidth, frameWidth},
.destRec = {GetScreenWidth() / 2, 0.0f, frameWidth, frameWidth},
.origin = {frameWidth / 2, frameWidth / 2},
.rotation = rotation,
.color = WHITE};
}
void UpdateBirds(Birds *birds){
Camera2D *camera = GetCamera();
Rectangle cameraRec = GetCameraRectangle();
static float InitBirdAnimation(Birds *birds, int i) {
float speedFactor = GetRandomValue(50, 180) / 100.0f;
int framesSpeed = (int)(2.0f * speedFactor + 0.5f);
if (framesSpeed < 1)
framesSpeed = 1;
int maxFrame = 2;
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});
}
birds->animation[i] =
(Animation){.state = 1,
.currentFrame = GetRandomValue(0, maxFrame - 1),
.maxFrame = maxFrame,
.framesCounter = 0,
.framesSpeed = framesSpeed,
.animTimer = GetRandomValue(0, 99) / 100.0f * (1.0f / (float)framesSpeed)};
return speedFactor;
}
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++)
{
RenderSprite(&birds->sprite[i]);
}
for (int i = 0; i < birds.count; i++) {
InitBirdSprite(&birds, i, texture, frameWidth, rotation);
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]);
}
}
+63
View File
@@ -0,0 +1,63 @@
#include "clouds.h"
#include "engineCamera.h"
#include "raylib.h"
#include "math.h"
Clouds CreateClouds(void) {
Clouds clouds = {0};
clouds.shader = LoadShader("shaders/clouds.vs", "shaders/clouds.fs");
clouds.time = 0.0f;
clouds.coverage = 1.0f;
return clouds;
}
void UpdateClouds(Clouds *clouds, float deltaTime) {
clouds->time += deltaTime;
int timeLoc = GetShaderLocation(clouds->shader, "time");
SetShaderValue(clouds->shader, timeLoc, &clouds->time, SHADER_UNIFORM_FLOAT);
float clearCoverage = 1.1f;
float overcastCoverage = 0.55f;
float percent = 0.5f + 0.5f * sinf(clouds->time * 0.05f);
clouds->coverage = clearCoverage - percent * (clearCoverage - overcastCoverage);
int coverageLoc = GetShaderLocation(clouds->shader, "coverage");
SetShaderValue(clouds->shader, coverageLoc, &clouds->coverage, SHADER_UNIFORM_FLOAT);
}
static Rectangle GetCloudCoverageRect(Camera2D *camera) {
Rectangle rec = GetCameraRectangle(camera);
float margin = 128.0f;
rec.x -= margin;
rec.y -= margin;
rec.width += margin * 2.0f;
rec.height += margin * 2.0f;
return rec;
}
void RenderCloudShadows(Clouds *clouds, Camera2D *camera) {
int isShadowLoc = GetShaderLocation(clouds->shader, "isShadowPass");
int isShadow = 1;
SetShaderValue(clouds->shader, isShadowLoc, &isShadow, SHADER_UNIFORM_INT);
Rectangle rec = GetCloudCoverageRect(camera);
BeginShaderMode(clouds->shader);
BeginBlendMode(BLEND_MULTIPLIED);
DrawRectangleRec(rec, WHITE);
EndBlendMode();
EndShaderMode();
}
void RenderClouds(Clouds *clouds, Camera2D *camera) {
int isShadowLoc = GetShaderLocation(clouds->shader, "isShadowPass");
int isShadow = 0;
SetShaderValue(clouds->shader, isShadowLoc, &isShadow, SHADER_UNIFORM_INT);
Rectangle rec = GetCloudCoverageRect(camera);
BeginShaderMode(clouds->shader);
DrawRectangleRec(rec, WHITE);
EndShaderMode();
}
void UnloadClouds(Clouds *clouds) {
UnloadShader(clouds->shader);
}
+126 -52
View File
@@ -1,102 +1,176 @@
#include "app.h"
#include <math.h>
#include <raylib.h>
#include "collisions.h"
#include "enginePhysics.h"
#include "engineVector.h"
float playerCollisionRadius = 8.0f;
float enemiesCollisionRadius = 8.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) {
for (int i = 0; i < enemies->count; i++) {
if (!enemies->alive[i])
continue;
Vector2 playerPos = player->physics.position;
Vector2 enemiesPos = enemies->physics[i].position;
float minDist = playerCollisionRadius + enemiesCollisionRadius;
CircleCollision collision =
CheckCircleCollision(playerPos, playerCollisionRadius, enemiesPos, enemiesCollisionRadius);
if (CheckCollisionCircles(playerPos, playerCollisionRadius, enemiesPos,
enemiesCollisionRadius)) {
if (!collision.overlapping)
continue;
player->health -= 1;
player->health -= 1;
if (player->health < 0)
player->health = 0;
float dist = GetDistanceBetweenTwoVectors(playerPos, enemiesPos);
float overlap = minDist - dist;
if (dist > 0.0f) {
Vector2 collisionVector = GetDirectionBetweenTwoVectors(
enemies->physics[i].position, player->physics.position);
player->physics.position.x += collisionVector.x * overlap;
player->physics.position.y += collisionVector.y * overlap;
Vector2 forward = RotationToVector2(player->rotation - 90.0f);
float side = Cross2D(forward, collisionVector);
player->rotation += side * 120.0f * GetFrameTime();
}
if (collision.hasDirection) {
player->physics.position.x += collision.direction.x * collision.overlap;
player->physics.position.y += collision.direction.y * collision.overlap;
Vector2 forward = RotationToVector2(player->rotation - 90.0f);
float side = Cross2D(forward, collision.direction);
ApplyCollisionSteer(&player->rotation, side, GetFrameTime());
}
}
}
void CheckEnemyCollisions(Enemies *enemies) {
for (int a = 0; a < enemies->count; a++) {
if (!enemies->alive[a])
continue;
for (int b = a + 1; b < enemies->count; b++) {
if (!enemies->alive[b])
continue;
Vector2 posA = enemies->physics[a].position;
Vector2 posB = enemies->physics[b].position;
float minDist = enemiesCollisionRadius * 2;
CircleCollision collision =
CheckCircleCollision(posA, enemiesCollisionRadius, posB, enemiesCollisionRadius);
if (CheckCollisionCircles(posA, enemiesCollisionRadius, posB,
enemiesCollisionRadius)) {
if (!collision.overlapping || !collision.hasDirection)
continue;
Vector2 collisionVectorA = GetDirectionBetweenTwoVectors(posB, posA);
Vector2 collisionVectorB = GetDirectionBetweenTwoVectors(posA, posB);
float dist = GetDistanceBetweenTwoVectors(posA, posB);
float overlap = minDist - dist;
enemies->physics[a].position.x += collision.direction.x * collision.overlap * 0.5f;
enemies->physics[a].position.y += collision.direction.y * collision.overlap * 0.5f;
if (dist > 0.0f) {
enemies->physics[a].position.x += collisionVectorA.x * overlap * 0.5f;
enemies->physics[a].position.y += collisionVectorA.y * overlap * 0.5f;
enemies->physics[b].position.x -= collision.direction.x * collision.overlap * 0.5f;
enemies->physics[b].position.y -= collision.direction.y * collision.overlap * 0.5f;
enemies->physics[b].position.x += collisionVectorB.x * overlap * 0.5f;
enemies->physics[b].position.y += collisionVectorB.y * overlap * 0.5f;
Vector2 forwardA =
RotationToVector2(enemies->sprite[a].rotation - 90.0f);
Vector2 forwardB =
RotationToVector2(enemies->sprite[b].rotation - 90.0f);
float sideA = Cross2D(forwardA, collisionVectorA);
float sideB = Cross2D(forwardB, collisionVectorB);
enemies->sprite[a].rotation += sideA * 120.0f * GetFrameTime();
enemies->sprite[b].rotation += sideB * 120.0f * GetFrameTime();
}
}
Vector2 forwardA = RotationToVector2(enemies->sprite[a].rotation - 90.0f);
Vector2 forwardB = RotationToVector2(enemies->sprite[b].rotation - 90.0f);
Vector2 collisionVectorB = {-collision.direction.x, -collision.direction.y};
float sideA = Cross2D(forwardA, collision.direction);
float sideB = Cross2D(forwardB, collisionVectorB);
ApplyCollisionSteer(&enemies->sprite[a].rotation, sideA, GetFrameTime());
ApplyCollisionSteer(&enemies->sprite[b].rotation, sideB, GetFrameTime());
}
}
}
void CheckProjectileCollisions(Projectiles *projectiles, Enemies *enemies,
Player *player) {
void CheckProjectileCollisions(Projectiles *projectiles, Enemies *enemies, Player *player) {
for (int i = 0; i < projectiles->count; i++) {
if (!projectiles->active[i])
continue;
Vector2 projectilePos = projectiles->physics[i].position;
for (int j = 0; j < enemies->count; j++) {
if (!enemies->alive[j])
continue;
Vector2 enemyPos = enemies->physics[j].position;
if (CheckCollisionCircles(projectilePos, projectilesCollisionRadius,
enemyPos, enemiesCollisionRadius)) {
player->gold += 20;
Rectangle cameraRec = GetCameraRectangle();
enemies->physics[j].position =
GetPositionOutsideRectangle(cameraRec, 100, 1000);
if (CheckCollisionCircles(projectilePos, projectilesCollisionRadius, enemyPos,
enemiesCollisionRadius)) {
PlayImpactSound(projectiles->soundImpact[i]);
enemies->alive[j] = false;
projectiles->active[i] = false;
if (projectiles->friendly[i]) {
player->gold += 20;
SavePlayer(player);
}
break;
}
}
if (!projectiles->active[i])
continue;
Vector2 playerPos = player->physics.position;
if (CheckCollisionCircles(projectilePos, projectilesCollisionRadius,
playerPos, playerCollisionRadius)) {
if (!projectiles->friendly[i]) {
Vector2 playerPos = player->physics.position;
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
View File
@@ -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] = {
{"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};
TextureAsset textureAsset = enemiesTextureAssets[BOAT];
Texture2D texture = LoadTexture(textureAsset.path);
Texture2D texture = LoadTextureCached(textureAsset.path);
float frameWidth = textureAsset.frameWidth;
float rotation = textureAsset.rotation;
for (int i = 0; i < enemies.count; i++) {
enemies.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};
enemies.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};
enemies.animation[i] = (Animation){.state = 1,
.currentFrame = 0,
@@ -27,60 +251,44 @@ Enemies CreateEnemies(void) {
.framesSpeed = 2.0f,
.animTimer = 0.0f};
Rectangle cameraRec = GetCameraRectangle();
Rectangle cameraRec = GetCameraRectangle(camera);
enemies.physics[i] =
(Physics){.position = GetPositionOutsideRectangle(cameraRec, 100, 1000),
(Physics){.position = GetSpawnPosition(terrain, cameraRec, camera->target, &enemies, i),
.direction = (Vector2){0.0f, 0.0f},
.speed = 5.0f,
.weight = 5.0f};
enemies.arrived[i] = true;
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.reloadTimer[i] = GetRandomValue(0, 100) / 100.0f * ENEMY_RELOAD_TIME;
}
return enemies;
}
void UpdateEnemies(Enemies *enemies) {
Camera2D *camera = GetCamera();
Rectangle cameraRec = GetCameraRectangle();
void UpdateEnemies(Enemies *enemies, Camera2D *camera, Projectiles *projectiles, Player *player,
Terrain *terrain) {
Rectangle cameraRec = GetCameraRectangle(camera);
for (int i = 0; i < enemies->count; i++) {
if (!enemies->alive[i])
continue;
RespawnDeadEnemy(enemies, i, terrain, cameraRec, camera);
if (enemies->arrived[i]) {
enemies->destPos[i] = GetPositionOutsideRectangle(cameraRec, 100, 1000);
enemies->arrived[i] = false;
}
Vector2 playerPos = player->physics.position;
float distanceToPlayer = GetDistanceBetweenTwoVectors(enemies->physics[i].position, playerPos);
if (CheckCollisionPointCircle(enemies->physics[i].position,
enemies->destPos[i], 10.0F)) {
enemies->arrived[i] = true;
}
if (GetDistanceBetweenTwoVectors(enemies->physics[i].position,
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});
UpdateEnemyTargeting(enemies, i, terrain, cameraRec, camera, playerPos, distanceToPlayer);
AdvanceEnemyAlongPath(enemies, i);
RecycleFarEnemy(enemies, i, terrain, cameraRec, camera);
SteerAndMoveEnemy(enemies, i);
UpdateEnemyAnimationFrame(enemies, i);
TryFireAtPlayer(enemies, i, projectiles, player);
}
}
+128 -78
View File
@@ -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");
Texture2D caustic = LoadTexture("assets/water_caustics.png");
Texture2D causticHighlight = LoadTexture("assets/water_caustics_highlights.png");
Texture2D noise = LoadTexture("assets/water_noise.png");
Texture2D cloud = LoadTexture("assets/water_cloudnoise.png");
Texture2D voronoise = LoadTexture("assets/water_voronoise.png");
static void BindWaterTexture(Shader shader, const char *uniformName, Texture2D texture, int slot) {
int loc = GetShaderLocation(shader, uniformName);
if (loc < 0)
return;
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};
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;
rlActiveTextureSlot(slot);
rlEnableTexture(texture.id);
SetShaderValue(shader, loc, &slot, SHADER_UNIFORM_INT);
}
void UpdateFloor(Floor *floor)
{
Camera2D *camera = GetCamera();
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);
static void PrepareTiledTexture(Texture2D texture) {
SetTextureWrap(texture, TEXTURE_WRAP_REPEAT);
GenTextureMipmaps(&texture);
SetTextureFilter(texture, TEXTURE_FILTER_TRILINEAR);
}
void RenderFloor(Floor *floor)
{
BeginShaderMode(floor->shader);
static Texture2D CreateDepthGradientTexture(void) {
Image gradientImage =
GenImageGradientLinear(64, 64, 90, (Color){12, 44, 84, 255}, (Color){64, 168, 186, 255});
Texture2D depthGradient = LoadTextureFromImage(gradientImage);
UnloadImage(gradientImage);
SetTextureWrap(depthGradient, TEXTURE_WRAP_CLAMP);
return depthGradient;
}
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);
}
static void ConfigureFloorShaderUniforms(Shader shader, Texture2D base, float worldTileSize,
float textureSize) {
float aspect = (float)GetScreenHeight() / (float)GetScreenWidth();
float pixelization = 2048.0f;
float noiseCoordinateScale = worldTileSize * ((float)base.width / textureSize);
int aspectLoc = GetShaderLocation(shader, "aspectRatio");
int pixelLoc = GetShaderLocation(shader, "pixelization");
int noiseCoordinateScaleLoc = GetShaderLocation(shader, "noiseCoordinateScale");
SetShaderValue(shader, aspectLoc, &aspect, SHADER_UNIFORM_FLOAT);
SetShaderValue(shader, pixelLoc, &pixelization, SHADER_UNIFORM_FLOAT);
SetShaderValue(shader, noiseCoordinateScaleLoc, &noiseCoordinateScale, SHADER_UNIFORM_FLOAT);
}
Floor CreateFloor(void) {
Texture2D base = LoadTextureCached("assets/water_base.png");
Texture2D caustic = LoadTextureCached("assets/water_caustics.png");
Texture2D causticHighlight = LoadTextureCached("assets/water_caustics_highlights.png");
Texture2D noise = LoadTextureCached("assets/water_noise.png");
Texture2D cloud = LoadTextureCached("assets/water_cloudnoise.png");
Texture2D voronoise = LoadTextureCached("assets/water_voronoise.png");
PrepareTiledTexture(caustic);
PrepareTiledTexture(causticHighlight);
PrepareTiledTexture(noise);
PrepareTiledTexture(cloud);
PrepareTiledTexture(voronoise);
Texture2D depthGradient = CreateDepthGradientTexture();
float textureWidth = 64.0f;
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)
{
UnloadShader(floor->shader);
void UnloadFloor(Floor *floor) {
UnloadShader(floor->shader);
UnloadTexture(floor->depthGradientTexture);
}
+189 -29
View File
@@ -1,39 +1,199 @@
#include "app.h"
#include "islands.h"
#include "engineCamera.h"
#include "enginePhysics.h"
#include "engineRectangle.h"
#include "engineVector.h"
#include <math.h>
#include <raylib.h>
#include <raymath.h>
const TextureAsset islandsTextureAssets[ISLANDS_TEX_COUNT] = {
{"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);
}
}
+60
View File
@@ -0,0 +1,60 @@
#include <raylib.h>
#include "app.h"
#include "gameover.h"
void InitGameOver(GameOverState *g) {
int windowWidth = GetScreenWidth();
int windowHeight = GetScreenHeight();
Vector2 buttonDimensions = {200, 60};
Vector2 buttonPos = {windowWidth / 2.0f, windowHeight / 2.0f + 80.0f};
g->restartButton = initButton("Restart", buttonDimensions, buttonPos);
}
void UpdateGameOver(App *app) {
if (isButtonClicked(&app->gameOver.restartButton)) {
ResetGame(app);
app->state.currentView = GAMEPLAY;
}
UpdateMouseScreen(&app->game.mouse);
OnWindowClosedStoppApp(app);
}
void RenderGameOver(App *app) {
BeginDrawing();
ClearBackground(BLACK);
int windowWidth = GetScreenWidth();
int windowHeight = GetScreenHeight();
const char *title = "GAME OVER";
int titleSize = app->font.baseSize * 2;
Vector2 titleDim = MeasureTextEx(app->font, title, titleSize, 2);
DrawTextEx(
app->font, title,
(Vector2){windowWidth / 2.0f - titleDim.x / 2.0f, windowHeight / 2.0f - titleDim.y - 40.0f},
titleSize, 2, TEXT_COLOR_LIGHT);
const char *goldText = TextFormat("Gold collected: %i", app->game.player.gold);
int goldSize = app->font.baseSize;
Vector2 goldDim = MeasureTextEx(app->font, goldText, goldSize, 2);
DrawTextEx(
app->font, goldText,
(Vector2){windowWidth / 2.0f - goldDim.x / 2.0f, windowHeight / 2.0f - goldDim.y / 2.0f},
goldSize, 2, TEXT_COLOR_LIGHT);
renderButton(&app->gameOver.restartButton);
Mouse *mouse = &app->game.mouse;
Rectangle mouseRectangle =
(Rectangle){0, 0, mouse->cursorTexture.width, mouse->cursorTexture.height};
DrawTextureRec(mouse->cursorTexture, mouseRectangle, mouse->screenPosition, WHITE);
if (app->flags.showFPS) {
DrawFPS(10, 10);
}
EndDrawing();
}
+22 -13
View File
@@ -1,19 +1,28 @@
#include "app.h"
#include "raylib.h"
#include <limits.h>
#include <raylib.h>
void RenderOverlay(Mouse *mouse) {
#include "engineInput.h"
#include "overlay.h"
#include "player.h"
#include "rose.h"
// Health
Player *player = GetPlayer();
const char *text = TextFormat("Health: %i\n"
"Gold: %i\n",
player->health, player->gold);
Overlay CreateOverlay(void) {
return (Overlay){.rose = CreateRose()};
}
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);
// Mouse
Rectangle mouseRectangle = (Rectangle){0, 0, mouse->cursorTexture.width,
mouse->cursorTexture.height};
DrawTextureRec(mouse->cursorTexture, mouseRectangle, mouse->screenPosition,
WHITE);
Rectangle mouseRectangle =
(Rectangle){0, 0, mouse->cursorTexture.width, mouse->cursorTexture.height};
DrawTextureRec(mouse->cursorTexture, mouseRectangle, mouse->screenPosition, WHITE);
RenderRose(rose);
}
+273
View File
@@ -0,0 +1,273 @@
#include "pathfinding.h"
#include <math.h>
#include <raymath.h>
#include <stdbool.h>
#include <stdlib.h>
#define PATH_GRID_MAX_DIM 24
#define PATH_MIN_CELL_SIZE 16.0f
#define PATH_GRID_MAX_NODES ((PATH_GRID_MAX_DIM + 2) * (PATH_GRID_MAX_DIM + 2))
#define PATH_LINE_SAMPLE_STEP 8.0f
#define CLEARANCE_GRID_STEP 8.0f
#define PATH_GRID_PADDING 256.0f
typedef struct {
float minX, minY;
float cellSize;
int cols, rows, nodeCount;
int startIdx, goalIdx;
} PathGrid;
static bool IsKnownWater(Terrain *terrain, Vector2 pos) {
return IsTerrainLoaded(terrain, pos) && !IsTerrainLand(terrain, pos);
}
bool IsWaterWithClearance(Terrain *terrain, Vector2 pos, float clearance) {
if (!IsKnownWater(terrain, pos))
return false;
int radiusSteps = (int)(clearance / CLEARANCE_GRID_STEP) + 1;
float clearanceSquared = clearance * clearance;
for (int gy = -radiusSteps; gy <= radiusSteps; gy++) {
for (int gx = -radiusSteps; gx <= radiusSteps; gx++) {
if (gx == 0 && gy == 0)
continue;
float ox = (float)gx * CLEARANCE_GRID_STEP;
float oy = (float)gy * CLEARANCE_GRID_STEP;
if (ox * ox + oy * oy > clearanceSquared)
continue;
if (!IsKnownWater(terrain, (Vector2){pos.x + ox, pos.y + oy}))
return false;
}
}
return true;
}
Vector2 GetRandomWaterPositionNear(Terrain *terrain, Vector2 origin, float maxRadius,
float clearance) {
float startAngle = (float)GetRandomValue(0, 359);
for (float r = 16.0f; r < maxRadius; r += 16.0f) {
for (float aStep = 0.0f; aStep < 360.0f; aStep += 30.0f) {
float a = startAngle + aStep;
Vector2 p = {origin.x + cosf(a * DEG2RAD) * r, origin.y + sinf(a * DEG2RAD) * r};
if (IsWaterWithClearance(terrain, p, clearance))
return p;
}
}
return origin;
}
static bool IsLineClear(Terrain *terrain, Vector2 a, Vector2 b, float clearance) {
float dist = Vector2Distance(a, b);
int steps = (int)(dist / PATH_LINE_SAMPLE_STEP) + 1;
for (int i = 0; i <= steps; i++) {
Vector2 p = Vector2Lerp(a, b, (float)i / (float)steps);
if (!IsWaterWithClearance(terrain, p, clearance))
return false;
}
return true;
}
static float Heuristic(int c1, int r1, int c2, int r2, float cellSize) {
int dx = abs(c1 - c2);
int dy = abs(r1 - r2);
int mn = dx < dy ? dx : dy;
int mx = dx < dy ? dy : dx;
return cellSize * (1.41421356f * (float)mn + (float)(mx - mn));
}
static int ClampToGrid(int value, int size) {
if (value < 0)
return 0;
if (value >= size)
return size - 1;
return value;
}
static PathGrid BuildPathGrid(Vector2 start, Vector2 goal) {
PathGrid grid = {0};
float minX = fminf(start.x, goal.x) - PATH_GRID_PADDING;
float maxX = fmaxf(start.x, goal.x) + PATH_GRID_PADDING;
float minY = fminf(start.y, goal.y) - PATH_GRID_PADDING;
float maxY = fmaxf(start.y, goal.y) + PATH_GRID_PADDING;
float spanX = maxX - minX;
float spanY = maxY - minY;
float cellSize = fmaxf(PATH_MIN_CELL_SIZE, fmaxf(spanX, spanY) / (float)PATH_GRID_MAX_DIM);
grid.minX = minX;
grid.minY = minY;
grid.cellSize = cellSize;
grid.cols = (int)(spanX / cellSize) + 1;
grid.rows = (int)(spanY / cellSize) + 1;
grid.nodeCount = grid.cols * grid.rows;
if (grid.nodeCount > PATH_GRID_MAX_NODES || grid.nodeCount <= 0) {
grid.nodeCount = 0;
return grid;
}
int startCol = ClampToGrid((int)((start.x - minX) / cellSize), grid.cols);
int startRow = ClampToGrid((int)((start.y - minY) / cellSize), grid.rows);
int goalCol = ClampToGrid((int)((goal.x - minX) / cellSize), grid.cols);
int goalRow = ClampToGrid((int)((goal.y - minY) / cellSize), grid.rows);
grid.startIdx = startRow * grid.cols + startCol;
grid.goalIdx = goalRow * grid.cols + goalCol;
return grid;
}
static bool RunAStar(Terrain *terrain, PathGrid grid, float clearance, int *cameFrom) {
static float gScore[PATH_GRID_MAX_NODES];
static float fScore[PATH_GRID_MAX_NODES];
static bool inOpen[PATH_GRID_MAX_NODES];
static bool inClosed[PATH_GRID_MAX_NODES];
int cols = grid.cols, rows = grid.rows;
int goalCol = grid.goalIdx % cols, goalRow = grid.goalIdx / cols;
for (int i = 0; i < grid.nodeCount; i++) {
gScore[i] = INFINITY;
cameFrom[i] = -1;
inOpen[i] = false;
inClosed[i] = false;
}
gScore[grid.startIdx] = 0.0f;
fScore[grid.startIdx] =
Heuristic(grid.startIdx % cols, grid.startIdx / cols, goalCol, goalRow, grid.cellSize);
inOpen[grid.startIdx] = true;
int openCount = 1;
static const int neighborCol[8] = {-1, 1, 0, 0, -1, -1, 1, 1};
static const int neighborRow[8] = {0, 0, -1, 1, -1, 1, -1, 1};
while (openCount > 0) {
int current = -1;
float best = INFINITY;
for (int i = 0; i < grid.nodeCount; i++) {
if (inOpen[i] && fScore[i] < best) {
best = fScore[i];
current = i;
}
}
if (current == -1)
break;
if (current == grid.goalIdx)
return true;
inOpen[current] = false;
openCount--;
inClosed[current] = true;
int curCol = current % cols;
int curRow = current / cols;
for (int n = 0; n < 8; n++) {
int nc = curCol + neighborCol[n];
int nr = curRow + neighborRow[n];
if (nc < 0 || nc >= cols || nr < 0 || nr >= rows)
continue;
int nIdx = nr * cols + nc;
if (inClosed[nIdx])
continue;
Vector2 cellCenter = {grid.minX + ((float)nc + 0.5f) * grid.cellSize,
grid.minY + ((float)nr + 0.5f) * grid.cellSize};
if (!IsWaterWithClearance(terrain, cellCenter, clearance))
continue;
if (neighborCol[n] != 0 && neighborRow[n] != 0) {
Vector2 side1 = {grid.minX + ((float)(curCol + neighborCol[n]) + 0.5f) * grid.cellSize,
grid.minY + ((float)curRow + 0.5f) * grid.cellSize};
Vector2 side2 = {grid.minX + ((float)curCol + 0.5f) * grid.cellSize,
grid.minY + ((float)(curRow + neighborRow[n]) + 0.5f) * grid.cellSize};
if (!IsWaterWithClearance(terrain, side1, clearance) ||
!IsWaterWithClearance(terrain, side2, clearance))
continue;
}
float moveCost = (neighborCol[n] != 0 && neighborRow[n] != 0) ? grid.cellSize * 1.41421356f
: grid.cellSize;
float tentativeG = gScore[current] + moveCost;
if (tentativeG < gScore[nIdx]) {
cameFrom[nIdx] = current;
gScore[nIdx] = tentativeG;
fScore[nIdx] = tentativeG + Heuristic(nc, nr, goalCol, goalRow, grid.cellSize);
if (!inOpen[nIdx]) {
inOpen[nIdx] = true;
openCount++;
}
}
}
}
return false;
}
static int ReconstructRawPath(PathGrid grid, const int *cameFrom, Vector2 goal,
Vector2 *rawPoints) {
int rawCount = 0;
for (int cur = grid.goalIdx; cur != -1; cur = cameFrom[cur]) {
int c = cur % grid.cols;
int r = cur / grid.cols;
rawPoints[rawCount++] = (Vector2){grid.minX + ((float)c + 0.5f) * grid.cellSize,
grid.minY + ((float)r + 0.5f) * grid.cellSize};
}
for (int i = 0; i < rawCount / 2; i++) {
Vector2 tmp = rawPoints[i];
rawPoints[i] = rawPoints[rawCount - 1 - i];
rawPoints[rawCount - 1 - i] = tmp;
}
rawPoints[rawCount - 1] = goal;
return rawCount;
}
static int SimplifyPathWithStringPulling(Terrain *terrain, Vector2 start, const Vector2 *rawPoints,
int rawCount, float clearance, Vector2 *outWaypoints) {
int count = 0;
int i = 0;
Vector2 anchor = start;
while (i < rawCount - 1 && count < PATH_MAX_WAYPOINTS) {
int farthest = i + 1;
for (int j = rawCount - 1; j > i; j--) {
if (IsLineClear(terrain, anchor, rawPoints[j], clearance)) {
farthest = j;
break;
}
}
anchor = rawPoints[farthest];
outWaypoints[count++] = anchor;
i = farthest;
}
return count;
}
Path FindWaterPath(Terrain *terrain, Vector2 start, Vector2 goal, float clearance) {
Path path = {0};
if (IsLineClear(terrain, start, goal, clearance)) {
path.waypoints[0] = goal;
path.count = 1;
return path;
}
PathGrid grid = BuildPathGrid(start, goal);
if (grid.nodeCount == 0)
return path;
static int cameFrom[PATH_GRID_MAX_NODES];
if (!RunAStar(terrain, grid, clearance, cameFrom))
return path;
static Vector2 rawPoints[PATH_GRID_MAX_NODES];
int rawCount = ReconstructRawPath(grid, cameFrom, goal, rawPoints);
path.count =
SimplifyPathWithStringPulling(terrain, start, rawPoints, rawCount, clearance, path.waypoints);
return path;
}
+108 -72
View File
@@ -1,8 +1,27 @@
#include "app.h"
#include "engine.h"
#include <raylib.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;
#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 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};
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++) {
if (playerSpeeds[i] == speed) {
return (playerSpeedID)i;
return (PlayerSpeedID)i;
}
}
return ANCHOR;
}
Player *GetPlayer(void) {
static Player player;
return &player;
static void RandomizeSpawn(Player *player, Terrain *terrain) {
player->physics.position = GetRandomWaterPositionNear(
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) {
InitConfig(&config, "config/player.ini");
Player CreatePlayer(Terrain *terrain) {
Player player = {0};
InitConfig(&player.config, "config/player.ini");
int PLAYER_HEALTH_MAX = GetConfigInt(&config, "HEALTH_MAX");
int START_GOLD = GetConfigInt(&config, "START_GOLD");
int PLAYER_HEALTH_MAX = GetConfigInt(&player.config, "HEALTH_MAX");
int START_GOLD = GetConfigInt(&player.config, "START_GOLD");
TextureAsset startShipAsset = playerTextureAssets[START_SHIP];
Texture2D texture = LoadTexture(startShipAsset.path);
Texture2D texture = LoadTextureCached(startShipAsset.path);
float frameWidth = startShipAsset.frameWidth;
float rotation =
(float)LoadStorageValue(STORAGE_DATA_FILE, STORAGE_POSITION_ROTATION);
Sprite playerSprite = {.texture = texture,
.frameSize = {frameWidth, frameWidth},
.sourceRec = {0.0f, 0.0f, frameWidth, frameWidth},
.destRec = {0.0f, 0.0f, frameWidth, frameWidth},
.origin = {frameWidth / 2, frameWidth / 2},
.rotation = rotation,
.color = WHITE};
PlayerSaveData saveData = {0};
LoadStorageData(STORAGE_DATA_FILE, PLAYER_SAVE_VERSION, &saveData, sizeof(saveData));
Animation playerAnimation = {.state = 1,
.currentFrame = 0,
.maxFrame = 3,
.framesCounter = 0,
.framesSpeed = 0.0f,
.animTimer = 0.0f};
player.sprite = (Sprite){.texture = texture,
.frameSize = {frameWidth, frameWidth},
.sourceRec = {0.0f, 0.0f, frameWidth, frameWidth},
.destRec = {0.0f, 0.0f, frameWidth, frameWidth},
.origin = {frameWidth / 2, frameWidth / 2},
.rotation = 0.0f,
.color = WHITE};
Physics playerPhysics = {
.position = {(float)LoadStorageValue(STORAGE_DATA_FILE,
STORAGE_POSITION_POSX),
(float)LoadStorageValue(STORAGE_DATA_FILE,
STORAGE_POSITION_POSY)},
.direction = {0.0f, 0.0f},
.speed = playerSpeeds[ANCHOR],
.weight = 10.0f};
player.animation = (Animation){.state = 1,
.currentFrame = 0,
.maxFrame = 3,
.framesCounter = 0,
.framesSpeed = 0.0f,
.animTimer = 0.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){
.sprite = playerSprite,
.animation = playerAnimation,
.physics = playerPhysics,
.rotation = rotation,
.soundFlapping = LoadSound(playerSoundAssets[FLAPPING].path),
.health = PLAYER_HEALTH_MAX,
.gold = LoadStorageValue(STORAGE_DATA_FILE, STORAGE_POSITION_GOLD)};
player.rotation = 0.0f;
player.soundFlapping = LoadSoundCached(playerSoundAssets[FLAPPING].path);
player.health = PLAYER_HEALTH_MAX;
player.gold = saveData.gold;
if (player->gold == 0) {
player->gold = START_GOLD;
if (player.gold == 0) {
player.gold = START_GOLD;
}
RandomizeSpawn(&player, terrain);
return player;
}
void UpdatePlayer(void) {
Player *player = GetPlayer();
if (IsKeyPressed(KEY_W) || IsKeyPressed(KEY_UP)) {
void ResetPlayer(Player *player, Terrain *terrain) {
int PLAYER_HEALTH_MAX = GetConfigInt(&player->config, "HEALTH_MAX");
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[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[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);
float rotationSpeed = 50.0f;
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();
}
}
UpdatePlayerRotationFromInput(player);
player->sprite.rotation = player->rotation;
Vector2 dir = RotationToVector2(player->rotation - 90.0f);
UpdatePhysics(&player->physics, dir);
UpdateSpriteDestRec(&player->sprite, &player->physics.position);
SaveStorageValue(STORAGE_DATA_FILE, STORAGE_POSITION_POSX,
(int)player->physics.position.x);
SaveStorageValue(STORAGE_DATA_FILE, STORAGE_POSITION_POSY,
(int)player->physics.position.y);
SaveStorageValue(STORAGE_DATA_FILE, STORAGE_POSITION_ROTATION,
(int)player->rotation);
UpdateWakeTrail(&player->wakeTrail, player->physics.position);
SavePlayer(player);
}
void RenderPlayer(void) {
Player *player = GetPlayer();
void RenderPlayer(Player *player) {
RenderSprite(&player->sprite);
}
+60 -55
View File
@@ -1,94 +1,99 @@
#include "app.h"
#include <raylib.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;
const SoundAsset soundAssets[SOUND_COUNT] = {{"assets/cannonball_shooting.mp3"},
{"assets/cannonball_impact.mp3"}};
#define PROJECTILE_RELOAD_TIME 1.0f
#define PROJECTILE_MUZZLE_OFFSET 12.0f
Projectiles CreateProjectiles(void) {
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++) {
projectiles.physics[i] =
(Physics){(Vector2){0.0f, 0.0f}, (Vector2){0.0f, 0.0f},
(Vector2){0.0f, 0.0f}, 50.0f};
projectiles.physics[i] = (Physics){(Vector2){0.0f, 0.0f}, (Vector2){0.0f, 0.0f},
(Vector2){0.0f, 0.0f}, PROJECTILE_SPEED};
projectiles.radius[i] = 1.0f;
projectiles.active[i] = false;
projectiles.friendly[i] = true;
projectiles.color[i] = GRAY;
projectiles.soundShooting[i] =
LoadSound(soundAssets[CANNONBALL_SHOOTING].path);
projectiles.soundImpact[i] = LoadSound(soundAssets[CANNONBALL_IMPACT].path);
projectiles.soundShooting[i] = LoadSoundAlias(shootingSound);
projectiles.soundImpact[i] = LoadSoundAlias(impactSound);
}
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;
const float reloadTime = 1.0f;
float currentTime = GetTime();
if (IsKeyDown(KEY_Q) && (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;
}
}
}
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;
}
}
}
TryFirePlayerBroadside(projectiles, player, ACTION_FIRE_PORT, -90.0f, currentTime, &lastShotTime);
TryFirePlayerBroadside(projectiles, player, ACTION_FIRE_STARBOARD, 90.0f, currentTime,
&lastShotTime);
for (int i = 0; i < projectiles->count; i++) {
if (!projectiles->active[i])
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);
}
}
void ResetProjectiles(Projectiles *projectiles) {
for (int i = 0; i < projectiles->count; i++) {
projectiles->active[i] = false;
}
}
void RenderProjectiles(Projectiles *projectiles) {
for (int i = 0; i < projectiles->count; i++) {
if (projectiles->active[i] == true) {
DrawCircle(projectiles->physics[i].position.x,
projectiles->physics[i].position.y, projectiles->radius[i],
projectiles->color[i]);
DrawCircleV(projectiles->physics[i].position, projectiles->radius[i], projectiles->color[i]);
}
}
}
+59
View File
@@ -0,0 +1,59 @@
#include <raylib.h>
#include "appCore.h"
#include "engineAssets.h"
#include "rose.h"
#include "engineSprite.h"
const TextureAsset roseTextureAssets[ROSE_TEX_COUNT] = {{
"assets/rose.png",
2560.0f,
2560.0f,
0.0f
}};
Rose CreateRose(void){
TextureAsset roseAsset = roseTextureAssets[ROSE];
Texture2D texture = LoadTextureCached(roseAsset.path);
float frameWidth = roseAsset.frameWidth;
Vector2 frameSize = {frameWidth, frameWidth};
float rotation = roseAsset.rotation;
Rectangle sourceRec = {0.0f, 0.0f, frameWidth, frameWidth};
float destSize = GetScreenWidth() * ROSE_SCREEN_WIDTH_FRACTION;
Rectangle destRec = {GetScreenWidth() / 2, GetScreenHeight(), destSize, destSize};
Vector2 origin = {destSize / 2, destSize / 2};
Color color = WHITE;
Sprite sprite = {
.texture = texture,
.frameSize = frameSize,
.sourceRec = sourceRec,
.destRec = destRec,
.origin = origin,
.rotation = rotation,
.color = color
};
Rose rose = {
.sprite = sprite
};
return rose;
}
void UpdateRose(Rose *rose, Player *player){
float rotationSpeed = player->rotation;
if (IsGameActionDown(ACTION_TURN_LEFT)) {
rose->sprite.rotation -= rotationSpeed * GetFrameTime();
}
if (IsGameActionDown(ACTION_TURN_RIGHT)) {
rose->sprite.rotation += rotationSpeed * GetFrameTime();
}
}
void RenderRose(Rose *rose){
RenderSprite(&rose->sprite);
}
+286
View File
@@ -0,0 +1,286 @@
#include <math.h>
#include <raylib.h>
#include <stdlib.h>
#include "terrain.h"
#define TERRAIN_BASE_SCALE 2.2f
#define TERRAIN_DETAIL_SCALE 9.0f
#define TERRAIN_DETAIL_WEIGHT 0.22f
#define TERRAIN_WATER_LEVEL 0.62f
#define TERRAIN_SAND_BAND 0.035f
#define TERRAIN_GRASS_BAND 0.12f
#define TERRAIN_SHORE_BAND_RADIUS 3
#define TERRAIN_STREAM_BUDGET 2
static int WorldToChunkCoord(float worldCoord) {
return (int)floorf(worldCoord / TERRAIN_CHUNK_WORLD_SIZE);
}
static void GenerateChunkHeight(int chunkX, int chunkY, int padding, int paddedResolution,
float *outHeight) {
int offsetX = chunkX * TERRAIN_CHUNK_RESOLUTION - padding;
int offsetY = chunkY * TERRAIN_CHUNK_RESOLUTION - padding;
Image base =
GenImagePerlinNoise(paddedResolution, paddedResolution, offsetX, offsetY, TERRAIN_BASE_SCALE);
Image detail = GenImagePerlinNoise(paddedResolution, paddedResolution, offsetX, offsetY,
TERRAIN_DETAIL_SCALE);
for (int y = 0; y < paddedResolution; y++) {
for (int x = 0; x < paddedResolution; x++) {
float baseValue = GetImageColor(base, x, y).r / 255.0f;
float detailValue = GetImageColor(detail, x, y).r / 255.0f;
outHeight[y * paddedResolution + x] =
baseValue * (1.0f - TERRAIN_DETAIL_WEIGHT) + detailValue * TERRAIN_DETAIL_WEIGHT;
}
}
UnloadImage(base);
UnloadImage(detail);
}
static Color GetLandColor(float height) {
Color sandColor = {233, 220, 178, 255};
Color grassColor = {80, 168, 84, 255};
Color hillColor = {52, 120, 64, 255};
if (height < TERRAIN_WATER_LEVEL + TERRAIN_SAND_BAND)
return sandColor;
if (height < TERRAIN_WATER_LEVEL + TERRAIN_GRASS_BAND)
return grassColor;
return hillColor;
}
static Color GetShoreAlphaColor(const float *height, int paddedResolution, int px, int py) {
float nearestDistSq = 1e9f;
int r = TERRAIN_SHORE_BAND_RADIUS;
for (int dy = -r; dy <= r; dy++) {
for (int dx = -r; dx <= r; dx++) {
if (height[(py + dy) * paddedResolution + (px + dx)] >= TERRAIN_WATER_LEVEL) {
float distSq = (float)(dx * dx + dy * dy);
if (distSq < nearestDistSq)
nearestDistSq = distSq;
}
}
}
if (nearestDistSq > (float)(r * r))
return BLANK;
float t = 1.0f - sqrtf(nearestDistSq) / (float)r;
float alphaValue = t * 254.0f;
if (alphaValue < 1.0f)
alphaValue = 1.0f;
if (alphaValue > 254.0f)
alphaValue = 254.0f;
return (Color){255, 255, 255, (unsigned char)alphaValue};
}
static void PaintChunkPixel(Image *image, bool *landMask, const float *height, int paddedResolution,
int padding, int x, int y) {
int px = x + padding, py = y + padding;
float h = height[py * paddedResolution + px];
bool isLand = h >= TERRAIN_WATER_LEVEL;
landMask[y * TERRAIN_CHUNK_RESOLUTION + x] = isLand;
if (isLand) {
ImageDrawPixel(image, x, y, GetLandColor(h));
return;
}
Color shoreColor = GetShoreAlphaColor(height, paddedResolution, px, py);
if (shoreColor.a > 0) {
ImageDrawPixel(image, x, y, shoreColor);
}
}
static void GenerateChunk(TerrainChunk *chunk, int chunkX, int chunkY) {
if (chunk->active) {
UnloadTexture(chunk->sprite.texture);
free(chunk->landMask);
}
chunk->landMask = malloc(TERRAIN_CHUNK_RESOLUTION * TERRAIN_CHUNK_RESOLUTION * sizeof(bool));
int padding = TERRAIN_SHORE_BAND_RADIUS;
int paddedResolution = TERRAIN_CHUNK_RESOLUTION + padding * 2;
float *height = malloc((size_t)paddedResolution * (size_t)paddedResolution * sizeof(float));
GenerateChunkHeight(chunkX, chunkY, padding, paddedResolution, height);
Image image = GenImageColor(TERRAIN_CHUNK_RESOLUTION, TERRAIN_CHUNK_RESOLUTION, BLANK);
for (int y = 0; y < TERRAIN_CHUNK_RESOLUTION; y++) {
for (int x = 0; x < TERRAIN_CHUNK_RESOLUTION; x++) {
PaintChunkPixel(&image, chunk->landMask, height, paddedResolution, padding, x, y);
}
}
free(height);
Texture2D texture = LoadTextureFromImage(image);
UnloadImage(image);
SetTextureFilter(texture, TEXTURE_FILTER_POINT);
chunk->active = true;
chunk->chunkX = chunkX;
chunk->chunkY = chunkY;
chunk->sprite =
(Sprite){.texture = texture,
.frameSize = {TERRAIN_CHUNK_RESOLUTION, TERRAIN_CHUNK_RESOLUTION},
.sourceRec = {0.0f, 0.0f, TERRAIN_CHUNK_RESOLUTION, TERRAIN_CHUNK_RESOLUTION},
.destRec = {chunkX * TERRAIN_CHUNK_WORLD_SIZE, chunkY * TERRAIN_CHUNK_WORLD_SIZE,
TERRAIN_CHUNK_WORLD_SIZE, TERRAIN_CHUNK_WORLD_SIZE},
.origin = {0.0f, 0.0f},
.rotation = 0.0f,
.color = WHITE};
}
static bool IsChunkLoaded(Terrain *terrain, int chunkX, int chunkY) {
for (int i = 0; i < TERRAIN_MAX_CHUNKS; i++) {
if (terrain->chunks[i].active && terrain->chunks[i].chunkX == chunkX &&
terrain->chunks[i].chunkY == chunkY) {
return true;
}
}
return false;
}
Terrain CreateTerrain(Camera2D *camera) {
Terrain terrain = {0};
terrain.shader = LoadShader("shaders/terrain.vs", "shaders/terrain.fs");
terrain.time = 0.0f;
int centerX = WorldToChunkCoord(camera->target.x);
int centerY = WorldToChunkCoord(camera->target.y);
int slot = 0;
for (int cy = centerY - TERRAIN_LOAD_RADIUS; cy <= centerY + TERRAIN_LOAD_RADIUS; cy++) {
for (int cx = centerX - TERRAIN_LOAD_RADIUS; cx <= centerX + TERRAIN_LOAD_RADIUS; cx++) {
GenerateChunk(&terrain.chunks[slot], cx, cy);
slot++;
}
}
return terrain;
}
static void UnloadChunk(TerrainChunk *chunk) {
UnloadTexture(chunk->sprite.texture);
free(chunk->landMask);
chunk->landMask = NULL;
chunk->active = false;
}
void UpdateTerrain(Terrain *terrain, Camera2D *camera) {
int centerX = WorldToChunkCoord(camera->target.x);
int centerY = WorldToChunkCoord(camera->target.y);
for (int i = 0; i < TERRAIN_MAX_CHUNKS; i++) {
if (!terrain->chunks[i].active)
continue;
int dx = terrain->chunks[i].chunkX - centerX;
int dy = terrain->chunks[i].chunkY - centerY;
if (dx < -TERRAIN_LOAD_RADIUS || dx > TERRAIN_LOAD_RADIUS || dy < -TERRAIN_LOAD_RADIUS ||
dy > TERRAIN_LOAD_RADIUS) {
UnloadChunk(&terrain->chunks[i]);
}
}
int budget = TERRAIN_STREAM_BUDGET;
for (int cy = centerY - TERRAIN_LOAD_RADIUS; cy <= centerY + TERRAIN_LOAD_RADIUS && budget > 0;
cy++) {
for (int cx = centerX - TERRAIN_LOAD_RADIUS; cx <= centerX + TERRAIN_LOAD_RADIUS && budget > 0;
cx++) {
if (IsChunkLoaded(terrain, cx, cy))
continue;
for (int i = 0; i < TERRAIN_MAX_CHUNKS; i++) {
if (!terrain->chunks[i].active) {
GenerateChunk(&terrain->chunks[i], cx, cy);
budget--;
break;
}
}
}
}
terrain->time += GetFrameTime();
int timeLoc = GetShaderLocation(terrain->shader, "time");
SetShaderValue(terrain->shader, timeLoc, &terrain->time, SHADER_UNIFORM_FLOAT);
}
void RenderTerrain(Terrain *terrain) {
BeginShaderMode(terrain->shader);
for (int i = 0; i < TERRAIN_MAX_CHUNKS; i++) {
if (terrain->chunks[i].active) {
RenderSprite(&terrain->chunks[i].sprite);
}
}
EndShaderMode();
}
void UnloadTerrain(Terrain *terrain) {
UnloadShader(terrain->shader);
for (int i = 0; i < TERRAIN_MAX_CHUNKS; i++) {
if (terrain->chunks[i].active) {
UnloadChunk(&terrain->chunks[i]);
}
}
}
static TerrainChunk *GetChunkAt(Terrain *terrain, Vector2 worldPos, int *outPx, int *outPy) {
int cx = WorldToChunkCoord(worldPos.x);
int cy = WorldToChunkCoord(worldPos.y);
for (int i = 0; i < TERRAIN_MAX_CHUNKS; i++) {
TerrainChunk *chunk = &terrain->chunks[i];
if (!chunk->active || chunk->chunkX != cx || chunk->chunkY != cy)
continue;
float localX = worldPos.x - (float)cx * TERRAIN_CHUNK_WORLD_SIZE;
float localY = worldPos.y - (float)cy * TERRAIN_CHUNK_WORLD_SIZE;
int px = (int)(localX / TERRAIN_CHUNK_WORLD_SIZE * TERRAIN_CHUNK_RESOLUTION);
int py = (int)(localY / TERRAIN_CHUNK_WORLD_SIZE * TERRAIN_CHUNK_RESOLUTION);
if (px < 0)
px = 0;
if (px >= TERRAIN_CHUNK_RESOLUTION)
px = TERRAIN_CHUNK_RESOLUTION - 1;
if (py < 0)
py = 0;
if (py >= TERRAIN_CHUNK_RESOLUTION)
py = TERRAIN_CHUNK_RESOLUTION - 1;
*outPx = px;
*outPy = py;
return chunk;
}
return NULL;
}
bool IsTerrainLand(Terrain *terrain, Vector2 worldPos) {
int px, py;
TerrainChunk *chunk = GetChunkAt(terrain, worldPos, &px, &py);
if (!chunk)
return false;
return chunk->landMask[py * TERRAIN_CHUNK_RESOLUTION + px];
}
bool IsTerrainLoaded(Terrain *terrain, Vector2 worldPos) {
int cx = WorldToChunkCoord(worldPos.x);
int cy = WorldToChunkCoord(worldPos.y);
for (int i = 0; i < TERRAIN_MAX_CHUNKS; i++) {
TerrainChunk *chunk = &terrain->chunks[i];
if (chunk->active && chunk->chunkX == cx && chunk->chunkY == cy)
return true;
}
return false;
}
+120
View File
@@ -0,0 +1,120 @@
#include <math.h>
#include "wake.h"
#define WAKE_TRAIL_MIN_SAMPLE_DIST 2.5f
#define WAKE_TRAIL_MAX_AGE 1.8
#define WAKE_MIN_SPEED 0.5f
#define WAKE_MAX_SPEED 10.0f
static void ExpireOldWakePoints(WakeTrail *trail, double now) {
while (trail->count > 0 && now - trail->timestamps[trail->count - 1] > WAKE_TRAIL_MAX_AGE) {
trail->count--;
}
}
static bool HasTraveledFarEnough(WakeTrail *trail, Vector2 position) {
if (trail->count == 0)
return true;
float dx = position.x - trail->points[0].x;
float dy = position.y - trail->points[0].y;
return dx * dx + dy * dy >= WAKE_TRAIL_MIN_SAMPLE_DIST * WAKE_TRAIL_MIN_SAMPLE_DIST;
}
void UpdateWakeTrail(WakeTrail *trail, Vector2 position) {
double now = GetTime();
ExpireOldWakePoints(trail, now);
if (!HasTraveledFarEnough(trail, position))
return;
for (int i = WAKE_TRAIL_POINTS - 1; i > 0; i--) {
trail->points[i] = trail->points[i - 1];
trail->timestamps[i] = trail->timestamps[i - 1];
}
trail->points[0] = position;
trail->timestamps[0] = now;
if (trail->count < WAKE_TRAIL_POINTS)
trail->count++;
}
void ResetWakeTrail(WakeTrail *trail) {
trail->count = 0;
}
Wake CreateWake(void) {
Wake wake = {0};
wake.shader = LoadShader("shaders/wake.vs", "shaders/wake.fs");
wake.time = 0.0f;
return wake;
}
void UpdateWake(Wake *wake, float deltaTime) {
wake->time += deltaTime;
int timeLoc = GetShaderLocation(wake->shader, "time");
SetShaderValue(wake->shader, timeLoc, &wake->time, SHADER_UNIFORM_FLOAT);
}
static void ComputeWakeAges(WakeTrail *trail, double now, float *outAges) {
for (int i = 0; i < WAKE_TRAIL_POINTS; i++) {
if (i >= trail->count) {
outAges[i] = 1.0f;
continue;
}
float age = (float)((now - trail->timestamps[i]) / WAKE_TRAIL_MAX_AGE);
outAges[i] = age < 0.0f ? 0.0f : (age > 1.0f ? 1.0f : age);
}
}
static Rectangle ComputeWakeBoundingBox(WakeTrail *trail) {
float minX = trail->points[0].x, maxX = trail->points[0].x;
float minY = trail->points[0].y, maxY = trail->points[0].y;
for (int i = 1; i < trail->count; i++) {
if (trail->points[i].x < minX)
minX = trail->points[i].x;
if (trail->points[i].x > maxX)
maxX = trail->points[i].x;
if (trail->points[i].y < minY)
minY = trail->points[i].y;
if (trail->points[i].y > maxY)
maxY = trail->points[i].y;
}
float margin = 10.0f;
return (Rectangle){minX - margin, minY - margin, (maxX - minX) + margin * 2.0f,
(maxY - minY) + margin * 2.0f};
}
void RenderShipWake(Wake *wake, WakeTrail *trail, float speed) {
if (trail->count < 2)
return;
float t = (fabsf(speed) - WAKE_MIN_SPEED) / (WAKE_MAX_SPEED - WAKE_MIN_SPEED);
if (t < 0.0f)
t = 0.0f;
if (t > 1.0f)
t = 1.0f;
float ages[WAKE_TRAIL_POINTS];
ComputeWakeAges(trail, GetTime(), ages);
int pointsLoc = GetShaderLocation(wake->shader, "trailPoints");
int agesLoc = GetShaderLocation(wake->shader, "trailAges");
int countLoc = GetShaderLocation(wake->shader, "trailCount");
int intensityLoc = GetShaderLocation(wake->shader, "wakeIntensity");
SetShaderValueV(wake->shader, pointsLoc, trail->points, SHADER_UNIFORM_VEC2, WAKE_TRAIL_POINTS);
SetShaderValueV(wake->shader, agesLoc, ages, SHADER_UNIFORM_FLOAT, WAKE_TRAIL_POINTS);
SetShaderValue(wake->shader, countLoc, &trail->count, SHADER_UNIFORM_INT);
SetShaderValue(wake->shader, intensityLoc, &t, SHADER_UNIFORM_FLOAT);
Rectangle box = ComputeWakeBoundingBox(trail);
BeginShaderMode(wake->shader);
DrawRectangleRec(box, WHITE);
EndShaderMode();
}
void UnloadWake(Wake *wake) {
UnloadShader(wake->shader);
}
+28
View File
@@ -0,0 +1,28 @@
#include "appCore.h"
#include <raylib.h>
typedef struct {
int keys[2];
} ActionBinding;
static const ActionBinding actionBindings[ACTION_COUNT] = {
[ACTION_FORWARD] = {{KEY_W, KEY_UP}},
[ACTION_BACKWARD] = {{KEY_S, KEY_DOWN}},
[ACTION_TURN_LEFT] = {{KEY_A, KEY_LEFT}},
[ACTION_TURN_RIGHT] = {{KEY_D, KEY_RIGHT}},
[ACTION_FIRE_PORT] = {{KEY_Q, KEY_NULL}},
[ACTION_FIRE_STARBOARD] = {{KEY_E, KEY_NULL}},
[ACTION_BACK] = {{KEY_ESCAPE, KEY_NULL}},
};
bool IsGameActionPressed(GameAction action) {
const ActionBinding *binding = &actionBindings[action];
return IsKeyPressed(binding->keys[0]) ||
(binding->keys[1] != KEY_NULL && IsKeyPressed(binding->keys[1]));
}
bool IsGameActionDown(GameAction action) {
const ActionBinding *binding = &actionBindings[action];
return IsKeyDown(binding->keys[0]) ||
(binding->keys[1] != KEY_NULL && IsKeyDown(binding->keys[1]));
}
+3 -2
View File
@@ -1,4 +1,5 @@
#include "app.h"
#include "logo.h"
#include <raylib.h>
void InitLogo(LogoState *l) {
SetTargetFPS(60);
@@ -16,7 +17,7 @@ void InitLogo(LogoState *l) {
void UpdateLogo(LogoState *l, View *currentView, bool *running) {
if (WindowShouldClose() && !IsKeyPressed(KEY_ESCAPE)) {
if (WindowShouldClose() && !IsGameActionPressed(ACTION_BACK)) {
*running = false;
}
+31 -38
View File
@@ -1,49 +1,42 @@
#include "app.h"
#include "menu.h"
void InitMenu(MenuState *m)
{
m->menuView = MAIN;
InitMenuSettings(&m->menuSettings);
InitMenuMain(&m->menuMain);
void InitMenu(MenuState *m) {
m->menuView = MAIN;
InitMenuSettings(&m->menuSettings);
InitMenuMain(&m->menuMain);
}
void UpdateMenu(MenuState *m, State *state, Flags *flags)
{
switch (m->menuView)
{
case MAIN:
{
UpdateMenuMain(&m->menuMain, state, flags, &m->menuView);
}break;
void UpdateMenu(MenuState *m, State *state, Flags *flags) {
switch (m->menuView) {
case MAIN:
UpdateMenuMain(&m->menuMain, state, flags, &m->menuView);
break;
case SETTINGS:
{
UpdateMenuSettings(&m->menuSettings, &m->menuView, state, flags);
}break;
default: break;
}
case SETTINGS:
UpdateMenuSettings(&m->menuSettings, &m->menuView, state, flags);
break;
default:
break;
}
}
void RenderMenu(MenuState *m, Font *font, const char *title, Flags *flags)
{
switch (m->menuView)
{
case MAIN:
{
RenderMenuMain(&m->menuMain, font, title, flags);
}break;
void RenderMenu(MenuState *m, Font *font, const char *title, Flags *flags, State *state) {
switch (m->menuView) {
case MAIN:
RenderMenuMain(&m->menuMain, font, title, flags, state->gameStarted);
break;
case SETTINGS:
{
RenderMenuSettings(&m->menuSettings, flags);
}
default: break;
}
case SETTINGS:
RenderMenuSettings(&m->menuSettings, flags);
break;
default:
break;
}
}
void CleanupMenu(MenuState *m)
{
CleanupMenuMain(&m->menuMain);
void CleanupMenu(MenuState *m) {
CleanupMenuMain(&m->menuMain);
CleanupMenuSettings(&m->menuSettings);
}
+147 -185
View File
@@ -1,209 +1,171 @@
#include "app.h"
#include <raylib.h>
#include "engineAssets.h"
#include "menu.h"
const TextureAsset menuTextureAssets[MENU_TEX_COUNT] = {
{"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/cloud_background.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}};
const SoundtrackAsset menuSoundtrackAssets[MENU_SOUNDTRACK_COUNT] = {{"assets/theme.mp3"}};
const SoundtrackAsset menuSoundtrackAssets[MENU_SOUNDTRACK_COUNT] = {
{"assets/theme.mp3"}
};
const SoundAsset menuSoundAssets[MENU_SOUND_COUNT] = {{"assets/button_hover.wav"},
{"assets/button_click.wav"}};
static void InitMenuMainAssets(MenuMain *m) {
m->assets = (Assets){.textureAssets = menuTextureAssets,
.soundAssets = menuSoundAssets,
.soundtrackAssets = menuSoundtrackAssets};
const SoundAsset menuSoundAssets[MENU_SOUND_COUNT] = {
{"assets/button_hover.wav"},
{"assets/button_click.wav"}};
for (int i = 0; i < MENU_TEX_COUNT; i++) {
m->textures[i] = LoadTextureCached(m->assets.textureAssets[i].path);
}
for (int i = 0; i < MENU_SOUND_COUNT; i++) {
m->sounds[i] = LoadSoundCached(m->assets.soundAssets[i].path);
}
void InitMenuMain(MenuMain *m){
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);
for (int i = 0; i < MENU_SOUNDTRACK_COUNT; i++) {
m->soundtracks[i] = LoadSoundtrack(m->assets.soundtrackAssets[i].path);
}
}
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 (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);
static void InitMenuMainBackgrounds(MenuMain *m) {
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]);
}
void RenderMenuMain(MenuMain *m, Font *font, const char *title, Flags *flags){
BeginDrawing();
ClearBackground(WHITE);
RenderBackground(&m->background);
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);
static void InitMenuMainBoard(MenuMain *m, Vector2 windowCenter) {
int boardWidth = GetScreenWidth() / 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);
if (flags->showFPS)
{
DrawFPS(10, 10);
}
EndDrawing();
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);
}
void CleanupMenuMain(MenuMain *m){
for (int i = 0; i < MENU_SOUND_COUNT; i++)
{
UnloadSound(m->sounds[i]);
}
for (int i = 0; i < MENU_TEX_COUNT; i++)
{
if (m->textures[i].id > 0)
{
UnloadTexture(m->textures[i]);
}
}
for (int i = 0; i < MENU_SOUNDTRACK_COUNT; i++)
{
UnloadMusicStream(m->soundtracks[i].music);
}
static void InitMenuMainButtons(MenuMain *m, Vector2 windowCenter) {
int buttonWidth = GetScreenWidth() / m->menuScale;
float buttonScaleFactor =
(float)buttonWidth / m->assets.textureAssets[MENU_BIG_BUTTONS_TEX].frameWidth;
int buttonHeight =
(int)(m->assets.textureAssets[MENU_BIG_BUTTONS_TEX].frameHeight * buttonScaleFactor);
int buttonSpacing = buttonHeight;
m->quitButton = CreateMenuButton(&m->textures[MENU_BIG_BUTTONS_TEX],
&m->assets.textureAssets[MENU_BIG_BUTTONS_TEX], 0, 1,
buttonWidth, buttonHeight, windowCenter, buttonSpacing);
m->playButton = CreateMenuButton(&m->textures[MENU_BIG_BUTTONS_TEX],
&m->assets.textureAssets[MENU_BIG_BUTTONS_TEX], 1, 0,
buttonWidth, buttonHeight, windowCenter, buttonSpacing);
m->settingsButton = CreateMenuButton(&m->textures[MENU_BIG_BUTTONS_TEX],
&m->assets.textureAssets[MENU_BIG_BUTTONS_TEX], 2, 2,
buttonWidth, buttonHeight, windowCenter, buttonSpacing);
Vector2 restartDimensions = {buttonWidth * 0.8f, buttonHeight * 0.4f};
float settingsBottom =
m->settingsButton.sprite.destRec.y + m->settingsButton.sprite.destRec.height;
Vector2 restartCenter = {windowCenter.x, settingsBottom + restartDimensions.y / 2.0f + 16.0f};
m->restartButton = initButton("Restart", restartDimensions, restartCenter);
}
void InitMenuMain(MenuMain *m) {
InitMenuMainAssets(m);
InitMenuMainBackgrounds(m);
m->mouse = CreateMouse(0.10f, 5.0f, 10.0f, &m->textures[MENU_CURSOR_TEX]);
m->menuScale = 4;
Vector2 windowCenter = {GetScreenWidth() / 2, GetScreenHeight() / 2};
InitMenuMainBoard(m, windowCenter);
InitMenuMainButtons(m, windowCenter);
}
void UpdateMenuMain(MenuMain *m, State *state, Flags *flags, MenuView *menuView) {
for (int i = 0; i < MENU_SOUNDTRACK_COUNT; i++) {
UpdateSoundtrack(&m->soundtracks[i], flags->soundtrackOn);
}
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
View File
@@ -1,58 +1,45 @@
#include "app.h"
#include <raylib.h>
void InitBackground(float scrollSpeed, Background *bg, Texture2D *texture, const TextureAsset *backgroundAsset){
bg->scrolling = 0.0f;
bg->scrollSpeed = scrollSpeed;
bg->scale = 1.0f;
bg->sprite = (Sprite){
.texture = *texture,
.sourceRec = (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
};
#include "menu.h"
void InitBackground(float scrollSpeed, Background *bg, Texture2D *texture,
const TextureAsset *backgroundAsset) {
bg->scrolling = 0.0f;
bg->scrollSpeed = scrollSpeed;
bg->scale = 1.0f;
bg->sprite = (Sprite){
.texture = *texture,
.sourceRec =
(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){
bg->scrolling -= bg->scrollSpeed;
void UpdateBackground(Background *bg) {
bg->scrolling -= bg->scrollSpeed;
if (bg->scrolling <= -bg->sprite.texture.width * bg->scale) {
bg->scrolling = 0;
}
if (bg->scrolling <= -bg->sprite.texture.width * bg->scale) {
bg->scrolling = 0;
}
bg->pos = (Vector2){ bg->scrolling, 0.0f };
UpdateSpriteDestRec(&bg->sprite, &bg->pos);
bg->pos = (Vector2){bg->scrolling, 0.0f};
UpdateSpriteDestRec(&bg->sprite, &bg->pos);
}
void RenderBackground(Background *bg){
Sprite temp0 = bg->sprite;
float newX0 = bg->sprite.destRec.x + bg->sprite.texture.width * bg->scale * 2;
Vector2 newPos0 = {newX0, bg->sprite.destRec.y};
UpdateSpriteDestRec(&temp0, &newPos0);
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);
static void RenderBackgroundTile(Background *bg, float tileOffset) {
Sprite tile = bg->sprite;
float x = bg->sprite.destRec.x + bg->sprite.texture.width * bg->scale * tileOffset;
Vector2 pos = {x, bg->sprite.destRec.y};
UpdateSpriteDestRec(&tile, &pos);
RenderSprite(&tile);
}
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
View File
@@ -1,67 +1,61 @@
#include "app.h"
#include <raylib.h>
void InitMenuSettings(MenuSettings *s)
{
int windowWidth = GetScreenWidth();
int windowHeight = GetScreenHeight();
Vector2 windowCenter = {windowWidth / 2, windowHeight / 2};
#include "menu.h"
Vector2 standardButtonDimensions = {100, 50};
int standardSpacing = standardButtonDimensions.y / 2;
void InitMenuSettings(MenuSettings *s) {
Vector2 windowCenter = {GetScreenWidth() / 2, GetScreenHeight() / 2};
Vector2 button1Pos = {windowCenter.x, windowCenter.y};
Vector2 button2Pos = {windowCenter.x, windowCenter.y + standardButtonDimensions.y * 1 + standardSpacing * 1};
Vector2 button3Pos = {windowCenter.x, windowCenter.y + standardButtonDimensions.y * 2 + standardSpacing * 2};
Vector2 standardButtonDimensions = {100, 50};
int standardSpacing = standardButtonDimensions.y / 2;
s->showFPS = initButton("showFPS", standardButtonDimensions, button1Pos);
s->limitFPS = initButton("limitFPS", standardButtonDimensions, button2Pos);
s->soundtrackOn = initButton("soundtrackOn", standardButtonDimensions, button3Pos);
Vector2 button1Pos = {windowCenter.x, windowCenter.y};
Vector2 button2Pos = {windowCenter.x,
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)){
flags->showFPS = !flags->showFPS;
}
if (isButtonClicked(&s->limitFPS)) {
flags->limitFPS = !flags->limitFPS;
}
if(isButtonClicked(&s->limitFPS)){
flags->limitFPS = !flags->limitFPS;
}
if (isButtonClicked(&s->soundtrackOn)) {
flags->soundtrackOn = !flags->soundtrackOn;
}
if(isButtonClicked(&s->soundtrackOn)){
flags->soundtrackOn = !flags->soundtrackOn;
}
if (IsGameActionPressed(ACTION_BACK)) {
*menuView = MAIN;
}
if (IsKeyPressed(KEY_ESCAPE)){
*menuView = MAIN;
}
if (WindowShouldClose() && !IsKeyPressed(KEY_ESCAPE)){
state->running = false;
}
if (WindowShouldClose() && !IsGameActionPressed(ACTION_BACK)) {
state->running = false;
}
}
void RenderMenuSettings(MenuSettings *s, Flags *flags)
{
BeginDrawing();
ClearBackground(BLACK);
void RenderMenuSettings(MenuSettings *s, Flags *flags) {
BeginDrawing();
ClearBackground(BLACK);
renderButton(&s->showFPS);
renderButton(&s->limitFPS);
renderButton(&s->soundtrackOn);
renderButton(&s->showFPS);
renderButton(&s->limitFPS);
renderButton(&s->soundtrackOn);
if (flags->showFPS){
DrawFPS(10,10);
}
if (flags->showFPS) {
DrawFPS(10, 10);
}
EndDrawing();
EndDrawing();
}
void ClenupMenuSettings(MenuSettings *s){
void CleanupMenuSettings(MenuSettings *s) {
(void)s;
}
+11 -3
View File
@@ -1,8 +1,8 @@
#include "app.h"
void OnEscapeChangeView(App *app, View view)
{
if (IsKeyPressed(KEY_ESCAPE))
{
if (IsGameActionPressed(ACTION_BACK))
{
app->state.currentView = view;
}
@@ -10,8 +10,16 @@ void OnEscapeChangeView(App *app, View view)
void OnWindowClosedStoppApp(App *app)
{
if (WindowShouldClose() && !IsKeyPressed(KEY_ESCAPE))
if (WindowShouldClose() && !IsGameActionPressed(ACTION_BACK))
{
app->state.running = false;
}
}
void OnBoolChangeView(bool flag, App *app, View view)
{
if (flag)
{
app->state.currentView = view;
}
}
+11 -11
View File
@@ -1,17 +1,17 @@
#include "engine.h"
#include "engineAnimation.h"
void UpdateAnimation(Animation *animation, float deltaTime) {
animation->animTimer += deltaTime;
float frameTime = 1.0f / animation->framesSpeed;
animation->animTimer += deltaTime;
float frameTime = 1.0f / animation->framesSpeed;
if (animation->animTimer < frameTime) {
return;
}
if (animation->animTimer < frameTime) {
return;
}
animation->animTimer -= frameTime;
animation->currentFrame++;
animation->animTimer -= frameTime;
animation->currentFrame++;
if (animation->currentFrame >= animation->maxFrame) {
animation->currentFrame = 0;
}
if (animation->currentFrame >= animation->maxFrame) {
animation->currentFrame = 0;
}
}
+2 -1
View File
@@ -1,4 +1,5 @@
#include "engine.h"
#include "engineArray.h"
#include <stdlib.h>
Array* CreateArray(unsigned int initCapacity)
{
+78
View File
@@ -0,0 +1,78 @@
#include "engineAssets.h"
#include <raylib.h>
#include <string.h>
#define ASSET_CACHE_CAPACITY 64
#define ASSET_PATH_MAX 128
typedef struct {
char path[ASSET_PATH_MAX];
Texture2D texture;
} CachedTexture;
typedef struct {
char path[ASSET_PATH_MAX];
Sound sound;
} CachedSound;
static CachedTexture textureCache[ASSET_CACHE_CAPACITY];
static int textureCacheCount = 0;
static CachedSound soundCache[ASSET_CACHE_CAPACITY];
static int soundCacheCount = 0;
Texture2D LoadTextureCached(const char *path) {
for (int i = 0; i < textureCacheCount; i++) {
if (strcmp(textureCache[i].path, path) == 0) {
return textureCache[i].texture;
}
}
Texture2D texture = LoadTexture(path);
if (textureCacheCount >= ASSET_CACHE_CAPACITY) {
TraceLog(LOG_WARNING, "ASSETS: Texture cache full, not caching %s", path);
return texture;
}
CachedTexture *entry = &textureCache[textureCacheCount++];
strncpy(entry->path, path, ASSET_PATH_MAX - 1);
entry->path[ASSET_PATH_MAX - 1] = '\0';
entry->texture = texture;
return texture;
}
Sound LoadSoundCached(const char *path) {
for (int i = 0; i < soundCacheCount; i++) {
if (strcmp(soundCache[i].path, path) == 0) {
return soundCache[i].sound;
}
}
Sound sound = LoadSound(path);
if (soundCacheCount >= ASSET_CACHE_CAPACITY) {
TraceLog(LOG_WARNING, "ASSETS: Sound cache full, not caching %s", path);
return sound;
}
CachedSound *entry = &soundCache[soundCacheCount++];
strncpy(entry->path, path, ASSET_PATH_MAX - 1);
entry->path[ASSET_PATH_MAX - 1] = '\0';
entry->sound = sound;
return sound;
}
void UnloadCachedAssets(void) {
for (int i = 0; i < textureCacheCount; i++) {
UnloadTexture(textureCache[i].texture);
}
textureCacheCount = 0;
for (int i = 0; i < soundCacheCount; i++) {
UnloadSound(soundCache[i].sound);
}
soundCacheCount = 0;
}
+10 -18
View File
@@ -1,13 +1,8 @@
#include "engine.h"
#include "engineCamera.h"
#include <raymath.h>
Camera2D* GetCamera(void){
static Camera2D camera;
return &camera;
}
Rectangle GetCameraRectangle(void)
Rectangle GetCameraRectangle(Camera2D *camera)
{
Camera2D *camera = GetCamera();
float screenWidth = GetScreenWidth();
float screenHeight = GetScreenHeight();
@@ -38,11 +33,10 @@ Rectangle GetCameraRectangle(void)
};
}
void CreateCamera(Vector2 playerPosition, float initZoom){
Camera2D* camera = GetCamera();
Camera2D CreateCamera(Vector2 playerPosition, float initZoom){
int windowWidth = GetScreenWidth();
int windowHeight = GetScreenHeight();
*camera = (Camera2D){
return (Camera2D){
.offset = (Vector2){windowWidth / 2.0f, windowHeight / 2.0f},
.target = playerPosition,
.rotation = 0.0f,
@@ -50,19 +44,17 @@ void CreateCamera(Vector2 playerPosition, float initZoom){
};
}
void UpdateCameraTarget(Vector2 *position, const float smoothness){
Camera2D* camera = GetCamera();
void UpdateCameraTarget(Camera2D *camera, Vector2 *position, const float smoothness){
camera->target = Vector2Lerp(camera->target, *position, smoothness * GetFrameTime());
}
void UpdateCameraZoom(const float *scaleFactor, const float *minZoom, const float *maxZoom){
Camera2D *camera = GetCamera();
void UpdateCameraZoom(Camera2D *camera, const float *scaleFactor, const float *minZoom, const float *maxZoom){
float targetZoom = Clamp(camera->zoom * *scaleFactor, *minZoom, *maxZoom);
camera->zoom = Lerp(camera->zoom, targetZoom, 0.1f);
}
void UpdateCamera2D(Vector2* position, Mouse* mouse){
UpdateCameraTarget(position, 5.0f);
UpdateCameraZoom(&mouse->scaleFactor, &mouse->minZoom, &mouse->maxZoom);
void UpdateCamera2D(Camera2D *camera, Vector2* position, Mouse* mouse){
UpdateCameraTarget(camera, position, 5.0f);
UpdateCameraZoom(camera, &mouse->scaleFactor, &mouse->minZoom, &mouse->maxZoom);
}
+22 -21
View File
@@ -1,56 +1,57 @@
#define RINI_IMPLEMENTATION
#define RINI_VALUE_DELIMITER '='
#include "engine.h"
#include "engineConfig.h"
void InitConfig(Config *config, const char* path) {
config->rini = rini_load_config(path);
if (config->rini.count == 0) {
printf("Warning: Loaded config has no entries. Path: %s\n", config->path);
}
#include <stdio.h>
void InitConfig(Config *config, const char *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) {
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) {
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) {
return rini_get_config_value_text(config->rini, key);
const char *GetConfigString(Config *config, const char *key) {
return rini_get_value_text(config->rini, key);
}
const char* GetConfigStringOrDefault(Config *config, const char *key, const char *default_value) {
return rini_get_config_value_text_fallback(config->rini, key, default_value);
const char *GetConfigStringOrDefault(Config *config, const char *key, const char *default_value) {
return rini_get_value_text_fallback(config->rini, key, default_value);
}
const char* GetConfigDescription(Config *config, const char *key) {
return rini_get_config_value_description(config->rini, key);
const char *GetConfigDescription(Config *config, const char *key) {
return rini_get_value_description(config->rini, key);
}
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) {
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) {
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) {
return rini_set_config_comment_line(&config->rini, comment);
return rini_set_comment_line(&config->rini, comment);
}
void SaveConfig(Config *config) {
rini_save_config(config->rini, config->path);
rini_save(config->rini, config->path);
}
void CleanUpConfig(Config *config) {
rini_unload_config(&config->rini);
rini_unload(&config->rini);
}
+6 -7
View File
@@ -1,11 +1,10 @@
#include "engine.h"
#include "engineFont.h"
void InitFont(Font *font, const char* path){
*font = LoadFont(path);
SetTextureFilter(font->texture, TEXTURE_FILTER_POINT);
void InitFont(Font *font, const char *path) {
*font = LoadFont(path);
SetTextureFilter(font->texture, TEXTURE_FILTER_POINT);
}
void CleanupFont(Font *font){
UnloadFont(*font);
void CleanupFont(Font *font) {
UnloadFont(*font);
}
-72
View File
@@ -1,72 +0,0 @@
#include "engine.h"
ImageButton CreateImageButton(ButtonState state, Texture2D *texture, const TextureAsset *asset, Vector2 *source, Vector2 *clicked, Vector2 *hover, Rectangle *destRec){
return (ImageButton){
.sprite = {
.texture = *texture,
.sourceRec = (Rectangle){ source->x, source->y, asset->frameWidth, asset->frameHeight},
.destRec = *destRec,
.frameSize = (Vector2){asset->frameWidth, asset->frameHeight},
.origin = (Vector2){0.0f, 0.0f},
.rotation = 0.0f,
.color = WHITE
},
.notClicked = (Vector2){source->x, source->y},
.hover = *hover,
.clicked = *clicked
};
}
ImageElement CreateImageElement(Texture2D *texture, const TextureAsset *asset, Vector2 *source, Rectangle *destRec){
return (ImageElement){
.sprite = {
.texture = *texture,
.sourceRec = (Rectangle){ source->x, source->y, asset->frameWidth, asset->frameHeight},
.destRec = *destRec,
.frameSize = (Vector2){asset->frameWidth, asset->frameHeight},
.origin = (Vector2){0.0f, 0.0f},
.rotation = 0.0f,
.color = WHITE
}
};
}
int isImageButtonClicked(ImageButton *b, Sound soundHover, Sound soundClick){
if (CheckCollisionPointRec(GetMousePosition(), b->sprite.destRec)) {
if (!b->isHovered) {
PlaySound(soundHover);
b->isHovered = true;
UpdateSpriteSourceRec(&b->sprite, &b->hover);
}
if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)){
if (!b->isClicked) {
PlaySound(soundClick);
b->isClicked = true;
UpdateSpriteSourceRec(&b->sprite, &b->clicked);
}
}
if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON))
{
b->isClicked = false;
b->isHovered = false;
UpdateSpriteSourceRec(&b->sprite, &b->notClicked);
return true;
}
} else {
UpdateSpriteSourceRec(&b->sprite, &b->notClicked);
b->isClicked = false;
b->isHovered = false;
}
return false;
}
void renderImageButton(ImageButton *b) {
RenderSprite(&b->sprite);
}
void renderImageElement(ImageElement *e){
RenderSprite(&e->sprite);
}
+94 -13
View File
@@ -1,23 +1,104 @@
#define RAYGUI_IMPLEMENTATION
#include "engine.h"
#include "engineGui.h"
Button initButton(char *text, Vector2 dimensions, Vector2 position)
{
Button b;
#include <raygui.h>
b.rec = (Rectangle){position.x - dimensions.x / 2, position.y - dimensions.y / 2, dimensions.x, dimensions.y};
b.text = text;
return b;
ImageButton CreateImageButton(Texture2D *texture, const TextureAsset *asset, Vector2 *source,
Vector2 *clicked, Vector2 *hover, Rectangle *destRec) {
return (ImageButton){.sprite = {.texture = *texture,
.sourceRec = (Rectangle){source->x, source->y, asset->frameWidth,
asset->frameHeight},
.destRec = *destRec,
.frameSize = (Vector2){asset->frameWidth, asset->frameHeight},
.origin = (Vector2){0.0f, 0.0f},
.rotation = 0.0f,
.color = WHITE},
.notClicked = (Vector2){source->x, source->y},
.hover = *hover,
.clicked = *clicked};
}
int isButtonClicked(Button *b)
{
return (CheckCollisionPointRec(GetMousePosition(), b->rec) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON));
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}};
}
void renderButton(Button *b)
{
GuiButton(b->rec, b->text);
int isImageButtonClicked(ImageButton *b, Sound soundHover, Sound soundClick) {
if (CheckCollisionPointRec(GetMousePosition(), b->sprite.destRec)) {
if (!b->isHovered) {
PlaySound(soundHover);
b->isHovered = true;
UpdateSpriteSourceRec(&b->sprite, &b->hover);
}
if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) {
if (!b->isClicked) {
PlaySound(soundClick);
b->isClicked = true;
UpdateSpriteSourceRec(&b->sprite, &b->clicked);
}
}
if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) {
b->isClicked = false;
b->isHovered = false;
UpdateSpriteSourceRec(&b->sprite, &b->notClicked);
return true;
}
} else {
UpdateSpriteSourceRec(&b->sprite, &b->notClicked);
b->isClicked = false;
b->isHovered = false;
}
return false;
}
ImageButton CreateMenuButton(Texture2D *texture, const TextureAsset *asset, int textureRow,
int screenRank, int buttonWidth, int buttonHeight,
Vector2 windowCenter, int buttonSpacing) {
float menuButtonWidth = asset->frameWidth;
float menuButtonHeight = asset->frameHeight;
Vector2 notClicked = {0.0f, menuButtonHeight * textureRow};
Vector2 hover = {menuButtonWidth, menuButtonHeight * textureRow};
Vector2 clicked = {menuButtonWidth * 2, menuButtonHeight * textureRow};
Rectangle destRec = {.x = windowCenter.x - (buttonWidth / 2),
.y = windowCenter.y - (buttonHeight / 2) + buttonSpacing * screenRank,
.width = buttonWidth,
.height = buttonHeight};
return CreateImageButton(texture, asset, &notClicked, &clicked, &hover, &destRec);
}
void renderImageButton(ImageButton *b) {
RenderSprite(&b->sprite);
}
void renderImageElement(ImageElement *e) {
RenderSprite(&e->sprite);
}
Button initButton(char *text, Vector2 dimensions, Vector2 position) {
Button b;
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
View File
@@ -1,4 +1,6 @@
#include "engine.h"
#include "engineInput.h"
#include <math.h>
#include <raymath.h>
Mouse CreateMouse(float zoomSpeed, float minZoom, float maxZoom, Texture2D *texture){
HideCursor();
@@ -15,8 +17,7 @@ void UpdateMouseScreen(Mouse *mouse){
}
void UpdateMouse(Mouse *mouse) {
Camera2D* camera = GetCamera();
void UpdateMouse(Mouse *mouse, Camera2D *camera) {
Vector2 pos = GetMousePosition();
mouse->screenPosition = pos;
mouse->worldPosition = GetScreenToWorld2D(pos, *camera);
+7 -1
View File
@@ -1,4 +1,10 @@
#include "engine.h"
#include "engineArray.h"
#include "enginePhysics.h"
#include "engineRectangle.h"
#include "engineVector.h"
#include <raymath.h>
#include <stdio.h>
#include <stdlib.h>
ObjectArray CreateObjectArray(const Object *prototype, const size_t count) {
ObjectArray objects = {0};
+35 -29
View File
@@ -1,43 +1,49 @@
#include "engine.h"
#include "enginePhysics.h"
Vector2 GetDirectionBetweenTwoVectors(Vector2 vec1, Vector2 vec2)
{
Vector2 dir = {vec2.x - vec1.x, vec2.y - vec1.y};
dir = Vector2Normalize(dir);
return dir;
#include <raymath.h>
#include "engineVector.h"
void UpdatePhysicsDirection(Vector2 *direction, Vector2 vector) {
*direction = Vector2Normalize(vector);
}
float GetDistanceBetweenTwoVectors(Vector2 vec1, Vector2 vec2)
{
float dx = vec1.x - vec2.x;
float dy = vec1.y - vec2.y;
return sqrtf(dx * dx + dy * dy);
void UpdatePhysicsVelocity(Vector2 *velocity, Vector2 *direction, float *speed) {
*velocity = Vector2Scale(*direction, *speed * GetFrameTime());
}
void UpdatePhysicsDirection(Vector2 *direction, Vector2 vector)
{
*direction = Vector2Normalize(vector);
void UpdatePhysicsPosition(Vector2 *position, Vector2 *velocity) {
*position = Vector2Add(*position, *velocity);
}
void UpdatePhysicsVelocity(Vector2 *velocity, Vector2 *direction, float *speed)
{
*velocity = Vector2Scale(*direction, *speed * GetFrameTime());
void UpdatePhysicsSpeed(float *speed, float *newSpeed) {
*speed = *newSpeed;
}
void UpdatePhysicsPosition(Vector2 *position, Vector2 *velocity)
{
*position = Vector2Add(*position, *velocity);
void UpdatePhysics(Physics *physics, Vector2 direction) {
UpdatePhysicsDirection(&physics->direction, direction);
UpdatePhysicsVelocity(&physics->velocity, &physics->direction, &physics->speed);
UpdatePhysicsPosition(&physics->position, &physics->velocity);
}
void UpdatePhysicsSpeed(float *speed, float *newSpeed)
{
*speed = *newSpeed;
float GetActualSpeed(Physics *physics) {
return physics->speed;
}
void UpdatePhysics(Physics *physics, Vector2 direction)
{
UpdatePhysicsDirection(&physics->direction, direction);
UpdatePhysicsVelocity(&physics->velocity, &physics->direction, &physics->speed);
UpdatePhysicsPosition(&physics->position, &physics->velocity);
}
CircleCollision CheckCircleCollision(Vector2 posA, float radiusA, Vector2 posB, float radiusB) {
CircleCollision result = {0};
result.overlapping = CheckCollisionCircles(posA, radiusA, posB, radiusB);
if (!result.overlapping) {
return result;
}
float dist = GetDistanceBetweenTwoVectors(posA, posB);
result.hasDirection = dist > 0.0f;
if (result.hasDirection) {
result.direction = GetDirectionBetweenTwoVectors(posB, posA);
result.overlap = (radiusA + radiusB) - dist;
}
return result;
}
+42 -25
View File
@@ -1,36 +1,53 @@
#include "engine.h"
#include "enginePostfx.h"
PostProcessing CreatePostFX(const char* shaderPath){
PostProcessing postFX = {0};
postFX.shader = LoadShader(NULL, shaderPath);
#include <stddef.h>
if (postFX.shader.id == 0) {
TraceLog(LOG_ERROR, "Failed to load shader: %s", shaderPath);
return postFX;
}
PostProcessing CreatePostFX(const char *shaderPath) {
PostProcessing postFX = {0};
postFX.shader = LoadShader(NULL, shaderPath);
postFX.strength = 0.0f;
postFX.resolutionLoc = GetShaderLocation(postFX.shader, "resolution");
postFX.strengthLoc = GetShaderLocation(postFX.shader, "strength");
Vector2 resolution = { (float)GetScreenWidth(), (float)GetScreenHeight() };
SetShaderValue(postFX.shader, postFX.resolutionLoc, &resolution, SHADER_UNIFORM_VEC2);
if (postFX.shader.id == 0) {
TraceLog(LOG_ERROR, "Failed to load shader: %s", shaderPath);
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){
if (postFX->shader.id == 0) return;
SetShaderValue(postFX->shader, postFX->strengthLoc, &postFX->strength, SHADER_UNIFORM_FLOAT);
Vector2 resolution = { (float)GetScreenWidth(), (float)GetScreenHeight() };
SetShaderValue(postFX->shader, postFX->resolutionLoc, &resolution, SHADER_UNIFORM_VEC2);
void UpdatePostFX(PostProcessing *postFX) {
if (postFX->shader.id == 0)
return;
SetShaderValue(postFX->shader, postFX->strengthLoc, &postFX->strength, SHADER_UNIFORM_FLOAT);
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){
BeginShaderMode(postFX->shader);
DrawTextureRec(target->texture, (Rectangle){ 0, 0, target->texture.width, -target->texture.height }, (Vector2){ 0, 0 }, WHITE);
EndShaderMode();
void UpdateShaders(PostProcessing *postFX, int count) {
for (int i = 0; i < count; i++) {
UpdatePostFX(&postFX[i]);
}
}
void UnloadPostFX(PostProcessing *postFX){
if (postFX->shader.id == 0) return;
UnloadShader(postFX->shader);
void RenderPostFX(PostProcessing *postFX, RenderTexture2D *target) {
BeginShaderMode(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
View File
@@ -1,40 +1,36 @@
#include "engine.h"
#include "engineRectangle.h"
Vector2 GetPositionInsideRectangle(Rectangle rec)
{
int randX = GetRandomValue(rec.x, rec.width);
int randY = GetRandomValue(rec.y, rec.height);
Vector2 randVector = {randX, randY};
return randVector;
Vector2 GetPositionInsideRectangle(Rectangle rec) {
int randX = GetRandomValue(rec.x, rec.width);
int randY = GetRandomValue(rec.y, rec.height);
return (Vector2){randX, randY};
}
Vector2 GetPositionOutsideRectangle(Rectangle rec, int min, int max)
{
int side = GetRandomValue(0, 3);
Vector2 pos = {0};
Vector2 GetPositionOutsideRectangle(Rectangle rec, int min, int max) {
int side = GetRandomValue(0, 3);
Vector2 pos = {0};
switch (side)
{
case 0: // left
pos.x = rec.x - GetRandomValue(min, max);
pos.y = GetRandomValue((int)rec.y, (int)(rec.y + rec.height));
break;
switch (side) {
case 0:
pos.x = rec.x - GetRandomValue(min, max);
pos.y = GetRandomValue((int)rec.y, (int)(rec.y + rec.height));
break;
case 1: // right
pos.x = rec.x + rec.width + GetRandomValue(min, max);
pos.y = GetRandomValue((int)rec.y, (int)(rec.y + rec.height));
break;
case 1:
pos.x = rec.x + rec.width + GetRandomValue(min, max);
pos.y = GetRandomValue((int)rec.y, (int)(rec.y + rec.height));
break;
case 2: // top
pos.x = GetRandomValue((int)rec.x, (int)(rec.x + rec.width));
pos.y = rec.y - GetRandomValue(min, max);
break;
case 2:
pos.x = GetRandomValue((int)rec.x, (int)(rec.x + rec.width));
pos.y = rec.y - GetRandomValue(min, max);
break;
case 3: // bottom
pos.x = GetRandomValue((int)rec.x, (int)(rec.x + rec.width));
pos.y = rec.y + rec.height + GetRandomValue(min, max);
break;
}
case 3:
pos.x = GetRandomValue((int)rec.x, (int)(rec.x + rec.width));
pos.y = rec.y + rec.height + GetRandomValue(min, max);
break;
}
return pos;
return pos;
}
+1 -1
View File
@@ -1,4 +1,4 @@
#include "engine.h"
#include "engineSoundtrack.h"
Soundtrack LoadSoundtrack(const char* musicPath){
Soundtrack soundtrack = {
+34 -31
View File
@@ -1,42 +1,45 @@
#include "engine.h"
#include "engineSprite.h"
#include <math.h>
void UpdateSpriteSourceRec(Sprite *sprite, const Vector2 *vector)
{
sprite->sourceRec.x = vector->x;
sprite->sourceRec.y = vector->y;
void UpdateSpriteSourceRec(Sprite *sprite, const Vector2 *vector) {
sprite->sourceRec.x = vector->x;
sprite->sourceRec.y = vector->y;
}
void UpdateSpriteDestRec(Sprite *sprite, const Vector2 *vector)
{
sprite->destRec.x = vector->x;
sprite->destRec.y = vector->y;
void UpdateSpriteDestRec(Sprite *sprite, const Vector2 *vector) {
sprite->destRec.x = vector->x;
sprite->destRec.y = vector->y;
}
void UpdateSpriteRotation(Sprite *sprite, float rotation)
{
sprite->rotation = Lerp(sprite->rotation, rotation, 0.1f);
static float ShortestAngleDifference(float from, float to) {
float diff = fmodf(to - from, 360.0f);
if (diff < -180.0f)
diff += 360.0f;
else if (diff > 180.0f)
diff -= 360.0f;
return diff;
}
void RenderSprite(const Sprite *sprite)
{
DrawTexturePro(
sprite->texture,
sprite->sourceRec,
sprite->destRec,
sprite->origin,
sprite->rotation,
sprite->color);
void UpdateSpriteRotation(Sprite *sprite, float rotation) {
float diff = ShortestAngleDifference(sprite->rotation, rotation);
sprite->rotation += diff * 0.1f;
}
void PrintSprite(const Sprite *sprite)
{
printf("Sprite:\n");
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);
printf(" Sprite.destRec: x = %f, y = %f, width = %f, height = %f\n", sprite->destRec.x, sprite->destRec.y, sprite->destRec.width, sprite->destRec.height);
printf(" Sprite.frameSize: x = %f, y = %f\n", sprite->frameSize.x, sprite->frameSize.y);
printf(" Sprite.origin: x = %f, y = %f\n", sprite->origin.x, sprite->origin.y);
printf(" Sprite.rotation: %f\n", sprite->rotation);
printf(" Sprite.color: r = %d, g = %d, b = %d, a = %d\n", sprite->color.r, sprite->color.g, sprite->color.b, sprite->color.a);
void UpdateSpriteRotationTowards(Sprite *sprite, float rotation, float degreesPerSecond,
float deltaTime) {
float diff = ShortestAngleDifference(sprite->rotation, rotation);
float maxStep = degreesPerSecond * deltaTime;
if (diff > maxStep)
diff = maxStep;
else if (diff < -maxStep)
diff = -maxStep;
sprite->rotation += diff;
}
void RenderSprite(const Sprite *sprite) {
DrawTexturePro(sprite->texture, sprite->sourceRec, sprite->destRec, sprite->origin,
sprite->rotation, sprite->color);
}
+41 -62
View File
@@ -1,71 +1,50 @@
#include "engine.h"
#include "engineStorage.h"
bool SaveStorageValue(const char* fileName, unsigned int position, int value){
bool success = false;
int dataSize = 0;
unsigned int newDataSize = 0;
unsigned char *fileData = LoadFileData(fileName, &dataSize);
unsigned char *newFileData = NULL;
#include <raylib.h>
#include <stdlib.h>
#include <string.h>
if (fileData != NULL)
{
if (dataSize <= (position*sizeof(int)))
{
newDataSize = (position + 1)*sizeof(int);
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;
}
bool SaveStorageData(const char *fileName, int version, const void *data, unsigned int dataSize) {
unsigned int totalSize = sizeof(int) + dataSize;
unsigned char *buffer = (unsigned char *)RL_MALLOC(totalSize);
if (buffer == NULL)
return false;
success = SaveFileData(fileName, newFileData, newDataSize);
RL_FREE(newFileData);
TraceLog(LOG_INFO, "FILEIO: [%s] Saved storage value: %i", fileName, value);
}
else
{
TraceLog(LOG_INFO, "FILEIO: [%s] File created successfully", fileName);
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);
}
memcpy(buffer, &version, sizeof(int));
memcpy(buffer + sizeof(int), data, dataSize);
bool success = SaveFileData(fileName, buffer, totalSize);
RL_FREE(buffer);
TraceLog(LOG_INFO, "FILEIO: [%s] Saved versioned storage data (version %i)", fileName, version);
return success;
}
int LoadStorageValue(const char * fileName, unsigned int position)
{
int value = 0;
int dataSize = 0;
unsigned char *fileData = LoadFileData(fileName, &dataSize);
if (fileData != NULL)
{
if (dataSize < ((int)(position*4))) TraceLog(LOG_WARNING, "FILEIO: [%s] Failed to find storage position: %i", fileName, position);
else
{
int *dataPtr = (int *)fileData;
value = dataPtr[position];
bool LoadStorageData(const char *fileName, int expectedVersion, void *data, unsigned int dataSize) {
int fileSize = 0;
unsigned char *fileData = LoadFileData(fileName, &fileSize);
if (fileData == NULL)
return false;
bool success = false;
unsigned int expectedSize = sizeof(int) + dataSize;
if ((unsigned int)fileSize == expectedSize) {
int fileVersion = 0;
memcpy(&fileVersion, fileData, sizeof(int));
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
View File
@@ -1,4 +1,19 @@
#include "engine.h"
#include "engineVector.h"
#include <math.h>
#include <raylib.h>
#include <raymath.h>
Vector2 GetDirectionBetweenTwoVectors(Vector2 vec1, Vector2 vec2) {
Vector2 dir = {vec2.x - vec1.x, vec2.y - vec1.y};
return Vector2Normalize(dir);
}
float GetDistanceBetweenTwoVectors(Vector2 vec1, Vector2 vec2) {
float dx = vec1.x - vec2.x;
float dy = vec1.y - vec2.y;
return sqrtf(dx * dx + dy * dy);
}
bool IsVectorZero(Vector2 v) {
float epsilon = 0.01f;
@@ -6,21 +21,32 @@ bool IsVectorZero(Vector2 v) {
}
Vector2 GetRandomVector(const Rectangle bounds) {
return (Vector2){
bounds.x + (float)GetRandomValue(0, 10000) / 10000.0f * bounds.width,
bounds.y + (float)GetRandomValue(0, 10000) / 10000.0f * bounds.height};
return (Vector2){bounds.x + (float)GetRandomValue(0, 10000) / 10000.0f * bounds.width,
bounds.y + (float)GetRandomValue(0, 10000) / 10000.0f * bounds.height};
}
Vector2 RotationToVector2(float rotation) {
float rad = rotation * (PI / 180.0f);
Vector2 vec = {cosf(rad), sinf(rad)};
return vec;
return (Vector2){cosf(rad), sinf(rad)};
}
float Vector2ToRotation(Vector2 vec) {
float rad = atan2f(vec.y, vec.x);
float deg = rad * (180.0f / PI);
return deg;
return rad * (180.0f / PI);
}
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;
}