diff options
-rw-r--r-- | Makefile | 26 | ||||
-rw-r--r-- | def.h | 9 | ||||
-rw-r--r-- | enemy.c | 198 | ||||
-rw-r--r-- | enemy.h | 68 | ||||
-rw-r--r-- | game-state.c | 150 | ||||
-rw-r--r-- | game-state.h | 43 | ||||
-rw-r--r-- | main.c | 180 | ||||
-rw-r--r-- | matematik.c | 16 | ||||
-rw-r--r-- | matematik.h | 11 | ||||
-rw-r--r-- | player.c | 166 | ||||
-rw-r--r-- | player.h | 66 | ||||
-rw-r--r-- | sprite-animator.c | 47 | ||||
-rw-r--r-- | sprite-animator.h | 27 | ||||
-rw-r--r-- | textures.c | 23 | ||||
-rw-r--r-- | textures.h | 23 | ||||
-rw-r--r-- | tower.c | 39 | ||||
-rw-r--r-- | tower.h | 29 |
17 files changed, 1121 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..16dcbfd --- /dev/null +++ b/Makefile @@ -0,0 +1,26 @@ +# SRC := main.c # player.o sprite-animator.o +CC := gcc +OUT := tarla +OBJ := player.o sprite-animator.o matematik.o enemy.o tower.o game-state.o textures.o main.o +LIBS := -lraylib -lGL -lm -lpthread -ldl -lrt -lX11 +FLAGS := -Wall + +all: $(OUT) + +%.o: %.c + $(CC) -c $(FLAGS) $< -o $@ + +$(OUT): $(OBJ) + $(CC) $(FLAGS) $(OBJ) -o $(OUT) $(LIBS) + +clean: + rm -fv $(OUT) *~ *.o + +zip: + mkdir tarla-src + cp -a *.c *.h Makefile tarla-src + zip -r tarla.zip tarla-src + rm -r tarla-src + +count: + wc -l *.c *.h @@ -0,0 +1,9 @@ +#ifndef TANIM_H +#define TANIM_H + +#define WINDOW_HEIGHT 600 +#define WINDOW_WIDTH 800 +#define FPS 60 + + +#endif @@ -0,0 +1,198 @@ +#include "enemy.h" +#include <stdlib.h> + +animation_player_t get_enemy_animation(enum enemy_type type){ + switch (type){ + case TEST_ENEMY: + return make_animation_player(&textures[T_PLAYER], 12, 8, 40, 64, 8, 0); + case SKELETON: + return make_animation_player(&textures[T_SKELETON_MOVE], 1, 10, 0, 10, 1, 0); + default: + fprintf(stderr, "get_enemy_animation() error"); + return (animation_player_t){}; + } +} + +/* Vector2 get_enemy_size(enum enemy_type type){ */ +/* switch(type){ */ +/* case TEST_ENEMY: */ +/* return (Vector2){ 13, 18 }; */ +/* case SKELETON: */ +/* return (Vector2){ 12, 17 }; */ +/* default: */ +/* return (Vector2){ 0, 0 }; */ +/* } */ +/* } */ + +Rectangle get_enemy_hitbox(enum enemy_type type){ + switch(type){ + case TEST_ENEMY: + return (Rectangle){ 1, 5, 13, 18 }; + case SKELETON: + return (Rectangle){ 7, 12, 12, 17 }; + default: + return (Rectangle){0}; + } +} + +void flip_enemy_y_axis(enemy_t *e){ + if (e->direction.x < 0){ + e->animation.frame_rec.width = -abs(e->animation.frame_rec.width); + e->hitbox.x = e->position.x + e->animation.frame_width - e->hb_offset.x - e->hitbox.width; + } else { + e->animation.frame_rec.width = abs(e->animation.frame_rec.width); + e->hitbox.x = e->position.x + e->hb_offset.x; + } +} + +enemy_t make_enemy(enum enemy_type type, int max_health, int x, int y, int speed){ + animation_player_t enemy_anim = get_enemy_animation(type); + Rectangle hitbox = get_enemy_hitbox(type); + Vector2 offset = (Vector2){ hitbox.x, hitbox.y }; + hitbox.x += x; + hitbox.y += y; + return (enemy_t) { + .animation = enemy_anim, + .type = type, + .health = max_health, + .health_max = max_health, + .position = (Vector2){ x, y }, + .hitbox = hitbox, + .hb_offset = offset, + /* .size = get_enemy_size(type), */ + /* .center = (Vector2){ x + enemy_anim.frame_width / 2, y + enemy_anim.frame_height / 2}, */ + .speed = speed, + .active = 1 + }; +} + +void move_enemy(enemy_t* e, float dt){ + float x_move = e->direction.x * e->speed * dt; + float y_move = e->direction.y * e->speed * dt; + e->position.x += x_move; + e->position.y += y_move; + e->hitbox.x += x_move; + e->hitbox.y += y_move; + /* e->center.x = e->position.x + e->size.x / 2; */ + /* e->center.y = e->position.y + e->size.y / 2; */ +} + +void update_enemy_basic(enemy_t *e, Vector2 target, float dt){ + float old = e->direction.x; + e->direction = normalize_vector(vector_diff(target, e->position)); + if (signbit(old) != signbit(e->direction.x)){ + flip_enemy_y_axis(e); + } +} + +void update_test_enemy(enemy_t *e, Vector2 target, float dt){ + e->direction = normalize_vector(vector_diff(target, e->position)); + int new_offset = get_dir(floorf(e->direction.x + 1) + 3 * floorf(e->direction.y + 1)); + if (new_offset != e->animation.index_offset){ + animation_player_t *anim = &e->animation; + anim->index_offset = new_offset; + anim->frame_current = anim->index_start + anim->index_offset; + } +} + +void update_enemy(enemy_t *e, Vector2 target, float dt){ + update_frame(&e->animation); + switch (e->type){ + case TEST_ENEMY: + update_test_enemy(e, target, dt); + break; + case SKELETON: + update_enemy_basic(e, target, dt); + break; + default: + break; + } + if (distance(target, e->position) > 1.0f){ + move_enemy(e, dt); + } +} + +/* animation_player_t *anim = &enemy->animation; */ +/* DrawTextureRec(*anim->sheet, anim->frame_rec, enemy->position, WHITE); */ +void draw_enemy(enemy_t *enemy){ + draw_frame(&enemy->animation, enemy->position, WHITE); + + DrawRectangleLinesEx(enemy->hitbox, 1, LIGHTGRAY); + + Rectangle hb = enemy->hitbox; + Rectangle health_box = { hb.x, hb.y + hb.height + 5, hb.width, 5}; + float per_health_width = health_box.width / enemy->health_max; + DrawRectangleLinesEx(health_box, 1, BLACK); + DrawRectangleRec((Rectangle){health_box.x, health_box.y, per_health_width * enemy->health, 5}, RED); + + /* animation_player_t *anim = &enemy->animation; */ + /* Vector2 pos = enemy->position; */ + /* DrawRectangleLines(pos.x, pos.y, anim->frame_width, anim->frame_height, SKYBLUE); */ + draw_frame_box(&enemy->animation, enemy->position); +} + +/* Vector2 p = enemy->position; */ +/* Vector2 s = enemy->size; */ +/* Vector2 pos = (Vector2) {p.x, p.y + s.y + 5}; */ +/* Rectangle rec = (Rectangle) { pos.x, pos.y, s.x, 5 }; */ +/* float box_width = s.x / enemy->health_max; */ +/* /\* box_width * enemy->health *\/ */ +/* DrawRectangleLinesEx(rec, 1, BLACK); */ +/* DrawRectangleV(pos, (Vector2){box_width * enemy->health, 5}, RED); */ +/* /\* DrawRectangleLinesEx((Rectangle){ p.x, p.y, s.x, s.y }, 2, LIGHTGRAY); *\/ */ +/* /\* DrawRectangleV(enemy->position, enemy->size, LIGHTGRAY); *\/ */ + +enemy_t spawn_enemy(enemy_spawner_t* spawner){ + Rectangle *s = &spawner->border; + int x = s->x, y = s->y; + if (rand() % 2){ + x += rand() % 2 ? 0 : s->width; + y += rand() % (int)s->height; + } else { + x += rand() % (int)s->width; + y += rand() % 2 ? 0 : s->height; + } + start_cooldown(spawner); + return make_enemy(rand() % ENEMY_TYPE_COUNT, 3, x, y, 40); +} + +void update_spawner(enemy_spawner_t* spawner, float dt){ + spawner->cooldown -= dt; +} + +void start_cooldown(enemy_spawner_t* spawner){ + spawner->cooldown = spawner->cooldown_max; +} + +bool can_spawn(const enemy_spawner_t* spawner){ + return spawner->cooldown <= 0; +} + +/* animation_player_t enemy_anim = { */ +/* .sheet = texture, */ +/* .frame_counter = 0, */ +/* .frame_speed = 5, */ +/* .frame_width = texture->width / 8.0, */ +/* .frame_height = texture->height / 12.0, */ +/* .index_start = 40, */ +/* .index_end = 64, */ +/* .index_increment = 8, */ +/* .index_offset = 0 */ +/* }; */ +/* enemy_anim.frame_current = enemy_anim.index_start; */ +/* enemy_anim.frame_rec = (Rectangle){ 0, 0, enemy_anim.frame_width, enemy_anim.frame_height}; */ + +/* enemy_t make_test_enemy(Texture2D *texture, int max_health, int x, int y, int speed){ */ +/* /\* animation_player_t enemy_anim = make_animation_player(texture, 12, 8, 40, 64, 8, 0); *\/ */ +/* animation_player_t enemy_anim = get_enemy_animation(TEST_ENEMY); */ +/* return (enemy_t) { */ +/* .animation = enemy_anim, */ +/* .type = TEST_ENEMY, */ +/* .health = max_health, */ +/* .health_max = max_health, */ +/* .position = (Vector2){ x, y }, */ +/* .size = (Vector2){ enemy_anim.frame_width, enemy_anim.frame_height }, */ +/* .center = (Vector2){ x + enemy_anim.frame_width / 2, y + enemy_anim.frame_height / 2}, */ +/* .speed = speed */ +/* }; */ +/* } */ @@ -0,0 +1,68 @@ +#ifndef DUSMAN_H +#define DUSMAN_H + +#include <raylib.h> +#include <math.h> +#include <stdbool.h> +#include "sprite-animator.h" +#include "matematik.h" +#include "player.h" +#include "textures.h" + +enum enemy_type { + TEST_ENEMY, + SKELETON, + ENEMY_TYPE_COUNT +}; + +typedef struct Enemy { + animation_player_t animation; + enum enemy_type type; + int health; + int health_max; + Vector2 position; + Vector2 size; + /* Vector2 center; */ + Vector2 direction; + Rectangle hitbox; + Vector2 hb_offset; + int speed; + int active; +} enemy_t; + +animation_player_t get_enemy_animation(enum enemy_type type); +Rectangle get_enemy_hitbox(enum enemy_type type); + +/* enemy_t make_test_enemy(Texture2D *texture, int max_health, int x, int y, int speed); */ +enemy_t make_enemy(enum enemy_type type, int max_health, int x, int y, int speed); +void draw_enemy(enemy_t *enemy); + +void move_enemy(enemy_t* e, float dt); +void flip_enemy_y_axis(enemy_t *enemy); + +void update_test_enemy(enemy_t *e, Vector2 target, float dt); +void update_enemy_basic(enemy_t *e, Vector2 target, float dt); +void update_enemy(enemy_t *e, Vector2 target, float dt); + +typedef struct EnemySpawner { + Rectangle border; + float cooldown_max; + float cooldown; +} enemy_spawner_t; + +enemy_t spawn_enemy(enemy_spawner_t* spawner); +void update_spawner(enemy_spawner_t* spawner, float dt); +void start_cooldown(enemy_spawner_t* spawner); +bool can_spawn(const enemy_spawner_t* spawner); + +/* Vector2 enemy_health = (Vector2){ */ +/* .x = 5, */ +/* .y = 5 */ +/* }; */ + +/* void draw_enemy(enemy_t *enemy){ */ +/* DrawRectangleV(enemy->position, enemy->size, RED); */ +/* /\* DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color color) *\/ */ +/* } */ + +#endif diff --git a/game-state.c b/game-state.c new file mode 100644 index 0000000..420c6d9 --- /dev/null +++ b/game-state.c @@ -0,0 +1,150 @@ +#include "game-state.h" +#include <stdlib.h> + +/* const int ENEMY_COUNT_MAX = 1000; */ +/* const int TOWER_COUNT_MAX = 5; */ +const enemy_spawner_t DEFAULT_ENEMY_SPAWNER = { + .border = { .x = 0, .y = 0, .width = WINDOW_WIDTH, .height = WINDOW_HEIGHT }, + .cooldown = 100.0f, + .cooldown_max = 100.0f +}; + +float mouse_x(gs_t *gs){ + Camera2D *cam = &gs->player.camera; + return cam->target.x + GetMouseX() - cam->offset.x; +} + +float mouse_y(gs_t *gs){ + Camera2D *cam = &gs->player.camera; + return cam->target.y + GetMouseY() - cam->offset.y; +} + +void add_tower(gs_t *gs, tower_t tower){ + if (gs->tower_count == TOWER_COUNT_MAX){ + return; + } + gs->towers[gs->tower_count++] = tower; +} + +void add_enemy(gs_t *gs, enemy_t enemy){ + if (gs->enemy_count == ENEMY_COUNT_MAX){ + return; + } + gs->enemies[gs->enemy_count++] = enemy; +} + +void add_beam(gs_t *gs, tower_beam_t beam){ + if (gs->beam_count == MAX_BEAM_COUNT){ + return; + } + gs->beams[gs->beam_count++] = beam; +} + +void kill_enemy(gs_t *gs, int index){ + /* enemy_t temp = gs->enemies[index]; */ + gs->enemies[index].active = 0; + gs->enemies[index] = gs->enemies[gs->enemy_count - 1]; + /* gs->enemies[gs->enemy_count] = temp; */ + gs->enemy_count--; + /* printf("enemy <%d> killed\n", index); */ +} + +void update_game(gs_t *gs, float dt){ + update_player(&gs->player, dt); + for (int i = 0; i < gs->enemy_count; i++){ + update_enemy(gs->enemies + i, gs->player.position, dt); + } + for (int i = 0; i < gs->tower_count; i++){ + update_tower(&gs->towers[i], dt); + } + for (int i = 0; i < gs->tower_count; i++){ + tower_t *tow = &gs->towers[i]; + if (tow->cooldown <= 0){ + for (int j = 0; j < gs->enemy_count; j++){ + enemy_t *e = &gs->enemies[j]; + if /* (distance(gs->towers[i].center, gs->enemies[j].center) */ + /* < gs->towers[i].range) */ + (e->active && CheckCollisionCircleRec(tow->center, tow->range, e->hitbox)){ + add_beam(gs, make_beam(tow->position, + /* gs->enemies[j].center */ + e->position, 0.1)); + e->health -= tow->damage; + tow->cooldown = tow->cooldown_max; + PlaySound(BEAM_SOUND); + if (e->health <= 0){ + kill_enemy(gs, j); + } + break; + } + } + } + } + for (int i = 0; i < gs->enemy_count; i++){ + if (CheckCollisionRecs(gs->enemies[i].hitbox, gs->player.hitbox)){ + int remaining = take_damage(&gs->player, 1); + if (remaining == 0){ + Camera2D camera = gs->player.camera; + gs->player = make_player(&textures[T_PLAYER]); + gs->player.camera.zoom = camera.zoom; + } + /* printf("collision\n"); */ + } + } + for (int i = 0; i < gs->beam_count; i++){ + gs->beams[i].live_time -= dt; + if (gs->beams[i].live_time <= 0){ + gs->beams[i] = gs->beams[gs->beam_count]; + gs->beam_count--; + } + } + + if (can_spawn(&gs->spawner) && gs->enemy_count < ENEMY_COUNT_MAX){ + add_enemy(gs, spawn_enemy(&gs->spawner)); + } + update_spawner(&gs->spawner, dt); + + if (IsKeyPressed(KEY_T)){ + add_tower(gs, make_tower(1, 100, mouse_x(gs), mouse_y(gs), 20, 30, &textures[T_TOWER])); + } + if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { + paint_tile(gs); + } +} + +void draw_game(gs_t *gs){ + draw_player(&gs->player); + for (int i = 0; i < gs->tower_count; i++){ + draw_tower(gs->towers + i); + } + for (int i = 0; i < gs->enemy_count; i++){ + draw_enemy(gs->enemies + i); + } + for (int i = 0; i < gs->beam_count; i++){ + DrawLineV(gs->beams[i].src, gs->beams[i].dest, MAGENTA); + } + /* if (IsKeyDown(KEY_E)){ */ + + /* } */ + DrawCircle(mouse_x(gs), mouse_y(gs), 5, VIOLET); +} + +void paint_tile(gs_t *gs){ + float zoom = gs->player.camera.zoom; + float edge_len = 16 / zoom; + Camera2D *c = &gs->player.camera; + int edge = edge_len * c->zoom; + int x = mouse_x(gs) / edge; + int y = (WINDOW_HEIGHT - mouse_y(gs)) / edge; + /* int y = (GetMouseY() - gs.player.camera.target.y) / edge_len; */ + printf("drawing tile(%d, %d) at %d %d\n", x, y, x * edge, y * edge); + if /* (x >= 0 && x < WINDOW_WIDTH && y >= 0 && y < WINDOW_HEIGHT) */ + (1){ + BeginTextureMode(gs->bg_tiles); + DrawTextureRec(gs->textures[T_DIRT], (Rectangle){ 0, 0, 16, 16 }, + (Vector2){x * edge /* / zoom */, y * edge /* / zoom */} + /* (Vector2){0, edge_len * yy} */ + , WHITE); + EndTextureMode(); + /* yy++; */ + } +} diff --git a/game-state.h b/game-state.h new file mode 100644 index 0000000..78e423f --- /dev/null +++ b/game-state.h @@ -0,0 +1,43 @@ +#ifndef GAME_STATE_H +#define GAME_STATE_H + +#include <raylib.h> + +#include "player.h" +#include "def.h" +#include "enemy.h" +#include "tower.h" + +extern const enemy_spawner_t DEFAULT_ENEMY_SPAWNER; +#define ENEMY_COUNT_MAX 1000 +#define TOWER_COUNT_MAX 100 +#define MAX_BEAM_COUNT 100 +/* extern const int ENEMY_COUNT_MAX; */ +/* extern const int TOWER_COUNT_MAX; */ + +typedef struct GameState { + player_t player; + tower_t towers[TOWER_COUNT_MAX]; + int tower_count; + enemy_t enemies[ENEMY_COUNT_MAX]; + int enemy_count; + tower_beam_t beams[MAX_BEAM_COUNT]; + int beam_count; + enemy_spawner_t spawner; + Texture2D *textures; + RenderTexture2D bg_tiles; +} gs_t; + +float mouse_x(gs_t *gs); +float mouse_y(gs_t *gs); + +void add_tower(gs_t *gs, tower_t tower); +void add_enemy(gs_t *gs, enemy_t enemy); +void add_beam(gs_t *gs, tower_beam_t beam); +void kill_enemy(gs_t *gs, int index); +void update_game(gs_t *gs, float dt); +void draw_game(gs_t *gs); + +void paint_tile(); + +#endif @@ -0,0 +1,180 @@ +#include <stdlib.h> +#include <stdio.h> +#include <math.h> +#include <time.h> + +#include <raylib.h> +#include "game-state.h" +#include "textures.h" + +/* Texture2D sheet = LoadTexture("asset/8-dir.png"); */ +/* Texture2D tile_texture = LoadTexture("asset/grass_tiles.png"); */ +/* Texture2D dirt_texture = LoadTexture("asset/dirt-path.png"); */ +/* Texture2D tower_texture = LoadTexture("asset/tower.png"); */ +/* Image dust_gif = LoadImageAnim("asset/dust.gif"); */ + + + +char _frame_info_buf[128]; +void _draw_info(gs_t *gs); + +int main(){ + srand(time(NULL)); + InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "oyun"); + InitAudioDevice(); + HideCursor(); + SetTargetFPS(FPS); + + load_textures(); + BEAM_SOUND = LoadSound("asset/laser.wav"); + + gs_t gs = { + .tower_count = 0, + .enemy_count = 0, + .textures = &textures[0], + .spawner = DEFAULT_ENEMY_SPAWNER + }; + add_tower(&gs, make_tower(1, 100, WINDOW_WIDTH / 2, WINDOW_HEIGHT / 2, 20, 30, &textures[T_TOWER])); + /* add_enemy(&gs, make_enemy(&sheet, 3, WINDOW_WIDTH / 2 + 80, WINDOW_HEIGHT / 2 - 80, 30)); */ + /* add_enemy(&gs, make_enemy(&sheet, 3, WINDOW_WIDTH / 2 - 80, WINDOW_HEIGHT / 2 - 80, 30)); */ + gs.player = make_player(&textures[T_PLAYER]); + + Rectangle grass_tile1_rect = (Rectangle){ 0, 0, 16, 16 }; + RenderTexture2D bg_tiles = LoadRenderTexture(WINDOW_WIDTH, WINDOW_HEIGHT); + gs.bg_tiles = bg_tiles; + // https://stackoverflow.com/questions/78609155/what-is-the-difference-between-begintexturemode-and-drawtexture-in-raylib + BeginTextureMode(bg_tiles); + int edge_len = 16; + for (int i = 0; i < WINDOW_WIDTH; i += edge_len) + for (int j = 0; j < WINDOW_HEIGHT; j += edge_len){ + DrawTextureRec(textures[T_GRASS], grass_tile1_rect, (Vector2){i, j}, WHITE); + /* char buf[32]; */ + /* snprintf(buf, 32, "%d", (i + j) % 10); */ + /* DrawText(buf, i, j + 2, 4, BLACK); */ + } + EndTextureMode(); + + /* int yy = 0; */ + int paused = 0; + while (!WindowShouldClose()){ + + if (IsKeyPressed(KEY_P)){ + paused = !paused; + } /* else if (IsKeyReleased(KEY_P)){ */ + /* paused = 0; */ + /* } */ + + if (!paused){ + update_game(&gs, GetFrameTime()); + } + /* update_frame(&anim); */ + + BeginDrawing(); + + ClearBackground(WHITE); + + /* draw_frame(&anim); */ + BeginMode2D(gs.player.camera); + + /* for (int i = 0; i < WINDOW_WIDTH; i += edge_len) */ + /* DrawLine(i, 0, i, WINDOW_HEIGHT, LIGHTGRAY); */ + /* for (int i = 0; i < WINDOW_HEIGHT; i += edge_len) */ + /* DrawLine(0, i, WINDOW_WIDTH, i, LIGHTGRAY); */ + /* for (int i = 0; i < WINDOW_WIDTH; i += edge_len) */ + /* for (int j = 0; j < WINDOW_HEIGHT; j += edge_len) */ + /* DrawTextureRec(tile_texture, grass_tile1_rect, (Vector2){i, j}, WHITE); */ + DrawTexture(bg_tiles.texture, 0, 0, WHITE); + /* DrawTextureRec(bg_tiles.texture, (Rectangle){ 0, 0, bg_tiles.texture.width, -bg_tiles.texture.height }, (Vector2){0, 0}, WHITE); */ + draw_game(&gs); + + EndMode2D(); + + _draw_info(&gs); + if (paused){ + DrawText("PAUSED", WINDOW_WIDTH / 2, WINDOW_HEIGHT / 2, 10, BLACK); + } + + EndDrawing(); + } + + unload_textures(); + /* UnloadTexture(sheet); */ + /* UnloadTexture(tile_texture); */ + /* UnloadTexture(tower_texture); */ + /* UnloadTexture(dirt_texture); */ + UnloadSound(BEAM_SOUND); + UnloadRenderTexture(bg_tiles); + + CloseAudioDevice(); + CloseWindow(); + return EXIT_SUCCESS; +} + + +void _draw_info(gs_t *gs){ + player_t *p = &gs->player; + sprintf(_frame_info_buf, "player (%.2f, %.2f)", p->position.x, p->position.y); + DrawText(_frame_info_buf, 20, 20, 20, BLACK); + + sprintf(_frame_info_buf, "mouse (%d, %d)", GetMouseX(), GetMouseY()); + DrawText(_frame_info_buf, 20, 50, 20, BLACK); + + sprintf(_frame_info_buf, "camera target (%.2f, %.2f)", p->camera.target.x, p->camera.target.y); + DrawText(_frame_info_buf, 20, 80, 20, BLACK); + + sprintf(_frame_info_buf, "camera offset (%.2f, %.2f)", p->camera.offset.x, p->camera.offset.y); + DrawText(_frame_info_buf, 20, 110, 20, BLACK); + + sprintf(_frame_info_buf, "enemy count %d / %d", gs->enemy_count, ENEMY_COUNT_MAX); + DrawText(_frame_info_buf, 20, 140, 20, BLACK); + + sprintf(_frame_info_buf, "tower count %d / %d", gs->tower_count, TOWER_COUNT_MAX); + DrawText(_frame_info_buf, 20, 170, 20, BLACK); + + + /* animation_player_t *anim = p->animation; */ + /* sprintf(_frame_info_buf, "counter %d", anim->frame_counter); */ + /* DrawText(_frame_info_buf, 20, 20, 20, BLACK); */ + + /* sprintf(_frame_info_buf, "current %d", anim->frame_current); */ + /* DrawText(_frame_info_buf, 20, 50, 20, BLACK); */ + + /* sprintf(_frame_info_buf, "frec.x %f", anim->frame_rec.x); */ + /* DrawText(_frame_info_buf, 20, 80, 20, BLACK); */ + + /* sprintf(_frame_info_buf, "frec.y %f", anim->frame_rec.y); */ + /* DrawText(_frame_info_buf, 20, 110, 20, BLACK); */ + + /* sprintf(_frame_info_buf, "pos.x %f", p->position.x); */ + /* DrawText(_frame_info_buf, 20, 140, 20, BLACK); */ + + /* sprintf(_frame_info_buf, "pos.y %f", p->position.y); */ + /* DrawText(_frame_info_buf, 20, 170, 20, BLACK); */ + + /* if (gs->enemy_count > 0){ */ + /* sprintf(_frame_info_buf, "(%f, %f)", */ + /* gs->enemies[0].direction.x, */ + /* gs->enemies[0].direction.y); */ + /* DrawText(_frame_info_buf, 20, 200, 20, BLACK); */ + + /* sprintf(_frame_info_buf, "hp %d", gs->enemies[0].health); */ + /* DrawText(_frame_info_buf, 20, 230, 20, BLACK); */ + + /* sprintf(_frame_info_buf, "dist %f", distance(gs->towers[0].center, gs->enemies[0].center)); */ + /* DrawText(_frame_info_buf, 20, 260, 20, BLACK); */ + + /* animation_player_t *anim = &gs->enemies[0].animation; */ + /* sprintf(_frame_info_buf, "counter %d", anim->frame_counter); */ + /* DrawText(_frame_info_buf, 20, 20, 20, BLACK); */ + + /* sprintf(_frame_info_buf, "current %d", anim->frame_current); */ + /* DrawText(_frame_info_buf, 20, 50, 20, BLACK); */ + + /* sprintf(_frame_info_buf, "frec.x %f", anim->frame_rec.x); */ + /* DrawText(_frame_info_buf, 20, 80, 20, BLACK); */ + + /* sprintf(_frame_info_buf, "frec.y %f", anim->frame_rec.y); */ + /* DrawText(_frame_info_buf, 20, 110, 20, BLACK); */ + + /* } */ +} diff --git a/matematik.c b/matematik.c new file mode 100644 index 0000000..c5e36b2 --- /dev/null +++ b/matematik.c @@ -0,0 +1,16 @@ +#include "matematik.h" + +float distance(Vector2 a, Vector2 b){ + return sqrt(pow(a.x - b.x, 2) + pow(a.y - b.y, 2)); +} + +Vector2 normalize_vector(Vector2 vec){ + float len = sqrt(vec.x * vec.x + vec.y * vec.y); + vec.x /= len; + vec.y /= len; + return vec; +} + +Vector2 vector_diff(Vector2 a, Vector2 b){ + return (Vector2) { a.x - b.x, a.y - b.y }; +} diff --git a/matematik.h b/matematik.h new file mode 100644 index 0000000..e042d2f --- /dev/null +++ b/matematik.h @@ -0,0 +1,11 @@ +#ifndef MATEMATIK_H +#define MATEMATIK_H + +#include <raylib.h> +#include <math.h> + +float distance(Vector2 a, Vector2 b); +Vector2 normalize_vector(Vector2 vec); +Vector2 vector_diff(Vector2 a, Vector2 b); + +#endif diff --git a/player.c b/player.c new file mode 100644 index 0000000..d9d0ad0 --- /dev/null +++ b/player.c @@ -0,0 +1,166 @@ +#include "player.h" + +void input_player_direction(player_t *p){ + Vector2 v = {0, 0}; + if (IsKeyDown(KEY_W)){ + v.y = -1; + } else if (IsKeyDown(KEY_S)) { + v.y = 1; + } + if (IsKeyDown(KEY_D)){ + v.x = 1; + } else if (IsKeyDown(KEY_A)){ + v.x = -1; + } + int new_offset = get_dir((v.x + 1) + 3 * (v.y + 1)); + if (new_offset != p->animation.index_offset){ + animation_player_t *anim = &p->animation; + anim->index_offset = new_offset; + anim->frame_current = anim->index_start + anim->index_offset; + } + if (v.x != 0 || v.y != 0){ + v = normalize_vector(v); + } + p->direction = v; +} + +void update_player(player_t *p, float dt){ + if (p->state.iframe_active){ + p->iframe_duration -= dt; + if (p->iframe_duration <= 0){ + p->state.iframe_active = 0; + p->iframe_duration = 0; + } + } + + input_player_direction(p); + update_frame(&p->animation); + + if (p->direction.x == 0 && p->direction.y == 0){ + p->state.move = PS_STANDING; + p->animation.frame_speed = 0; + p->animation.frame_current = 20; + animation_player_t *anim = &p->animation; + anim->frame_rec.x = (anim->frame_current % 8) * anim->frame_width; + anim->frame_rec.y = (anim->frame_current / 8) * anim->frame_height; + } else if (IsKeyDown(KEY_LEFT_SHIFT)){ + p->speed = PLAYER_SPEED_SPRINT; + p->state.move = PS_RUNNING; + p->animation.frame_speed = 10; + } else /* (IsKeyReleased(KEY_LEFT_SHIFT)) */ { + p->speed = PLAYER_SPEED_NORMAL; + p->state.move = PS_WALKING; + p->animation.frame_speed = 5; + } + + float delta_x = p->direction.x * p->speed * dt; + float delta_y = p->direction.y * p->speed * dt; + p->position.x += delta_x; + p->position.y += delta_y; + p->hitbox.x += delta_x; + p->hitbox.y += delta_y; + p->camera.target = p->position; + + p->camera.zoom += ((float)GetMouseWheelMove()*0.05f); + + if (p->camera.zoom > 3.0f) p->camera.zoom = 3.0f; + else if (p->camera.zoom < 0.1f) p->camera.zoom = 0.1f; + +} + +void draw_player(player_t *p){ + draw_frame(&p->animation, p->position, p->state.iframe_active ? RED : WHITE); + draw_frame_box(&p->animation, p->position); + DrawRectangleLinesEx(p->hitbox, 1, LIGHTGRAY); + + Rectangle hb = p->hitbox; + Rectangle health_box = { hb.x, hb.y + hb.height + 5, hb.width, 5}; + float per_health_width = health_box.width / p->health_max; + DrawRectangleLinesEx(health_box, 1, BLACK); + DrawRectangleRec((Rectangle){health_box.x, health_box.y, per_health_width * p->health, 5}, RED); + + /* animation_player_t *anim = &p->animation; */ + /* /\* draw_frame(anim); *\/ */ + /* DrawTextureRec(*anim->sheet, anim->frame_rec, p->position, WHITE); */ + /* if (p->state == RUNNING){ */ + /* /\* DrawTexture(Texture2D texture, int posX, int posY, Color tint); *\/ */ + /* } */ +} + +player_t make_player(Texture2D *texture){ + animation_player_t anim = { + .sheet = texture, + .frame_counter = 0, + .frame_speed = 5, + .frame_width = texture->width / 8.0, + .frame_height = texture->height / 12.0, + .index_start = 8, + .index_end = 32, + .index_increment = 8, + .index_offset = 0 + }; + anim.frame_current = anim.index_start; + anim.frame_rec = (Rectangle){0, 0, anim.frame_width, anim.frame_height}; + + player_t player = { + .position = (Vector2){ WINDOW_WIDTH / 2, WINDOW_HEIGHT / 2 - 100 }, + .speed = PLAYER_SPEED_NORMAL, + .animation = anim, + .health = 3, + .health_max = 3, + .iframe_duration = 0, + .iframe_duration_max = 1, + .state = (player_state_t){ + .move = PS_STANDING, + .can_move = 1, + .iframe_active = 0, + .alive = 1 + } + }; + player.hitbox = (Rectangle){ + player.position.x, + player.position.y + 5, + anim.frame_rec.width, + anim.frame_height - 5 + }; + player.camera = (Camera2D){ + .target = (Vector2){ player.position.x, player.position.y }, + .offset = (Vector2){ WINDOW_WIDTH/2.0f, WINDOW_HEIGHT /2.0f }, + .rotation = 0.0f, + .zoom = 1.0f + }; + return player; +} + +sprite_dir_enum _directions[9] = { + SP_NORTH_WEST, + SP_NORTH, + SP_NORTH_EAST, + SP_WEST, + SP_SOUTH, + SP_EAST, + SP_SOUTH_WEST, + SP_SOUTH, + SP_SOUTH_EAST, +}; + +sprite_dir_enum get_dir(int index){ + return _directions[index]; +} + +int take_damage(player_t *p, int amount){ + if (p->state.iframe_active){ + return p->health; + } + + p->health -= amount; + p->state.iframe_active = 1; + p->iframe_duration = p->iframe_duration_max; + + if (p->health <= 0){ + p->state.alive = 0; + return 0; + } else { + return p->health; + } +} diff --git a/player.h b/player.h new file mode 100644 index 0000000..1a08a71 --- /dev/null +++ b/player.h @@ -0,0 +1,66 @@ +#ifndef OYUNCU_H +#define OYUNCU_H + +#include <raylib.h> +#include "sprite-animator.h" +#include "def.h" +#include "matematik.h" + +#define PLAYER_SPEED_NORMAL 40 +#define PLAYER_SPEED_SPRINT 65 + +/* enum player_states { */ +/* PS_STANDING = 1, */ +/* PS_WALKING = 2, */ +/* PS_RUNNING = 4, */ +/* PS_MOVE_STATE = PS_STANDING | PS_WALKING | PS_RUNNING, */ +/* PS_CAN_MOVE = 8, */ +/* PS_IFRAME_ACTIVE = 16 */ +/* }; */ + +#define PS_STANDING 0 +#define PS_WALKING 1 +#define PS_RUNNING 2 + +typedef struct { + unsigned int move : 2; + unsigned int can_move : 1; + unsigned int iframe_active : 1; + unsigned int alive : 1; +} player_state_t; + +typedef struct Player { + Vector2 position; + Vector2 direction; + int speed; + int health; + int health_max; + Rectangle hitbox; + float iframe_duration; + float iframe_duration_max; + animation_player_t animation; + Camera2D camera; + player_state_t state; +} player_t; + +void input_player_direction(player_t *p); +void update_player(player_t *p, float dt); +void draw_player(player_t *p); +player_t make_player(Texture2D *texture); +int take_damage(player_t *p, int amount); + + +typedef enum SpriteDirections { + SP_NORTH, + SP_NORTH_EAST, + SP_EAST, + SP_SOUTH_EAST, + SP_SOUTH, + SP_SOUTH_WEST, + SP_WEST, + SP_NORTH_WEST, +} sprite_dir_enum; + +sprite_dir_enum get_dir(int index); + +#endif diff --git a/sprite-animator.c b/sprite-animator.c new file mode 100644 index 0000000..b77d225 --- /dev/null +++ b/sprite-animator.c @@ -0,0 +1,47 @@ +#include "sprite-animator.h" + +void update_frame(animation_player_t *anim){ + if (anim->frame_speed == 0){ + return; + } + + anim->frame_counter++; + if (anim->frame_counter >= (FPS / anim->frame_speed)){ + anim->frame_counter = 0; + + anim->frame_current = anim->frame_current + anim->index_increment; + if (anim->frame_current >= anim->index_end) { + anim->frame_current = anim->index_start + anim->index_offset; + } + + anim->frame_rec.x = (anim->frame_current % 8) * anim->frame_width; + anim->frame_rec.y = (anim->frame_current / 8) * anim->frame_height; + } +} + +void draw_frame(animation_player_t *anim, Vector2 pos, Color tint){ + DrawTextureRec(*anim->sheet, anim->frame_rec, pos, tint); +} + +animation_player_t make_animation_player +(Texture2D *texture, int row, int col, int start, int end, int inc, int offset){ + float w = texture->width / col; + float h = texture->height / row; + return (animation_player_t){ + .sheet = texture, + .frame_counter = 0, + .frame_speed = 5, + .frame_width = w, + .frame_height = h, + .index_start = start, + .index_end = end, + .index_increment = inc, + .index_offset = offset, + .frame_current = start, + .frame_rec = (Rectangle){ 0, 0, w, h } + }; +} + +void draw_frame_box(animation_player_t *anim, Vector2 pos){ + DrawRectangleLines(pos.x, pos.y, anim->frame_width, anim->frame_height, SKYBLUE); +} diff --git a/sprite-animator.h b/sprite-animator.h new file mode 100644 index 0000000..ab61dd7 --- /dev/null +++ b/sprite-animator.h @@ -0,0 +1,27 @@ +#ifndef SPRITE_SHEET_ANIMATOR_H +#define SPRITE_SHEET_ANIMATOR_H + +#include <raylib.h> +#include "def.h" + +typedef struct AnimationPlayer { + Texture2D *sheet; + int frame_counter; + int frame_speed; + int frame_current; + Rectangle frame_rec; + float frame_width; + float frame_height; + + int index_start; + int index_offset; + int index_end; + int index_increment; +} animation_player_t; + +animation_player_t make_animation_player(Texture2D *texture, int row, int col, int start, int end, int inc, int offset); +void update_frame(animation_player_t *anim); +void draw_frame(animation_player_t *anim, Vector2 pos, Color tint); +void draw_frame_box(animation_player_t *anim, Vector2 pos); + +#endif diff --git a/textures.c b/textures.c new file mode 100644 index 0000000..52a745b --- /dev/null +++ b/textures.c @@ -0,0 +1,23 @@ +#include "textures.h" + +Texture2D textures[T_COUNT]; + +Texture2D load_texture(const char *path){ + char buf[128]; + snprintf(buf, 128, ASSET_ROOT "%s", path); + return LoadTexture(buf); +} + +void load_textures(){ + textures[T_PLAYER] = load_texture("8-dir.png"); + textures[T_GRASS] = load_texture("grass_tiles.png"); + textures[T_DIRT] = load_texture("dirt-path.png"); + textures[T_TOWER] = load_texture("tower.png"); + textures[T_SKELETON_MOVE] = load_texture("skeleton1/movement.png"); +} + +void unload_textures(){ + for (size_t i = 0; i < T_COUNT; i++){ + UnloadTexture(textures[i]); + } +} diff --git a/textures.h b/textures.h new file mode 100644 index 0000000..01b8873 --- /dev/null +++ b/textures.h @@ -0,0 +1,23 @@ +#ifndef ASSET_LOADER_H +#define ASSET_LOADER_H + +#include <raylib.h> +#include <stdio.h> + +enum texture_enum { + T_PLAYER, + T_GRASS, + T_DIRT, + T_TOWER, + T_SKELETON_MOVE, + T_COUNT +}; + +extern Texture2D textures[T_COUNT]; + +#define ASSET_ROOT "asset/" +Texture2D load_texture(const char *path); +void load_textures(); +void unload_textures(); + +#endif @@ -0,0 +1,39 @@ +#include "tower.h" + +Sound BEAM_SOUND; + +tower_t make_tower(int damage, int range, int x, int y, int width, int height, Texture2D *texture){ + return (tower_t) { + .texture = texture, + .damage = damage, + .range = range, + .position = (Vector2){ x, y }, + .size = (Vector2){ width, height }, + .center = (Vector2){ x + width / 2, y + height / 2}, + .cooldown = 0.0, + .cooldown_max = 1.0 + }; +} + +tower_beam_t make_beam(Vector2 src, Vector2 dest, float live_time){ + return (tower_beam_t){ .src = src, .dest = dest, .live_time = live_time }; +} + +char _tower_cooldown_text[16]; +void draw_tower(tower_t *tower){ + DrawTexture(*tower->texture, tower->position.x, tower->position.y, WHITE); + /* DrawRectangleV(tower->position, tower->size, BLUE); */ + DrawCircleLinesV(tower->center, tower->range, LIGHTGRAY); + const Vector2 p = tower->position; + const Vector2 s = tower->size; + DrawRectangle(p.x, p.y - 15, s.x * ((tower->cooldown_max - tower->cooldown) / tower->cooldown_max), 5, BLUE); + DrawRectangleLines(p.x, p.y - 15, s.x, 5, BLACK); + /* snprintf(_tower_cooldown_text, 16, "%.2f", tower->cooldown); + DrawText(_tower_cooldown_text, tower->position.x + (tower->size.x / 2), tower->position.y - 25, 10, LIGHTGRAY); */ +} + +void update_tower(tower_t *t, float dt){ + if (t->cooldown > 0){ + t->cooldown -= dt; + } +} @@ -0,0 +1,29 @@ +#ifndef KULE_H +#define KULE_H + +#include <raylib.h> + +typedef struct Tower { + Texture2D* texture; + int damage; + int range; + Vector2 position; + Vector2 size; + Vector2 center; + float cooldown; + float cooldown_max; +} tower_t; + +extern Sound BEAM_SOUND; +typedef struct TowerBeam { + Vector2 dest; + Vector2 src; + float live_time; +} tower_beam_t; + +tower_t make_tower(int damage, int range, int x, int y, int width, int height, Texture2D *texture); +tower_beam_t make_beam(Vector2 src, Vector2 dest, float live_time); +void draw_tower(tower_t *tower); +void update_tower(tower_t *t, float dt); + +#endif |