From 9a838cd1055dd2385e40b20a5ffc1f9d8d6a0477 Mon Sep 17 00:00:00 2001 From: Abdulmujeeb Raji Date: Thu, 29 Aug 2024 14:32:56 +0100 Subject: [PATCH] Weapons with damage :D --- assets/startersword.png | Bin 0 -> 191 bytes entity.c | 45 +++++++++ entry_helpless.c | 200 ++++++++++++++++++++++++---------------- gjk.c | 159 ++++++++++++++++++++++++++++++++ sprite.c | 28 ++++++ util.c | 18 ++++ 6 files changed, 371 insertions(+), 79 deletions(-) create mode 100644 assets/startersword.png create mode 100644 entity.c create mode 100644 gjk.c create mode 100644 sprite.c create mode 100644 util.c diff --git a/assets/startersword.png b/assets/startersword.png new file mode 100644 index 0000000000000000000000000000000000000000..b7f438f70f2c8ce1a5b60304ec31699fffe0c419 GIT binary patch literal 191 zcmeAS@N?(olHy`uVBq!ia0vp@Ak4uAB#T}@sR2@q#X;^)4C~IxyaaM8JzX3_G$u~H zWXtE^Akg-(+|26vh2YmRhZI-LaoJwT*1q7SW41A0tUc!&R%&b?Y64 ziX261nFK#7$$!Y>YV;RjW{`B)K4rb5JcB{r;`r4HJ0|3oX)gQ2=entities[i]; + if (!existing_entity->alive) { + entity_found = existing_entity; + break; + } + } + assert(entity_found, "Entity Overflow!"); + entity_found->alive = true; + return entity_found; +} + +void entity_destroy(Entity* entity) { + memset(entity, 0, sizeof(Entity)); +} \ No newline at end of file diff --git a/entry_helpless.c b/entry_helpless.c index d797d77..d5c1c15 100644 --- a/entry_helpless.c +++ b/entry_helpless.c @@ -1,76 +1,40 @@ -typedef struct Sprite { - Gfx_Image* image; - Vector2 size; -} Sprite; - -typedef enum Sprite_ID { - SPRITE_player, - SPRITE_spider, - SPRITE_mutant, - SPRITE_tree, - SPRITE_MAX, -} Sprite_ID; -Sprite sprites[SPRITE_MAX]; - -Sprite* sprite_get(Sprite_ID id) { - if (id >= 0 && id < SPRITE_MAX) { - return &sprites[id]; - } - return &sprites[0]; -} - -typedef enum Entity_Archetype { - ARCH_nil = 0, - ARCH_player = 1, - ARCH_spider = 2, - ARCH_mutant = 3, - ARCH_tree = 4, -} Entity_Archetype; - -typedef struct Entity { - bool alive; - Entity_Archetype arch; - Vector2 pos; - Sprite_ID sprite_id; - bool renderable; -} Entity; - -#define MAX_ENTITIES 1024 -typedef struct World { - Entity entities[MAX_ENTITIES]; -} World; -World* world = 0; - -Entity* entity_create() { - Entity* entity_found = 0; - for (int i = 0; i < MAX_ENTITIES; i++) { - Entity* existing_entity = &world->entities[i]; - if (!existing_entity->alive) { - entity_found = existing_entity; - break; - } - } - assert(entity_found, "Entity Overflow!"); - entity_found->alive = true; - return entity_found; -} - -void entity_destroy(Entity* entity) { - memset(entity, 0, sizeof(Entity)); -} +#include "util.c" +#include "sprite.c" +#include "entity.c" +#include "gjk.c" void player_setup(Entity* en) { en->arch = ARCH_player; en->sprite_id = SPRITE_player; + en->renderable = true; + en->health = 100; } void spider_setup(Entity* en) { en->arch = ARCH_spider; en->sprite_id = SPRITE_spider; + en->renderable = true; + en->health = 30; en->pos = v2(get_random_float32_in_range(-200, 200), get_random_float32_in_range(-200, 200)); + en->hitbox[0] = v2(0, 0); + en->hitbox[1] = v2(0, 7); + en->hitbox[2] = v2(20, 0); + en->hitbox[3] = v2(20, 7); +} + +void startersword_setup(Entity* en) { + en->arch = ARCH_weapon; + en->sprite_id = SPRITE_startersword; + en->renderable = true; + en->damage = 10; + en->hitbox[0] = v2(0, 0); + en->hitbox[1] = v2(0, 13); + en->hitbox[2] = v2(13, 0); + en->hitbox[3] = v2(13, 13); } int entry(int argc, char **argv) { + seed_for_random = rdtsc(); // This is how we (optionally) configure the window. // You can set this at any point in the runtime and it will @@ -82,28 +46,28 @@ int entry(int argc, char **argv) { window.scaled_height = 720; window.clear_color = hex_to_rgba(0x222034ff); window.allow_resize = false; - window.fullscreen = true; + window.fullscreen = false; world = alloc(get_heap_allocator(), sizeof(World)); - Gfx_Image *player_image = load_image_from_disk(STR("assets/player.png"), get_heap_allocator()); - assert(player_image, "Player image not found D:"); - sprites[SPRITE_player].image = player_image; - sprites[SPRITE_player].size = v2((float32)player_image->width, (float32)player_image->height); - - Gfx_Image *spider_image = load_image_from_disk(STR("assets/spider.png"), get_heap_allocator()); - assert(spider_image, "Spider image not found D:"); - sprites[SPRITE_spider].image = spider_image; - sprites[SPRITE_spider].size = v2((float32)spider_image->width, (float32)spider_image->height); + sprite_load(STR("assets/player.png"), SPRITE_player); + sprite_load(STR("assets/spider.png"), SPRITE_spider); + sprite_load(STR("assets/startersword.png"), SPRITE_startersword); Entity* player = entity_create(); player_setup(player); + Entity* startersword = entity_create(); + startersword_setup(startersword); + for (int i = 0; i < 10; i++) { Entity* spider = entity_create(); spider_setup(spider); } + float64 zoom = 5.3; + Vector2 camera_pos = v2(0, 0); + float64 last_time = os_get_elapsed_seconds(); while (!window.should_close) { float64 now_time = os_get_elapsed_seconds(); @@ -112,8 +76,15 @@ int entry(int argc, char **argv) { draw_frame.projection = m4_make_orthographic_projection(window.width * -0.5, window.width * 0.5, window.height * -0.5, window.height * 0.5, -1, 10); - float64 zoom = 5.3; - draw_frame.camera_xform = m4_make_scale(v3(1.0/zoom, 1.0/zoom, 1.0)); + // :camera + { + Vector2 target_pos = player->pos; + animate_to_target_v2(&camera_pos, target_pos, delta_time, 7.5f); + + draw_frame.camera_xform = m4_identity(); + draw_frame.camera_xform = m4_translate(draw_frame.camera_xform, v3(camera_pos.x, camera_pos.y, 0.0)); + draw_frame.camera_xform = m4_scale(draw_frame.camera_xform, v3(1.0/zoom, 1.0/zoom, 1.0)); + } if (is_key_just_pressed('F')) { window.fullscreen = !window.fullscreen; @@ -124,34 +95,105 @@ int entry(int argc, char **argv) { } Vector2 move_axis = v2(0.0, 0.0); - if (is_key_down('W')) { + if (is_key_down(KEY_ARROW_UP)) { move_axis.y += 1.0; } - if (is_key_down('A')) { + if (is_key_down(KEY_ARROW_LEFT)) { move_axis.x -= 1.0; } - if (is_key_down('S')) { + if (is_key_down(KEY_ARROW_DOWN)) { move_axis.y -= 1.0; } - if (is_key_down('D')) { + if (is_key_down(KEY_ARROW_RIGHT)) { move_axis.x += 1.0; } move_axis = v2_normalize(move_axis); - player->pos = v2_add(player->pos, v2_mulf(move_axis, 30 * delta_time)); + player->pos = v2_add(player->pos, v2_mulf(move_axis, 75 * delta_time)); + startersword->weapon_owner_pos = player->pos; + if (move_axis.x != 0 || move_axis.y != 0) { + startersword->weapon_dir = move_axis; + } + // :generic simulation (i.e. not a specific boss or player) + for (int i = 0; i < MAX_ENTITIES; i++) { + Entity* en = &world->entities[i]; + if (en->alive) { + switch (en->arch) { + case ARCH_weapon: { + if (is_key_just_pressed('Z')) { + for (int i = 0; i < MAX_ENTITIES; i++) { + Entity* other_en = &world->entities[i]; + + Vector2 hitbox[4] = { + v2_add(en->hitbox[0], en->pos), + v2_add(en->hitbox[1], en->pos), + v2_add(en->hitbox[2], en->pos), + v2_add(en->hitbox[3], en->pos), + }; + + Vector2 other_hitbox[4] = { + v2_add(other_en->hitbox[0], other_en->pos), + v2_add(other_en->hitbox[1], other_en->pos), + v2_add(other_en->hitbox[2], other_en->pos), + v2_add(other_en->hitbox[3], other_en->pos), + }; + + if (other_en->alive && gjk(hitbox, 4, other_hitbox, 4)) { + other_en->health -= en->damage; + } + } + } + } break; + + default: { + if (en->health <= 0) { + en->alive = false; + } + } break; + } + } + } + + // :rendering + // TODO add a layer system, so that you place render instructions anywhere for (int i = 0; i < MAX_ENTITIES; i++) { Entity* en = &world->entities[i]; if (en->alive && en->renderable) { switch (en->arch) { + case ARCH_weapon: { + Sprite* sprite = sprite_get(en->sprite_id); + + // move the weapon away from its owner + Vector2 dist_from_owner = v2_mulf(sprite->size, 1.25f); + dist_from_owner.x = max(20, dist_from_owner.x); + dist_from_owner.y = max(20, dist_from_owner.y); + + Vector2 target_pos = v2_add(en->weapon_owner_pos, v2_mul(en->weapon_dir, dist_from_owner)); + animate_to_target_v2(&(en->pos), target_pos, delta_time, 30.0f); + + float32 target_rads = atan2(en->weapon_dir.x, en->weapon_dir.y) - 45 * RAD_PER_DEG; + animate_to_target_f32(&(en->weapon_rads), target_rads, delta_time, 15.0f); + + Matrix4 xform = m4_scalar(1.0); + xform = m4_translate(xform, v3(en->pos.x, en->pos.y, 0)); + xform = m4_rotate_z(xform, en->weapon_rads); + + xform = m4_translate(xform, v3(sprite->size.x * -0.5, sprite->size.y * -0.5, 0)); + draw_image_xform(sprite->image, xform, sprite->size, COLOR_WHITE); + + } break; + default: { Sprite* sprite = sprite_get(en->sprite_id); Matrix4 xform = m4_scalar(1.0); xform = m4_translate(xform, v3(en->pos.x, en->pos.y, 0)); - xform = m4_translate(xform, v3(sprite->size.x * -0.5, 0, 0)); + + xform = m4_translate(xform, v3(sprite->size.x * -0.5, sprite->size.y * -0.5, 0)); draw_image_xform(sprite->image, xform, sprite->size, COLOR_WHITE); - } + + } break; } } } diff --git a/gjk.c b/gjk.c new file mode 100644 index 0000000..4f140bc --- /dev/null +++ b/gjk.c @@ -0,0 +1,159 @@ +// Created by Igor Kroitor on 29/12/15. + +//----------------------------------------------------------------------------- +// Gilbert-Johnson-Keerthi (GJK) collision detection algorithm in 2D +// http://www.dyn4j.org/2010/04/gjk-gilbert-johnson-keerthi/ +// http://mollyrocket.com/849 +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Basic vector arithmetic operations + +Vector2 v2_negate(Vector2 v) { v.x = -v.x; v.y = -v.y; return v; } +Vector2 v2_perpendicular(Vector2 v) { Vector2 p = { v.y, -v.x }; return p; } +float v2_squared_length(Vector2 v) { return v.x * v.x + v.y * v.y; } + +//----------------------------------------------------------------------------- +// Triple product expansion is used to calculate perpendicular normal vectors +// which kinda 'prefer' pointing towards the Origin in Minkowski space + +Vector2 v2_triple_product(Vector2 a, Vector2 b, Vector2 c) { + + Vector2 r; + + float ac = a.x * c.x + a.y * c.y; // perform a.dot(c) + float bc = b.x * c.x + b.y * c.y; // perform b.dot(c) + + // perform b * a.dot(c) - a * b.dot(c) + r.x = b.x * ac - a.x * bc; + r.y = b.y * ac - a.y * bc; + return r; +} + +//----------------------------------------------------------------------------- +// This is to compute average center (roughly). It might be different from +// Center of Gravity, especially for bodies with nonuniform density, +// but this is ok as initial direction of simplex search in GJK. + +Vector2 average_point (const Vector2 * vertices, size_t count) { + Vector2 avg = { 0.f, 0.f }; + for (size_t i = 0; i < count; i++) { + avg.x += vertices[i].x; + avg.y += vertices[i].y; + } + avg.x /= count; + avg.y /= count; + return avg; +} + +//----------------------------------------------------------------------------- +// Get furthest vertex along a certain direction + +size_t furthest_point_index (const Vector2 * vertices, size_t count, Vector2 d) { + + float maxProduct = v2_dot (d, vertices[0]); + size_t index = 0; + for (size_t i = 1; i < count; i++) { + float product = v2_dot (d, vertices[i]); + if (product > maxProduct) { + maxProduct = product; + index = i; + } + } + return index; +} + +//----------------------------------------------------------------------------- +// Minkowski sum support function for GJK + +Vector2 support (const Vector2 * vertices1, size_t count1, + const Vector2 * vertices2, size_t count2, Vector2 d) { + + // get furthest point of first body along an arbitrary direction + size_t i = furthest_point_index (vertices1, count1, d); + + // get furthest point of second body along the opposite direction + size_t j = furthest_point_index (vertices2, count2, v2_negate (d)); + + // subtract (Minkowski sum) the two points to see if bodies 'overlap' + return v2_sub (vertices1[i], vertices2[j]); +} + +//----------------------------------------------------------------------------- +// The GJK yes/no test + +int iter_count = 0; + +bool gjk (const Vector2 * vertices1, size_t count1, + const Vector2 * vertices2, size_t count2) { + + size_t index = 0; // index of current vertex of simplex + Vector2 a, b, c, d, ao, ab, ac, abperp, acperp, simplex[3]; + + Vector2 position1 = average_point (vertices1, count1); // not a CoG but + Vector2 position2 = average_point (vertices2, count2); // it's ok for GJK ) + + // initial direction from the center of 1st body to the center of 2nd body + d = v2_sub (position1, position2); + + // if initial direction is zero – set it to any arbitrary axis (we choose X) + if ((d.x == 0) && (d.y == 0)) + d.x = 1.f; + + // set the first support as initial point of the new simplex + a = simplex[0] = support (vertices1, count1, vertices2, count2, d); + + if (v2_dot (a, d) <= 0) + return false; // no collision + + d = v2_negate (a); // The next search direction is always towards the origin, so the next search direction is v2_negate(a) + + while (1) { + iter_count++; + + a = simplex[++index] = support (vertices1, count1, vertices2, count2, d); + + if (v2_dot (a, d) <= 0) + return 0; // no collision + + ao = v2_negate (a); // from point A to Origin is just negative A + + // simplex has 2 points (a line segment, not a triangle yet) + if (index < 2) { + b = simplex[0]; + ab = v2_sub (b, a); // from point A to B + d = v2_triple_product (ab, ao, ab); // normal to AB towards Origin + if (v2_squared_length (d) == 0) + d = v2_perpendicular (ab); + continue; // skip to next iteration + } + + b = simplex[1]; + c = simplex[0]; + ab = v2_sub (b, a); // from point A to B + ac = v2_sub (c, a); // from point A to C + + acperp = v2_triple_product (ab, ac, ac); + + if (v2_dot (acperp, ao) >= 0) { + + d = acperp; // new direction is normal to AC towards Origin + + } else { + + abperp = v2_triple_product (ac, ab, ab); + + if (v2_dot (abperp, ao) < 0) + return true; // collision + + simplex[0] = simplex[1]; // swap first element (point C) + + d = abperp; // new direction is normal to AB towards Origin + } + + simplex[1] = simplex[2]; // swap element in the middle (point B) + --index; + } + + return false; +} \ No newline at end of file diff --git a/sprite.c b/sprite.c new file mode 100644 index 0000000..18cfbe6 --- /dev/null +++ b/sprite.c @@ -0,0 +1,28 @@ +typedef struct Sprite { + Gfx_Image* image; + Vector2 size; +} Sprite; + +typedef enum Sprite_ID { + SPRITE_player, + SPRITE_spider, + SPRITE_mutant, + SPRITE_tree, + SPRITE_startersword, + SPRITE_MAX, +} Sprite_ID; +Sprite sprites[SPRITE_MAX]; + +void sprite_load(string path, Sprite_ID id) { + Gfx_Image *img = load_image_from_disk(path, get_heap_allocator()); + assert(img, "Image not found D:"); + sprites[id].image = img; + sprites[id].size = v2((float32)img->width, (float32)img->height); +} + +Sprite* sprite_get(Sprite_ID id) { + if (id >= 0 && id < SPRITE_MAX) { + return &sprites[id]; + } + return &sprites[0]; +} \ No newline at end of file diff --git a/util.c b/util.c new file mode 100644 index 0000000..edc225d --- /dev/null +++ b/util.c @@ -0,0 +1,18 @@ +bool almost_equals(float a, float b, float epsilon) { + return fabs(a - b) <= epsilon; +} + +bool animate_to_target_f32(float* value, float target, float delta_time, float rate) { + *value += (target - *value) * (1.0 - pow(2.0f, -rate * delta_time)); + if (almost_equals(*value, target, 0.001f)) { + *value = target; + return true; // finished animation + } + return false; +} + +bool animate_to_target_v2(Vector2* value, Vector2 target, float delta_time, float rate) { + bool result_x = animate_to_target_f32(&(value->x), target.x, delta_time, rate); + bool result_y = animate_to_target_f32(&(value->y), target.y, delta_time, rate); + return result_x && result_y; +} \ No newline at end of file