base frame of an item system is standing, rendering is working. Fixed a bug with the renderer

This commit is contained in:
Magnus Küderli
2025-10-30 20:35:16 +01:00
parent 0a2de9ffc9
commit a07d1cdc89
27 changed files with 323 additions and 84 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 922 B

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 624 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 537 B

+2
View File
@@ -0,0 +1,2 @@
- https://nyknck.itch.io/fx084
-
+3 -3
View File
@@ -14,10 +14,10 @@ typedef struct Enemy {
path_t path;
int vision_radius;
void* data;
void (*set_vel_func)(world_t*, enemy_t*, player_t*);
int (*move_func)(enemy_t*, player_t*, world_t*);
void (*update_anim_func)(enemy_t*, player_t*);
void (*execute_state)(world_t*, enemy_t*, player_t*);
void (*update_state)(world_t*, enemy_t*, player_t*);
} enemy_t;
+9 -1
View File
@@ -1,14 +1,21 @@
# pragma once
# include <raylib.h>
# include <raylib.h>
# include <sys/time.h>
# include "../include/tilemap.h"
# include "../include/anim.h"
typedef struct Player player_t;
typedef struct Entity {
int health;
// visual
animator_t anim;
timeval_t action_timer;
Color tint;
int bb_size; // bounding box
Vector2 sprite_size;
@@ -28,4 +35,5 @@ void set_entity_center(entity_t* entity, Vector2 center_pos);
Vector2 get_entity_center(entity_t* entity);
float get_entity_bottom(entity_t* entity);
Rectangle get_entity_rect(entity_t* entity);
void entity_face_player(entity_t* entity, player_t* player);
Rectangle** get_col_tiles_around_entity(entity_t* entity, tilemap_t* col_map);
+22
View File
@@ -0,0 +1,22 @@
# pragma once
#include <raylib.h>
typedef struct Player player_t;
typedef struct World world_t;
typedef enum ItemType {
MEELE_WEAPON,
} itemtype_t;
typedef struct Item {
Texture2D sprite;
Vector2 render_offset;
itemtype_t type;
void* item_data;
void (*item_action)(player_t*, world_t*);
} item_t;
void free_item(item_t* item);
+9
View File
@@ -0,0 +1,9 @@
typedef struct ItemDataMeele {
int damage;
} itemdata_meele_t;
itemdata_meele_t* get_meele_data(void* data) {
return (itemdata_meele_t*) data;
}
+6
View File
@@ -0,0 +1,6 @@
# pragma once
typedef struct Item item_t;
item_t* create_item_sword();
+10
View File
@@ -1,9 +1,11 @@
# pragma once
#include <raylib.h>
#include <sys/time.h>
#include "../include/world.h"
#include "../include/entity.h"
#include "../include/item.h"
typedef enum State {
// index == spritesheet row
@@ -12,12 +14,20 @@ typedef enum State {
} state_t;
typedef struct Player {
item_t* current_item;
// visual
float anim_speed;
timeval_t last_damage;
Texture2D no_arms_tex;
entity_t entity;
} player_t;
void hurt_player(player_t* player, int damage);
void equip_item(player_t* player, item_t* item);
void unequip_item(player_t* player);
void init_player(player_t* player);
void update_player(player_t* player, world_t* world);
void render_player(player_t* player, world_t* world);
+2
View File
@@ -5,6 +5,8 @@ typedef enum Rendertype {
} rendertype_t;
typedef struct Renderdata {
// debug
char name[21];
// texture pro
rendertype_t type;
float ypos;
+1 -1
View File
@@ -22,7 +22,7 @@ typedef struct Path {
bool finished;
Vector2 current_dir;
float current_speed;
bool currently_moving;
} path_t;
void init_world(world_t* world, char* collision_map);
+4 -2
View File
@@ -5,6 +5,8 @@
#include "../include/world.h"
void pathfind_player(world_t* world, enemy_t* enemy, player_t* player);
void init_zombie_enemy(enemy_t* enemy);
void update_animation_from_path(enemy_t* enemy, player_t* player);
void update_animation_from_velocity(enemy_t* enemy, player_t* player);
void zombie_execute_state(world_t* world, enemy_t* enemy, player_t* player);
void zombie_update_state(world_t* world, enemy_t* enemy, player_t* player);
+2 -5
View File
@@ -4,7 +4,6 @@
# include "../include/anim.h"
# include "../include/entity.h"
# include "../include/config.h"
# include "../include/renderer.h"
# include "../include/world.h"
@@ -22,7 +21,6 @@ Rectangle get_animation_frame(entity_t* entity) {
size,
};
// printf("%f|%f player: \n", rect.x, rect.y);
return rect;
}
@@ -71,9 +69,8 @@ void render_entity(entity_t* entity, world_t* world) {
float bottom_ypos = get_entity_bottom(entity);
// DrawTexturePro(entity->anim.texture, source_rect, dest_rect, (Vector2) { 0.0, 0.0 }, 0, WHITE);
//
push_to_render_buffer((renderdata_t) {
.name = "Entity",
.type = TEXTURE_PRO,
.ypos = bottom_ypos,
.texture = entity->anim.texture,
@@ -81,6 +78,6 @@ void render_entity(entity_t* entity, world_t* world) {
.dest = dest_rect,
.origin = (Vector2) { 0.0f, 0.0f },
.rotation = 0.0f,
.tint = WHITE
.tint = entity->tint,
});
}
+4 -8
View File
@@ -22,15 +22,11 @@ enemy_t create_enemy(enemy_type_t type) {
}
void update_enemy(enemy_t* enemy, player_t* player, world_t* world) {
assert(enemy->set_vel_func != NULL);
assert(enemy->move_func != NULL);
assert(enemy->update_anim_func != NULL);
assert(enemy->execute_state != NULL);
assert(enemy->update_state != NULL);
enemy->set_vel_func(world, enemy, player);
enemy->move_func(enemy, player, world);
enemy->update_anim_func(enemy, player);
entity_face_moving_dir(&(enemy->entity));
enemy->execute_state(world, enemy, player);
enemy->update_state(world, enemy, player);
}
void update_enemies(enemy_t* enemies, int enemy_count, player_t* player, world_t* world) {
+5
View File
@@ -7,6 +7,7 @@
#include "../include/entity.h"
#include "../include/config.h"
#include "../include/astar.h"
#include "../include/player.h"
Rectangle** get_col_tiles_around_entity(entity_t* entity, tilemap_t* col_map) {
const int ts = TILE_SIZE; // tile size
@@ -80,6 +81,10 @@ float get_entity_bottom(entity_t* entity) {
return get_entity_center(entity).y + 0.5*entity->sprite_size.y;
}
void entity_face_player(entity_t* entity, player_t* player) {
entity->anim.flipped = entity->position.x > player->entity.position.x;
}
void set_entity_center(entity_t* entity, Vector2 center_pos) {
Vector2 offset = entity->sprite_size;
entity->position = Vector2Subtract(center_pos, offset);
+12 -2
View File
@@ -10,7 +10,8 @@
#include "../include/config.h"
#include "../include/debug.h"
#include "../include/enemy.h"
#include "../include/zombie_enemy.h"
#include "../include/item.h"
#include "../include/item_sword.h"
int start_game() {
player_t player;
@@ -34,7 +35,7 @@ int start_game() {
tilemap_t* tilemap_ground = load_tilemap("assets/levels/test_ground.csv");
printf("\n[!] Loaded tilemaps successfully!\n\n");
spawn_enemy(ZOMBIE, &world, Vector2Zero());
spawn_enemy(ZOMBIE, &world, (Vector2) { 200, 10 } );
while (!WindowShouldClose()) {
// UPDATE
@@ -43,6 +44,15 @@ int start_game() {
update_enemies(world.enemies, world.enemy_counter, &player, &world);
read_debug_input();
if (IsKeyPressed(KEY_E)) {
if (!player.current_item) {
equip_item(&player, create_item_sword());
}
else {
unequip_item(&player);
}
}
// RENDER
start_render(&camera);
+8
View File
@@ -0,0 +1,8 @@
# include <stdlib.h>
# include "../include/item.h"
void free_item(item_t* item) {
free(item->item_data);
free(item);
}
View File
+23
View File
@@ -0,0 +1,23 @@
# include <stdlib.h>
# include <assert.h>
# include "../include/item_sword.h"
# include "../include/item.h"
# include "../include/item_meele.h"
item_t* create_item_sword() {
item_t* item_p = malloc(sizeof(item_t));
assert(item_p);
item_p->item_data = (itemdata_meele_t*)malloc(sizeof(itemdata_meele_t));
assert(item_p->item_data);
// item_p->icon = LoadTexture("");
item_p->sprite = LoadTexture("assets/sprites/items/sword.png");
item_p->render_offset = (Vector2) { 6, -6 };
item_p->type = MEELE_WEAPON;
item_p->item_action = NULL;
return item_p;
}
+3 -7
View File
@@ -1,5 +1,4 @@
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include "../include/world.h"
@@ -15,10 +14,8 @@
int enemy_follow_path(enemy_t* enemy, player_t* player, world_t* world) {
if (enemy->path.finished) {
printf("closing in!\n");
// target is reached
Vector2 player_pos = get_entity_center(&player->entity);
player_pos.y -= 3;
Vector2 p_to_e = Vector2Subtract(get_entity_center(&enemy->entity), player_pos);
Vector2 p_to_e_len_5 = Vector2Scale(Vector2Normalize(p_to_e), 5.0);
@@ -28,7 +25,7 @@ int enemy_follow_path(enemy_t* enemy, player_t* player, world_t* world) {
float speed = 2.0;
Vector2 step = Vector2Scale(dir_target, GetFrameTime() * speed);
enemy->entity.position = Vector2Add(enemy->entity.position, step);
enemy->path.current_speed = (int)(Vector2Length(step) * 100);
enemy->path.currently_moving = Vector2Length(step) > 0.001;
return 0;
}
@@ -43,7 +40,7 @@ int enemy_follow_path(enemy_t* enemy, player_t* player, world_t* world) {
Vector2 norm_dir = Vector2Normalize(dir);
enemy->path.current_dir = norm_dir;
enemy->path.current_speed = dir_len;
enemy->path.currently_moving = dir_len > 0.2;
// printf("Next node posi [%d|%d]\n", (int)dir.x, (int)dir.y);
// printf("Next direction [%f|%f]\n", norm_dir.x, norm_dir.y);
@@ -66,9 +63,8 @@ int enemy_follow_path(enemy_t* enemy, player_t* player, world_t* world) {
enemy->path.finished = true;
enemy->path.index++;
enemy->path.current_dir = Vector2Zero();
enemy->path.current_speed = 0;
enemy->path.currently_moving = false;
// GOAL REACHED
printf("just reached the goal\n");
return 0;
}
enemy->path.index++;
+107 -3
View File
@@ -1,4 +1,5 @@
#include <raylib.h>
#include <assert.h>
#include <raymath.h>
#include <stdio.h>
#include <stdlib.h>
@@ -9,10 +10,16 @@
#include "../include/entity.h"
#include "../include/anim.h"
#include "../include/movement.h"
#include "../include/renderer.h"
void init_player(player_t* player) {
player->current_item = NULL;
player->no_arms_tex = LoadTexture("assets/sprites/player_no_hand.png");
assert(IsTextureValid(player->no_arms_tex));
// visual
animator_t animt = {
.texture = LoadTexture("assets/sprites/player.png"),
.anim_speed = 0.008f,
@@ -21,13 +28,20 @@ void init_player(player_t* player) {
.frame_count = 4,
};
if (!IsTextureValid(animt.texture)) { printf("WARINING: Player texture loading failed\n"); exit(1); }
gettimeofday(&player->last_damage, NULL);
player->last_damage.tv_usec = 0;
player->last_damage.tv_sec = 0;
player->entity = (entity_t){
.health = 20,
.anim = animt,
.bb_size = 16,
.sprite_size = (Vector2) { 8, 8 }, // Bug, collision only works with some values
.sprite_offset = (Vector2) { -1, -5 }, // X axis relative to player movement direction
.action_timer = 0,
// Physics
.position = Vector2Zero(),
.velocity = Vector2Zero(),
@@ -39,6 +53,18 @@ void init_player(player_t* player) {
};
}
void hurt_player(player_t* player, int damage) {
player->entity.health -= damage;
gettimeofday(&player->last_damage, NULL);
printf("Player took damage, Health: %d\n", player->entity.health);
if (player->entity.health <= 0) {
printf("We died :(\n");
player->entity.health = 10;
}
}
Rectangle get_player_sprite(player_t* player, unsigned long total_ms) {
int frame = (int)(total_ms*player->entity.anim.anim_speed) % 4;
@@ -80,6 +106,46 @@ void player_update_velocity_by_input(player_t* player) {
ply_ent->velocity.y = ply_ent->velocity.y < -ply_ent->speed_cap ? -ply_ent->speed_cap : ply_ent->velocity.y;
}
void equip_item(player_t* player, item_t* item) {
player->current_item = item;
}
void unequip_item(player_t* player) {
UnloadTexture(player->current_item->sprite);
free_item(player->current_item);
player->current_item = NULL;
}
void render_player_item(player_t* player) {
item_t* item = player->current_item;
int what_the_flip = player->entity.anim.flipped ? -1 : 1;
Vector2 item_size = { item->sprite.width, item->sprite.height };
Vector2 flip_rend_offs = item->render_offset;
flip_rend_offs.x *= what_the_flip;
Rectangle src = { 0, 0, item_size.x * what_the_flip, item_size.y };
Rectangle dest = {
player->entity.position.x + flip_rend_offs.x,
player->entity.position.y + flip_rend_offs.y,
item_size.x, item_size.y
};
float player_ypos = get_entity_bottom(&player->entity);
push_to_render_buffer((renderdata_t) {
.name = "sword",
.type = TEXTURE_PRO,
.ypos = player_ypos - 0.001,
.texture = item->sprite,
.src = src,
.dest = dest,
.origin = (Vector2) { 0.0f, 0.0f },
.rotation = 0.0f,
.tint = WHITE,
});
}
void player_update_state(player_t* player) {
if (Vector2Length(player->entity.velocity) > 10.0) {
play_animation(&(player->entity.anim), (int)RUNNING);
@@ -102,6 +168,44 @@ void update_player(player_t* player, world_t* world) {
}
void render_player(player_t* player, world_t* world) {
render_entity(&player->entity, world);
Color tint = get_time_since_ms(player->last_damage) < 300 ? RED : WHITE;
player->entity.tint = tint;
if (player->current_item) {
// RENDER ITEM
render_player_item(player);
}
// Render player
entity_t* entity = &player->entity;
Vector2 offset = {
.x = entity->sprite_offset.x * (entity->anim.flipped?-1:1),
.y = entity->sprite_offset.y,
};
Rectangle dest_rect = {
entity->position.x + offset.x,
entity->position.y + offset.y,
entity->bb_size,
entity->bb_size,
};
Rectangle source_rect = get_animation_frame(entity);
float bottom_ypos = get_entity_bottom(entity);
push_to_render_buffer((renderdata_t) {
.name = "player",
.type = TEXTURE_PRO,
.ypos = bottom_ypos,
.texture = player->current_item ? player->no_arms_tex : entity->anim.texture,
.src = source_rect,
.dest = dest_rect,
.origin = (Vector2) { 0.0f, 0.0f },
.rotation = 0.0f,
.tint = entity->tint,
});
}
+11 -1
View File
@@ -66,12 +66,22 @@ void sort_render_buffer() {
qsort(r->buffer, r->buffer_counter, sizeof(renderdata_t), compare_objects);
}
void print_render_buffer_names() {
renderer_t* r = get_renderer_ptr();
for (int i = 0; i < r->buffer_counter; i++) {
printf("[%s] ", r->buffer[i].name);
}
printf("\n");
}
void ysort_and_render_to_screen() {
renderer_t* r = get_renderer_ptr();
sort_render_buffer();
for (int i = 0; i < r->buffer_capacity; i++) {
// print_render_buffer_names();
for (int i = 0; i < r->buffer_counter; i++) {
// render
int type = r->buffer[i].type;
renderdata_t* data = &r->buffer[i];
+2 -2
View File
@@ -107,7 +107,7 @@ path_t world_get_path(world_t* world, Vector2 start, Vector2 target, int max_sea
.index = -1,
.finished = true,
.current_dir = (Vector2) { -1, -1 },
.current_speed = -1,
.currently_moving = false,
};
}
}
@@ -129,7 +129,7 @@ path_t world_get_path(world_t* world, Vector2 start, Vector2 target, int max_sea
.index = 1, // skip start node, we should already be there
.finished = false,
.current_dir = (Vector2) { 0.0f, 0.0f },
.current_speed = 0,
.currently_moving = false,
};
}
+78 -49
View File
@@ -30,7 +30,7 @@ void init_zombie_enemy(enemy_t* enemy) {
assert(IsTextureValid(anim.texture));
// path
enemy->vision_radius = 30,
enemy->vision_radius = 100,
enemy->path = (path_t) {
.len = -1,
.nodes = NULL,
@@ -39,12 +39,16 @@ void init_zombie_enemy(enemy_t* enemy) {
.finished=false,
.current_dir = Vector2Zero(),
.current_speed = 0,
.currently_moving = 0,
};
// entity
enemy->entity = (entity_t) {
.health = 20,
.anim = anim,
.action_timer = 0,
.tint = WHITE,
.bb_size = 16,
.sprite_size = (Vector2) { 8.0f, 8.0f },
@@ -59,9 +63,8 @@ void init_zombie_enemy(enemy_t* enemy) {
};
// unique functions
enemy->set_vel_func = &pathfind_player;
enemy->move_func = &enemy_follow_path;
enemy->update_anim_func = &update_animation_from_path;
enemy->execute_state = &zombie_execute_state;
enemy->update_state = &zombie_update_state;
}
void new_path(world_t* world, enemy_t* enemy, player_t* player) {
@@ -72,37 +75,9 @@ void new_path(world_t* world, enemy_t* enemy, player_t* player) {
if (enemy->path.len == 0) {
enemy->path.finished = true;
}
// DEBUG:
// printf("PATH: ");
// for (int i = 0; i < enemy->path.len; i++) {
// printf("[%d, %d] ", (int)enemy->path.nodes[i].x, (int)enemy->path.nodes[i].y);
// }
// printf("\n");
}
void zombie_state_machine(world_t* world, enemy_t* enemy, player_t* player) {
switch (enemy->entity.anim.state) {
case ZOMBIE_ENEMY_IDLE:
break;
case ZOMBIE_ENEMY_MOVE:
// check if a new path is needed (player moved, no path exists yet, )
pathfind_player(world, enemy, player);
// move to player, first by path then directly
enemy_follow_path(enemy, player, world);
break;
case ZOMBIE_ENEMY_ATTACK:
break;
}
}
void pathfind_player(world_t* world, enemy_t* enemy, player_t* player) {
int dist = Vector2Length(Vector2Subtract(player->entity.position, enemy->entity.position));
if (dist < 16) {
return;
}
if (enemy->path.nodes) {
float target_pos_change = Vector2Length(Vector2Subtract(get_entity_center(&player->entity), tilemap_to_world_coord(enemy->path.target_pos)));
if (target_pos_change > MAX_TARGET_POS_CHANGE) {
@@ -117,22 +92,76 @@ void pathfind_player(world_t* world, enemy_t* enemy, player_t* player) {
}
}
void update_animation_from_path(enemy_t* enemy, player_t* player) {
// printf("FINISHED: %d, LEN: %d\n", enemy->path.finished, enemy->path.len);
void reset_action_timer(enemy_t* enemy) {
gettimeofday(&enemy->entity.action_timer, NULL);
}
if (enemy->path.current_dir.x != 0) {
// look in direction of path if there is horizontal movement
enemy->entity.anim.flipped = enemy->path.current_dir.x < 0;
}
else {
// when walking only vertiacally look at player
enemy->entity.anim.flipped = player->entity.position.x < enemy->entity.position.x;
}
if (enemy->path.current_speed > 0) {
play_animation(&enemy->entity.anim, ZOMBIE_ENEMY_MOVE);
}
else {
play_animation(&enemy->entity.anim, ZOMBIE_ENEMY_ATTACK);
void zombie_tick_attack(enemy_t* enemy, player_t* player) {
int attack_frequency = 500;
long int time_since = get_time_since_ms(enemy->entity.action_timer);
if (time_since >= attack_frequency) {
gettimeofday(&enemy->entity.action_timer, NULL);
hurt_player(player, 1);
}
}
void zombie_execute_state(world_t* world, enemy_t* enemy, player_t* player) {
// printf("path finished: %d\n", (int)enemy->path.finished);
switch (enemy->entity.anim.state) {
case ZOMBIE_ENEMY_IDLE:
break;
case ZOMBIE_ENEMY_MOVE:
// check if a new path is needed (player moved, no path exists yet, )
pathfind_player(world, enemy, player);
// move to player, first by path then directly
enemy_follow_path(enemy, player, world);
break;
case ZOMBIE_ENEMY_ATTACK:
zombie_tick_attack(enemy, player);
break;
}
}
void zombie_update_state(world_t* world, enemy_t* enemy, player_t* player) {
float dist_player = Vector2Length(Vector2Subtract(player->entity.position, enemy->entity.position));
switch (enemy->entity.anim.state) {
case ZOMBIE_ENEMY_IDLE:
entity_face_player(&enemy->entity, player);
if (dist_player < enemy->vision_radius) {
play_animation(&enemy->entity.anim, ZOMBIE_ENEMY_MOVE);
}
break;
case ZOMBIE_ENEMY_MOVE:
// --- FACE PATH OR PLAYER
if (enemy->path.current_dir.x != 0) {
// look in direction of path if there is horizontal movement
enemy->entity.anim.flipped = enemy->path.current_dir.x < 0;
}
else {
// when walking only vertiacally look at player
enemy->entity.anim.flipped = player->entity.position.x < enemy->entity.position.x;
}
// SWITCH STATE
if (!enemy->path.currently_moving) {
play_animation(&enemy->entity.anim, ZOMBIE_ENEMY_ATTACK);
reset_action_timer(enemy);
}
break;
case ZOMBIE_ENEMY_ATTACK:
entity_face_player(&enemy->entity, player);
// if distance to player too big switch to movement
if (dist_player > 10) {
play_animation(&enemy->entity.anim, ZOMBIE_ENEMY_MOVE);
}
break;
}
}
BIN
View File
Binary file not shown.