Weapons with damage :D
This commit is contained in:
parent
8c2d878ead
commit
9a838cd105
6 changed files with 371 additions and 79 deletions
BIN
assets/startersword.png
Normal file
BIN
assets/startersword.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 191 B |
45
entity.c
Normal file
45
entity.c
Normal 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));
|
||||
}
|
200
entry_helpless.c
200
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
159
gjk.c
Normal file
159
gjk.c
Normal 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
28
sprite.c
Normal 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
18
util.c
Normal 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;
|
||||
}
|
Reference in a new issue