[{"content":" Featured Project # BareBones Engine A custom 2D game engine built entirely from scratch in C++.\nRecent Writing # Within the laws of physics, anything is possible\nRevolutionising game funding\nAdversity sparks growth\nLiving with intention\n","date":"17 February 2025","externalUrl":null,"permalink":"/","section":"Home","summary":"","title":"Home","type":"page"},{"content":"","date":"17 February 2025","externalUrl":null,"permalink":"/posts/","section":"Posts","summary":"","title":"Posts","type":"posts"},{"content":"You can simply \u0026ldquo;do the thing\u0026rdquo;.\nMaybe I speak for myself only here, but I have often fallen into the assumption that you should stay on the path you\u0026rsquo;re on. The sunk cost fallacy is a hell of a judge, as are your friends and family.\nThis is a rope to help you out of the pit. There are no hard-set rules for how to live. There is no certainty in this world. There is no path but the one you forge. We live one life; let\u0026rsquo;s make what we want of it.\nIf there\u0026rsquo;s something calling your name, have a go. Within the laws of physics, anything is possible. If not now, when?\n","date":"17 February 2025","externalUrl":null,"permalink":"/posts/within_the_laws_of_physics/","section":"Posts","summary":"","title":"Within the laws of physics, anything is possible","type":"posts"},{"content":"Recent trends in the games industry have rightly sparked discussions about its future direction. While these issues may initially seem disparate, they raise a common question: How can we build a system where artistic vision and quality thrive alongside financial stability?\nManifestations # Unfinished Games # One major issue, especially in the \u0026ldquo;AAA\u0026rdquo; scene, has been games releasing in a state of questionable quality - suffering from performance issues, glitches, and balancing problems. The \u0026ldquo;day-one patch\u0026rdquo; has become a well-established phenomenon, often followed by months of updates before the game reaches an \u0026ldquo;acceptable\u0026rdquo; quality that better matches the creative vision.\nThis situation often involves a struggle between:\nDevelopers: Who consider the creative vision paramount but require funding to employ their teams. Publishers/Investors: Who provide capital in return for profit, often wanting quick returns on investment. Many games are undeniably works of art, evoking strong emotions through storytelling (like \u0026lsquo;A Short Hike\u0026rsquo; or \u0026lsquo;Outer Wilds\u0026rsquo;) or providing satisfying challenges (such as \u0026lsquo;Returnal\u0026rsquo; or \u0026lsquo;Demon\u0026rsquo;s Souls\u0026rsquo;). Creating art takes time, and the creative process is inherently spontaneous, unpredictable, and iterative. Giving this process the time it needs often results in art that resonates with more people.\nThe Content Conundrum # There\u0026rsquo;s been a push for more and more \u0026ldquo;content\u0026rdquo; in games, with studios often aiming for tens to over a hundred hours of gameplay. This can lead to repeated and lower-quality content, turning the game into a grind. While some games, like \u0026lsquo;The Witcher 3\u0026rsquo; and \u0026lsquo;The Legend of Zelda: Breath of the Wild\u0026rsquo;, have successfully created content-rich experiences, they are exceptions rather than the rule.\nThis push for content may be due to genuine high ambitions, feature creep inherent to the creative process, or pressure from investors. To me, the key is to prioritise creating an engaging, fun experience over simply \u0026ldquo;creating content.\u0026rdquo;\nThe Live Service Paradigm # Following the massive financial successes of games like Fortnite, Call of Duty: Warzone, and Apex Legends, Free-to-Play (F2P) Games as a Service (GaaS) have taken over the market. These games promise free, unlimited content with regular updates, monetised primarily through microtransactions.\nThis trend has led to a shift in investment patterns, with investors seemingly less likely to back the development of complete, particularly single-player, experiences. From a business perspective, this risk-averse behaviour is understandable, especially given the recent astronomical profits of some F2P games. However, from an enthusiast\u0026rsquo;s perspective, it raises concerns about games being considered primarily as products to make money, rather than art that enriches lives.\nThe Human Element # The misalignment of incentives also affects the people behind the games:\nCrunch: Developers often work excessive hours for weeks or months prior to release to ensure a game reaches a certain state. This prioritises short-term profits at the expense of developers\u0026rsquo; quality of life. Crunch may naturally be part of the creative process, but it should be minimised nonetheless. Studio Closures \u0026amp; Layoffs: Sometimes occurring unexpectedly, these often feel unwarranted and can be seen as a method of cutting costs at the first sign of \u0026ldquo;failure\u0026rdquo;. This contributes to high employment volatility in the industry. Realigning Incentives: Potential Solutions # To address this problem of incentives, we need to explore ways to balance artistic integrity with financial sustainability:\nEmbracing Variety in Scope: Not all games have to be the next blockbuster! Instead, let\u0026rsquo;s celebrate a diverse ecosystem of games, including shorter, more focused experiences and innovative art styles that don\u0026rsquo;t necessarily require cutting-edge technology. Redefining Success: Let\u0026rsquo;s expand our definition of success beyond just financial metrics to include measures of artistic achievement, player satisfaction, and positive industry impact. However, game developers and publishers are, ultimately, companies with a commitment to generate returns for investors; profit is a necessity. Therefore, there is no guarantee that even the most well-meaning developers and publishers won\u0026rsquo;t, in time, resort to attempting to maximise profits.\nSo, let\u0026rsquo;s shake up the system\u0026hellip;\nInnovative Funding Models: Let\u0026rsquo;s explore alternative funding sources that provide more flexibility and stability for artistic projects, such as blockchain-based crowdfunding, or public-private partnerships for games with educational or cultural value. Decentralised Autonomous Organisations # The decentralised science (DeSci) movement, while in its nascent stage, is revolutionising the funding of research. Research is being funded that wouldn\u0026rsquo;t be considered a \u0026ldquo;safe bet\u0026rdquo; by conventional funding bodies, nor a potential cash cow by investors. As a result, scientists can earn a living more sustainably, and patients will receive new treatments that wouldn\u0026rsquo;t otherwise be developed.\nLet\u0026rsquo;s apply the same approach to art. By decentralising the funding of games, there would be no central, profit-maximising entity to affect the developers\u0026rsquo; creative vision. Crowdfunding platforms like Kickstarter and Indiegogo have shown the beauty of crowdfunding - and blockchain technology can take this further.\nFor example, a decentralised autonomous organisation (DAO) would enable the raising of capital - through the purchasing of its token - and capital distribution. Such distribution would be automated - the beauty of blockchain technology - with no need for a middleman like Kickstarter. Kickstarter takes a 5% cut of donations, and 5% more is taken by the payment platform - an effective cut of 10%. Blockchain \u0026ldquo;gas\u0026rdquo; fees will move ever closer to 0% with optimisations and new architectures, meaning developers will have increased profits.\nTo distribute funds in a pool fairly, quadratic funding (QF) can be used. QF is an algorithm that more heavily weights the number of votes for a particular project than the amount of money donated to each project: this means that funders with the most capital are not the most powerful.\nBetter yet, streaming quadratic funding (SQF) could provide a more stable source of income for artistic teams. SQF describes a continuous flow of funds - the amount of which is determined according to the QF formula - released at regular intervals. This could provide the financial security that is current lacking in the games industry.\nTo incentivise contribution, funders could receive royalties - made automatic and reliable by the underlying blockchain technology. Also, some projects may give contributors governance rights to help make decisions about the game, allowing more community-driven development.\nThe Path Forward # The games industry stands at a crossroads, with the potential to evolve into a beacon of artistic expression and innovation. By realigning incentives to balance the creation of games as both art and commerce, we can chart a course towards a future where artistic integrity and financial sustainability go hand in hand.\nThis evolution will require collaboration between developers passionate about their craft and players who value and support games that push the medium forward.\nAs we move forward, let\u0026rsquo;s embrace the idea that games can be more than just products – they can be transformative works of art that resonate with people, enrich lives and inspire future creators. To ensure games in development are treated this way, a realignment of incentives is required.\n","date":"18 October 2024","externalUrl":null,"permalink":"/posts/revolutionising_game_funding/","section":"Posts","summary":"","title":"Revolutionising game funding","type":"posts"},{"content":"If we want to improve ourselves, facing uncertainty is the only option. We don\u0026rsquo;t grow by blindly following the same path. Sticking purely to routines leads to stagnation, both in enjoyment and progress. Embracing uncertainty means discovering novelty, both good and bad.\nFacing Your Fears # There\u0026rsquo;s an old saying, \u0026ldquo;In sterquiliniis invenitur,\u0026rdquo; which roughly means \u0026ldquo;in filth, it will be found.\u0026rdquo; A useful interpretation is that what you need is often where you least want to look. Facing something difficult head-on usually leads to positive outcomes. For example, studying the subjects you struggle with most before an exam, or dealing with suppressed emotions after a loss to heal in the long run.\nFinding the Right Balance # While there\u0026rsquo;s comfort in the familiar, we all need some adventure. There seems to be an optimal way to explore new challenges called the \u0026ldquo;zone of proximal development,\u0026rdquo; introduced by psychologist Lev Vygotsky. It\u0026rsquo;s just beyond what\u0026rsquo;s comfortable, but not so far that it\u0026rsquo;s overwhelming. Finding this balance can lead to personal growth and a satisfying life. For instance, playing a game against someone slightly better than you is often the most fun and helps you improve.\nThis balance is similar to \u0026ldquo;exploration vs. exploitation\u0026rdquo; in reinforcement learning. \u0026ldquo;Exploitation\u0026rdquo; means repeating familiar behaviours, while \u0026ldquo;exploration\u0026rdquo; refers to trying new actions with unknown outcomes. We use this in everyday choices like picking restaurants or planning trips. It\u0026rsquo;s especially important in career decisions, where we often overestimate the risks of change. For anyone not satisfied with their life but worried about making a change, Tim Ferriss’ ‘fear-setting’ exercise helped me put my fears of a career change into perspective.\nDealing with Unexpected Challenges # When faced with overwhelming challenges, it helps to remember two things:\nWe can\u0026rsquo;t accurately predict outcomes, good or bad. A difficult situation might lead to unexpected positive changes; for example, a health issue may rejuvenate someone\u0026rsquo;s appreciation for life. Events themselves aren\u0026rsquo;t inherently good or bad. It\u0026rsquo;s our perception that affects us. If we can change our perspective, the event does not have to be bad. To round off, it\u0026rsquo;s important to face life\u0026rsquo;s challenges voluntarily and head-on, or they can rule us. Dealing with adversity is often the key to progress. And for those thinking about taking a risk: what\u0026rsquo;s the worst that can happen? That leap of faith is often not as high as it seems.\nEdit 9/6/23: The Pathless Path by Paul Millerd is an inspiring read for those unsure about making a change to their current trajectory.\n","date":"13 August 2024","externalUrl":null,"permalink":"/posts/adversity_sparks_growth/","section":"Posts","summary":"","title":"Adversity sparks growth","type":"posts"},{"content":" What, and Why? # Ever wondered what makes Unreal Engine, Unity, or Godot tick? That curiosity sparked my adventure into crafting a 2D game engine in C++. Inspired by Dave Churchill\u0026rsquo;s lectures at Memorial University, I set out to demystify the magic behind game development.\nArchitecture # The engine uses an entity-component-system (ECS) architecture:\nEntities are the actors on your game\u0026rsquo;s stage; game objects which can possess components. Components are the building blocks that give entities their unique traits; structures of data (and sometimes, functions) attached to each entity. Systems are the directors orchestrating how entities behave based on their components; functions that enact the game logic on each entity according to their components\u0026rsquo; data. For example, take the Player entity. It has components like Input, Transform, and Animation. The Input component stores key presses (e.g., pressing \u0026lsquo;up\u0026rsquo; makes \u0026ldquo;up\u0026rdquo; true), and the Movement system, called every game frame, reads those inputs, updates the player’s velocity (from the Transform component), and changes their position according to this velocity.\nvoid ScenePlatformer::spawnPlayer() { m_player = m_entityManager.addEntity(\u0026#34;Player\u0026#34;); m_player-\u0026gt;addComponent\u0026lt;CInput\u0026gt;(); m_player-\u0026gt;addComponent\u0026lt;CBody\u0026gt;(Vec2f(m_playerConfig.BB_WIDTH, m_playerConfig.BB_HEIGHT), 1.0f); m_player-\u0026gt;addComponent\u0026lt;CAnimation\u0026gt;(m_game-\u0026gt;getAssets().getAnimation(\u0026#34;ArcherIdle\u0026#34;), true); // More components ... } I opted for ECS over traditional object-oriented architecture for its modularity, performance, and flexibility. Unlike the rigid class hierarchies of OOP, ECS separates the “what” (components) from the “how” (systems), making it much easier to extend and maintain, while keeping memory and CPU usage efficient.\nCore Components # Scene Management # The scene management system organises the various states of the game, such as menus, levels, and game over screens.\nEach scene is derived from a base Scene class, which provides essential functionality like action registration and rendering. The GameEngine class maintains a map of available scenes, allowing for quick access and dynamic changes. When transitioning between scenes, the changeScene() method of the GameEngine class updates the current scene and ensures that the new scene is properly initialised.\nThis modular design not only enhances organisation but also simplifies the addition of new scenes, making it easier to expand a game’s content.\nvoid GameEngine::changeScene(const std::string\u0026amp; sceneName, std::shared_ptr\u0026lt;Scene\u0026gt; scene) { m_sceneMap[sceneName] = scene; m_currentScene = sceneName; } void GameEngine::update() { sUserInput(); getCurrentScene()-\u0026gt;update(); // } Input Handling with Action System # The input system is designed to be flexible and responsive, allowing players to interact with the game seamlessly. It utilises a mapping of keyboard inputs to specific actions, which are registered in each scene. The sUserInput() method in the GameEngine class polls for events from the SFML window, checking for key presses and releases.\nWhen a key is pressed or released, the system looks up the corresponding action in the current scene\u0026rsquo;s action map (a mapping of SFML key code to a string) and triggers the appropriate response.\nThis design allows for easy remapping of controls, ensures that each scene can define its own unique set of actions, and enables an action-replay system.\n// At scene level: add potential actions to scene\u0026#39;s action map void ScenePlatformer::init() { registerAction(sf::Keyboard::W, \u0026#34;UP\u0026#34;); registerAction(sf::Keyboard::A, \u0026#34;LEFT\u0026#34;); registerAction(sf::Keyboard::D, \u0026#34;RIGHT\u0026#34;); registerAction(sf::Keyboard::Space, \u0026#34;SHOOT\u0026#34;); } // At game engine level: listen for input, and if action exists in the current scene\u0026#39;s action map, make the scene enact the action void GameEngine::sUserInput() { sf::Event event; while (m_window.pollEvent(event)) { switch (event.type) { case sf::Event::KeyPressed: case sf::Event::KeyReleased: // If action exists in current scene\u0026#39;s action map, scene performs action if (getCurrentScene()-\u0026gt;getActionMap().find(event.key.code) != getCurrentScene()-\u0026gt;getActionMap().end()) { const std::string actionType = (event.type == sf::Event::KeyPressed) ? \u0026#34;START\u0026#34; : \u0026#34;END\u0026#34;; getCurrentScene()-\u0026gt;sDoAction(Action(getCurrentScene()-\u0026gt;getActionMap().at(event.key.code), actionType)); } // More cases... } } } Movement System # The movement system is responsible for updating the positions of entities based on player input and physics calculations.\nEach entity can have a Body component that contains properties such as velocity and acceleration. The sMovement() method in the ScenePlatformer class processes input actions to adjust the entity\u0026rsquo;s velocity accordingly.\nFor example, pressing the left or right keys modifies the horizontal velocity, while the jump key changes the vertical velocity if the player is grounded.\nThe system also incorporates gravity, which continuously affects the vertical velocity of the player, ensuring realistic movement dynamics.\nCollision System # Your browser cannot play this video. Download video.\nThe collision system is crucial for maintaining the integrity of the game world by preventing entities from overlapping inappropriately. It employs an axis-aligned bounding box (AABB) approach to detect collisions between entities.\nThe sCollision() method in the ScenePlatformer class iterates through pairs of entities, checking for overlaps using their bounding boxes. When a collision is detected, the system determines the type of collision (e.g., player with tile, arrow with solid) and executes the appropriate response, such as pushing the player out of an object or destroying an entity.\nThis system is crucial for ensuring entities react with each other realistically, for example stopping the player falling through the floor, or detecting an arrow hitting another entity, causing it\u0026rsquo;s destruction.\n// Part of sCollisions() in ScenePlatformer class EntityVector\u0026amp; entities = m_entityManager.getEntities(); size_t entityCount = entities.size(); for (size_t i = 0; i \u0026lt; entityCount; ++i) { auto\u0026amp; a = entities[i]; if (!a-\u0026gt;hasComponent\u0026lt;CBody\u0026gt;()) { continue; } for (int j = i + 1; j \u0026lt; entityCount; ++j) { auto\u0026amp; b = entities[j]; if (!b-\u0026gt;hasComponent\u0026lt;CBody\u0026gt;()) { continue; } Vec2f overlap = Physics::getOverlap(a, b); if (overlap.x \u0026gt; 0 \u0026amp;\u0026amp; overlap.y \u0026gt; 0) { if (a == m_player || b == m_player) { handlePlayerCollision((a == m_player) ? b : a); } // If a combination of arrow and solid, handle collision according else if((a-\u0026gt;getTag() == \u0026#34;Arrow\u0026#34; || b-\u0026gt;getTag() == \u0026#34;Arrow\u0026#34;) \u0026amp;\u0026amp; (a-\u0026gt;getTag() == \u0026#34;Solid\u0026#34; || b-\u0026gt;getTag() == \u0026#34;Solid\u0026#34;)) { bool aIsArrow = a-\u0026gt;getTag() == \u0026#34;Arrow\u0026#34;; handleArrowCollision(aIsArrow ? a : b, aIsArrow ? b : a); } } } } Animation System # Your browser cannot play this video. Download video.\nYour browser cannot play this video. Download video.\nThe animation system is designed to provide smooth and dynamic character movements. Each entity can have an Animation component that stores the current animation and its properties, such as whether it should loop. Player animations are linked to the player\u0026rsquo;s State component, which can take values including running, jumping, or shooting, ensuring that the correct animation plays in response to player actions.\nThe system assumes horizontally-organised sprite sheets, and uses the information specified in assets.txt (specifically, the number of frames the animation consists of) to display the correct portion of the sprite sheet each frame. This approach simplifies the management of sprite assets and enhances the flexibility of the animation system, enabling developers to easily adjust and expand animations as needed.\nThe system efficiently checks for animation completion and handles transitions seamlessly, allowing for features like looping animations or one-time effects.\nBy decoupling the animation logic from the game entities, developers can easily extend and modify animations without affecting the core gameplay mechanics, fostering a flexible and maintainable codebase.\nvoid ScenePlatformer::sAnimation() { for (auto\u0026amp; entity : m_entityManager.getEntities()) { // If entity has Animation component, either update it, or remove if has ended if (entity-\u0026gt;hasComponent\u0026lt;CAnimation\u0026gt;()) { bool hasEnded = entity-\u0026gt;getComponent\u0026lt;CAnimation\u0026gt;().update(); if (hasEnded) { endAnimation(entity); } if (entity == m_player) { // If player state has changed, change animation const CState\u0026amp; stateComponent = m_player-\u0026gt;getComponent\u0026lt;CState\u0026gt;(); if (stateComponent.currentState != stateComponent.previousState) { changePlayerAnimation(); } } } } } Rendering System # The window, graphical rendering, and sound are handled by SFML, to enable me to focus on the higher-level engine features.\nThe rendering system is responsible for visually displaying the game world and its entities on the screen. It operates within the sRender() method, which clears the window, sets the view, and draws all entities in the correct order.\nThe system first renders background elements and then iterates through the entity manager to draw each entity using the renderEntity() method. Special care is taken to render the player last, ensuring it appears on top of other entities.\n// Part of sRender() in ScenePlatformer class EntityVector\u0026amp; entities = m_entityManager.getEntities(); // Check whether player exists auto playerIterator = std::find_if(entities.begin(), entities.end(), [](std::shared_ptr\u0026lt;Entity\u0026gt; e) { return e-\u0026gt;getTag() == \u0026#34;Player\u0026#34;; }); // If player exists, move to end of entity vector, so only a single pass needed if (playerIterator != entities.end()) { std::shared_ptr\u0026lt;Entity\u0026gt; player = *playerIterator; entities.erase(playerIterator); entities.push_back(player); } // Drawing of textures/animations and bounding boxes for (auto entity : entities) { renderEntity(entity); } Additionally, the system includes options for debugging, such as rendering bounding boxes and a grid, which can be toggled on or off:\nDesign Considerations # A key design consideration was the implementation of collision detection and resolution within the Physics namespace.\nIn my engine, collision detection relies on axis-aligned bounding boxes (AABB). To detect a collision, the overlap between two entities\u0026rsquo; bounding boxes is calculated. If there’s overlap along both the X and Y axes, this indicates a collision.\nTo resolve the collision, the direction in which to push the entity must be determined — either along the Y axis (for example, when the player lands on a block) or the X axis (such as when the player runs into a wall). To achieve this, the overlap between the two objects in the previous frame is calculated:\nbool resolveCollision(std::shared_ptr\u0026lt;Entity\u0026gt; a, std::shared_ptr\u0026lt;Entity\u0026gt; b) { const Vec2f previousOverlap = Physics::getPreviousOverlap(a, b); bool isXDirection = previousOverlap.x \u0026lt;= 0 \u0026amp;\u0026amp; previousOverlap.y \u0026gt; 0; moveEntity(a, b, isXDirection); return isXDirection; } To avoid code duplication, I implemented a helper function, getOverlapHelper, which calculates the overlap for either the current or the previous frame based on a boolean flag. This function is placed in an anonymous namespace to limit its scope to the current file, ensuring encapsulation. Only the public-facing functions, getOverlap and getPreviousOverlap, are part of the Physics namespace:\n// \u0026#34;Public\u0026#34; interface namespace Physics { Vec2f getOverlap(std::shared_ptr\u0026lt;Entity\u0026gt; a, std::shared_ptr\u0026lt;Entity\u0026gt; b) { return getOverlapHelper(a, b, false); } Vec2f getPreviousOverlap(std::shared_ptr\u0026lt;Entity\u0026gt; a, std::shared_ptr\u0026lt;Entity\u0026gt; b) { return getOverlapHelper(a, b, true); } } These functions rely on getOverlapHelper to handle the core logic:\n// Anonymous namespace -\u0026gt; essentially \u0026#34;private\u0026#34; helper function namespace { // Helper function to avoid code repetition Vec2f getOverlapHelper(std::shared_ptr\u0026lt;Entity\u0026gt; a, std::shared_ptr\u0026lt;Entity\u0026gt; b, bool usePrevious) { if (a-\u0026gt;hasComponent\u0026lt;CBody\u0026gt;() \u0026amp;\u0026amp; b-\u0026gt;hasComponent\u0026lt;CBody\u0026gt;() \u0026amp;\u0026amp; a-\u0026gt;hasComponent\u0026lt;CTransform\u0026gt;() \u0026amp;\u0026amp; b-\u0026gt;hasComponent\u0026lt;CTransform\u0026gt;()) { const Vec2f positionA = usePrevious ? a-\u0026gt;getComponent\u0026lt;CTransform\u0026gt;().previousPosition : a-\u0026gt;getComponent\u0026lt;CTransform\u0026gt;().position; const Vec2f positionB = usePrevious ? b-\u0026gt;getComponent\u0026lt;CTransform\u0026gt;().previousPosition : b-\u0026gt;getComponent\u0026lt;CTransform\u0026gt;().position; const Vec2f halfSizeA = a-\u0026gt;getComponent\u0026lt;CBody\u0026gt;().bBox.size / 2.0f; const Vec2f halfSizeB = b-\u0026gt;getComponent\u0026lt;CBody\u0026gt;().bBox.size / 2.0f; Vec2f overlap; float diffX = std::abs(positionA.x - positionB.x); overlap.x = halfSizeA.x + halfSizeB.x - diffX; float diffY = std::abs(positionA.y - positionB.y); overlap.y = halfSizeA.y + halfSizeB.y - diffY; return overlap; } std::cerr \u0026lt;\u0026lt; \u0026#34;Physics.cpp: one or both entities lack a CBody or CTransform.\u0026#34; \u0026lt;\u0026lt; std::endl; return Vec2f(0.0f, 0.0f); } } Future Improvements # Several enhancements are planned:\nThe collision handling in the sCollision method iterates through all entities in a nested loop, which can lead to performance bottleneck, especially with a large number of entities. Instead, I could implement spatial partitioning techniques (like quad-trees) to reduce the number of collision checks.\nThe player state management in ScenePlatformer uses a series of if-else statements to determine the player\u0026rsquo;s state based on velocity. This can become cumbersome and error-prone as more states are added. A state machine design pattern could improve clarity and maintainability.\nFor clarity, I have used strings to hold information, for example the names of animations, scenes, and actions and their types (i.e. start or end). However, a more efficient data structure to store the names of actions and animations would be an enum.\nNext Steps # I’m excited to continue developing this engine by implementing a level creation tool and a save system.\nI will then further generalise the engine to facilitate the development of a rhythm-based top-down shooter (think Metal: Hellsinger, neon geometric shapes, and house music!), for which I will also implement a particle effects system, global illumination using radiance cascades, and NPC pathfinding.\nAcknowledgements # Assets used in the project include the Elementals: Leaf Ranger asset pack by chierit, Fantasy World Explosion pack by Kenrick ML, and Super Mario World block and coin sprites.\n","date":"13 August 2024","externalUrl":null,"permalink":"/projects/barebonesengine/","section":"Projects","summary":"","title":"Building BareBones: A 2D Game Engine","type":"projects"},{"content":"","date":"13 August 2024","externalUrl":null,"permalink":"/projects/","section":"Projects","summary":"","title":"Projects","type":"projects"},{"content":"“There was danger, he felt, of overrunning himself, and he had to hold onto his awareness of the present, sensing the blurred deflection of experience, the flowing moment, the continual solidification of that-which-is into the perpetual-was.“\n– from Dune by Frank Herbert.\nTime is a river. # We often hear that life only consists of the present – a series of moments that are past us before we realise they occurred. We stand in a river, and the flowing water is the passing of time. The future and past can never be experienced; we can only experience now. And now. And now. While obvious, it\u0026rsquo;s worth remembering.\nTurn off autopilot. # It’s vital to stop and think about this often, so we remember to appreciate the moment, and not always postpone enjoyment of life to ensure a “better future”. If we are always focused on our future circumstances, we may forget to appreciate what we have already. \u0026ldquo;Living in the future\u0026rdquo; – whether preparing for or worrying about it – is necessary, but potentially very harmful, if left unbounded.\nA net for catching days. # I find regular reflection helps to turn off autopilot: things like journaling, meditation, and being mindful while walking and eating. Journaling is particularly useful – a net for catching days. By criticising my thoughts, feelings, and how I’m spending my time, I get less lost in the frenzy of life. To me, that is so important: to remain aware of your direction, to scrutinise whether this direction is correct, and check whether your actions are in keeping with this direction. In other words, to live with intention.\n","date":"28 February 2024","externalUrl":null,"permalink":"/posts/living_with_intention/","section":"Posts","summary":"","title":"Living with intention","type":"posts"},{"content":"","externalUrl":null,"permalink":"/about/","section":"About","summary":"","title":"About","type":"about"},{"content":"","externalUrl":null,"permalink":"/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"},{"content":"","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"}]