The first luxe dev log spoke of renewed focus and intent and we've been working hard on figuring out the direct path to luxe v1.0.0. Along this path are (sooo) many choices, some difficult and some easy, but each choice has an impact on what luxe is and becomes. Positioning is also quite important, since it influences how luxe is perceived.
In this post we're going to have a look at some of the intentions behind luxe, by defining the audience it is intended for. This will help illuminate some design pillars that are factored in when making choices, and as we move towards migrating over it should help you understand the choices better.
note the images in this post are unrelated and just for interest/hinting sake.
The audience is a fairly critical design pillar for an engine, at least in my view. Many engines (and libraries, frameworks, etc) fall into the trap of targeting a really wide audience either intentionally, or unintentionally.
Even for a minimally scoped engine like luxe, the audience is a tool that is necessary to make sense of what we're trying to achieve. Worded differently: if the time spent on the engine isn't solving the problem it's designed to solve, why spend the time? If you don't even know what you're trying to solve...well! For luxe at least this is well defined (as you'll see later).
An audience though can be tricky to define, and deciding on one doesn't create a concrete boundary but rather a blurry and variable gradient which is quite fluid. It's more of a soft boundary, which can and will change often. One new addition can drastically widen the audience, so it's expected to change.
An obvious example of a soft boundary: Your engine audience may explicitly target 3D games, but 2D games could easily be made - the user may decide if they're in the audience based on many factors like experience, documentation, example games doing it already, and much more. In some cases (like support for a specific platform) the difficulty to cross that boundary can become too wide (experience) or impossible (source/platform access).
When the friction to cross the boundary becomes too high, they are likely going to look elsewhere at other solutions (if they haven't already been). This is a good thing and is totally fine/expected/normal. There are always going to be users that don't align with any particular tool, imagining that everyone that ever touches your tool is going to stick around will make you feel awful any time a user moves along. Don't trick yourself.
It's also useful to see that the inverse is true, a user may have high friction and apparent incompatibility but the overall benefits, vision, goals, team, community, and all the things that make up the existence of a given engine may outweigh these costs, keeping them around. Ideally these users end up growing personally (and as a developer) by facing new challenges. They may also be spending the time for the long term outcomes.
This gives us an audience that looks like a blurry shape with unclear edges. The centre region that is clear encompasses the primary target audience that directly map to the engine design goals, and expands to a gradient as it goes outward to include users that don't match as closely. Let's call this the direct audience.
This shape is then further adjusted by the ecosystem surrounding the engine which can add to the edges of this shape, indirectly, via modules. Let's call this the indirect audience.
The engine audience then includes both the direct audience and the indirect audience, so it's much wider than would be feasible, but the point here is that the core engine only caters specifically to the direct audience.
Some examples of this will be expanded a bit lower down!
Now that you are imagining a blurry shape in motion - when I try to clarify the intended audience as we move forward, please consider the fluidity of the definition, and the intended purpose of defining the audience. I would like it if you included yourself by default and worked out from there if you're not able to use luxe, rather than assuming you're not able/allowed/welcome/included.
Everyone is welcome, everyone is included, but whether or not the tool will solve what you're trying to do (and how much extra effort that might add) comes after that.
So what is the benefit of defining an audience on the engine side if the user can side step that definition? Some of the key purposes of defining an audience for me is scope management and user expectation.
As an easy example, If you know the engine is targeted to 2D at the time and you want to make a 3D game, you have to set your expectations correctly when deciding how much effort will go into what you're trying to achieve. It may turn out to be minimal effort, and it may turn out to be too much. This is user dependent.
The scope management aspect comes down to making decisions where to spend time as the development team. There is only so much time available regardless of team size, and making effective use of the time available is in the best interest of the engine. There's no getting around that.
It is also in the best interest of the users that are closer to the direct audience to focus there. It seems in my experience that having many users with a mediocre experience doesn't compare well to one with a great experience that has a narrower focus (but at any time may lack some tertiary features any user may consider important).
Defining the audience this way helps decide critical choices with regards to time available vs engine goals vs requirements.
It also helps you as a user to frame your expectations better.
A lot of people these days are exposed to many engines and paradigms which is fantastic, though it does bring expectations and assumptions along too. This isn't a bad thing, often it helps! But it does then fall on the user to take note of the differences in design philosophy.
By talking about luxe this way, we can illuminate a design philosophy pillar: The core engine itself missing something you want is not perhaps a fault but by design (a design which allows the engine to exist at all). The design is that a module will be solving those needs, and if not you'll have to solve it for your own game (ideally as a module), and if not you'll have to find another option.
Take something specific for consideration - like an audio API. When deciding what the audio API looks like, how low or high level it is, what tools it provides, what cross platform parity is available - all this must consider audience (among other things).
If your direct audience targets a user that doesn't care about any of that, you can be very high level and provide something like a music player (which just cycles tracks sequentially) and
play sound. However if the direct audience is inclined to write streaming generative audio for their game, your API has to facilitate a whole lot more on the low level in order to do that.
This changes the scope quite a bit, but at least the development team now has a tool to use as a lens, a way to view any system or tool within the engine based on the intended usage, as well as the side effects or foundations that system provides for the indirect audience.
system design scope
This helps with system design too. If the audience is known/expected to have technical understanding of a particular domain, the engine API can cater directly to this domain without needing anything in between. An example is rendering: in order to use the direct renderer API effectively, you would be required to know how rendering generally works. The engine can then focus on solidifying the tools it provides, instead of being spread thin across different audiences.
So a key goal of the engine itself then is to facilitate. It is clear that the lower level access facilitates higher level systems. Since the engine has always been designed around this core/modules paradigm, it becomes clear as to why some of the choices are made already. (You may notice that the current code base has this separation, just that some modules reside in the same code tree for convenience).
In my experience having access to the lowest level available has proved invaluable, and allows a variety of higher level approaches to co-exist. This influences the direct audience for the engine, but improves the ecosystem for the indirect audience by making the available options down the road more expressive.
One useful way to measure this is in terms of % of user coverage. The low level audio API and low level rendering API are used by 100% of users, directly or indirectly. A music player that plays sequential songs might only be only 5% of users, as writing one of those can be game specific. Another wording for this metric is "game specific" or "application domain" - things that your game might want but aren't necessary for the engine to be useful.
This extends to scope explosion as well, like if the music player plays sequential songs but your game wants to play random tracks. And tomorrow another game wants to play random tracks based on some game specific events. And the day after a new game wants to play random tracks sequentially based on game events with a variable looping structure. The line between engine and game needs to be explicit or the scope of the engine is unbounded.
This leads to situations where it may appear that the engine provides what you want, and you invest time and effort only to find out that random tracks sequentially triggered by game events with variable looping falls just short, it JUST isn't enough for your game. Now you have to engineer a whole solution, look for a module, or change the code. Maybe the changes can be contributed back, but this is a different view of the same unbounded scope!
In how I evaluate tools and systems within luxe, this factors heavily into the distinction between a module and the core - there will be no music player API in the core luxe engine, ever. See how this rigid and strict assertion on the design can help scope? I can definitely consider how to facilitate frequent use cases like that, but the engine must only facilitate it.
Let's try this on something more common (keep reading if worried): tilemaps.
For a lot of games tilemaps are a staple, but for me I almost never use them. That's not to say they're not really great, they are, but when considering the direct audience of luxe, tilemaps are not a high enough % AND they also suffer from a bunch of scope explosion. This means justifying their existence in the core engine isn't feasible - by the engine design rules - they won't exist in the core.
This particular example sounds like anyone wanting to make a game with tilemaps isn't able to use luxe, but this obviously isn't the case. There'll be a module that handles the tilemaps just like there is now - it just won't be in the core, it'll be a module you reference.
This is why it's crucial that the audience definition includes both the direct and indirect audience, so that modules can be designed by users who understand the problem domain the module is solving, and the engine developers don't have to maintain millions of lines of code for everyone to succeed at their goals.
Without this design pillar in place, the engine scope is infinite and finishing the engine becomes impractical.
What does the direct audience look like then?
Since you may wonder who the direct audience for luxe is...
The answer makes my work quite easy:
me; I am the intended audience.
This could also be misread that I don't care about other users or that the engine is inaccessible or as a glib remark - it's nothing like that - but the engine is designed based on my experience, my needs, my goals, my style and for my ability to make my own games quickly and effectively. There are lots of people that have similar needs and perspectives.
This isn't a downside in my opinion - luxe is a personal tool - it's a handcrafted experience. It's not a product and it never will be, it's mine in the sense that I get to choose where it goes and how it can empower me and others. Things like portability, extensibility, flexibility and the ability to adapt quickly to game markets as they change are factored into the design.
Having this deep insight and intimately knowing and understanding every need as it arrives, knowing every opinion and desire of the direct audience makes the engine what it is. Even in alpha/exploring state people seemed to get it in ways I wasn't sure anyone would. This makes me happy.
And of course, even though it's "mine", I'm happy to share it with others and have gained a lot by doing so. This aspect of it will never change (and is part of the engine design goals). There are also many developers similar to me, who have similar goals and needs, who have similar tastes in simplicity/design/elegance/so on - many of whom have found luxe and appreciate what it's aiming to do, which is motivating.
If this is taken to the extreme, then the only user ever is me and the engine will still have met it's design goals perfectly.
Since the engine has design goals that include ease of use and empowering people, it also tends to cater to people making simpler games and those that are learning. It can also offer space to make tools within it to empower others further. This I value a lot.
None of this has changed, and never will.
This can though make it a little more difficult for inevitable comparisons to other tools, but I think knowing what the engine is and it's intent will benefit users by being able to compare their own needs directly, rather than what popular opinion suggests an engine should be/do. luxe just isn't designed that way, even if it has similarity on many axes.
core vs modules
I do think it's important to state this directly as we get closer to 1.0 so that expectations are aligned - the engine is designed to be a minimal core. Once the core engine is wrapped up, I'll be coding alongside everyone else on the module level. I'll also be working on some higher level user workflow tools (which I'll cover in a near future post, (yes there are tools to talk about soon)).
But everything after the core engine 'falls out' of the work that goes into making games, as modules.
My goal was to build an engine that acts as a foundation that I can rely on to build games over a longer time frame, write game specific modules for, and write tools in. I don't want to have to dive into the engine constantly to do that. It is intended to stay out of the way, and definitely not to grow indefinitely. The core had to be designed to handle this, hence all the design exploring.
One might say that an engine being done is sort of a oxymoron, which is why I say done as defined by the design goals. Another design goal was a core that carries the weight of making games so that I can focus on doing so. Another is that every game I make with it doesn't carry the burden of every game before it, i.e you pay for what you use, and nothing more.
Since this may make it sound like the engine core is bare bones, I've been including examples of the core systems as images in this post and the previous dev log. Most core systems are high level and expressive, the core engine is ready to make games out of the gate. In many ways it is a bunch ahead of the current version even, and in near future dev logs I'll expand on all of this in detail - you'll see.
One other critical factor you'll see mentioned plenty: the design of luxe was for a longer term trajectory. Some choices only make sense as the engine matures, which influences the immediate availability of some things for some users, but that's another topic that will be expanded clearly in a future post.
And above all of the core systems, we get ...
module design goals
All game specific stuff lies above the engine in the module space. This module space includes "sensible defaults" for different users, which no longer live in the engine but are in templates (a future post).
These modules will form the ecosystem around the engine, but are a part of the engine experience all the same. There will be a dedicated home for these, and the workflow is tied directly into the how the engine works - so don't worry about that stuff yet.
Examples include obvious stuff like mobile specific libraries, navigation, AI, templates and so on. It's useful to keep in mind that modules aren't necessary small, but modular.
A possibly surprising module would be 3D lighting and shadows. I don't actually consider lighting and shadows an engine level design goal since it's highly game specific (more than people realize/admit) and I also don't believe much in the idea of 100% solutions or lowest common denominator approach. This means that if the % of any given solution for any specific game will rarely (if ever) be ideal: it makes more sense that there are multiple game specific options available.
Games are complex and nuanced, and varied and this is a beautiful aspect of creating things, the engine treating them that way is a key design goal too.
The direct user
This comes back to the direct audience, users like me would have full access to all the requirements to make a nice easy to use lighting module that is well integrated and share it with others. I also tend to want to write game specific rendering pipelines for each game that don't suffer baggage from other games, while also minimizing redundancy across games, even across platforms.
The indirect user
So because of this, there very likely will be a drop in solution for lighting and shadows, it just won't be on the engine level. This frees the engine to focus on what it needs to, and not cater to every game around, ever, for infinity.
Ideally over time there will be a variety of options, based on different needs and requirements for different types of games, tailored for the use cases.
So how do these fit together?
facilitation and foundation: the core engine provides a common foundation for rendering + lighting to be built, but doesn't fill in the gaps. It provides structure and control over details of the rendering; materials; frame control; target control; a way to deal with cross platform rendering and shaders, cross hardware rendering (quality scaling), and it provides a way to structure the game world in a shared way provided by the game systems.
This allows modules to share critical things like the world structure, available rendering systems (which allow common rendering pipelines to be shared), all of which reduces the amount of redundancy and compatibility issues between multiple modules. The goal would be that modules facilitate other modules in a lower to higher structure as well.
This kind of sysetm will take working carefully with module developers at first, but long term I think it's the better option. Since this is a newer aspect, we can expect some growing into it.
If you're inclined to implement things like this for anything once the new systems roll out (or even before), let's work together.
I've already been talking to some developers that are interested in providing and maintaining modules at this level, if you are interested, message me directly or let me know in the feedback what your thoughts are.
Down the line, a curated list of modules will allow the users confidence in choosing ones that are up to date, well supported, and designed to fit the engine philosophy - even if they're not provided by the engine developers - the ecosystem will be a product of the community working together and collaborating. It is part of the engine design.
There is all manner of fun to be had here - but more on that later!
Alright, so we have a strong set of design goals, a clear vision, some strict limitations and an audience which allows us to focus on the parts that contribute directly to finishing v1.0.0 before anything else. So to bring luxe to 1.0 means to combine all of that and the design exploration into a cohesive whole. Where to start?!
This is what I'll talk about in the next dev log!
For now, hopefully you have a better footing on what I'm trying to achieve and where we're going, and I hope that you'll follow the upcoming posts for more details. If you have any thoughts, please share them in the feedback discussion (or to me directly on chat or email).
And, as you may have guessed, all this is what I've been working on the last few months with the help of a couple of testers and collaborators. Being able to focus on the core and design it for completion has been great for progress, and we're well into the final spec already.
I can't wait to talk about it, and I definitely can't wait for you to use it but I am determined to actually finish it - so I must take the necessary steps to do that. Currently that means laser focus, but I'll start talking in more detail about the things that we are now committed to very soon.
Feedback on this post can be found in this discussion.