Weapons with damage :D

This commit is contained in:
Abdulmujeeb Raji 2024-08-29 14:32:56 +01:00
parent 8c2d878ead
commit 9a838cd105
6 changed files with 371 additions and 79 deletions

BIN
assets/startersword.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 B

45
entity.c Normal file
View file

@ -0,0 +1,45 @@
typedef enum Entity_Archetype {
ARCH_nil = 0,
ARCH_player = 1,
ARCH_spider = 2,
ARCH_mutant = 3,
ARCH_tree = 4,
ARCH_weapon = 5,
} Entity_Archetype;
typedef struct Entity {
bool alive;
bool renderable;
Entity_Archetype arch;
Vector2 pos;
Sprite_ID sprite_id;
float32 health, damage;
Vector2 hitbox[4];
Vector2 weapon_owner_pos;
Vector2 weapon_dir;
float32 weapon_rads;
} 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));
}

View file

@ -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;
}
}
}

159
gjk.c Normal file
View file

@ -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;
}

28
sprite.c Normal file
View file

@ -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];
}

18
util.c Normal file
View file

@ -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;
}