ECS feedback

The Partridge Family were neither partridges nor a family. Discuss.
Post Reply
User avatar
Yumtard
Posts: 575
Joined: January 19th, 2017, 10:28 pm
Location: Idiot from northern Europe

ECS feedback

Post by Yumtard » April 1st, 2018, 3:45 pm

Hey guys,

I was bored this morning so I made an ecs (or rather the base for what could become an ecs)

I tried doing this using a very different approach from when I did one in school. Would very much like to hear opinions.
Spoiler:
In school I made an ecs where I had a component interface which I derrived other components from. The components contained both logic and data, and entities held a vector of components
Is this a bad system? why?
Is it a decent system? why?
What would you change?

please consider the fact that I quickly threw this together so it doesn't really do anything yet, the components are placeholders and there's some details I'll change later.


I have these components:

Code: Select all

enum ComponentTypes
{
	TYPE_TRANSFORM,
	TYPE_SPRITE,
	TYPE_VELOCITY
};

Code: Select all

struct ComponentTransform
{
	float x;
	float y;
	float angle;
};

Code: Select all

#include "Sprite.h"

struct ComponentSprite
{
	int width;
	int height;
	Sprite sprite;
};

Code: Select all

struct ComponentVelocity
{
	float x;
	float y;
};
World.h

Code: Select all

#pragma once

#include "ComponentTypes.h"
#include "ComponentTransform.h"
#include "ComponentSprite.h"
#include "ComponentVelocity.h"
#include <map>

typedef unsigned int Entity;

class World
{
public:
	World();
	Entity CreateEntity();
	void AddComponent(Entity entity, ComponentTypes type, void* data);
	void RemoveComponent(Entity entity, ComponentTypes type);

	void Update();

	void MovementSystem();
	void RenderSystem();

private:
	std::map<Entity, ComponentTransform> _transform_components;
	std::map<Entity, ComponentSprite> _sprite_components;
	std::map<Entity, ComponentVelocity> _velocity_components;

	Entity _entities;
};
World.cpp

Code: Select all

#include "World.h"

World::World()
{
	_entities = 0;
}

unsigned int World::CreateEntity()
{
	return _entities++;
}

void World::AddComponent(Entity entity, ComponentTypes type, void * data)
{
	switch (type)
	{
	case TYPE_TRANSFORM:
	{
		ComponentTransform* transform = static_cast<ComponentTransform*>(data); 
		_transform_components[entity] = *transform;
		break;
	}
	case TYPE_SPRITE:
	{
		ComponentSprite* sprite = static_cast<ComponentSprite*>(data);
		_sprite_components[entity] = *sprite;
		break;
	}
	case TYPE_VELOCITY:
	{
		ComponentVelocity velocity = static_cast<ComponentVelocity*>(data);
		_velocity_components[entity] = *velocity;
		break;
	}
	default:
		//invalid
		break;
	}
}

void World::RemoveComponent(Entity entity, ComponentTypes type)
{
	switch (type)
	{
	case TYPE_SPRITE:
		_sprite_components.erase(entity);
		break;
	case TYPE_TRANSFORM:
		_transform_components.erase(entity);
		break;
	case TYPE_VELOCITY:
		_velocity_components.erase(entity);
		break;
	default:
		break;
	}
}

void World::Update()
{
	MovementSystem();
	RenderSystem();
}

void World::MovementSystem()
{
	std::map<Entity, ComponentVelocity>::iterator it = _velocity_components.begin();
	for (; it != _velocity_components.end(); ++it)
	{
		ComponentVelocity& velocity = it->second;
		ComponentTransform& transform = _transform_components[it->first];
		transform.x += velocity.x;
		transform.y += velocity.y;
	}
}

void World::RenderSystem()
{
	std::map<Entity, ComponentSprite>::iterator it = _sprite_components.begin();
	for (; it != _sprite_components.end(); ++it)
	{
		ComponentSprite& sprite = it->second;
		sprite.sprite.Render();
	}
}

Creating an entity in game.cpp would look something like this:

Code: Select all

unsigned int Player = world.CreateEntity();

	ComponentTransform transform;
	transform.x = 100.0f;
	transform.y = 100.0f;

	ComponentVelocity velocity;
	velocity.x = 100.0f;
	velocity.y = 100.0f;

	world.AddComponent(Player, TYPE_TRANSFORM, &transform);
	world.AddComponent(Player, TYPE_VELOCITY, &velocity);

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: ECS feedback

Post by albinopapa » April 1st, 2018, 8:41 pm

I'm no expert on the ECS, but there are a few things that threw me off about this implementation.

TransformComponent seems to really be a PositionComponent. Velocity, acceleration, scaling and rotation would be things that transform an object. I would have thought a TransformComponent would be the thing to apply transforms to a PositionComponent.

The S in ECS would be a System, something that from what I understand handles the "Game" logic, and in this case, is only functions inside a World class. I would think that Game would hold a MovmeentSystem object and a RenderSystem object, as well as the World object. Passing the World object to the MovementSystem for updating, passing to a PhysicsSystem to resolved collisions, then passed to the RenderSystem for drawing.

Again, I may have all this wrong in my head, but that's how I understood ECS design.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

User avatar
Yumtard
Posts: 575
Joined: January 19th, 2017, 10:28 pm
Location: Idiot from northern Europe

Re: ECS feedback

Post by Yumtard » April 12th, 2018, 11:39 pm

Sorry for late response! Wrote this code kinda quickly and after posting here this little "project" was put on ice due to me being swamped with our game project.

Thanks for your input will look into a better way of doing systems once this game project is done :)

Like I mentioned the components are pretty much placeholder.

Transforms are position, rotation and scale

You'd change the position/rotation/scaling vectors in the transform matrix and then you apply the transform matrix to components such as modelComponent

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: ECS feedback

Post by albinopapa » April 13th, 2018, 4:52 am

Oh, ok, I see why you made it a transform, it's a displacement.

I've been messing with ECS a little myself and I'm not a fan. There are some things it makes easy and some things that it make more difficult. The things it makes easy is stuff that can be generalized. Updating a position based on some physics like gravity or some other force or impulse, checking collisions somewhat, though you still need to know the types of objects like rectangle to rectangle or rectangle to circle ( or their 3D counterparts ), drawing as long as they all take the same parameters, luckily sprites usually only need a position they can store their own sizes.

The things it makes more difficult: dealing with events specific to a given entity, cross communication between the different systems, setting up each of the components for the entities.

As far as the setting up of the components, I can see why people have chosen to use data files. Load the data from the files, apply to component, then send component to entity.

As for the communication between Systems, using the observer pattern seems to be popular, though I've noticed a few of my systems are needing to be both observer and subject. For instance, if I want an entity to be collectible that restores health for the player, I send a message from the physics system saying there was a collision. This makes the collectible an observer. The entity then will need to notify the health system so the health component can be updated, thus making the collectible a Subject as well.

One thing that I haven't quite sorted out is where does specific logic go? In the case above, I can keep everything in one System or another. So, if Systems are where game logic goes, then am I suppose to have a System that matches each Entity type like PlayerSystem, TrollSystem, OrcSystem, HeartSystem, SwordSystem, etc...?

The system part is where information on the internet really seems to break down. Some say Entity is just an ID, like what you have up top. Others say Entity is just a class to acquire a unique ID, and still others say Entity is a class where you store an array of components.

If Entity is just an ID, then where do you store the components? Would you store the EntityID with a component?

Components are just data, this has been pretty universal on the internet.

Systems however, seemingly only get the "it's where the logic goes" treatment. If this is the case, as asked above, how specific does the logic get? If for instance, I was making a Zelda clone, would I need a LinkSystem with all the components that describe Link? Then a GuardSystem and BatSystem, then a system for each of the different types of enemies, because each would have their own behavior or state machine or decision tree.

If so, then what is the purpose of the Entity ID again? lol.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

Post Reply