Demo update

-added 'dot' function to vector math
-fixed Circle tool rendering
-fixed some potentiall memory leaks
-added 'collision' module which now separates ShootGrid from SpaceInvaders demo
-separate some systems from demos to 'basic' module to better demos functionality sharing
-slow down snake
-added new graphics
-BrickBreaker demo now works (without blocks breaking and with bugged collision detection)

-some bug fixes
This commit is contained in:
Mergul 2020-07-08 22:09:10 +02:00
parent a0efa4e67d
commit 74179b4fc8
14 changed files with 871 additions and 468 deletions

View file

@ -1,11 +1,18 @@
module game_core.basic;
import bubel.ecs.core;
import bubel.ecs.attributes;
import ecs_utils.math.vector;
import gui.attributes;
import ecs_utils.utils;
import app : launcher;
import bindbc.sdl;
struct CLocation
{
mixin ECS.Component;
@ -56,4 +63,193 @@ struct CSelected
mixin ECS.Component;
bool value = false;
}
struct CInput
{
mixin ECS.Component;
}
struct CDamping
{
mixin ECS.Component;
alias value this;
@GUIRange(0,9) byte value = 1;
}
struct CVelocity
{
mixin ECS.Component;
alias value this;
vec2 value = vec2(0,0);
}
struct CVelocityFactor
{
mixin ECS.Component;
alias value this;
vec2 value = vec2(1);
}
struct DampingSystem
{
mixin ECS.System!32;
struct EntitiesData
{
uint length;
const (Entity)[] entity;
@readonly CDamping[] damping;
CVelocity[] velocity;
}
float[20] damp = 0;
bool onBegin()
{
foreach(i;0..20)
{
damp[i] = powf((0.99 - cast(float)i * 0.01),launcher.delta_time*0.1);
}
return true;
}
void onUpdate(EntitiesData data)
{
foreach(i; 0..data.length)
{
data.velocity[i] = data.velocity[i] * damp[data.damping[i]];
}
}
}
struct MoveSystem
{
mixin ECS.System!64;
struct EntitiesData
{
uint length;
CLocation[] location;
@readonly CVelocity[] velocity;
@optional @readonly CVelocityFactor[] vel_factor;
}
void onUpdate(EntitiesData data)
{
if(data.vel_factor)
{
foreach(i; 0..data.length)
{
data.location[i] += data.velocity[i] * data.vel_factor[i] * launcher.delta_time;
}
}
else
{
foreach(i; 0..data.length)
{
data.location[i] += data.velocity[i] * launcher.delta_time;
}
}
}
}
/**
*System is responsible for movement of objects with CInput component.
*In this example every entity has same speed when using movement system.
*/
struct InputMovementSystem
{
mixin ECS.System!32;
vec2 move_vector;
struct EntitiesData
{
uint length;
//read only components can be marked with @readonly attribute or with const expression instead
const (CInput)[] input;
//components are treated as required by default
//CLocation[] locations;
CVelocity[] velocity;
//CTexture[] textures;
}
/**
*onBegin gives opportunity to check keys once and call update on entities only when
*one key is pressed.
*/
bool onBegin()
{
move_vector = vec2(0,0);
if(launcher.getKeyState(SDL_SCANCODE_W))
{
move_vector += vec2(0,1);
}
else if(launcher.getKeyState(SDL_SCANCODE_S))
{
move_vector += vec2(0,-1);
}
if(launcher.getKeyState(SDL_SCANCODE_A))
{
move_vector += vec2(-1,0);
}
else if(launcher.getKeyState(SDL_SCANCODE_D))
{
move_vector += vec2(1,0);
}
if(move_vector.x != 0 ||move_vector.y != 0)
{
move_vector = move_vector / sqrtf(move_vector.x * move_vector.x + move_vector.y * move_vector.y);
return true;
}
//don't call system update because no key pressed
return false;
}
/**
*Update is called multiple times in one "manager.update()" call.
*Number of "onUpdate" calls is count of buffers which must be updated during pass.
*When multithreading is used, number of "onUpdate" calls can be greater due to fact that
*JobSystem can split buffers for better data packing.
*/
void onUpdate(EntitiesData data)
{
/*if(move_vector.x == 0)
{
foreach(i; 0..data.length)
{
data.textures[i].coords = vec4(0*px,80*px,48*px,32*px);
}
//return;
}*/
//move every entity using movement vector
//if(move_vector.x != 0 || move_vector.y != 0)
foreach(i; 0..data.length)
{
data.velocity[i] += move_vector * launcher.delta_time * 0.005;
if(data.velocity[i].x > 0.5)data.velocity[i].x = 0.5;
else if(data.velocity[i].x < -0.5)data.velocity[i].x = -0.5;
if(data.velocity[i].y > 0.5)data.velocity[i].y = 0.5;
else if(data.velocity[i].y < -0.5)data.velocity[i].y = -0.5;
//data.locations[i].x += move_vector.x * launcher.delta_time * 0.25;
//data.locations[i].y += move_vector.y * launcher.delta_time * 0.25;
//if(move_vector.x > 0)data.textures[i].coords = vec4(48*px,80*px,48*px,32*px);
//else data.textures[i].coords = vec4(0*px,80*px,48*px,32*px);
}
/*else
foreach(i; 0..data.length)
{
data.velocity[i] = vec2(0,0);
}*/
}
}

View file

@ -0,0 +1,232 @@
module game_core.collision;
import bubel.ecs.attributes;
import bubel.ecs.core;
import bubel.ecs.std;
import ecs_utils.math.vector;
import game_core.basic;
void registerCollisionModule(EntityManager* manager)
{
manager.registerDependency(ShootGridDependency);
manager.registerComponent!CShootGrid;
manager.registerComponent!CShootGridMask;
manager.registerComponent!CColliderScale;
manager.registerSystem!ShootGridManager(-80);
manager.registerSystem!ShootGridCleaner(-101);
}
enum ShootGridDependency = "ShootGridDependency";
struct CShootGrid
{
mixin ECS.Component;
}
struct CShootGridMask
{
mixin ECS.Component;
alias value this;
ubyte value;
}
struct CColliderScale
{
mixin ECS.Component;
alias value this;
vec2 value = vec2(16,16);
}
struct ShootGrid
{
~this() @nogc nothrow
{
if(nodes)Mallocator.dispose(nodes);
if(masks)Mallocator.dispose(masks);
}
struct Node
{
alias entity this;
EntityID entity;
}
void create(ivec2 nodes_count, vec2 node_size)
{
this.size = nodes_count;
this.node_size = node_size;
inv_node_size = vec2(1.0/node_size.x, 1.0/node_size.y);
nodes = Mallocator.makeArray!Node(nodes_count.x * nodes_count.y);
masks = Mallocator.makeArray!ubyte(nodes.length);
}
void mark(EntityID id, vec2 beg, vec2 end, ubyte mask)
{
ivec2 ibeg = cast(ivec2)(beg * inv_node_size);
ivec2 iend = cast(ivec2)(end * inv_node_size + 0.5);
if(ibeg.x < 0)ibeg.x = 0;
if(ibeg.y < 0)ibeg.y = 0;
if(iend.x > size.x)iend.x = size.x;
if(iend.y > size.y)iend.y = size.y;
foreach(i; ibeg.y .. iend.y)
{
foreach(j; ibeg.x .. iend.x)
{
nodes[i * size.x + j] = id;
masks[i * size.x +j] = mask;
}
}
}
void clear()
{
size_t size = nodes.length * EntityID.sizeof;
memset(nodes.ptr, 0, size);
memset(masks.ptr, 0, masks.length);
}
bool test(out EntityID id, vec2 beg, vec2 end, ubyte mask)
{
ivec2 ibeg = cast(ivec2)(beg * inv_node_size);
ivec2 iend = cast(ivec2)(end * inv_node_size + 0.5);
if(ibeg.x < 0)ibeg.x = 0;
if(ibeg.y < 0)ibeg.y = 0;
if(iend.x > size.x)iend.x = size.x;
if(iend.y > size.y)iend.y = size.y;
foreach(i; ibeg.y .. iend.y)
{
foreach(j; ibeg.x .. iend.x)
{
uint index = i * size.x + j;
if(nodes[index].id != 0)
{
if((masks[index] & mask) == 0)continue;
id = nodes[index];
return true;
}
}
}
return false;
}
bool test(out EntityID id, vec2 pos, ubyte mask)
{
ivec2 ipos = cast(ivec2)(pos * inv_node_size - 0.5);
if(ipos.x < 0)ipos.x = 0;
if(ipos.y < 0)ipos.y = 0;
if(ipos.x >= size.x)ipos.x = size.x - 1;
if(ipos.y >= size.y)ipos.y = size.y - 1;
size_t index = ipos.y * size.x + ipos.x;
if((masks[index] & mask) == 0)return false;
if(nodes[index].id != 0)
{
id = nodes[index];
return true;
}
return false;
}
vec2 inv_node_size;
ivec2 size;
vec2 node_size;
Node[] nodes;
ubyte[] masks;
}
struct ShootGridCleaner
{
mixin ECS.System!1;
struct EntitiesData
{
}
ShootGrid* grid;
bool onBegin()
{
grid = gEM.getSystem!ShootGridManager().grid;
if(grid != null)return true;
else return false;
}
void onUpdate(EntitiesData data)
{
if(grid)grid.clear();
}
}
struct ShootGridManager
{
mixin ECS.System!128;
mixin ECS.WritableDependencies!(ShootGridDependency);
struct EntitiesData
{
uint length;
//uint thread_id;
const (Entity)[] entity;
@readonly CLocation[] locations;
@readonly CShootGrid[] grid_flag;
//@readonly CGuild[] guild;
@optional @readonly CShootGridMask[] mask;
@optional @readonly CScale[] scale;
@optional @readonly CColliderScale[] collider_scale;
}
ShootGrid* grid;
void onCreate()
{
//grid = space_invaders.shoot_grid;
grid = Mallocator.make!ShootGrid;
grid.create(ivec2(80,60), vec2(5,5));
}
void onDestroy()
{
Mallocator.dispose(grid);
}
// bool onBegin()
// {
// //if(!grid)return false;
// //grid.clear();
// return true;
// }
void onUpdate(EntitiesData data)
{
vec2[] scale;
if(data.collider_scale)scale = cast(vec2[])data.collider_scale;
else if(data.scale)scale = cast(vec2[])data.scale;
else return;
if(data.mask is null)
{
foreach(i; 0..data.length)
{
vec2 half_scale = scale[i] * 0.5;
grid.mark(data.entity[i].id, data.locations[i] - half_scale, data.locations[i] + half_scale, ubyte.max);//cast(ubyte)(1 << data.guild[i].guild));
}
}
else foreach(i; 0..data.length)
{
vec2 half_scale = scale[i] * 0.5;
grid.mark(data.entity[i].id, data.locations[i] - half_scale, data.locations[i] + half_scale, data.mask[i]);//cast(ubyte)(1 << data.guild[i].guild));
}
}
}