Compare commits
6
Commits
b26070a518
...
5bcf3053b7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5bcf3053b7 | ||
|
|
a2ffb45fc5 | ||
|
|
c80b4887e9 | ||
|
|
9b483135ea | ||
|
|
0c31f2f3e0 | ||
|
|
dda0d38e22 |
@@ -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,3 +1,4 @@
|
||||
bin/
|
||||
build/
|
||||
compile_commands.json
|
||||
*.data
|
||||
|
||||
@@ -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
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 558 KiB |
Vendored
+396
-223
File diff suppressed because it is too large
Load Diff
+18
-263
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,69 @@
|
||||
#version 330
|
||||
|
||||
in vec2 fragWorldPos;
|
||||
out vec4 finalColor;
|
||||
|
||||
uniform float time;
|
||||
uniform float cloudScale = 0.018;
|
||||
uniform vec2 driftDirection = vec2(1.0, 0.35);
|
||||
uniform float driftSpeed = 4.0;
|
||||
uniform float coverage = 0.99;
|
||||
uniform float softness = 0.12;
|
||||
uniform vec4 cloudColor = vec4(1.0, 1.0, 1.0, 0.8);
|
||||
|
||||
uniform bool isShadowPass = false;
|
||||
uniform vec2 shadowWorldOffset = vec2(48.0, 72.0);
|
||||
uniform vec4 shadowColor = vec4(0.0, 0.05, 0.1, 0.35);
|
||||
|
||||
vec2 pixelizeCoordinates(vec2 coordinates)
|
||||
{
|
||||
return floor(coordinates);
|
||||
}
|
||||
|
||||
float hash(vec2 p)
|
||||
{
|
||||
p = fract(p * vec2(123.34, 456.21));
|
||||
p += dot(p, p + 45.32);
|
||||
return fract(p.x * p.y);
|
||||
}
|
||||
|
||||
float valueNoise(vec2 p)
|
||||
{
|
||||
vec2 i = floor(p);
|
||||
vec2 f = fract(p);
|
||||
float a = hash(i);
|
||||
float b = hash(i + vec2(1.0, 0.0));
|
||||
float c = hash(i + vec2(0.0, 1.0));
|
||||
float d = hash(i + vec2(1.0, 1.0));
|
||||
vec2 u = f * f * (3.0 - 2.0 * f);
|
||||
return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
|
||||
}
|
||||
|
||||
float cloudField(vec2 p)
|
||||
{
|
||||
float value = 0.0;
|
||||
float amplitude = 0.5;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
value += amplitude * valueNoise(p);
|
||||
p *= 2.0;
|
||||
amplitude *= 0.5;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 worldPos = fragWorldPos;
|
||||
if (isShadowPass) {
|
||||
worldPos -= shadowWorldOffset;
|
||||
}
|
||||
|
||||
vec2 pixelized = pixelizeCoordinates(worldPos);
|
||||
vec2 drift = normalize(driftDirection) * time * driftSpeed;
|
||||
float density = cloudField((pixelized + drift) * cloudScale);
|
||||
|
||||
float mask = smoothstep(coverage - softness, coverage + softness, density);
|
||||
|
||||
vec4 layerColor = isShadowPass ? shadowColor : cloudColor;
|
||||
finalColor = vec4(layerColor.rgb, layerColor.a * mask);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#version 330
|
||||
in vec3 vertexPosition;
|
||||
in vec2 vertexTexCoord;
|
||||
in vec4 vertexColor;
|
||||
|
||||
out vec2 fragWorldPos;
|
||||
|
||||
uniform mat4 mvp;
|
||||
|
||||
void main()
|
||||
{
|
||||
fragWorldPos = vertexPosition.xy;
|
||||
gl_Position = mvp * vec4(vertexPosition, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#version 330
|
||||
|
||||
uniform sampler2D texture0;
|
||||
uniform vec2 resolution;
|
||||
uniform float time;
|
||||
|
||||
// Screen-space UV of the sun — kept off in a corner like a low sun rather
|
||||
// than dead center, so the rays read as directional light rather than a
|
||||
// starburst sitting on top of the view. This shader samples the already-
|
||||
// rendered scene texture (RenderPostFX draws it via a source rect with a
|
||||
// negative height to correct raylib's render-texture V-flip for texture0
|
||||
// itself), but fragTexCoord's V still runs top-of-screen=0 in that same
|
||||
// flipped sense — so a Y near 0 here actually lands near the BOTTOM of
|
||||
// what's on screen; 0.88 is what puts the sun near the top.
|
||||
uniform vec2 sunScreenPos = vec2(0.82, 0.88);
|
||||
uniform float rayCount = 10.0;
|
||||
uniform float raySharpness = 3.0;
|
||||
uniform float rayStrength = 0.35;
|
||||
uniform float raySpeed = 0.03;
|
||||
|
||||
in vec2 fragTexCoord;
|
||||
out vec4 finalColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 uv = fragTexCoord;
|
||||
vec4 baseColor = texture(texture0, uv);
|
||||
|
||||
// Cheap screen-space light shafts: no scene depth/occlusion to sample,
|
||||
// so this fakes them as angular streaks radiating from the sun position
|
||||
// that fade out with distance, rather than true raymarched shafts.
|
||||
vec2 aspectFix = vec2(resolution.x / resolution.y, 1.0);
|
||||
vec2 toSun = (uv - sunScreenPos) * aspectFix;
|
||||
float dist = length(toSun);
|
||||
float angle = atan(toSun.y, toSun.x);
|
||||
|
||||
float streaks = sin(angle * rayCount + time * raySpeed) * 0.5 + 0.5;
|
||||
streaks = pow(streaks, raySharpness);
|
||||
|
||||
float falloff = clamp(1.0 - dist * 0.9, 0.0, 1.0);
|
||||
falloff *= falloff;
|
||||
|
||||
vec3 rayColor = vec3(1.0, 0.92, 0.75) * streaks * falloff * rayStrength;
|
||||
finalColor = vec4(baseColor.rgb + rayColor, baseColor.a);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
#version 330
|
||||
|
||||
in vec2 fragTexCoord;
|
||||
in vec2 fragWorldPos;
|
||||
out vec4 finalColor;
|
||||
|
||||
uniform sampler2D texture0;
|
||||
uniform float time;
|
||||
|
||||
uniform vec4 foamColor = vec4(0.96, 0.99, 1.0, 1.0);
|
||||
uniform float foamAnimSpeed = 1.6;
|
||||
uniform float foamNoiseScale = 0.55;
|
||||
|
||||
float hash(vec2 p)
|
||||
{
|
||||
p = fract(p * vec2(123.34, 456.21));
|
||||
p += dot(p, p + 45.32);
|
||||
return fract(p.x * p.y);
|
||||
}
|
||||
|
||||
float valueNoise(vec2 p)
|
||||
{
|
||||
vec2 i = floor(p);
|
||||
vec2 f = fract(p);
|
||||
float a = hash(i);
|
||||
float b = hash(i + vec2(1.0, 0.0));
|
||||
float c = hash(i + vec2(0.0, 1.0));
|
||||
float d = hash(i + vec2(1.0, 1.0));
|
||||
vec2 u = f * f * (3.0 - 2.0 * f);
|
||||
return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 tex = texture(texture0, fragTexCoord);
|
||||
|
||||
if (tex.a >= 0.999) {
|
||||
finalColor = tex;
|
||||
return;
|
||||
}
|
||||
|
||||
if (tex.a <= 0.001) {
|
||||
discard;
|
||||
}
|
||||
|
||||
vec2 pixelized = floor(fragWorldPos);
|
||||
float n1 = valueNoise(pixelized * foamNoiseScale + vec2(time * foamAnimSpeed, 0.0));
|
||||
float n2 = valueNoise(pixelized * foamNoiseScale * 2.1 - vec2(0.0, time * foamAnimSpeed * 0.7));
|
||||
float churn = n1 * 0.6 + n2 * 0.4;
|
||||
|
||||
float mask = tex.a * smoothstep(0.2, 0.7, churn + tex.a * 0.6);
|
||||
finalColor = vec4(foamColor.rgb, foamColor.a * mask);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#version 330
|
||||
in vec3 vertexPosition;
|
||||
in vec2 vertexTexCoord;
|
||||
in vec4 vertexColor;
|
||||
|
||||
out vec2 fragTexCoord;
|
||||
out vec2 fragWorldPos;
|
||||
|
||||
uniform mat4 mvp;
|
||||
|
||||
void main()
|
||||
{
|
||||
fragTexCoord = vertexTexCoord;
|
||||
fragWorldPos = vertexPosition.xy;
|
||||
gl_Position = mvp * vec4(vertexPosition, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
#version 330
|
||||
|
||||
in vec2 fragWorldPos;
|
||||
out vec4 finalColor;
|
||||
|
||||
uniform float time;
|
||||
|
||||
#define TRAIL_POINTS 12
|
||||
uniform vec2 trailPoints[TRAIL_POINTS];
|
||||
uniform float trailAges[TRAIL_POINTS];
|
||||
uniform int trailCount;
|
||||
uniform float wakeIntensity;
|
||||
|
||||
uniform vec4 foamColor = vec4(0.96, 0.99, 1.0, 1.0);
|
||||
uniform float wakeHalfWidthStart = 2.0;
|
||||
uniform float wakeHalfWidthEnd = 7.0;
|
||||
uniform float foamNoiseScale = 0.5;
|
||||
uniform float foamAnimSpeed = 2.4;
|
||||
|
||||
float hash(vec2 p)
|
||||
{
|
||||
p = fract(p * vec2(123.34, 456.21));
|
||||
p += dot(p, p + 45.32);
|
||||
return fract(p.x * p.y);
|
||||
}
|
||||
|
||||
float valueNoise(vec2 p)
|
||||
{
|
||||
vec2 i = floor(p);
|
||||
vec2 f = fract(p);
|
||||
float a = hash(i);
|
||||
float b = hash(i + vec2(1.0, 0.0));
|
||||
float c = hash(i + vec2(0.0, 1.0));
|
||||
float d = hash(i + vec2(1.0, 1.0));
|
||||
vec2 u = f * f * (3.0 - 2.0 * f);
|
||||
return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
|
||||
}
|
||||
|
||||
float segmentDistance(vec2 p, vec2 a, vec2 b, out float segT)
|
||||
{
|
||||
vec2 ab = b - a;
|
||||
float lenSq = dot(ab, ab);
|
||||
segT = lenSq > 0.0001 ? clamp(dot(p - a, ab) / lenSq, 0.0, 1.0) : 0.0;
|
||||
vec2 closest = a + ab * segT;
|
||||
return length(p - closest);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
if (trailCount < 2) discard;
|
||||
|
||||
float bestDist = 1e9;
|
||||
float bestAge = 0.0;
|
||||
for (int i = 0; i < TRAIL_POINTS - 1; i++) {
|
||||
if (i >= trailCount - 1) break;
|
||||
float segT;
|
||||
float d = segmentDistance(fragWorldPos, trailPoints[i], trailPoints[i + 1], segT);
|
||||
if (d < bestDist) {
|
||||
bestDist = d;
|
||||
bestAge = mix(trailAges[i], trailAges[i + 1], segT);
|
||||
}
|
||||
}
|
||||
|
||||
float halfWidth = mix(wakeHalfWidthStart, wakeHalfWidthEnd, bestAge);
|
||||
if (bestDist > halfWidth) discard;
|
||||
|
||||
float edgeFade = 1.0 - smoothstep(halfWidth * 0.55, halfWidth, bestDist);
|
||||
float ageFade = 1.0 - bestAge;
|
||||
float envelope = edgeFade * ageFade;
|
||||
|
||||
vec2 pixelized = floor(fragWorldPos);
|
||||
float n = valueNoise(pixelized * foamNoiseScale + vec2(time * foamAnimSpeed, 0.0));
|
||||
float churn = mix(0.55, 1.0, n);
|
||||
|
||||
float speedScale = mix(0.65, 1.0, wakeIntensity);
|
||||
|
||||
float mask = envelope * churn * speedScale;
|
||||
finalColor = vec4(foamColor.rgb, foamColor.a * mask);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#version 330
|
||||
in vec3 vertexPosition;
|
||||
in vec2 vertexTexCoord;
|
||||
in vec4 vertexColor;
|
||||
|
||||
out vec2 fragWorldPos;
|
||||
|
||||
uniform mat4 mvp;
|
||||
|
||||
void main()
|
||||
{
|
||||
fragWorldPos = vertexPosition.xy;
|
||||
gl_Position = mvp * vec4(vertexPosition, 1.0);
|
||||
}
|
||||
+9
-23
@@ -1,17 +1,12 @@
|
||||
#version 330
|
||||
|
||||
// -----------------------------
|
||||
// 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
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
#include "clouds.h"
|
||||
#include "engineCamera.h"
|
||||
#include "raylib.h"
|
||||
#include "math.h"
|
||||
|
||||
Clouds CreateClouds(void) {
|
||||
Clouds clouds = {0};
|
||||
clouds.shader = LoadShader("shaders/clouds.vs", "shaders/clouds.fs");
|
||||
clouds.time = 0.0f;
|
||||
clouds.coverage = 1.0f;
|
||||
return clouds;
|
||||
}
|
||||
|
||||
void UpdateClouds(Clouds *clouds, float deltaTime) {
|
||||
clouds->time += deltaTime;
|
||||
int timeLoc = GetShaderLocation(clouds->shader, "time");
|
||||
SetShaderValue(clouds->shader, timeLoc, &clouds->time, SHADER_UNIFORM_FLOAT);
|
||||
|
||||
float clearCoverage = 1.1f;
|
||||
float overcastCoverage = 0.55f;
|
||||
float percent = 0.5f + 0.5f * sinf(clouds->time * 0.05f);
|
||||
clouds->coverage = clearCoverage - percent * (clearCoverage - overcastCoverage);
|
||||
int coverageLoc = GetShaderLocation(clouds->shader, "coverage");
|
||||
SetShaderValue(clouds->shader, coverageLoc, &clouds->coverage, SHADER_UNIFORM_FLOAT);
|
||||
}
|
||||
|
||||
static Rectangle GetCloudCoverageRect(Camera2D *camera) {
|
||||
Rectangle rec = GetCameraRectangle(camera);
|
||||
float margin = 128.0f;
|
||||
rec.x -= margin;
|
||||
rec.y -= margin;
|
||||
rec.width += margin * 2.0f;
|
||||
rec.height += margin * 2.0f;
|
||||
return rec;
|
||||
}
|
||||
|
||||
void RenderCloudShadows(Clouds *clouds, Camera2D *camera) {
|
||||
int isShadowLoc = GetShaderLocation(clouds->shader, "isShadowPass");
|
||||
int isShadow = 1;
|
||||
SetShaderValue(clouds->shader, isShadowLoc, &isShadow, SHADER_UNIFORM_INT);
|
||||
|
||||
Rectangle rec = GetCloudCoverageRect(camera);
|
||||
BeginShaderMode(clouds->shader);
|
||||
BeginBlendMode(BLEND_MULTIPLIED);
|
||||
DrawRectangleRec(rec, WHITE);
|
||||
EndBlendMode();
|
||||
EndShaderMode();
|
||||
}
|
||||
|
||||
void RenderClouds(Clouds *clouds, Camera2D *camera) {
|
||||
int isShadowLoc = GetShaderLocation(clouds->shader, "isShadowPass");
|
||||
int isShadow = 0;
|
||||
SetShaderValue(clouds->shader, isShadowLoc, &isShadow, SHADER_UNIFORM_INT);
|
||||
|
||||
Rectangle rec = GetCloudCoverageRect(camera);
|
||||
BeginShaderMode(clouds->shader);
|
||||
DrawRectangleRec(rec, WHITE);
|
||||
EndShaderMode();
|
||||
}
|
||||
|
||||
void UnloadClouds(Clouds *clouds) {
|
||||
UnloadShader(clouds->shader);
|
||||
}
|
||||
+126
-52
@@ -1,102 +1,176 @@
|
||||
#include "app.h"
|
||||
#include <math.h>
|
||||
#include <raylib.h>
|
||||
|
||||
#include "collisions.h"
|
||||
#include "enginePhysics.h"
|
||||
#include "engineVector.h"
|
||||
|
||||
float playerCollisionRadius = 8.0f;
|
||||
float 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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
#include <raylib.h>
|
||||
|
||||
#include "app.h"
|
||||
#include "gameover.h"
|
||||
|
||||
void InitGameOver(GameOverState *g) {
|
||||
int windowWidth = GetScreenWidth();
|
||||
int windowHeight = GetScreenHeight();
|
||||
Vector2 buttonDimensions = {200, 60};
|
||||
Vector2 buttonPos = {windowWidth / 2.0f, windowHeight / 2.0f + 80.0f};
|
||||
g->restartButton = initButton("Restart", buttonDimensions, buttonPos);
|
||||
}
|
||||
|
||||
void UpdateGameOver(App *app) {
|
||||
if (isButtonClicked(&app->gameOver.restartButton)) {
|
||||
ResetGame(app);
|
||||
app->state.currentView = GAMEPLAY;
|
||||
}
|
||||
|
||||
UpdateMouseScreen(&app->game.mouse);
|
||||
|
||||
OnWindowClosedStoppApp(app);
|
||||
}
|
||||
|
||||
void RenderGameOver(App *app) {
|
||||
BeginDrawing();
|
||||
ClearBackground(BLACK);
|
||||
|
||||
int windowWidth = GetScreenWidth();
|
||||
int windowHeight = GetScreenHeight();
|
||||
|
||||
const char *title = "GAME OVER";
|
||||
int titleSize = app->font.baseSize * 2;
|
||||
Vector2 titleDim = MeasureTextEx(app->font, title, titleSize, 2);
|
||||
DrawTextEx(
|
||||
app->font, title,
|
||||
(Vector2){windowWidth / 2.0f - titleDim.x / 2.0f, windowHeight / 2.0f - titleDim.y - 40.0f},
|
||||
titleSize, 2, TEXT_COLOR_LIGHT);
|
||||
|
||||
const char *goldText = TextFormat("Gold collected: %i", app->game.player.gold);
|
||||
int goldSize = app->font.baseSize;
|
||||
Vector2 goldDim = MeasureTextEx(app->font, goldText, goldSize, 2);
|
||||
DrawTextEx(
|
||||
app->font, goldText,
|
||||
(Vector2){windowWidth / 2.0f - goldDim.x / 2.0f, windowHeight / 2.0f - goldDim.y / 2.0f},
|
||||
goldSize, 2, TEXT_COLOR_LIGHT);
|
||||
|
||||
renderButton(&app->gameOver.restartButton);
|
||||
|
||||
Mouse *mouse = &app->game.mouse;
|
||||
Rectangle mouseRectangle =
|
||||
(Rectangle){0, 0, mouse->cursorTexture.width, mouse->cursorTexture.height};
|
||||
DrawTextureRec(mouse->cursorTexture, mouseRectangle, mouse->screenPosition, WHITE);
|
||||
|
||||
if (app->flags.showFPS) {
|
||||
DrawFPS(10, 10);
|
||||
}
|
||||
|
||||
EndDrawing();
|
||||
}
|
||||
+22
-13
@@ -1,19 +1,28 @@
|
||||
#include "app.h"
|
||||
#include "raylib.h"
|
||||
#include <limits.h>
|
||||
#include <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);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,273 @@
|
||||
#include "pathfinding.h"
|
||||
#include <math.h>
|
||||
#include <raymath.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define PATH_GRID_MAX_DIM 24
|
||||
#define PATH_MIN_CELL_SIZE 16.0f
|
||||
#define PATH_GRID_MAX_NODES ((PATH_GRID_MAX_DIM + 2) * (PATH_GRID_MAX_DIM + 2))
|
||||
#define PATH_LINE_SAMPLE_STEP 8.0f
|
||||
#define CLEARANCE_GRID_STEP 8.0f
|
||||
#define PATH_GRID_PADDING 256.0f
|
||||
|
||||
typedef struct {
|
||||
float minX, minY;
|
||||
float cellSize;
|
||||
int cols, rows, nodeCount;
|
||||
int startIdx, goalIdx;
|
||||
} PathGrid;
|
||||
|
||||
static bool IsKnownWater(Terrain *terrain, Vector2 pos) {
|
||||
return IsTerrainLoaded(terrain, pos) && !IsTerrainLand(terrain, pos);
|
||||
}
|
||||
|
||||
bool IsWaterWithClearance(Terrain *terrain, Vector2 pos, float clearance) {
|
||||
if (!IsKnownWater(terrain, pos))
|
||||
return false;
|
||||
|
||||
int radiusSteps = (int)(clearance / CLEARANCE_GRID_STEP) + 1;
|
||||
float clearanceSquared = clearance * clearance;
|
||||
for (int gy = -radiusSteps; gy <= radiusSteps; gy++) {
|
||||
for (int gx = -radiusSteps; gx <= radiusSteps; gx++) {
|
||||
if (gx == 0 && gy == 0)
|
||||
continue;
|
||||
float ox = (float)gx * CLEARANCE_GRID_STEP;
|
||||
float oy = (float)gy * CLEARANCE_GRID_STEP;
|
||||
if (ox * ox + oy * oy > clearanceSquared)
|
||||
continue;
|
||||
if (!IsKnownWater(terrain, (Vector2){pos.x + ox, pos.y + oy}))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Vector2 GetRandomWaterPositionNear(Terrain *terrain, Vector2 origin, float maxRadius,
|
||||
float clearance) {
|
||||
float startAngle = (float)GetRandomValue(0, 359);
|
||||
for (float r = 16.0f; r < maxRadius; r += 16.0f) {
|
||||
for (float aStep = 0.0f; aStep < 360.0f; aStep += 30.0f) {
|
||||
float a = startAngle + aStep;
|
||||
Vector2 p = {origin.x + cosf(a * DEG2RAD) * r, origin.y + sinf(a * DEG2RAD) * r};
|
||||
if (IsWaterWithClearance(terrain, p, clearance))
|
||||
return p;
|
||||
}
|
||||
}
|
||||
return origin;
|
||||
}
|
||||
|
||||
static bool IsLineClear(Terrain *terrain, Vector2 a, Vector2 b, float clearance) {
|
||||
float dist = Vector2Distance(a, b);
|
||||
int steps = (int)(dist / PATH_LINE_SAMPLE_STEP) + 1;
|
||||
for (int i = 0; i <= steps; i++) {
|
||||
Vector2 p = Vector2Lerp(a, b, (float)i / (float)steps);
|
||||
if (!IsWaterWithClearance(terrain, p, clearance))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static float Heuristic(int c1, int r1, int c2, int r2, float cellSize) {
|
||||
int dx = abs(c1 - c2);
|
||||
int dy = abs(r1 - r2);
|
||||
int mn = dx < dy ? dx : dy;
|
||||
int mx = dx < dy ? dy : dx;
|
||||
return cellSize * (1.41421356f * (float)mn + (float)(mx - mn));
|
||||
}
|
||||
|
||||
static int ClampToGrid(int value, int size) {
|
||||
if (value < 0)
|
||||
return 0;
|
||||
if (value >= size)
|
||||
return size - 1;
|
||||
return value;
|
||||
}
|
||||
|
||||
static PathGrid BuildPathGrid(Vector2 start, Vector2 goal) {
|
||||
PathGrid grid = {0};
|
||||
|
||||
float minX = fminf(start.x, goal.x) - PATH_GRID_PADDING;
|
||||
float maxX = fmaxf(start.x, goal.x) + PATH_GRID_PADDING;
|
||||
float minY = fminf(start.y, goal.y) - PATH_GRID_PADDING;
|
||||
float maxY = fmaxf(start.y, goal.y) + PATH_GRID_PADDING;
|
||||
|
||||
float spanX = maxX - minX;
|
||||
float spanY = maxY - minY;
|
||||
float cellSize = fmaxf(PATH_MIN_CELL_SIZE, fmaxf(spanX, spanY) / (float)PATH_GRID_MAX_DIM);
|
||||
|
||||
grid.minX = minX;
|
||||
grid.minY = minY;
|
||||
grid.cellSize = cellSize;
|
||||
grid.cols = (int)(spanX / cellSize) + 1;
|
||||
grid.rows = (int)(spanY / cellSize) + 1;
|
||||
grid.nodeCount = grid.cols * grid.rows;
|
||||
if (grid.nodeCount > PATH_GRID_MAX_NODES || grid.nodeCount <= 0) {
|
||||
grid.nodeCount = 0;
|
||||
return grid;
|
||||
}
|
||||
|
||||
int startCol = ClampToGrid((int)((start.x - minX) / cellSize), grid.cols);
|
||||
int startRow = ClampToGrid((int)((start.y - minY) / cellSize), grid.rows);
|
||||
int goalCol = ClampToGrid((int)((goal.x - minX) / cellSize), grid.cols);
|
||||
int goalRow = ClampToGrid((int)((goal.y - minY) / cellSize), grid.rows);
|
||||
|
||||
grid.startIdx = startRow * grid.cols + startCol;
|
||||
grid.goalIdx = goalRow * grid.cols + goalCol;
|
||||
return grid;
|
||||
}
|
||||
|
||||
static bool RunAStar(Terrain *terrain, PathGrid grid, float clearance, int *cameFrom) {
|
||||
static float gScore[PATH_GRID_MAX_NODES];
|
||||
static float fScore[PATH_GRID_MAX_NODES];
|
||||
static bool inOpen[PATH_GRID_MAX_NODES];
|
||||
static bool inClosed[PATH_GRID_MAX_NODES];
|
||||
|
||||
int cols = grid.cols, rows = grid.rows;
|
||||
int goalCol = grid.goalIdx % cols, goalRow = grid.goalIdx / cols;
|
||||
|
||||
for (int i = 0; i < grid.nodeCount; i++) {
|
||||
gScore[i] = INFINITY;
|
||||
cameFrom[i] = -1;
|
||||
inOpen[i] = false;
|
||||
inClosed[i] = false;
|
||||
}
|
||||
|
||||
gScore[grid.startIdx] = 0.0f;
|
||||
fScore[grid.startIdx] =
|
||||
Heuristic(grid.startIdx % cols, grid.startIdx / cols, goalCol, goalRow, grid.cellSize);
|
||||
inOpen[grid.startIdx] = true;
|
||||
int openCount = 1;
|
||||
|
||||
static const int neighborCol[8] = {-1, 1, 0, 0, -1, -1, 1, 1};
|
||||
static const int neighborRow[8] = {0, 0, -1, 1, -1, 1, -1, 1};
|
||||
|
||||
while (openCount > 0) {
|
||||
int current = -1;
|
||||
float best = INFINITY;
|
||||
for (int i = 0; i < grid.nodeCount; i++) {
|
||||
if (inOpen[i] && fScore[i] < best) {
|
||||
best = fScore[i];
|
||||
current = i;
|
||||
}
|
||||
}
|
||||
if (current == -1)
|
||||
break;
|
||||
if (current == grid.goalIdx)
|
||||
return true;
|
||||
|
||||
inOpen[current] = false;
|
||||
openCount--;
|
||||
inClosed[current] = true;
|
||||
|
||||
int curCol = current % cols;
|
||||
int curRow = current / cols;
|
||||
|
||||
for (int n = 0; n < 8; n++) {
|
||||
int nc = curCol + neighborCol[n];
|
||||
int nr = curRow + neighborRow[n];
|
||||
if (nc < 0 || nc >= cols || nr < 0 || nr >= rows)
|
||||
continue;
|
||||
|
||||
int nIdx = nr * cols + nc;
|
||||
if (inClosed[nIdx])
|
||||
continue;
|
||||
|
||||
Vector2 cellCenter = {grid.minX + ((float)nc + 0.5f) * grid.cellSize,
|
||||
grid.minY + ((float)nr + 0.5f) * grid.cellSize};
|
||||
if (!IsWaterWithClearance(terrain, cellCenter, clearance))
|
||||
continue;
|
||||
|
||||
if (neighborCol[n] != 0 && neighborRow[n] != 0) {
|
||||
Vector2 side1 = {grid.minX + ((float)(curCol + neighborCol[n]) + 0.5f) * grid.cellSize,
|
||||
grid.minY + ((float)curRow + 0.5f) * grid.cellSize};
|
||||
Vector2 side2 = {grid.minX + ((float)curCol + 0.5f) * grid.cellSize,
|
||||
grid.minY + ((float)(curRow + neighborRow[n]) + 0.5f) * grid.cellSize};
|
||||
if (!IsWaterWithClearance(terrain, side1, clearance) ||
|
||||
!IsWaterWithClearance(terrain, side2, clearance))
|
||||
continue;
|
||||
}
|
||||
|
||||
float moveCost = (neighborCol[n] != 0 && neighborRow[n] != 0) ? grid.cellSize * 1.41421356f
|
||||
: grid.cellSize;
|
||||
float tentativeG = gScore[current] + moveCost;
|
||||
if (tentativeG < gScore[nIdx]) {
|
||||
cameFrom[nIdx] = current;
|
||||
gScore[nIdx] = tentativeG;
|
||||
fScore[nIdx] = tentativeG + Heuristic(nc, nr, goalCol, goalRow, grid.cellSize);
|
||||
if (!inOpen[nIdx]) {
|
||||
inOpen[nIdx] = true;
|
||||
openCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int ReconstructRawPath(PathGrid grid, const int *cameFrom, Vector2 goal,
|
||||
Vector2 *rawPoints) {
|
||||
int rawCount = 0;
|
||||
for (int cur = grid.goalIdx; cur != -1; cur = cameFrom[cur]) {
|
||||
int c = cur % grid.cols;
|
||||
int r = cur / grid.cols;
|
||||
rawPoints[rawCount++] = (Vector2){grid.minX + ((float)c + 0.5f) * grid.cellSize,
|
||||
grid.minY + ((float)r + 0.5f) * grid.cellSize};
|
||||
}
|
||||
|
||||
for (int i = 0; i < rawCount / 2; i++) {
|
||||
Vector2 tmp = rawPoints[i];
|
||||
rawPoints[i] = rawPoints[rawCount - 1 - i];
|
||||
rawPoints[rawCount - 1 - i] = tmp;
|
||||
}
|
||||
rawPoints[rawCount - 1] = goal;
|
||||
|
||||
return rawCount;
|
||||
}
|
||||
|
||||
static int SimplifyPathWithStringPulling(Terrain *terrain, Vector2 start, const Vector2 *rawPoints,
|
||||
int rawCount, float clearance, Vector2 *outWaypoints) {
|
||||
int count = 0;
|
||||
int i = 0;
|
||||
Vector2 anchor = start;
|
||||
while (i < rawCount - 1 && count < PATH_MAX_WAYPOINTS) {
|
||||
int farthest = i + 1;
|
||||
for (int j = rawCount - 1; j > i; j--) {
|
||||
if (IsLineClear(terrain, anchor, rawPoints[j], clearance)) {
|
||||
farthest = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
anchor = rawPoints[farthest];
|
||||
outWaypoints[count++] = anchor;
|
||||
i = farthest;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
Path FindWaterPath(Terrain *terrain, Vector2 start, Vector2 goal, float clearance) {
|
||||
Path path = {0};
|
||||
|
||||
if (IsLineClear(terrain, start, goal, clearance)) {
|
||||
path.waypoints[0] = goal;
|
||||
path.count = 1;
|
||||
return path;
|
||||
}
|
||||
|
||||
PathGrid grid = BuildPathGrid(start, goal);
|
||||
if (grid.nodeCount == 0)
|
||||
return path;
|
||||
|
||||
static int cameFrom[PATH_GRID_MAX_NODES];
|
||||
if (!RunAStar(terrain, grid, clearance, cameFrom))
|
||||
return path;
|
||||
|
||||
static Vector2 rawPoints[PATH_GRID_MAX_NODES];
|
||||
int rawCount = ReconstructRawPath(grid, cameFrom, goal, rawPoints);
|
||||
|
||||
path.count =
|
||||
SimplifyPathWithStringPulling(terrain, start, rawPoints, rawCount, clearance, path.waypoints);
|
||||
return path;
|
||||
}
|
||||
+108
-72
@@ -1,8 +1,27 @@
|
||||
#include "app.h"
|
||||
#include "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
@@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
#include <raylib.h>
|
||||
#include "appCore.h"
|
||||
#include "engineAssets.h"
|
||||
#include "rose.h"
|
||||
#include "engineSprite.h"
|
||||
|
||||
const TextureAsset roseTextureAssets[ROSE_TEX_COUNT] = {{
|
||||
"assets/rose.png",
|
||||
2560.0f,
|
||||
2560.0f,
|
||||
0.0f
|
||||
}};
|
||||
|
||||
Rose CreateRose(void){
|
||||
|
||||
TextureAsset roseAsset = roseTextureAssets[ROSE];
|
||||
Texture2D texture = LoadTextureCached(roseAsset.path);
|
||||
float frameWidth = roseAsset.frameWidth;
|
||||
Vector2 frameSize = {frameWidth, frameWidth};
|
||||
float rotation = roseAsset.rotation;
|
||||
Rectangle sourceRec = {0.0f, 0.0f, frameWidth, frameWidth};
|
||||
float destSize = GetScreenWidth() * ROSE_SCREEN_WIDTH_FRACTION;
|
||||
Rectangle destRec = {GetScreenWidth() / 2, GetScreenHeight(), destSize, destSize};
|
||||
Vector2 origin = {destSize / 2, destSize / 2};
|
||||
Color color = WHITE;
|
||||
|
||||
Sprite sprite = {
|
||||
.texture = texture,
|
||||
.frameSize = frameSize,
|
||||
.sourceRec = sourceRec,
|
||||
.destRec = destRec,
|
||||
.origin = origin,
|
||||
.rotation = rotation,
|
||||
.color = color
|
||||
};
|
||||
|
||||
Rose rose = {
|
||||
.sprite = sprite
|
||||
};
|
||||
|
||||
return rose;
|
||||
|
||||
}
|
||||
|
||||
void UpdateRose(Rose *rose, Player *player){
|
||||
float rotationSpeed = player->rotation;
|
||||
|
||||
if (IsGameActionDown(ACTION_TURN_LEFT)) {
|
||||
rose->sprite.rotation -= rotationSpeed * GetFrameTime();
|
||||
}
|
||||
|
||||
if (IsGameActionDown(ACTION_TURN_RIGHT)) {
|
||||
rose->sprite.rotation += rotationSpeed * GetFrameTime();
|
||||
}
|
||||
}
|
||||
|
||||
void RenderRose(Rose *rose){
|
||||
RenderSprite(&rose->sprite);
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
#include <math.h>
|
||||
#include <raylib.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "terrain.h"
|
||||
|
||||
#define TERRAIN_BASE_SCALE 2.2f
|
||||
#define TERRAIN_DETAIL_SCALE 9.0f
|
||||
#define TERRAIN_DETAIL_WEIGHT 0.22f
|
||||
|
||||
#define TERRAIN_WATER_LEVEL 0.62f
|
||||
#define TERRAIN_SAND_BAND 0.035f
|
||||
#define TERRAIN_GRASS_BAND 0.12f
|
||||
|
||||
#define TERRAIN_SHORE_BAND_RADIUS 3
|
||||
|
||||
#define TERRAIN_STREAM_BUDGET 2
|
||||
|
||||
static int WorldToChunkCoord(float worldCoord) {
|
||||
return (int)floorf(worldCoord / TERRAIN_CHUNK_WORLD_SIZE);
|
||||
}
|
||||
|
||||
static void GenerateChunkHeight(int chunkX, int chunkY, int padding, int paddedResolution,
|
||||
float *outHeight) {
|
||||
int offsetX = chunkX * TERRAIN_CHUNK_RESOLUTION - padding;
|
||||
int offsetY = chunkY * TERRAIN_CHUNK_RESOLUTION - padding;
|
||||
|
||||
Image base =
|
||||
GenImagePerlinNoise(paddedResolution, paddedResolution, offsetX, offsetY, TERRAIN_BASE_SCALE);
|
||||
Image detail = GenImagePerlinNoise(paddedResolution, paddedResolution, offsetX, offsetY,
|
||||
TERRAIN_DETAIL_SCALE);
|
||||
|
||||
for (int y = 0; y < paddedResolution; y++) {
|
||||
for (int x = 0; x < paddedResolution; x++) {
|
||||
float baseValue = GetImageColor(base, x, y).r / 255.0f;
|
||||
float detailValue = GetImageColor(detail, x, y).r / 255.0f;
|
||||
outHeight[y * paddedResolution + x] =
|
||||
baseValue * (1.0f - TERRAIN_DETAIL_WEIGHT) + detailValue * TERRAIN_DETAIL_WEIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
UnloadImage(base);
|
||||
UnloadImage(detail);
|
||||
}
|
||||
|
||||
static Color GetLandColor(float height) {
|
||||
Color sandColor = {233, 220, 178, 255};
|
||||
Color grassColor = {80, 168, 84, 255};
|
||||
Color hillColor = {52, 120, 64, 255};
|
||||
|
||||
if (height < TERRAIN_WATER_LEVEL + TERRAIN_SAND_BAND)
|
||||
return sandColor;
|
||||
if (height < TERRAIN_WATER_LEVEL + TERRAIN_GRASS_BAND)
|
||||
return grassColor;
|
||||
return hillColor;
|
||||
}
|
||||
|
||||
static Color GetShoreAlphaColor(const float *height, int paddedResolution, int px, int py) {
|
||||
float nearestDistSq = 1e9f;
|
||||
int r = TERRAIN_SHORE_BAND_RADIUS;
|
||||
for (int dy = -r; dy <= r; dy++) {
|
||||
for (int dx = -r; dx <= r; dx++) {
|
||||
if (height[(py + dy) * paddedResolution + (px + dx)] >= TERRAIN_WATER_LEVEL) {
|
||||
float distSq = (float)(dx * dx + dy * dy);
|
||||
if (distSq < nearestDistSq)
|
||||
nearestDistSq = distSq;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nearestDistSq > (float)(r * r))
|
||||
return BLANK;
|
||||
|
||||
float t = 1.0f - sqrtf(nearestDistSq) / (float)r;
|
||||
float alphaValue = t * 254.0f;
|
||||
if (alphaValue < 1.0f)
|
||||
alphaValue = 1.0f;
|
||||
if (alphaValue > 254.0f)
|
||||
alphaValue = 254.0f;
|
||||
return (Color){255, 255, 255, (unsigned char)alphaValue};
|
||||
}
|
||||
|
||||
static void PaintChunkPixel(Image *image, bool *landMask, const float *height, int paddedResolution,
|
||||
int padding, int x, int y) {
|
||||
int px = x + padding, py = y + padding;
|
||||
float h = height[py * paddedResolution + px];
|
||||
bool isLand = h >= TERRAIN_WATER_LEVEL;
|
||||
landMask[y * TERRAIN_CHUNK_RESOLUTION + x] = isLand;
|
||||
|
||||
if (isLand) {
|
||||
ImageDrawPixel(image, x, y, GetLandColor(h));
|
||||
return;
|
||||
}
|
||||
|
||||
Color shoreColor = GetShoreAlphaColor(height, paddedResolution, px, py);
|
||||
if (shoreColor.a > 0) {
|
||||
ImageDrawPixel(image, x, y, shoreColor);
|
||||
}
|
||||
}
|
||||
|
||||
static void GenerateChunk(TerrainChunk *chunk, int chunkX, int chunkY) {
|
||||
if (chunk->active) {
|
||||
UnloadTexture(chunk->sprite.texture);
|
||||
free(chunk->landMask);
|
||||
}
|
||||
chunk->landMask = malloc(TERRAIN_CHUNK_RESOLUTION * TERRAIN_CHUNK_RESOLUTION * sizeof(bool));
|
||||
|
||||
int padding = TERRAIN_SHORE_BAND_RADIUS;
|
||||
int paddedResolution = TERRAIN_CHUNK_RESOLUTION + padding * 2;
|
||||
float *height = malloc((size_t)paddedResolution * (size_t)paddedResolution * sizeof(float));
|
||||
GenerateChunkHeight(chunkX, chunkY, padding, paddedResolution, height);
|
||||
|
||||
Image image = GenImageColor(TERRAIN_CHUNK_RESOLUTION, TERRAIN_CHUNK_RESOLUTION, BLANK);
|
||||
|
||||
for (int y = 0; y < TERRAIN_CHUNK_RESOLUTION; y++) {
|
||||
for (int x = 0; x < TERRAIN_CHUNK_RESOLUTION; x++) {
|
||||
PaintChunkPixel(&image, chunk->landMask, height, paddedResolution, padding, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
free(height);
|
||||
|
||||
Texture2D texture = LoadTextureFromImage(image);
|
||||
UnloadImage(image);
|
||||
SetTextureFilter(texture, TEXTURE_FILTER_POINT);
|
||||
|
||||
chunk->active = true;
|
||||
chunk->chunkX = chunkX;
|
||||
chunk->chunkY = chunkY;
|
||||
chunk->sprite =
|
||||
(Sprite){.texture = texture,
|
||||
.frameSize = {TERRAIN_CHUNK_RESOLUTION, TERRAIN_CHUNK_RESOLUTION},
|
||||
.sourceRec = {0.0f, 0.0f, TERRAIN_CHUNK_RESOLUTION, TERRAIN_CHUNK_RESOLUTION},
|
||||
.destRec = {chunkX * TERRAIN_CHUNK_WORLD_SIZE, chunkY * TERRAIN_CHUNK_WORLD_SIZE,
|
||||
TERRAIN_CHUNK_WORLD_SIZE, TERRAIN_CHUNK_WORLD_SIZE},
|
||||
.origin = {0.0f, 0.0f},
|
||||
.rotation = 0.0f,
|
||||
.color = WHITE};
|
||||
}
|
||||
|
||||
static bool IsChunkLoaded(Terrain *terrain, int chunkX, int chunkY) {
|
||||
for (int i = 0; i < TERRAIN_MAX_CHUNKS; i++) {
|
||||
if (terrain->chunks[i].active && terrain->chunks[i].chunkX == chunkX &&
|
||||
terrain->chunks[i].chunkY == chunkY) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Terrain CreateTerrain(Camera2D *camera) {
|
||||
Terrain terrain = {0};
|
||||
|
||||
terrain.shader = LoadShader("shaders/terrain.vs", "shaders/terrain.fs");
|
||||
terrain.time = 0.0f;
|
||||
|
||||
int centerX = WorldToChunkCoord(camera->target.x);
|
||||
int centerY = WorldToChunkCoord(camera->target.y);
|
||||
|
||||
int slot = 0;
|
||||
for (int cy = centerY - TERRAIN_LOAD_RADIUS; cy <= centerY + TERRAIN_LOAD_RADIUS; cy++) {
|
||||
for (int cx = centerX - TERRAIN_LOAD_RADIUS; cx <= centerX + TERRAIN_LOAD_RADIUS; cx++) {
|
||||
GenerateChunk(&terrain.chunks[slot], cx, cy);
|
||||
slot++;
|
||||
}
|
||||
}
|
||||
|
||||
return terrain;
|
||||
}
|
||||
|
||||
static void UnloadChunk(TerrainChunk *chunk) {
|
||||
UnloadTexture(chunk->sprite.texture);
|
||||
free(chunk->landMask);
|
||||
chunk->landMask = NULL;
|
||||
chunk->active = false;
|
||||
}
|
||||
|
||||
void UpdateTerrain(Terrain *terrain, Camera2D *camera) {
|
||||
int centerX = WorldToChunkCoord(camera->target.x);
|
||||
int centerY = WorldToChunkCoord(camera->target.y);
|
||||
|
||||
for (int i = 0; i < TERRAIN_MAX_CHUNKS; i++) {
|
||||
if (!terrain->chunks[i].active)
|
||||
continue;
|
||||
|
||||
int dx = terrain->chunks[i].chunkX - centerX;
|
||||
int dy = terrain->chunks[i].chunkY - centerY;
|
||||
if (dx < -TERRAIN_LOAD_RADIUS || dx > TERRAIN_LOAD_RADIUS || dy < -TERRAIN_LOAD_RADIUS ||
|
||||
dy > TERRAIN_LOAD_RADIUS) {
|
||||
UnloadChunk(&terrain->chunks[i]);
|
||||
}
|
||||
}
|
||||
|
||||
int budget = TERRAIN_STREAM_BUDGET;
|
||||
for (int cy = centerY - TERRAIN_LOAD_RADIUS; cy <= centerY + TERRAIN_LOAD_RADIUS && budget > 0;
|
||||
cy++) {
|
||||
for (int cx = centerX - TERRAIN_LOAD_RADIUS; cx <= centerX + TERRAIN_LOAD_RADIUS && budget > 0;
|
||||
cx++) {
|
||||
if (IsChunkLoaded(terrain, cx, cy))
|
||||
continue;
|
||||
|
||||
for (int i = 0; i < TERRAIN_MAX_CHUNKS; i++) {
|
||||
if (!terrain->chunks[i].active) {
|
||||
GenerateChunk(&terrain->chunks[i], cx, cy);
|
||||
budget--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
terrain->time += GetFrameTime();
|
||||
int timeLoc = GetShaderLocation(terrain->shader, "time");
|
||||
SetShaderValue(terrain->shader, timeLoc, &terrain->time, SHADER_UNIFORM_FLOAT);
|
||||
}
|
||||
|
||||
void RenderTerrain(Terrain *terrain) {
|
||||
BeginShaderMode(terrain->shader);
|
||||
for (int i = 0; i < TERRAIN_MAX_CHUNKS; i++) {
|
||||
if (terrain->chunks[i].active) {
|
||||
RenderSprite(&terrain->chunks[i].sprite);
|
||||
}
|
||||
}
|
||||
EndShaderMode();
|
||||
}
|
||||
|
||||
void UnloadTerrain(Terrain *terrain) {
|
||||
UnloadShader(terrain->shader);
|
||||
for (int i = 0; i < TERRAIN_MAX_CHUNKS; i++) {
|
||||
if (terrain->chunks[i].active) {
|
||||
UnloadChunk(&terrain->chunks[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static TerrainChunk *GetChunkAt(Terrain *terrain, Vector2 worldPos, int *outPx, int *outPy) {
|
||||
int cx = WorldToChunkCoord(worldPos.x);
|
||||
int cy = WorldToChunkCoord(worldPos.y);
|
||||
|
||||
for (int i = 0; i < TERRAIN_MAX_CHUNKS; i++) {
|
||||
TerrainChunk *chunk = &terrain->chunks[i];
|
||||
if (!chunk->active || chunk->chunkX != cx || chunk->chunkY != cy)
|
||||
continue;
|
||||
|
||||
float localX = worldPos.x - (float)cx * TERRAIN_CHUNK_WORLD_SIZE;
|
||||
float localY = worldPos.y - (float)cy * TERRAIN_CHUNK_WORLD_SIZE;
|
||||
|
||||
int px = (int)(localX / TERRAIN_CHUNK_WORLD_SIZE * TERRAIN_CHUNK_RESOLUTION);
|
||||
int py = (int)(localY / TERRAIN_CHUNK_WORLD_SIZE * TERRAIN_CHUNK_RESOLUTION);
|
||||
if (px < 0)
|
||||
px = 0;
|
||||
if (px >= TERRAIN_CHUNK_RESOLUTION)
|
||||
px = TERRAIN_CHUNK_RESOLUTION - 1;
|
||||
if (py < 0)
|
||||
py = 0;
|
||||
if (py >= TERRAIN_CHUNK_RESOLUTION)
|
||||
py = TERRAIN_CHUNK_RESOLUTION - 1;
|
||||
|
||||
*outPx = px;
|
||||
*outPy = py;
|
||||
return chunk;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool IsTerrainLand(Terrain *terrain, Vector2 worldPos) {
|
||||
int px, py;
|
||||
TerrainChunk *chunk = GetChunkAt(terrain, worldPos, &px, &py);
|
||||
if (!chunk)
|
||||
return false;
|
||||
return chunk->landMask[py * TERRAIN_CHUNK_RESOLUTION + px];
|
||||
}
|
||||
|
||||
bool IsTerrainLoaded(Terrain *terrain, Vector2 worldPos) {
|
||||
int cx = WorldToChunkCoord(worldPos.x);
|
||||
int cy = WorldToChunkCoord(worldPos.y);
|
||||
|
||||
for (int i = 0; i < TERRAIN_MAX_CHUNKS; i++) {
|
||||
TerrainChunk *chunk = &terrain->chunks[i];
|
||||
if (chunk->active && chunk->chunkX == cx && chunk->chunkY == cy)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
#include <math.h>
|
||||
|
||||
#include "wake.h"
|
||||
|
||||
#define WAKE_TRAIL_MIN_SAMPLE_DIST 2.5f
|
||||
#define WAKE_TRAIL_MAX_AGE 1.8
|
||||
#define WAKE_MIN_SPEED 0.5f
|
||||
#define WAKE_MAX_SPEED 10.0f
|
||||
|
||||
static void ExpireOldWakePoints(WakeTrail *trail, double now) {
|
||||
while (trail->count > 0 && now - trail->timestamps[trail->count - 1] > WAKE_TRAIL_MAX_AGE) {
|
||||
trail->count--;
|
||||
}
|
||||
}
|
||||
|
||||
static bool HasTraveledFarEnough(WakeTrail *trail, Vector2 position) {
|
||||
if (trail->count == 0)
|
||||
return true;
|
||||
|
||||
float dx = position.x - trail->points[0].x;
|
||||
float dy = position.y - trail->points[0].y;
|
||||
return dx * dx + dy * dy >= WAKE_TRAIL_MIN_SAMPLE_DIST * WAKE_TRAIL_MIN_SAMPLE_DIST;
|
||||
}
|
||||
|
||||
void UpdateWakeTrail(WakeTrail *trail, Vector2 position) {
|
||||
double now = GetTime();
|
||||
ExpireOldWakePoints(trail, now);
|
||||
|
||||
if (!HasTraveledFarEnough(trail, position))
|
||||
return;
|
||||
|
||||
for (int i = WAKE_TRAIL_POINTS - 1; i > 0; i--) {
|
||||
trail->points[i] = trail->points[i - 1];
|
||||
trail->timestamps[i] = trail->timestamps[i - 1];
|
||||
}
|
||||
trail->points[0] = position;
|
||||
trail->timestamps[0] = now;
|
||||
if (trail->count < WAKE_TRAIL_POINTS)
|
||||
trail->count++;
|
||||
}
|
||||
|
||||
void ResetWakeTrail(WakeTrail *trail) {
|
||||
trail->count = 0;
|
||||
}
|
||||
|
||||
Wake CreateWake(void) {
|
||||
Wake wake = {0};
|
||||
wake.shader = LoadShader("shaders/wake.vs", "shaders/wake.fs");
|
||||
wake.time = 0.0f;
|
||||
return wake;
|
||||
}
|
||||
|
||||
void UpdateWake(Wake *wake, float deltaTime) {
|
||||
wake->time += deltaTime;
|
||||
int timeLoc = GetShaderLocation(wake->shader, "time");
|
||||
SetShaderValue(wake->shader, timeLoc, &wake->time, SHADER_UNIFORM_FLOAT);
|
||||
}
|
||||
|
||||
static void ComputeWakeAges(WakeTrail *trail, double now, float *outAges) {
|
||||
for (int i = 0; i < WAKE_TRAIL_POINTS; i++) {
|
||||
if (i >= trail->count) {
|
||||
outAges[i] = 1.0f;
|
||||
continue;
|
||||
}
|
||||
float age = (float)((now - trail->timestamps[i]) / WAKE_TRAIL_MAX_AGE);
|
||||
outAges[i] = age < 0.0f ? 0.0f : (age > 1.0f ? 1.0f : age);
|
||||
}
|
||||
}
|
||||
|
||||
static Rectangle ComputeWakeBoundingBox(WakeTrail *trail) {
|
||||
float minX = trail->points[0].x, maxX = trail->points[0].x;
|
||||
float minY = trail->points[0].y, maxY = trail->points[0].y;
|
||||
for (int i = 1; i < trail->count; i++) {
|
||||
if (trail->points[i].x < minX)
|
||||
minX = trail->points[i].x;
|
||||
if (trail->points[i].x > maxX)
|
||||
maxX = trail->points[i].x;
|
||||
if (trail->points[i].y < minY)
|
||||
minY = trail->points[i].y;
|
||||
if (trail->points[i].y > maxY)
|
||||
maxY = trail->points[i].y;
|
||||
}
|
||||
|
||||
float margin = 10.0f;
|
||||
return (Rectangle){minX - margin, minY - margin, (maxX - minX) + margin * 2.0f,
|
||||
(maxY - minY) + margin * 2.0f};
|
||||
}
|
||||
|
||||
void RenderShipWake(Wake *wake, WakeTrail *trail, float speed) {
|
||||
if (trail->count < 2)
|
||||
return;
|
||||
|
||||
float t = (fabsf(speed) - WAKE_MIN_SPEED) / (WAKE_MAX_SPEED - WAKE_MIN_SPEED);
|
||||
if (t < 0.0f)
|
||||
t = 0.0f;
|
||||
if (t > 1.0f)
|
||||
t = 1.0f;
|
||||
|
||||
float ages[WAKE_TRAIL_POINTS];
|
||||
ComputeWakeAges(trail, GetTime(), ages);
|
||||
|
||||
int pointsLoc = GetShaderLocation(wake->shader, "trailPoints");
|
||||
int agesLoc = GetShaderLocation(wake->shader, "trailAges");
|
||||
int countLoc = GetShaderLocation(wake->shader, "trailCount");
|
||||
int intensityLoc = GetShaderLocation(wake->shader, "wakeIntensity");
|
||||
SetShaderValueV(wake->shader, pointsLoc, trail->points, SHADER_UNIFORM_VEC2, WAKE_TRAIL_POINTS);
|
||||
SetShaderValueV(wake->shader, agesLoc, ages, SHADER_UNIFORM_FLOAT, WAKE_TRAIL_POINTS);
|
||||
SetShaderValue(wake->shader, countLoc, &trail->count, SHADER_UNIFORM_INT);
|
||||
SetShaderValue(wake->shader, intensityLoc, &t, SHADER_UNIFORM_FLOAT);
|
||||
|
||||
Rectangle box = ComputeWakeBoundingBox(trail);
|
||||
|
||||
BeginShaderMode(wake->shader);
|
||||
DrawRectangleRec(box, WHITE);
|
||||
EndShaderMode();
|
||||
}
|
||||
|
||||
void UnloadWake(Wake *wake) {
|
||||
UnloadShader(wake->shader);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#include "appCore.h"
|
||||
#include <raylib.h>
|
||||
|
||||
typedef struct {
|
||||
int keys[2];
|
||||
} ActionBinding;
|
||||
|
||||
static const ActionBinding actionBindings[ACTION_COUNT] = {
|
||||
[ACTION_FORWARD] = {{KEY_W, KEY_UP}},
|
||||
[ACTION_BACKWARD] = {{KEY_S, KEY_DOWN}},
|
||||
[ACTION_TURN_LEFT] = {{KEY_A, KEY_LEFT}},
|
||||
[ACTION_TURN_RIGHT] = {{KEY_D, KEY_RIGHT}},
|
||||
[ACTION_FIRE_PORT] = {{KEY_Q, KEY_NULL}},
|
||||
[ACTION_FIRE_STARBOARD] = {{KEY_E, KEY_NULL}},
|
||||
[ACTION_BACK] = {{KEY_ESCAPE, KEY_NULL}},
|
||||
};
|
||||
|
||||
bool IsGameActionPressed(GameAction action) {
|
||||
const ActionBinding *binding = &actionBindings[action];
|
||||
return IsKeyPressed(binding->keys[0]) ||
|
||||
(binding->keys[1] != KEY_NULL && IsKeyPressed(binding->keys[1]));
|
||||
}
|
||||
|
||||
bool IsGameActionDown(GameAction action) {
|
||||
const ActionBinding *binding = &actionBindings[action];
|
||||
return IsKeyDown(binding->keys[0]) ||
|
||||
(binding->keys[1] != KEY_NULL && IsKeyDown(binding->keys[1]));
|
||||
}
|
||||
+3
-2
@@ -1,4 +1,5 @@
|
||||
#include "app.h"
|
||||
#include "logo.h"
|
||||
#include <raylib.h>
|
||||
|
||||
void InitLogo(LogoState *l) {
|
||||
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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -1,4 +1,5 @@
|
||||
#include "engine.h"
|
||||
#include "engineArray.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
Array* CreateArray(unsigned int initCapacity)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
#include "engineAssets.h"
|
||||
#include <raylib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define ASSET_CACHE_CAPACITY 64
|
||||
#define ASSET_PATH_MAX 128
|
||||
|
||||
typedef struct {
|
||||
char path[ASSET_PATH_MAX];
|
||||
Texture2D texture;
|
||||
} CachedTexture;
|
||||
|
||||
typedef struct {
|
||||
char path[ASSET_PATH_MAX];
|
||||
Sound sound;
|
||||
} CachedSound;
|
||||
|
||||
static CachedTexture textureCache[ASSET_CACHE_CAPACITY];
|
||||
static int textureCacheCount = 0;
|
||||
|
||||
static CachedSound soundCache[ASSET_CACHE_CAPACITY];
|
||||
static int soundCacheCount = 0;
|
||||
|
||||
Texture2D LoadTextureCached(const char *path) {
|
||||
for (int i = 0; i < textureCacheCount; i++) {
|
||||
if (strcmp(textureCache[i].path, path) == 0) {
|
||||
return textureCache[i].texture;
|
||||
}
|
||||
}
|
||||
|
||||
Texture2D texture = LoadTexture(path);
|
||||
|
||||
if (textureCacheCount >= ASSET_CACHE_CAPACITY) {
|
||||
TraceLog(LOG_WARNING, "ASSETS: Texture cache full, not caching %s", path);
|
||||
return texture;
|
||||
}
|
||||
|
||||
CachedTexture *entry = &textureCache[textureCacheCount++];
|
||||
strncpy(entry->path, path, ASSET_PATH_MAX - 1);
|
||||
entry->path[ASSET_PATH_MAX - 1] = '\0';
|
||||
entry->texture = texture;
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
Sound LoadSoundCached(const char *path) {
|
||||
for (int i = 0; i < soundCacheCount; i++) {
|
||||
if (strcmp(soundCache[i].path, path) == 0) {
|
||||
return soundCache[i].sound;
|
||||
}
|
||||
}
|
||||
|
||||
Sound sound = LoadSound(path);
|
||||
|
||||
if (soundCacheCount >= ASSET_CACHE_CAPACITY) {
|
||||
TraceLog(LOG_WARNING, "ASSETS: Sound cache full, not caching %s", path);
|
||||
return sound;
|
||||
}
|
||||
|
||||
CachedSound *entry = &soundCache[soundCacheCount++];
|
||||
strncpy(entry->path, path, ASSET_PATH_MAX - 1);
|
||||
entry->path[ASSET_PATH_MAX - 1] = '\0';
|
||||
entry->sound = sound;
|
||||
|
||||
return sound;
|
||||
}
|
||||
|
||||
void UnloadCachedAssets(void) {
|
||||
for (int i = 0; i < textureCacheCount; i++) {
|
||||
UnloadTexture(textureCache[i].texture);
|
||||
}
|
||||
textureCacheCount = 0;
|
||||
|
||||
for (int i = 0; i < soundCacheCount; i++) {
|
||||
UnloadSound(soundCache[i].sound);
|
||||
}
|
||||
soundCacheCount = 0;
|
||||
}
|
||||
+10
-18
@@ -1,13 +1,8 @@
|
||||
#include "engine.h"
|
||||
#include "engineCamera.h"
|
||||
#include <raymath.h>
|
||||
|
||||
Camera2D* GetCamera(void){
|
||||
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
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
#include "engine.h"
|
||||
|
||||
ImageButton CreateImageButton(ButtonState state, Texture2D *texture, const TextureAsset *asset, Vector2 *source, Vector2 *clicked, Vector2 *hover, Rectangle *destRec){
|
||||
return (ImageButton){
|
||||
.sprite = {
|
||||
.texture = *texture,
|
||||
.sourceRec = (Rectangle){ source->x, source->y, asset->frameWidth, asset->frameHeight},
|
||||
.destRec = *destRec,
|
||||
.frameSize = (Vector2){asset->frameWidth, asset->frameHeight},
|
||||
.origin = (Vector2){0.0f, 0.0f},
|
||||
.rotation = 0.0f,
|
||||
.color = WHITE
|
||||
},
|
||||
.notClicked = (Vector2){source->x, source->y},
|
||||
.hover = *hover,
|
||||
.clicked = *clicked
|
||||
};
|
||||
}
|
||||
|
||||
ImageElement CreateImageElement(Texture2D *texture, const TextureAsset *asset, Vector2 *source, Rectangle *destRec){
|
||||
return (ImageElement){
|
||||
.sprite = {
|
||||
.texture = *texture,
|
||||
.sourceRec = (Rectangle){ source->x, source->y, asset->frameWidth, asset->frameHeight},
|
||||
.destRec = *destRec,
|
||||
.frameSize = (Vector2){asset->frameWidth, asset->frameHeight},
|
||||
.origin = (Vector2){0.0f, 0.0f},
|
||||
.rotation = 0.0f,
|
||||
.color = WHITE
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
int isImageButtonClicked(ImageButton *b, Sound soundHover, Sound soundClick){
|
||||
if (CheckCollisionPointRec(GetMousePosition(), b->sprite.destRec)) {
|
||||
|
||||
if (!b->isHovered) {
|
||||
PlaySound(soundHover);
|
||||
b->isHovered = true;
|
||||
UpdateSpriteSourceRec(&b->sprite, &b->hover);
|
||||
}
|
||||
|
||||
if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)){
|
||||
if (!b->isClicked) {
|
||||
PlaySound(soundClick);
|
||||
b->isClicked = true;
|
||||
UpdateSpriteSourceRec(&b->sprite, &b->clicked);
|
||||
}
|
||||
}
|
||||
if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON))
|
||||
{
|
||||
b->isClicked = false;
|
||||
b->isHovered = false;
|
||||
UpdateSpriteSourceRec(&b->sprite, &b->notClicked);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
UpdateSpriteSourceRec(&b->sprite, &b->notClicked);
|
||||
b->isClicked = false;
|
||||
b->isHovered = false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void renderImageButton(ImageButton *b) {
|
||||
RenderSprite(&b->sprite);
|
||||
}
|
||||
|
||||
void renderImageElement(ImageElement *e){
|
||||
RenderSprite(&e->sprite);
|
||||
}
|
||||
|
||||
+94
-13
@@ -1,23 +1,104 @@
|
||||
#define RAYGUI_IMPLEMENTATION
|
||||
|
||||
#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, ¬Clicked, &clicked, &hover, &destRec);
|
||||
}
|
||||
|
||||
void renderImageButton(ImageButton *b) {
|
||||
RenderSprite(&b->sprite);
|
||||
}
|
||||
|
||||
void renderImageElement(ImageElement *e) {
|
||||
RenderSprite(&e->sprite);
|
||||
}
|
||||
|
||||
Button initButton(char *text, Vector2 dimensions, Vector2 position) {
|
||||
Button b;
|
||||
b.rec = (Rectangle){position.x - dimensions.x / 2, position.y - dimensions.y / 2, dimensions.x,
|
||||
dimensions.y};
|
||||
b.text = text;
|
||||
return b;
|
||||
}
|
||||
|
||||
int isButtonClicked(Button *b) {
|
||||
return (CheckCollisionPointRec(GetMousePosition(), b->rec) &&
|
||||
IsMouseButtonPressed(MOUSE_LEFT_BUTTON));
|
||||
}
|
||||
|
||||
void renderButton(Button *b) {
|
||||
GuiButton(b->rec, b->text);
|
||||
}
|
||||
|
||||
+4
-3
@@ -1,4 +1,6 @@
|
||||
#include "engine.h"
|
||||
#include "engineInput.h"
|
||||
#include <math.h>
|
||||
#include <raymath.h>
|
||||
|
||||
Mouse CreateMouse(float zoomSpeed, float minZoom, float maxZoom, Texture2D *texture){
|
||||
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
@@ -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
@@ -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
@@ -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
@@ -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,4 +1,4 @@
|
||||
#include "engine.h"
|
||||
#include "engineSoundtrack.h"
|
||||
|
||||
Soundtrack LoadSoundtrack(const char* musicPath){
|
||||
Soundtrack soundtrack = {
|
||||
|
||||
+34
-31
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user