Lord Crocosquirrel plays RetroWar
23 Jun 2019Proving that some of the games can be played single player (although they are more fun with friends!).
Proving that some of the games can be played single player (although they are more fun with friends!).
ECS is a programming paradigm, similar to object-oriented programming.
Computer memory stores information as a series of binary numbers. Mostly these numbers represent ‘data’ i.e. decimal numbers, text, images, sounds. However it may also represent a uniquely different type of information: machine code. This ‘code’ consists of instructions that tells the microprocessors what to do.
Programmers (‘coders’) produce code. The microprocessors run the code which tells them how to receive input data, modify data, and output data. Some data may be supplied by the programmer e.g. the graphics for a game. Some data will be supplied by the user, e.g. the text of a word processor document.
Because code is stored in memory exactly the same way as data it is possible to write code that modifies code. In the early days of programming this was considered an impressive trick. However it makes code difficult to understand if it changes while you're trying to understand it, so it came to be considered bad practice. It was also possible for one program to modify the code and hence the behaviour of another program, or for user to input special data into a program to trick it into modifying itself. Nowadays self-modifying code is considered a security risk and modern microprocessors mark sections of memory for data and code, and refuse to allow the code to be modified.
The evolution of programming languages can be seen as a search to find the best way of relating code to data. Object-oriented programming does this by grouping code and data together into ‘objects’. An object contains some data and some methods (functions, code) that operates on that data. One object is prohibited from modifying the data of another object except by using that object's methods. This makes it easier to understand one object without needing to understand the behaviour of every other object in the system.
Object-oriented programming has been found to work very well for most applications. However it has some snags when programming games:
Game code must execute many millions of times per and must execute quickly. If many thousands of objects are created they will fill the available memory and necessitate unused objects being deleted. Some systems (e.g. Android) are quite poor at doing this and introduce pauses into the game.
RAM access is relatively slow, so modern CPUs copy data into small high performance caches where they can operate on it quickly. Most OO systems (at least though without value types) store object data in a heap and give no guarantee that similar objects will be store near one another, so they may not be in the cache when needed. Further, even if objects are stored contiguously, you don't usually need every field of the object at once. For example, when updating physics you might need the (x, y) co-ordinates of every object, but having the objects' image data in the cache is just a waste.
Traditionally OOP relies on inheritance to combine different object types together and reuse code. Objects like game sprites have such variety that they can be difficult to model in an inheritance based class hierarchy. Hence composition tends to be used instead.
ECS takes composition to its logical conclusion. Instead of objects we have entities. An entity consists of a number of components. Each component is just a piece of data, no code. So there is no code in an entity. Instead the code is placed in separate systems. But systems do not operate on entities! Systems operate on groups of components.
Now a system could be defined to operate on exactly the same group of components that make up an entity. For example an asteroid entity might consist of an image component and a position component. A drawing system operates on all entities with image components and position components to draw the image at the position on the screen. That is it operates on asteroids. But let's add another entity: a spaceship that can move. It has image, position and velocity components. The same drawing system will draw the spaceship with no changes required to the code!
Entities are very lightweight and have no real identity in the way that objects do. An entity is just some data components stored in memory tagged with the same ID number.
There are no ECS languages in the way there are OOP languages. But language level support is not required!
It is possible to do OOP in languages that do not provide explicit support for it. For example many C programs are written in an OOP style. But it requires the programmer to be disciplined because the language will not enforce type safety or other OOP restrictions.
Similarly ECS can be used in an OOP language. Each entity can be represented by an object composed of component data objects. Each system can be represented by an object containing code (a method). There exist several ECS libraries to make the syntax for this nicer and the internal entity/object representation more efficient.
For Java I recommend Ashley if you want an ECS library that is easy-to-use or Artemis ODB if you want one that is high-performance.
Yes, in some libraries there is the archetype, which produces entities with a certain set of components. However components could always be added or removed from the entity after creation.
Yes, languages like Ruby and Javascript can modify any object to add any component after creation. Also using ‘duck typing’ they can address every object that has a certain component - those that don’t have the component will simply not respond. However they are not as fast or as type safe as Java or C++ with an ECS library.
When starting with ECS the great temptation is to use it for everything. Anywhere you could have used an object, you use an entity. This gives you the convenience of having the entire state of your game stored in a single ECS which can be serialised when you want to save game.
However, I have come to think that ECS should only be used for what were traditionally called ‘sprites’, that is things that exist in the universe of the game and are represented on screen. It is sprites that are a difficult fit for OOP, and it is sprites that need to be rapidly created, modified and destroyed. So I would use ECS for a spaceship, and it would have components for image, position, velocity, player input, weapons, health.
Initially I used ECS for other things. For example menus (they are displayed on the screen, right?), objects representing each player in a multiplayer game, score, the game background. Currently I use standard OOP for these. The game background still has a system in the ECS to draw it, but it operates on a simple image object. I don’t see the point of putting a singleton entity/component into the ECS. For a more complicated game the background might be broken up into separate objects for every tile and then these objects could be components in the ECS.
Currently UniBlaster (the main engine of RetroWar) supports four different games / character types. They are similar in many ways, but the controls vary slightly.
I've been thinking how to create a more general abstraction of these differences.
Instead of one weapon, each character would have three weapon slots:
This would allow characters to carry multiple weapons and use all three without any switching and only needing one fire button.
Possible uses:
Finally, I've been thinking about how RetroWar could be played on actual 8-bit joysticks. Currently 8-way d-pad movement is supported in all games as a virtual stick, (although it puts you at a disadvantage in most of them that aren't grid based.)
But then how to aim when there is no second stick for aiming? You could just fire in whatever direction the sprite is facing when the button is pressed. But that would fire the C type weapon, which is likely to be non-directional bomb anyway, and leave no way to fire the A directional gun. Games like Paradroid handled this by requiring you to move a bit before pressing fire. So the virtual input would create a second stick pointing in the same direction as the real left stick when you press the button with no input from any right stick. Many games also had a locking mechanic, so once you started shooting you held down the button to lock the firing direction while you could move in a different direction. Not sure how that could work in my scheme.