Skip to content

Instantly share code, notes, and snippets.

@elamhut
Last active July 8, 2026 07:21
Show Gist options
  • Select an option

  • Save elamhut/21fed18483148eb753eca33bf38a4da7 to your computer and use it in GitHub Desktop.

Select an option

Save elamhut/21fed18483148eb753eca33bf38a4da7 to your computer and use it in GitHub Desktop.
Ryan Fleury's example code for Fat Structs
typedef struct Entity Entity;
struct Entity
{
// NOTE(rjf): Properties/Generation
u64 properties[(EntityProperty_MAX+63)/64];
u64 generation;
String8 tag;
// NOTE(rjf): Tree Data
Entity *parent;
Entity *next;
Entity *prev;
Entity *first_child;
Entity *last_child;
// NOTE(rjf): Timed Removal
f32 seconds_to_removal;
// NOTE(rjf): General Rendering Info
Vec4 inverse_tint; // Additive inverse to zero-initialize to full opacity + white.
f32 hurt_transition;
// NOTE(rjf): Sprite
R_Texture *sprite_texture;
Rect2D sprite_source;
f32 sprite_tilt;
// NOTE(rjf): Rendered Item
ItemHandle render_item;
f32 render_item_tilt;
// NOTE(rjf): Rendered Equip Slot
EntityHandle render_equip_slot_entity;
EquipSlot render_equip_slot;
f32 render_equip_slot_tilt;
// NOTE(rjf): Velocity Bob
Vec3 velocity_bob_vector;
f32 velocity_bob_size;
f32 velocity_bob_speed_factor;
f32 velocity_bob_sin_pos;
f32 velocity_bob_transition;
// NOTE(rjf): Time Bob
Vec3 time_bob_vector;
f32 time_bob_size;
f32 time_bob_speed_factor;
f32 time_bob_sin_pos;
// NOTE(rjf): Time Squish
f32 time_squish_size;
f32 time_squish_speed_factor;
f32 time_squish_sin_pos;
// NOTE(rjf): Position, Physics, Size info
Vec3 position;
Body body;
Vec3 offset_from_parent;
Vec3 position_snap;
Vec3 position_snap_target;
f32 position_snap_rate;
f32 position_snap_decay_rate;
// NOTE(rjf): Material Properties
f32 heat;
// NOTE(rjf): Particle Generation
ParticleGenParameters particle_gen;
f32 seconds_to_next_particle_generation;
// NOTE(rjf): Hit-Particle Info
ParticleKind hit_particle_kind;
// NOTE(rjf): Trigger
Trigger trigger;
// NOTE(rjf): Inventory/Equipment
u64 item_count;
Item *first_item;
Item *last_item;
Item *equips[EquipSlot_MAX];
};
// NOTE(rjf): Basic Type Conversions
internal String8 StringFromTriggerKind(TriggerKind kind);
// NOTE(rjf): Entity Introspection
internal Entity *RootFromEntity(Entity *entity);
internal Entity *ChildFromTag(Entity *parent, String8 tag);
internal Vec3 PositionFromEntity(Entity *entity);
internal Vec3 VelocityFromEntity(Entity *entity);
internal f32 MassFromEntity(Entity *entity);
internal Vec4 TintFromEntity(Entity *entity);
internal f32 SquishFromEntity(Entity *entity);
internal void SnapEntityPosition(Entity *entity, Vec3 offset, f32 rate, f32 decay_rate);
internal b32 EntityHasProperty(Entity *entity, EntityProperty property);
// NOTE(rjf): Entity Constructor Helpers
internal Entity *SetEntityProperty(Entity *entity, EntityProperty property);
internal Entity *RemoveEntityProperty(Entity *entity, EntityProperty property);
internal Entity *SetEntityBody(Entity *entity, Body body);
internal Entity *SetEntityTimedRemoval(Entity *entity, f32 seconds);
internal Entity *SetEntitySprite(Entity *entity, R_Texture *texture, Rect2D source, f32 tilt);
internal Entity *SetEntityRenderItem(Entity *entity, ItemHandle item, f32 tilt);
internal Entity *SetEntityRenderEquipSlot(Entity *entity, EntityHandle equip_entity, EquipSlot slot, f32 tilt);
internal Entity *SetEntityVelocityBob(Entity *entity, Vec3 bob_vector, f32 bob_size, f32 bob_speed);
internal Entity *SetEntityTimeBob(Entity *entity, Vec3 bob_vector, f32 bob_size, f32 bob_speed);
internal Entity *SetEntityTimeSquish(Entity *entity, f32 squish_size, f32 squish_speed);
internal Entity *SetEntityParticleGeneration(Entity *entity, ParticleGenParameters params);
internal Entity *SetEntityTrigger(Entity *entity, TriggerKind kind, Input input, Shape shape, ItemKind item_kind, Item *item, EntityHandle source_entity, AttackFlags attack_flags);
internal Entity *SetEntityTrigger_Container(Entity *entity, TriggerKind kind, Input input, Shape shape);
internal Entity *SetEntityTrigger_Hit(Entity *entity, Shape shape, AttackFlags attack_flags);
internal Entity *SetEntityTrigger_ItemKind(Entity *entity, Input input, Shape shape, ItemKind item_kind, EntityHandle source_entity);
internal Entity *SetEntityTrigger_Item(Entity *entity, Input input, Shape shape, Item *item, EntityHandle source_entity);
internal Entity *SetEntityHitParticles(Entity *entity, ParticleKind kind);
internal Entity *TagEntity(Entity *entity, String8 string);
internal void PushEntityChild(Entity *parent, Entity *child, Vec3 offset);
// NOTE(rjf): Handles
internal EntityHandle NullEntityHandle(void);
internal b32 EntityHandleIsNull(EntityHandle handle);
internal EntityHandle HandleFromEntityIndexAndGeneration(u64 index, u64 generation);
internal u64 EntityIndexFromHandle(EntityHandle handle);
// NOTE(rjf): Items
internal void PushEntityItem(Entity *entity, Item *item);
internal void InsertEntityItem(Entity *entity, Item *item, Item *prev_item);
internal void RemoveEntityItem(Entity *entity, Item *item);
internal void EquipEntityItem(Entity *entity, EquipSlot slot, Item *item);
internal Entity *
G_MakeHumanoid(Vec3 position,
int head_style,
int hand_style,
int body_style,
int hair_style,
int hair_color,
ParticleKind bleed_style)
{
Rect2D head_source = ArtSource(ArtAnnotation_Heads, R2D(16*head_style, 0, 16, 16));
Rect2D hand_source = ArtSource(ArtAnnotation_Hands, R2D(16*hand_style, 0, 16, 16));
Rect2D body_source = ArtSource(ArtAnnotation_Bodies, R2D(16*body_style, 0, 16, 16));
Rect2D hair_source = ArtSource(ArtAnnotation_Hair, R2D(16*hair_style,
16*hair_color,
16, 16));
Entity *base = G_AllocateEntity();
base->position = position;
SetEntityProperty(base, EntityProperty_AllowTriggers);
SetEntityBody(base, MakeBody(MakeShape(ShapeKind_Sphere, V3(0.5f)), 0.8f, 0.2f));
Entity *body = G_AllocateEntity();
{
SetEntitySprite(body, &game-;>art, body_source, 1.f);
SetEntityProperty(body, EntityProperty_AllowTriggers);
SetEntityVelocityBob(body, V3(0, 0, 1), 0.02f, 1.f);
SetEntityHitParticles(body, bleed_style);
PushEntityChild(base, body, V3(0, +0.f, 0.f));
}
Entity *head = G_AllocateEntity();
{
TagEntity(head, S8Lit("Head"));
SetEntitySprite(head, &game-;>art, head_source, 0.8f);
SetEntityVelocityBob(head, V3(0, 0, 1), 0.02f, 1.f);
SetEntityTimeBob(head, V3(0, 0, 1), 0.02f, 4.f);
SetEntityProperty(head, EntityProperty_SnapOnHit);
SetEntityHitParticles(head, bleed_style);
PushEntityChild(body, head, V3(0, 0.05f, 0.6f));
}
Entity *hair = G_AllocateEntity();
{
SetEntitySprite(hair, &game-;>art, hair_source, 0.8f);
PushEntityChild(head, hair, V3(0, 0, 0));
}
Entity *head_equip = G_AllocateEntity();
{
TagEntity(head_equip, S8Lit("Head Equip"));
SetEntityRenderEquipSlot(head_equip, G_HandleFromEntity(base), EquipSlot_Head, 0.8f);
PushEntityChild(head, head_equip, V3(0, 0.01f, 0.01f));
}
Entity *left_hand = G_AllocateEntity();
{
TagEntity(left_hand, S8Lit("Left Hand"));
SetEntitySprite(left_hand, &game-;>art, hand_source, 1.f);
SetEntityVelocityBob(left_hand, V3(0, 1, 0), 0.1f, 0.5f);
SetEntityTimeBob(left_hand, V3(0, 0, -1), 0.015f, 4.f);
SetEntityProperty(left_hand, EntityProperty_SnapOnHit);
SetEntityHitParticles(left_hand, bleed_style);
PushEntityChild(body, left_hand, V3(-0.3f, 0.01f, 0));
}
Entity *left_hand_equip = G_AllocateEntity();
{
TagEntity(left_hand_equip, S8Lit("Left Hand Equip"));
SetEntityRenderEquipSlot(left_hand_equip, G_HandleFromEntity(base), EquipSlot_LeftHand, 0.5f);
PushEntityChild(left_hand, left_hand_equip, V3(0, 0.2f, 0.1f));
}
Entity *right_hand = G_AllocateEntity();
{
TagEntity(right_hand, S8Lit("Right Hand"));
SetEntitySprite(right_hand, &game-;>art, hand_source, 1.f);
SetEntityVelocityBob(right_hand, V3(0, -1, 0), 0.1f, 0.5f);
SetEntityTimeBob(right_hand, V3(0, 0, -1), 0.015f, 4.f);
SetEntityProperty(right_hand, EntityProperty_SnapOnHit);
SetEntityHitParticles(right_hand, bleed_style);
PushEntityChild(body, right_hand, V3(+0.3f, 0.01f, 0));
}
Entity *right_hand_equip = G_AllocateEntity();
{
TagEntity(right_hand_equip, S8Lit("Right Hand Equip"));
SetEntityRenderEquipSlot(right_hand_equip, G_HandleFromEntity(base), EquipSlot_RightHand, 0.5f);
PushEntityChild(right_hand, right_hand_equip, V3(0, 0.2f, 0.1f));
}
return base;
}
internal Entity *
G_MakeSkeleton(Vec3 position)
{
return G_MakeHumanoid(position, 4, 4, 0, 0, 0, ParticleKind_Bone);
}
internal Entity *
G_MakeMage(Vec3 position)
{
return G_MakeHumanoid(position, 5, 5, 2, 0, 0, ParticleKind_Blood);
}
internal Entity *
G_MakeJelly(Vec3 position)
{
Entity *base = G_AllocateEntity();
base->position = position;
SetEntityProperty(base, EntityProperty_AllowTriggers);
SetEntityBody(base, MakeBody(MakeShape(ShapeKind_Sphere, V3(0.4f)), 0.9f, 1.f));
SetEntitySprite(base, &game-;>art, ArtSource(ArtAnnotation_Heads, R2D(48, 0, 16, 16)), 0.8f);
SetEntityTimeSquish(base, 0.05f, 3.f);
SetEntityTrigger_Hit(base, ScaleShape(base->body.shape, 1.1f), AttackFlag_Clobber);
SetEntityHitParticles(base, ParticleKind_Jelly);
return base;
}
internal Entity *
G_MakeItemCollectible(Vec3 position, ItemKind item_kind)
{
Entity *base = G_AllocateEntity();
base->position = position;
SetEntityBody(base, MakeBody(MakeShape(ShapeKind_Sphere, V3(0.5f)), 0.f, 0.2f));
SetEntitySprite(base, &game-;>art,
ArtSource(ArtAnnotation_Items, item_kind_metadata[item_kind].source),
0.4f);
SetEntityTimeBob(base, V3(0, 0, 1), 0.2f, 3.f);
SetEntityTrigger_ItemKind(base, 0, ScaleShape(base->body.shape, 1.1f), item_kind, NullEntityHandle());
SetEntityProperty(base, EntityProperty_DieOnTrigger);
return base;
}
internal Entity *
G_MakeItemCollectibleFromItem(Vec3 position, Item *item, Entity *source_entity)
{
Entity *base = G_AllocateEntity();
base->position = position;
SetEntityBody(base, MakeBody(MakeShape(ShapeKind_Sphere, V3(0.5f)), 0.2f, 0.2f));
SetEntityRenderItem(base, HandleFromItem(item), 0.4f);
SetEntityTimeBob(base, V3(0, 0, 1), 0.2f, 3.f);
SetEntityTrigger_Item(base, 0, ScaleShape(base->body.shape, 1.1f), item, G_HandleFromEntity(source_entity));
SetEntityProperty(base, EntityProperty_DieOnTrigger);
RemoveEntityItem(item->entity, item);
PushEntityItem(base, item);
return base;
}
internal Entity *
G_MakeTreeLeaf(f32 height, b32 big)
{
Entity *entity = G_AllocateEntity();
SetEntityProperty(entity, EntityProperty_SnapOnHit);
SetEntityHitParticles(entity, ParticleKind_Leaf);
f32 tilt = 0.3f;
if(big)
{
SetEntitySprite(entity, &game-;>art,
ArtSource(ArtAnnotation_Trees, R2D(16, 0, 32, 32)),
tilt);
}
else
{
SetEntitySprite(entity, &game-;>art,
ArtSource(ArtAnnotation_Trees, R2D(48+16*((int)RandomF32(0, 4)), 0, 16, 16)),
tilt);
}
SetEntityTimeBob(entity, V3(1, 0, 0), 0.1f, 0.25f+RandomF32(-0.08f, +0.08f));
if(height < 1.f)
{
int number_of_children = (int)RandomF32(0, 4);
for(int i = 0; i < number_of_children; i += 1)
{
Entity *sub_leaf = G_MakeTreeLeaf(height+0.5f, 0);
PushEntityChild(entity, sub_leaf, V3(RandomF32(-0.6f, +0.6f),
RandomF32(-0.6f, +0.6f)-0.01f,
0.3f));
}
}
return entity;
}
internal Entity *
G_MakeTree(Vec3 position)
{
Entity *base = G_AllocateEntity();
base->position = position;
SetEntityProperty(base, EntityProperty_Static);
SetEntityProperty(base, EntityProperty_AllowTriggers);
SetEntityHitParticles(base, ParticleKind_Wood);
SetEntityBody(base, MakeBody(MakeShape(ShapeKind_Sphere, V3(0.5f)), INFINITY, 0.2f));
Entity *stump = G_AllocateEntity();
SetEntitySprite(stump, &game-;>art, ArtSource(ArtAnnotation_Trees, R2D(0, 0, 16, 32)), 0.7f);
int number_of_big_leaves = (int)RandomF32(2, 6);
for(int i = 0; i < number_of_big_leaves; i += 1)
{
PushEntityChild(stump, G_MakeTreeLeaf(0.f, 1), V3(RandomF32(-1.f, +1.f),
RandomF32(-1.f, +1.f)-0.2f,
1.2f));
}
PushEntityChild(base, stump, V3(0, 0, 0.3f));
return base;
}
internal Entity *
G_MakeCampFire(Vec3 position)
{
Entity *base = G_AllocateEntity();
base->position = position;
SetEntityBody(base, MakeBody(MakeShape(ShapeKind_Sphere, V3(0.5f)), 4.f, 0.2f));
SetEntitySprite(base, &game-;>art, ArtSource(ArtAnnotation_Props, R2D(0, 0, 16, 16)), 0.4f);
G_SetEntityOnFire(base, -1.f);
SetEntityProperty(base, EntityProperty_AllowTriggers);
return base;
}
internal Entity *
G_MakeWorkbenchLeg(void)
{
Entity *leg = G_AllocateEntity();
SetEntityBody(leg, MakeBody(MakeShape(ShapeKind_Sphere, V3(0.5f)), 0.f, 0.2f));
SetEntitySprite(leg, &game-;>art, ArtSource(ArtAnnotation_Props, R2D(16, 0, 16, 16)), 0.8f);
SetEntityProperty(leg, EntityProperty_Static);
return leg;
}
internal Entity *
G_MakeWorkbench(Vec3 position)
{
Entity *base = G_AllocateEntity();
base->position = position;
SetEntityBody(base, MakeBody(MakeShape(ShapeKind_Cuboid, V3(1.3f, 0.3f, 0.5f)), INFINITY, 0.8f));
SetEntityTrigger_Container(base, TriggerKind_Craft, Input_Interact,
ScaleShape(base->body.shape, 1.2f));
SetEntityProperty(base, EntityProperty_Static);
Entity *table = G_AllocateEntity();
{
SetEntitySprite(table, &game-;>art, ArtSource(ArtAnnotation_Props, R2D(32, 0, 48, 16)), 0.3f);
PushEntityChild(table, G_MakeWorkbenchLeg(), V3(-1.3f, -0.3f, -0.4f));
PushEntityChild(table, G_MakeWorkbenchLeg(), V3(+1.3f, -0.3f, -0.4f));
PushEntityChild(table, G_MakeWorkbenchLeg(), V3(-1.3f, +0.3f, -0.6f));
PushEntityChild(table, G_MakeWorkbenchLeg(), V3(+1.3f, +0.3f, -0.6f));
PushEntityChild(base, table, V3(0, 0, 0.1f));
}
Entity *saw = G_AllocateEntity();
SetEntitySprite(saw, &game-;>art, ArtSource(ArtAnnotation_Props, R2D(80, 0, 16, 16)), 0.4f);
PushEntityChild(table, saw, V3(-1.f, 0.2f, +0.2f));
Entity *hammer = G_AllocateEntity();
SetEntitySprite(hammer, &game-;>art, ArtSource(ArtAnnotation_Props, R2D(96, 0, 16, 16)), 0.4f);
PushEntityChild(table, hammer, V3(+1.f, 0.2f, +0.2f));
Entity *page1 = G_AllocateEntity();
SetEntitySprite(page1, &game-;>art, ArtSource(ArtAnnotation_Props, R2D(112, 0, 16, 16)), 0.2f);
PushEntityChild(table, page1, V3(-0.3f, -0.1f, +0.2f));
Entity *page2 = G_AllocateEntity();
SetEntitySprite(page2, &game-;>art, ArtSource(ArtAnnotation_Props, R2D(128, 0, 16, 16)), 0.2f);
PushEntityChild(table, page2, V3(+0.1f, 0.2f, +0.2f));
return base;
}
internal Entity *
G_MakeChest(Vec3 position)
{
Entity *base = G_AllocateEntity();
base->position = position;
SetEntityBody(base, MakeBody(MakeShape(ShapeKind_Cuboid, V3(0.5f, 0.5f, 0.5f)), 4.f, 0.f));
SetEntityHitParticles(base, ParticleKind_Wood);
SetEntitySprite(base, &game-;>art, ArtSource(ArtAnnotation_Props, R2D(144, 0, 32, 16)), 0.5f);
SetEntityProperty(base, EntityProperty_AllowTriggers);
SetEntityTrigger_Container(base, TriggerKind_Open, Input_Interact, ScaleShape(base->body.shape, 1.5f));
Entity *cover = G_AllocateEntity();
SetEntityHitParticles(cover, ParticleKind_Wood);
SetEntitySprite(cover, &game-;>art, ArtSource(ArtAnnotation_Props, R2D(176, 0, 32, 16)), 0.3f);
PushEntityChild(base, cover, V3(0, 0, 0.3f));
return base;
}
internal Entity *
G_MakeHitBox(Shape shape, f32 seconds, AttackFlags attack_flags)
{
Entity *box = G_AllocateEntity();
SetEntityTrigger_Hit(box, shape, attack_flags);
SetEntityTimedRemoval(box, seconds);
SetEntityProperty(box, EntityProperty_DieOnTrigger);
return box;
}
internal Entity *
G_MakeProjectile(Vec3 position, Vec3 velocity, f32 duration)
{
Entity *p = G_AllocateEntity();
p->position = position;
SetEntityBody(p, MakeBody(MakeShape(ShapeKind_Sphere, V3(0.3f)), 0.8f, 1.f));
ApplyImpulseToBody(&p-;>body, velocity);
SetEntityTimedRemoval(p, duration);
return p;
}
internal void
G_RenderEntity(Entity *entity)
{
Vec3 p = PositionFromEntity(entity);
Vec4 tint = TintFromEntity(entity);
if(EntityHasProperty(entity, EntityProperty_RenderSprite))
{
R_Texture *texture = entity->sprite_texture;
Rect2D source = entity->sprite_source;
f32 tilt = entity->sprite_tilt;
f32 squish = SquishFromEntity(entity);
RenderSprite(p, source, texture, tint, tilt, squish);
}
if(EntityHasProperty(entity, EntityProperty_RenderItem))
{
Item *item = ItemFromHandle(entity->render_item);
if(item != 0)
{
Matrix4x4 xform = MakeMatrix4x4(1.f);
xform = Multiply4x4(Rotate4x4(-90*entity->render_item_tilt, V3(1, 0, 0)), xform);
xform = Multiply4x4(Translate4x4(p), xform);
RenderItem(item, xform);
}
}
if(EntityHasProperty(entity, EntityProperty_RenderEquipSlot))
{
Entity *equip_entity = G_EntityFromHandle(entity->render_equip_slot_entity);
if(equip_entity != 0)
{
Item *item = equip_entity->equips[entity->render_equip_slot];
if(item != 0)
{
Matrix4x4 xform = MakeMatrix4x4(1.f);
//xform = Multiply4x4(Rotate4x4(-20.f, V3(0, 0, 1)), xform);
xform = Multiply4x4(Rotate4x4(-90*entity->render_equip_slot_tilt, V3(1, 0, 0)), xform);
xform = Multiply4x4(Translate4x4(p), xform);
RenderItem(item, xform);
}
}
}
}
// simple snippet from particle generation implementation...
if(EntityHasProperty(entity, EntityProperty_GenerateParticles))
{
ParticleGenParameters *params = &entity-;>particle_gen;
entity->seconds_to_next_particle_generation -= APP_DT();
if(entity->seconds_to_next_particle_generation <= 0.f)
{
entity->seconds_to_next_particle_generation = params->seconds_between_generations;
Vec3 entity_p = PositionFromEntity(entity);
for(u16 i = 0; i < params->particles_per_generation; i += 1)
{
Vec3 position =
{
entity_p.x + params->position_variance.x * RandomF32(-1.f, 1.f),
entity_p.y + params->position_variance.y * RandomF32(-1.f, 1.f),
entity_p.z + params->position_variance.z * RandomF32(-1.f, 1.f),
};
Vec3 velocity =
{
params->velocity_weight_vector.x + params->velocity_variance.x * RandomF32(-1.f, 1.f),
params->velocity_weight_vector.y + params->velocity_variance.y * RandomF32(-1.f, 1.f),
params->velocity_weight_vector.z + params->velocity_variance.z * RandomF32(-1.f, 1.f),
};
G_PushParticle(params->kind, position, velocity, 1.f + params->life_variance * RandomF32(-1.f, 1.f));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment