Add particles!
This commit is contained in:
BIN
assets/game.png
BIN
assets/game.png
Binary file not shown.
|
Before Width: | Height: | Size: 440 KiB After Width: | Height: | Size: 441 KiB |
File diff suppressed because one or more lines are too long
@@ -8,6 +8,11 @@ ECS_COMPONENT_DECLARE(Resource);
|
||||
|
||||
ECS_COMPONENT_DECLARE(Owner);
|
||||
|
||||
ECS_COMPONENT_DECLARE(EmitterAttachment);
|
||||
ECS_COMPONENT_DECLARE(ParticleEmitter);
|
||||
ECS_COMPONENT_DECLARE(ParticleLayer0);
|
||||
ECS_COMPONENT_DECLARE(ParticleLayer1);
|
||||
|
||||
ECS_COMPONENT_DECLARE(SpatialGridID);
|
||||
ECS_COMPONENT_DECLARE(Position);
|
||||
ECS_COMPONENT_DECLARE(Size);
|
||||
@@ -50,6 +55,11 @@ void initComponentIDs(ecs_world_t *ecs) {
|
||||
|
||||
ECS_COMPONENT_DEFINE(ecs, Owner);
|
||||
|
||||
ECS_COMPONENT_DEFINE(ecs, EmitterAttachment);
|
||||
ECS_COMPONENT_DEFINE(ecs, ParticleEmitter);
|
||||
ECS_COMPONENT_DEFINE(ecs, ParticleLayer0);
|
||||
ECS_COMPONENT_DEFINE(ecs, ParticleLayer1);
|
||||
|
||||
ECS_COMPONENT_DEFINE(ecs, SpatialGridID);
|
||||
ECS_COMPONENT_DEFINE(ecs, Position);
|
||||
ECS_COMPONENT_DEFINE(ecs, Size);
|
||||
|
||||
@@ -37,7 +37,69 @@ typedef struct Owner {
|
||||
} Owner;
|
||||
extern ECS_COMPONENT_DECLARE(Owner);
|
||||
|
||||
/**********************************************************
|
||||
* Particle components
|
||||
*********************************************************/
|
||||
|
||||
typedef struct EmitterAttachment {
|
||||
ecs_entity_t baseEntity;
|
||||
Vector2 offset;
|
||||
} EmitterAttachment;
|
||||
extern ECS_COMPONENT_DECLARE(EmitterAttachment);
|
||||
typedef struct ParticleEmitter {
|
||||
f32 emitterElapsed;
|
||||
f32 emitterLifetime;
|
||||
Vector2 pos;
|
||||
|
||||
struct ParticleEmitterData {
|
||||
// offset
|
||||
Vector2 minOffset, maxOffset;
|
||||
// startVel
|
||||
Vector2 minStartVel, maxStartVel;
|
||||
// endVel
|
||||
Vector2 minEndVel, maxEndVel;
|
||||
// startSize
|
||||
f32 minStartSize, maxStartSize;
|
||||
// endSize
|
||||
f32 minEndSize, maxEndSize;
|
||||
// startRotSpeed
|
||||
f32 minStartRotSpeed, maxStartRotSpeed;
|
||||
// endRotSpeed
|
||||
f32 minEndRotSpeed, maxEndRotSpeed;
|
||||
// startColor
|
||||
Color minStartColor, maxStartColor;
|
||||
// endColor
|
||||
Color minEndColor, maxEndColor;
|
||||
// lifetime
|
||||
f32 minLifetime, maxLifetime;
|
||||
|
||||
BlendMode blend;
|
||||
BzTileID tileID;
|
||||
} data;
|
||||
|
||||
ecs_entity_t targetParticles; // ParticleLayer0 / ParticleLayer1
|
||||
} ParticleEmitter;
|
||||
extern ECS_COMPONENT_DECLARE(ParticleEmitter);
|
||||
|
||||
typedef struct Particle {
|
||||
Vector2 pos;
|
||||
f32 rotation;
|
||||
const Vector2 startVel, endVel;
|
||||
const f32 startSize, endSize;
|
||||
const f32 startRotSpeed, endRotSpeed;
|
||||
const Color startColor, endColor;
|
||||
const BlendMode blend: 8;
|
||||
const BzTileID tileID: 24;
|
||||
const f32 lifetime;
|
||||
f32 elapsed;
|
||||
} Particle;
|
||||
static_assert(sizeof(Particle) == 64, "");
|
||||
|
||||
// 2 Separate components (Before entity, After entity), so we don't need to sort
|
||||
typedef Particle ParticleLayer0, ParticleLayer1;
|
||||
|
||||
extern ECS_COMPONENT_DECLARE(ParticleLayer0);
|
||||
extern ECS_COMPONENT_DECLARE(ParticleLayer1);
|
||||
|
||||
/**********************************************************
|
||||
* Movement components
|
||||
|
||||
@@ -94,6 +94,8 @@ typedef struct Game {
|
||||
f32 elapsed;
|
||||
|
||||
ecs_query_t *drawQuery;
|
||||
ecs_query_t *particles0Query;
|
||||
ecs_query_t *particles1Query;
|
||||
} Game;
|
||||
|
||||
extern ecs_world_t *ECS;
|
||||
|
||||
@@ -34,6 +34,45 @@ static OwnerType getOwnerType(BzTileID tile) {
|
||||
}
|
||||
}
|
||||
|
||||
typedef enum ParticleType {
|
||||
PARTICLE_NONE = -1,
|
||||
PARTICLE_CIRCLE,
|
||||
PARTICLE_SMALL_SQUARE,
|
||||
PARTICLE_SQUARE,
|
||||
PARTICLE_COUNT,
|
||||
} ParticleType;
|
||||
|
||||
static BzTileID getParticleTypeTile(ParticleType type) {
|
||||
switch (type) {
|
||||
case PARTICLE_SQUARE: return 1559;
|
||||
case PARTICLE_CIRCLE: return 1560;
|
||||
case PARTICLE_SMALL_SQUARE: return 1561;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static ParticleType getParticleType(BzTileID tile) {
|
||||
switch (tile) {
|
||||
case 1559:
|
||||
return PARTICLE_SQUARE;
|
||||
case 1560:
|
||||
return PARTICLE_CIRCLE;
|
||||
case 1561:
|
||||
return PARTICLE_SMALL_SQUARE;
|
||||
default:
|
||||
return PARTICLE_COUNT;
|
||||
}
|
||||
}
|
||||
|
||||
static const char *getParticleTypeStr(ParticleType type) {
|
||||
switch (type) {
|
||||
case PARTICLE_SQUARE: return "square";
|
||||
case PARTICLE_CIRCLE: return "circle";
|
||||
case PARTICLE_SMALL_SQUARE: return "small_square";
|
||||
default: return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
typedef enum TerrainType {
|
||||
TERRAIN_NONE = -1,
|
||||
TERRAIN_GOLD_ORE,
|
||||
|
||||
78
game/main.c
78
game/main.c
@@ -16,6 +16,7 @@
|
||||
|
||||
#include "pathfinding.h"
|
||||
#include "sounds.h"
|
||||
#include "entity_factory.h"
|
||||
|
||||
// Constants
|
||||
const char *GAME_DATA_SAVE_PATH = "game_data.ini";
|
||||
@@ -192,6 +193,12 @@ bool init(void *userData) {
|
||||
{ ecs_id(TextureRegion) }
|
||||
},
|
||||
});
|
||||
game->particles0Query = ecs_query(ECS, {
|
||||
.filter.terms = { { ecs_id(ParticleLayer0) } }
|
||||
});
|
||||
game->particles1Query = ecs_query(ECS, {
|
||||
.filter.terms = { { ecs_id(ParticleLayer1) } }
|
||||
});
|
||||
|
||||
{
|
||||
ECS_COMPONENT_DEFINE(ECS, InputState);
|
||||
@@ -354,6 +361,8 @@ void deinit(void *userData) {
|
||||
// Destroy queries
|
||||
ecs_query_fini(input->queries.selected);
|
||||
ecs_query_fini(game->drawQuery);
|
||||
ecs_query_fini(game->particles0Query);
|
||||
ecs_query_fini(game->particles1Query);
|
||||
|
||||
Game gameCopy = *game;
|
||||
InputState inputCopy = *input;
|
||||
@@ -530,6 +539,22 @@ static void renderGame(Game *game, float dt) {
|
||||
DrawTexturePro(tex, draw.src, draw.dst, draw.origin, draw.rotation, c);
|
||||
}
|
||||
bzTileMapClearCollisionLayer(&game->map, COLL_LAYER_TRANSPARENCY);
|
||||
|
||||
// Draw particle layer 1
|
||||
it = ecs_query_iter(ECS, game->particles1Query);
|
||||
ecs_defer_begin(ECS);
|
||||
while (ecs_iter_next(&it)) {
|
||||
Particle *particle = ecs_field(&it, Particle, 1);
|
||||
for (i32 i = 0; i < it.count; i++) {
|
||||
DrawCircleV(particle[i].pos, 2.0f, RED);
|
||||
if (updateParticle(tex, &particle[i], dt))
|
||||
ecs_delete(ECS, it.entities[i]);
|
||||
}
|
||||
}
|
||||
ecs_defer_end(ECS);
|
||||
|
||||
|
||||
#if 0
|
||||
Vector2 target = GetMousePosition();
|
||||
target = GetScreenToWorld2D(target, game->camera);
|
||||
static f32 elapsed = 0;
|
||||
@@ -543,7 +568,6 @@ static void renderGame(Game *game, float dt) {
|
||||
elapsed += dt * 2;
|
||||
elapsed = Clamp(elapsed, 0, 1.0f);
|
||||
attack = false;
|
||||
#if 0
|
||||
if (worker && false) {
|
||||
Position *pos = ecs_get_mut(ECS, worker, Position);
|
||||
DrawCircle(pos->x, pos->y, 2.0f, BLUE);
|
||||
@@ -709,6 +733,58 @@ void imguiRender(float dt, void *userData) {
|
||||
break;
|
||||
}
|
||||
igText("Input state: %s", inputState);
|
||||
if (igCollapsingHeader_TreeNodeFlags("ParticleEditor", 0)) {
|
||||
static ParticleEmitter emitter = {0.0f};
|
||||
emitter.data.tileID = getParticleTypeTile(PARTICLE_CIRCLE);
|
||||
emitter.data.blend = BLEND_ADDITIVE;
|
||||
struct ParticleEmitterData *data = &emitter.data;
|
||||
igSliderFloat2("Pos", &emitter.pos.x, 0.0f, 1000.0f, "%.2f", 0);
|
||||
igSliderFloat2("minOffset", &data->minOffset.x, -50.0f, 50.0f, "%.2f", 0);
|
||||
igSliderFloat2("maxOffset", &data->maxOffset.x, -50.0f, 50.0f, "%.2f", 0);
|
||||
|
||||
igSliderFloat2("minStartVel", &data->minStartVel.x, -50.0f, 50.0f, "%.2f", 0);
|
||||
igSliderFloat2("maxStartVel", &data->maxStartVel.x, -50.0f, 50.0f, "%.2f", 0);
|
||||
|
||||
igSliderFloat2("minEndVel", &data->minEndVel.x, -50.0f, 50.0f, "%.2f", 0);
|
||||
igSliderFloat2("maxEndVel", &data->maxEndVel.x, -50.0f, 50.0f, "%.2f", 0);
|
||||
|
||||
igSliderFloat("minStartSize", &data->minStartSize, -50.0f, 50.0f, "%.2f", 0);
|
||||
igSliderFloat("maxStartSize", &data->maxStartSize, -50.0f, 50.0f, "%.2f", 0);
|
||||
|
||||
igSliderFloat("minEndSize", &data->minEndSize, -50.0f, 50.0f, "%.2f", 0);
|
||||
igSliderFloat("maxEndSize", &data->maxEndSize, -50.0f, 50.0f, "%.2f", 0);
|
||||
|
||||
igSliderFloat("minStartRotSpeed", &data->minStartRotSpeed, -50.0f, 50.0f, "%.2f", 0);
|
||||
igSliderFloat("maxStartRotSpeed", &data->maxStartRotSpeed, -50.0f, 50.0f, "%.2f", 0);
|
||||
|
||||
igSliderFloat("minEndRotSpeed", &data->minEndRotSpeed, -50.0f, 50.0f, "%.2f", 0);
|
||||
igSliderFloat("maxEndRotSpeed", &data->maxEndRotSpeed, -50.0f, 50.0f, "%.2f", 0);
|
||||
|
||||
#define igColor(label, color) \
|
||||
do { \
|
||||
\
|
||||
float cols[4] = { color.r / 255.0f, color.g / 255.0f, \
|
||||
color.b / 255.0f, color.a / 255.0f }; \
|
||||
igColorEdit4(label, cols, 0); \
|
||||
color.r = cols[0] * 255.0f; \
|
||||
color.g = cols[1] * 255.0f; \
|
||||
color.b = cols[2] * 255.0f; \
|
||||
color.a = cols[3] * 255.0f; \
|
||||
} while (0)
|
||||
|
||||
|
||||
igColor("minStartColor", data->minStartColor);
|
||||
igColor("maxStartColor", data->maxStartColor);
|
||||
igColor("minEndColor", data->minEndColor);
|
||||
igColor("maxEndColor", data->maxEndColor);
|
||||
#undef igColor
|
||||
|
||||
igSliderFloat("minLifetime", &data->minLifetime, 0.0f, 6.0f, "%.2f", 0);
|
||||
igSliderFloat("maxLifetime", &data->maxLifetime, 0.0f, 6.0f, "%.2f", 0);
|
||||
Particle particle = spawnParticle(&emitter);
|
||||
ecs_entity_t e = entityCreateEmpty();
|
||||
ecs_set_ptr(ECS, e, ParticleLayer1, &particle);
|
||||
}
|
||||
if (igCollapsingHeader_TreeNodeFlags("Selection", 0)) {
|
||||
switch (input->state) {
|
||||
case INPUT_SELECTED_UNITS:
|
||||
|
||||
@@ -3,6 +3,109 @@
|
||||
#include "../game_state.h"
|
||||
|
||||
#include <raymath.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
void updateParticleEmitter(ecs_iter_t *it) {
|
||||
ParticleEmitter *emitter = ecs_field(it, ParticleEmitter, 1);
|
||||
f32 dt = GetFrameTime();
|
||||
|
||||
for (i32 i = 0; i < it->count; i++) {
|
||||
ecs_entity_t entity = it->entities[i];
|
||||
if (ecs_has(ECS, entity, EmitterAttachment)) {
|
||||
EmitterAttachment attachment = *ecs_get(ECS, entity, EmitterAttachment);
|
||||
if (!ecs_is_alive(ECS, attachment.baseEntity)) {
|
||||
ecs_delete(ECS, entity);
|
||||
continue;
|
||||
}
|
||||
Vector2 pos = *ecs_get(ECS, entity, Position);
|
||||
pos = Vector2Add(pos, attachment.offset);
|
||||
emitter->pos = pos;
|
||||
}
|
||||
|
||||
emitter[i].emitterElapsed += dt;
|
||||
if (emitter[i].emitterElapsed >= emitter[i].emitterLifetime)
|
||||
ecs_delete(ECS, entity);
|
||||
}
|
||||
}
|
||||
|
||||
static inline int lerpInt(int start, int end, int amount)
|
||||
{
|
||||
float result = start + amount*(end - start);
|
||||
|
||||
return result;
|
||||
}
|
||||
bool updateParticle(const Texture2D tex, Particle *particle, f32 dt) {
|
||||
f32 alpha = particle->elapsed / particle->lifetime;
|
||||
|
||||
Vector2 vel = Vector2Lerp(particle->startVel, particle->endVel, alpha);
|
||||
f32 size = Lerp(particle->startSize, particle->endSize, alpha);
|
||||
f32 rot = Lerp(particle->startRotSpeed, particle->endRotSpeed, alpha);
|
||||
Color startC = particle->startColor;
|
||||
Color endC = particle->endColor;
|
||||
Color color = {
|
||||
.r = lerpInt(startC.r, endC.r, alpha),
|
||||
.g = lerpInt(startC.g, endC.g, alpha),
|
||||
.b = lerpInt(startC.b, endC.b, alpha),
|
||||
.a = lerpInt(startC.a, endC.a, alpha)
|
||||
};
|
||||
|
||||
//BeginBlendMode(particle->blend);
|
||||
f32 hSize = size * 0.5f;
|
||||
Vector2 center = particle->pos;
|
||||
Rectangle src = getTextureRect(particle->tileID);
|
||||
src.x += 0.1f;
|
||||
src.y += 0.1f;
|
||||
src.width -= 0.2f;
|
||||
src.height -= 0.2f;
|
||||
DrawTexturePro(tex, src, (Rectangle) {
|
||||
center.x,
|
||||
center.y,
|
||||
size, size
|
||||
}, (Vector2) {hSize, hSize}, rot, color);
|
||||
//EndBlendMode();
|
||||
|
||||
particle->pos = Vector2Add(particle->pos, Vector2Scale(vel, dt));
|
||||
particle->elapsed += dt;
|
||||
return alpha >= 1.0f;
|
||||
}
|
||||
// https://stackoverflow.com/questions/13408990/how-to-generate-random-float-number-in-c
|
||||
static inline f32 randFloatRange(f32 min, f32 max) {
|
||||
float scale = rand() / (float) RAND_MAX; /* [0, 1.0] */
|
||||
return min + scale * ( max - min ); /* [min, max] */
|
||||
}
|
||||
static inline Vector2 randVector2Range(Vector2 from, Vector2 to) {
|
||||
return (Vector2) {
|
||||
randFloatRange(from.x, to.x),
|
||||
randFloatRange(from.y, to.y)
|
||||
};
|
||||
}
|
||||
static inline Color randColorRange(Color from, Color to) {
|
||||
return (Color) {
|
||||
.r = GetRandomValue(from.r, to.r),
|
||||
.g = GetRandomValue(from.g, to.g),
|
||||
.b = GetRandomValue(from.b, to.b),
|
||||
.a = GetRandomValue(from.a, to.a),
|
||||
};
|
||||
}
|
||||
Particle spawnParticle(const ParticleEmitter *emitter) {
|
||||
const struct ParticleEmitterData *data = &emitter->data;
|
||||
return (Particle) {
|
||||
.pos = Vector2Add(emitter->pos, randVector2Range(data->minOffset, data->maxOffset)),
|
||||
.rotation = 0.0f,
|
||||
.startVel = randVector2Range(data->minStartVel, data->maxStartVel),
|
||||
.endVel = randVector2Range(data->minEndVel, data->maxEndVel),
|
||||
.startSize = randFloatRange(data->minStartSize, data->maxStartSize),
|
||||
.endSize = randFloatRange(data->minEndSize, data->maxEndSize),
|
||||
.startRotSpeed = randFloatRange(data->minStartRotSpeed, data->maxStartRotSpeed),
|
||||
.endRotSpeed = randFloatRange(data->minEndRotSpeed, data->maxEndRotSpeed),
|
||||
.startColor = randColorRange(data->minStartColor, data->maxStartColor),
|
||||
.endColor = randColorRange(data->minEndColor, data->maxEndColor),
|
||||
.blend = data->blend,
|
||||
.tileID = data->tileID,
|
||||
.elapsed = 0.0f,
|
||||
.lifetime = randFloatRange(data->minLifetime, data->maxLifetime),
|
||||
};
|
||||
}
|
||||
|
||||
void updateAnimationState(ecs_iter_t *it) {
|
||||
Animation *anim = ecs_field(it, Animation, 1);
|
||||
|
||||
@@ -32,6 +32,14 @@ void setAIBehaviour(ecs_entity_t entity, const BzBTNode *root,
|
||||
* Animation Systems
|
||||
**********************************/
|
||||
|
||||
/*
|
||||
* 1: ParticleEmitter
|
||||
*/
|
||||
void updateParticleEmitter(ecs_iter_t *it);
|
||||
|
||||
bool updateParticle(const Texture2D tex, Particle *particle, f32 dt);
|
||||
|
||||
Particle spawnParticle(const ParticleEmitter *emitter);
|
||||
|
||||
/*
|
||||
* 1: Animation
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 440 KiB After Width: | Height: | Size: 441 KiB |
@@ -2411,6 +2411,36 @@
|
||||
"value":""
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id":1559,
|
||||
"properties":[
|
||||
{
|
||||
"name":"particle",
|
||||
"type":"string",
|
||||
"value":""
|
||||
}],
|
||||
"type":"square"
|
||||
},
|
||||
{
|
||||
"id":1560,
|
||||
"properties":[
|
||||
{
|
||||
"name":"particle",
|
||||
"type":"string",
|
||||
"value":""
|
||||
}],
|
||||
"type":"circle"
|
||||
},
|
||||
{
|
||||
"id":1561,
|
||||
"properties":[
|
||||
{
|
||||
"name":"particle",
|
||||
"type":"string",
|
||||
"value":""
|
||||
}],
|
||||
"type":"small_square"
|
||||
},
|
||||
{
|
||||
"animation":[
|
||||
{
|
||||
|
||||
@@ -1108,6 +1108,21 @@
|
||||
<property name="terrain" value=""/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="1559" type="square">
|
||||
<properties>
|
||||
<property name="particle" value=""/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="1560" type="circle">
|
||||
<properties>
|
||||
<property name="particle" value=""/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="1561" type="small_square">
|
||||
<properties>
|
||||
<property name="particle" value=""/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="1563" type="mage">
|
||||
<properties>
|
||||
<property name="animation" value="idle"/>
|
||||
|
||||
Binary file not shown.
@@ -41,6 +41,7 @@ building_tiles = extract_by_property(all_tiles, "building")
|
||||
entity_tiles = extract_by_property(all_tiles, "entity")
|
||||
item_tiles = extract_by_property(all_tiles, "item")
|
||||
ownership_tiles = extract_by_property(all_tiles, "ownership")
|
||||
particle_tiles = extract_by_property(all_tiles, "particle")
|
||||
|
||||
writer.header_guard_start()
|
||||
script_name = os.path.basename(__file__)
|
||||
@@ -56,10 +57,16 @@ terrain_writer = EnumWriter(writer, terrain_tiles, "terrain")
|
||||
building_writer = EnumWriter(writer, building_tiles, "building")
|
||||
entity_writer = EnumWriter(writer, entity_tiles, "entity")
|
||||
ownership_writer = EnumWriter(writer, ownership_tiles, "owner")
|
||||
particle_write = EnumWriter(writer, particle_tiles, "particle")
|
||||
|
||||
ownership_writer.output_enum()
|
||||
ownership_writer.output_tile_to_enum("getOwnerType")
|
||||
|
||||
particle_write.output_enum()
|
||||
particle_write.output_enum_to_tile("getParticleTypeTile")
|
||||
particle_write.output_tile_to_enum("getParticleType")
|
||||
particle_write.output_enum_to_str("getParticleTypeStr")
|
||||
|
||||
terrain_writer.output_enum()
|
||||
terrain_writer.output_tile_has_anim("terrainHasAnimation")
|
||||
terrain_writer.output_tile_anim_sequence("terrainGetAnimationSequence")
|
||||
|
||||
Reference in New Issue
Block a user