Jump to content
  • Advertisement

crancran

Member
  • Content Count

    159
  • Joined

  • Last visited

Everything posted by crancran

  1. crancran

    General C++ class questions

    Personally, I would tackle your problem a bit differently. I would either use a factory class or at the very least a static factory method that does the load / exception logic outside of the model's constructor. Only once the file could be located and loaded does the factory class or method allocate a model and populate it. No, the problem is how you're declaring the GameWindow variable. I would have expected something more along the lines of the following instead: class Game { public: Game(HINSTANCE hInstance) : gameWindow( new GameWindow( hInstance ) ) {} ~Game() { delete gameWindow; } private: GameWindow *gameWindow; } Or better yet, use a smart_ptr.
  2. I looked over your code and I have a few suggestions / comments. What purpose does making Manager a singleton?  That choice effectively implies your World is a singleton and to me that imposes an unnecessary design restriction that serves no purpose.  It's not uncommon to manage different simultaneous simulations, particularly in game editors.   As an editor example, you have a surface where you render your game world simulation where you're editing the level, one where you render the game world in play-mode, and others where you render things like a material editor or some compositor node hierarchy.  You might actually have different levels loaded all being rendered separately, all which require their own World instance.  In all these situations, you may also have different systems at play and thus each World simulation should be tied to a specific finite list of applicable systems.  A generalized class like Manager to me seems like an unnecessary abstraction, one which a World can fill easily. Also somewhat related, you have several methods on Manager which seem ill-placed like create_entity, add_velocity, and add_position2D.  I would probably suggest something like a ComponentFactory and possibly specializations for each component-type that you interact with to create your components.  The allocation/destruction of entities would be a function of your World or some associated Scene object that your world has a reference to.   In the end, this notion of Manager goes away and it leaves you with a World where you might actually register the systems with instead.  If you truly want to avoid having to register systems with each constructed World, you could use the factory pattern again: class WorldFactory { typedef std::vector<System*> SystemVector; SystemVector systems_; public: //! Register a system with the WorldFactory void registerSystem(System* system); //! Creates a world, adding each registered system to the World's simulation list. World* createWorld(); } I still disagree to a degree because I believe each World should be constructed with its own necessary ticked systems and nothing which is unnecessary, but if you at least want something generalized, this approach at least provides an flexible and useful design pattern instead of the Manager singleton you had.  You decide based on your need whether you create and reuse the same WorldFactory or if you create multiple instances as needed. Why not? Lets take a system that renders a sprite at a specific position.  Such a system checks the entity when the "OnEntityAdded()" callback is fired and looks to see if that entity has a sprite and transform component.  If it does, it adds it to that internal list and if not, it isn't added.  It's assumed at this point that entity has both components.  Then when the "OnEntityRemoved" callback is fired, the system simply removes that entity from any internal lists it maintains because the entity is being removed from the simulation. Just remember that a system doesn't necessarily imply a 1-to-1 relationship to a component.  There are definitely use cases where different component types (where applicable) are processed by the same system.  For example, my RenderSystem knows how to draw 2D sprites and 3D meshes.  While the components for these are different, the same system operates on these.  So internally, that system maintains multiple lists and just handles updating each list accordingly during that system's update tick.   I personally advocate that your components represent the user interface into the underlying framework.  The systems may expose various means for other systems to interact with one another but generally I would say that from a game designer perspective, the component's attributes are their path into influencing behavior.
  3. crancran

    ECS design

    There is also the idea of a layer of indirection and the use of the observer pattern.   Consider for a moment when an entity or component is either added or removed.  In these cases, systems would be notified of the state change, allowing them to deduce whether the entity should remain registered in the system or removed.  It's at this point where the system would also create a single, unified representation of state that it iterates during the frame update.   One benefit here is that your system update loop no longer must be concerned with determining the right entity list to update.  When entities or components are added or removed, that internal unified representation is manipulated, which is the basis for your game update loop for that system.   Now the system's update becomes focused solely about data.  Perhaps some event sourcing system is used to track state change and replicate it to other systems.  Perhaps the system uses incremental small update loops to replicate state from one component to the unified representation.  Perhaps your unified representation holds a weak-reference of sorts to the real components, which allows for quick, easy lookup of the component.  But the goal here is that the system should be as solely based on the unified representation as possible, not the component.   In short, I prefer to view components as simple data bags who's sole responsibility is to seed user state to the simulation and allow easy ways to query and get state from the entity system.  The introduction of the extra layer of abstraction with a unified system keeps system-private state out of the component, allowing the component cache array to remain as small in size and on par with its intent.  Should one system need something specific not exposed by a component from another, perhaps an event should be raised to send that value across systems or merely have system 2 consult system 1 for said data.   The extra layer of abstraction also allows the component and system unified object to change independently of one another.  As new contextual values are determined for the system to operate, the internal representation changes without impact to the component.  The only real consequence here is you need a few loops to copy data periodically, but such loops are again often cache friendly and benefit from parallelism.
  4. crancran

    Avoiding the God Class

    I would say you're already doing quite well by separating distinct units of code into modules.     But be careful not to over think the single responsibility principle or separation of concerns.  Introducing an Engine or Application class that acts as glue between those module units isn't necessarily something which needs to be pruned immediately.  It's often a good way to let new ideas and solutions percolate and as you see enough code of a specific use case building up, refactor that into its own module or class as needed.   At the end of the day, all engines have their fair share of glue code. 
  5. crancran

    entity system questions

      If your sector has a collider component, when new entities collide with it (aka enters the sector), the collision system informs other systems of said event.  Your CutSceneSystem may be interested in collisions between Sectors and Entities and if such a collision event is raised, it gets the current sector id/name, locates the cutscene files and begins their playback for the user experience.  In this case, its a combination of the Physics System and a Game System interacting by way of events/messages/callbacks.   As for gates taking players to new sectors, this again is basically a collider component in the ECS that raises a collision event that causes your SectorTeleportSystem to fire.  It begins by playing a wormhole warp cutscene sequence while the new sector is loaded from disk.  After loading is complete the cutscene sequence ends and you find yourself in the new sector.   As phil_t said, some systems can respond to game world events/triggers but are not necessarily manipulating entities.  The events/triggers they do react to however can very much be something that the entity component system triggered based on some component system that manages a particular game aspect.
  6.   In complex situations where a system requires multiple components to perform it's job, I tend to side on the caching approach.  Basically the system uses the notification callback when entities are added, changed, or destroyed to manage an internal list, a list I prefer to call node list.  How and when you elect to transfer component data to the node list is entirely dependent on how the system must update in accordance with your game loop.  You may need to use a command pattern to delay the update until the system's update loop or it may be safe to immediately update the node list directly.   A render system for example may require a Transform and Renderable.  As entities are added, changed, or destroyed the render system maintains a list of RenderNode instances that basically cache the transform and renderable data from the components.  The render system's update loop uses the RenderNode list rather than the components themselves as the basis for it's update loop iteration to maximize on cache friendliness.   Nothing says a system must maintain a single node list either.  In fact, it really should be a system detail that dictates how many node lists it must maintain in order to efficiently perform it's update phase.  Multiple lists are often used to avoid if/else branch statements inside the loop which can be costly on cache friendliness.
  7.   An entity manager generally doesn't exist more than once, at least not within a given simulation.  So unless we're splitting hairs over terminology here, I certainly would not advocate for such a complex setup with multiple EntityManagers because, if those entities need to interact with entities of another EntityManager; you'll have a bit of work to make that happen.   I would split your problem into two layers.   First, create an empire system that acts as a broker for all entities that exist in a particular empire.  When an empire gets created, this system knows about it.  When an entity is spawned that has an empire component that references a specific empire, this system associates that entity to the given empire.  Any communication among entities within an empire could be funneled through this system since it's goal is to act as a broker.   Next, I would have another system that sits on top of the empire system that acts as a mapper between a Player and an empire or league of empires.  This way if you decide to allow a player to control multiple empires, this system can coordinate that and handles the relationship to a specific player instance.    If you keep your system contracts clean, you should be able to change how an empire works without it impacting it's relationship to the player and vice versa.     A little bit of abstraction can go a long way to keeping code clean, easy to maintain, but also allows to future growth in game concepts.  It's just important not to take it too far to the extreme.
  8. How you interact with the information in-memory at run-time does not have to dictate how you need to serialize it.  For example, if you were using a database to store quest and achievement data, you would typically store that in a normalized manor across several tables.  During the start-up phase of your game, you would read your database and construct in-memory objects of this data that are designed for run-time efficiency. 
  9.   The reason you find so many variants of an entity framework is because there is no standard practice.   As for the queue, you could implement a single or multiple queues depending on your needs.  Chances are a single queue would be sufficient as a starting point and build upon that idea long-term if you find you need to separate the queues for any particular reason.
  10. Correct.  Think of this as a message that contains the id of the entity, the position, and orientation.  The render system takes the entity id, locates the Irrlicht scene entity by a reverse lookup table and then modifies the render scene node appropriately.     Correct.  The wrapper system transfers all information from the entity's components into the Irrlicht scene such as position, orientation, setting appropriate mesh, material, etc.  After all that has occured, this same system could just then call Irrlicht's update to send the render queue to the graphics card and swap buffers.
  11. There are two approaches you can consider for replicating information to your render system and a lot of it depends on how coupled you want your code.   The first approach would be to decouple your entity system and rendering system entirely.  There is lots of benefits to this approach as it will allow you to easily replace your rendering engine with another but does come with it's own set of concerns.  To decouple the two entirely, you would use a command queue where your ECS systems emit commands as they perform various operations.  These commands are placed into the said queue and during the render system's update phase, it would parse these commands and perform the necessary rendering operations.  It's important that all pertinent information be included in the command to avoid the rendering system having a need to query the ECS at all.     The second approach would be to live with the fact there will be some coupling.  In this case, you would have a specialized ECS system or game loop step that runs after you have completed your logical updates and would query various entities and replicate necessary information from the ECS into the render system's scene manager.  This system acts as a wrapper around calling Irrichlt's "render/update" call as it would perform the various scene updates and then render a single frame.   Both approaches have advantages and disadvantages and you can easily change between one or the other.  Pick one that makes the most sense for now and move on.  You can always come back later and change it and improve upon it as the need arises.
  12.   To avoid this, edge cases should be worked in not as hacks to the existing code base but as full fledged iterative parts of the overall solution.  But unfortunately, developers tend to be lazy and take the least path of work due to various reasons and this is one of the largest contributors to the problem you described above.  Whether you're doing something insanely simple or something far more complex and involved, the same series of iterative steps should be considered, validated, and executed.  
  13. If you ever create a simple basic windows based application, you'll notice that shortly after the window has been created, you enter into an infinite loop that looks something like the following: MSG msg; while(GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } This message loop calls GetMessage which blocks until a message arrives on the event queue.  When an event arrives, the loop translates it's information into locale-specific values and then dispatches it to the appropriate receiving window's procedure (WNDPROC) handler.   The above code works well if you want your main UI thread to be dormant while it waits for incoming events from the operating system, but is far from ideal if you want to continue doing things in the loop.  This is where PeekMessage becomes useful. MSG msg; while(!bExit) { while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); if(msg.message == WM_QUIT) { bExit = true; } } /* do your stuff here */ } Whether you're doing console-based or window-based Win32 applications, the above loop will allow you to handle operating system events, allowing the window to remain responsive while also allowing you to perform whatever tasks you need to do for your main loop.     The biggest downside to the above is that if your stuff takes a bit of time per main loop iteration to complete, this can introduce input lag to your window message loop and could still make the window's title reflect "(Not Responding)".  You'll need to experiment to see whether the above approach with PeekMessage will be sufficient for your needs or not.  Chances are it should be more than sufficient at first but as your processing for stuff takes more and more time, you might find the need to split the two loops into two separate threads.  The general idea here is that you'd use the first loop I presented above in conjunction with a bit of logic to capture input from the message loop, convert it into some data structure and dispatch it to a command queue that your second stuff thread inspects during each of it's loop iterations and processes input.     My suggestion is not to introduce any parallel threading to the mix until absolutely necessary as it does add a layer of complexity which can be hard to manage if you're not familiar with multithreading and shared data access requirements across multiple threads.
  14. crancran

    Did I begin learning late?

    Late?  Not even close.  In fact, I'd encourage you not to forget to be a kid either.     Lots of times, avid programmers who start at a young age find themselves buried in learning all this information that is at our finger tips and we forget to be kids and have fun and do the stuff kids do.  One day, that "me" time won't be as prevalent as responsibilities take hold and you start a family and begin in the workforce.   So cherish your time wisely.  If you want to learn how to program, networking or whatever.  Do it for you and not anyone else.  Do it because you want to and not because you feel compelled to prove your self worth to someone else.  But as with anything, moderation and don't forget to have fun along that journey! 
  15. crancran

    Component-Entity game design issue

    What JTippetts described for the animation scenario is just one of several ways to approach the problem.  Another is to approach it in a more event-oriented fashion where the AnimationController subscribes to various events that indicate combat, movement, idle, etc.  When those events are fired, the AnimationController merely updates it's internal state machine.  The state transitions trigger a series of additional events that the AnimatedModel has subscribed to and it plays out those animations.   But in this case, what you've done is abstracted away what really is an explicit dependency between two components.  Sure, one could argue that you could replace the AnimatedModel component with some other behavior that listens to the same series of events and you could then swap between the implementations on a per entity basis.  This has some merit, but such a design should come from actual need rather than over engineering a solution that you may never utilize and you find is much harder to maintain than had you just kept the dependency explicit.   There is no formal approach here and there a literal dozens of ways you can approach this.  Some people advocate duplicating data in cases of transform to avoid cache misses during various update passes while others design some elaborate handle-to-id lookup indirection table so that the cost to get a component of a type on a per-entity basis inside a tight loop of components is minimized.  But in both of these designs, this should be something that comes from profiling to determine whether its really a bottleneck.     You could also have system A that processes component A lookup whether the entity has component B when the system A is told that a component has been added/removed from the entity.  The system maintains a list of entities w/component A & B and a list that has component A but no B.  This way you can do specialized cache friendly operations on the entities with A&B and perhaps other operations cache friendly on those with just A.  This avoids the lookup and branch problems that come up with if-conditions inside loops.   In the end, all this boils down to tuning and profiling more so than the design and implementation of a component entity system.  
  16. crancran

    forget c++

    I would not fret of remembering a syntax of any given language.  With anything you don't use on a regular basis, it will fade.     The crucial part here is to learn the design principles and patterns that cross over into any language.  Just because the implementation of the observer, factory, or insert any other pattern may vary from language to language, the goal and purpose of that pattern remains the same and this is where the biggest value comes from.  Once you recognize that and recognize the importance of Single Responsibility and Separation of Concerns -- there really isn't any problem too big you can't solve regardless of the language's syntax.
  17. Simply have a logic thread system that runs which collects dirty/changed notifications for game objects.  It queues those up on the logic thread and then at a sync point, this system will synchronize with the network thread, swapping the contents of the logic thread's send queue for the network thread's receive queue.  The system can then decode the network packets that were received, translate them into commands and dispatch commands to the logic thread's command queue which will be inspected and interpolated in the current/next frame depending on where you place your network logic system update pass (end of loop vs top of loop); i prefer top right after "input".
  18. A very general term might be Platform API.  If you've ever looked at libraries or commercial products that are developed for multiple systems such as Unix, Windows, consoles and so forth.... the wrapper classes that abstract the various systems is typically part of a Platform library that exposes these systems as a unified API to the remainder of the system.
  19. Just a word of caution to be careful returning raw pointers to objects.  My general preference and what I have always been taught is that an API should return a weak pointer to the object indicating lack of ownership or at the very least have the API return value be a reference rather than the raw pointer like as follows. // PositionComponent& position = mEntityManager.GetComponent<PositionComponent>(entityId); // This way, it's pretty clear that whatever the calling code is that is getting position doesn't own the object but merely holds a reference to it.  Of course, there is nothing stopping the user of the position from turning it into a pointer and calling delete on it, absolutely.  But the above API is more self documenting and is clearer about the ownership of the object.
  20. I prefer to leverage game states as a means to orchestrate chunks of behavior that must coordinate with a broad range of engine subsystems.     For example, you may have a cutscene/movie subsystem that knows how to play avi files and a game state is responsible for checking whether the player has watched the intro movie and if not, instructs the movie subsystem to play it; otherwise transition to the login screen.  The login state is responsible for preparing the login UI with the UI subsystem, perhaps loading the game's theme music and waiting for the user to enter their credentials.  Our entire game menu/settings system which can be invoked from the login screen, character select screen, and while in game is all driven inside the game state system by pushing that state onto the active state list.   Another simple example of the game state system at work is when a user transitions from one map to another.  You simply signal that you need to transition to a loading state, when you enter the loading state you know which map you need to load and so you can display an appropriate loading screen.  During the update loop of this state, perhaps various things are being performed, specific tasks completion statuses being checked and when loading has finished, you pop the state which removes the loading screen and returns the player back to the game play state.  Such a transition disables input to the controllers and perhaps other aspects.   As for event detection/dispatch, the observer pattern is simple and yet powerful.  Most of my subsystems expose some type of listener interface that I just implement where needed.  It's a very simple solution to implement, has minimal overhead and allows you to focus your energy in other places rather than loosing momentum designing something that is likely overkill for where you are at right now.  Then when an event is fired, the class holding the listeners simply iterates the listeners, calls some virtual method and the listener implementation fires it's logic.  :)   Good luck!
  21. You are not likely going to be iterating game objects in the loop but rather their components which make up their full behavior.  Because of this, you will want to store your component structures in some cache efficient way.  As a first pass, a std::vector would suffice.     The next question is where to place these vectors?     I have seen some recommendations where the systems who transform the data own the vectors, but I tend to disagree with this approach.  I don't believe any one system owns this information.  Systems are designed to read/write data, transforming it along the way as a serial set of inputs & outputs; nothing more.  That's not to say these systems won't have their own internal data structures to aid them in their task, but the "input" data from the components feels more like public knowledge information.   I prefer the idea that my game object system owns a series of object pools, each of a specific type of class.  These pools are dynamically created either through code when the engine initializes (core components or those extended by user code) or via scripts.  The transformation subsystems that operate on my components have a central place they interact with to obtain not only information about a game object but any of the components which make up it's total behavior (aka the game object system).   The beauty is you can accomplish this without any Actor class or a base Component class.  That's not to say one shouldn't have them because there are instances where having wrapper classes can simplify an API making it less brittle and easier to alter through code iterations.  But none of what I described implies their necessity at all.
  22. crancran

    MMOs and modern scaling techniques

      Be careful not to confuse client-side rendering concerns with Kylotan's server-side distributed focus.     In your situation, its still important to render the world as it exists on the server within the client but the client can impose some rules which simplify the rendering phase but still doesn't impair the player's ability to find their friend, trade with them, converse via say, whisper, etc or even inspect their gear or what not.     If we were to consider WoW and consider Kylotan's questions, several systems quickly popup that likely are implemented as separate processes on the server side.     The first service is chat.  The actual game client opens a separate connection to a chat service when you enter the game world.  This has two immediate benefits on the server side architecture as that the world servers don't have to worry with any asynchronous operations between processes for chat, and secondly that chat is not a concern of those servers at all.  The chat servers themselves now bear the brunt of logging and any other operation necessary that revolve around chat, including resolving who gets communication sent over /yell and /say based on proximity and distributed communications in public and private channels.   Another distributed service is likely something regarding player inventory.  With the inception of cross-realm services, your game object is interacting with other game objects from other shards in a single hosted world simulation.  But your inventory is still managed by your local realm shard.  Assuming we both played on the same realm and are standing in Goldshire, it's possible that area of the world is hosted by another realm which isn't our home realm, thanks to cross-realms.  Now I go to trade you a pet I recently captured from a pet battle.  The realm that Goldshire is hosted on will receive the trade request, distribute that to the inventory service for our home realm and thus deduct the item from my inventory and add it to yours.   These are two overly simplified examples, but hopefully it puts Kylotan's questions into a better perspective for you.
  23. Look at the situation from the opposing side and it might make a bit more sense.  First, your physics system simply steps your simulation and determines where collisions have happened.  It emits those collisions in some fashion for the related actors and it's done.  You then have other systems that listen or inspect the actors for these collisions and respond accordingly.   Taking your example, the bullets which are to be collected upon collision are actors maintained in a pickup system because they have a pickup component.  Upon a collision situation with these actors, it looks at the collision component to see whom it collided with.  If the collision was with a player, it simply adds itself to the player's inventory in question and then signals the actor (bullet) to be removed.  In the case of shooting a bullet at another player, this bullet is managed by a damage system because it has a damage component.  Upon collision, the damage system determines the collision actor list, calculates the damage and emits the damage caused to the related actors.  The damage system then signals the actor (bullet) to be removed.   Now you can add coins and other pickup items that go into the player's inventory by simply giving them the same pickup component.  The pickup component can have flags that determine what it does upon pickup (add to inventory, increment ammo counter, increment powerup level, etc).  But in the end, the reaction between the actors is identical, collision implies pickup.   Similarly, you can add additional things that do damage such as melee attacks, castable spells, falling rocks, etc.  The damage system is then the place that inspects that collision list of actors for a given actor that can do damage, determines the damage done and emits those events to the related actors.   Like with anything in programming, break things down into their smallest yet reasonable interactions and you'll see how quickly things can come together, particularly when you are working with an actor/entity system.
  24. I would be weary of capturing any default system functionality in an attempt to disable or prevent it's actions.  I've played games that have done just this and find the behavior intrusive and having no merit.  Users should be capable of using their systems as they see fit and it should be their responsibility to make intelligent decisions about bindings, button usage, etc that maximizes their own play style that they find effective; not the game itself.   The best middle ground IMO would be that if you wish to offer such a feature, be sure to make it something that the user can toggle ON/OFF depending on their preferences in some game menu screen.  This way, those who fall into the category of hitting the button mid-combat have a reprieve to their mistake while those who want to keep the default button functionality available can do so.
  25.     Honestly, introducing singletons or static anything in this the mix is a recipe for trouble.  You're only making the solution far more rigid and brittle at the same time which will be inflexible and hard to maintain long-term.     Additionally, systems should be instantiated because they'll likely need to maintain internal state and not introducing singletons or static state implies you can easily run two copies of the system on perhaps different entity manager data sets or different game states and perform multiple simulations in parallel or sequentially without any trouble.     A clear architecture has nothing to do with the design patterns which it uses.  In fact, an architecture tends to be cleaner when the right design pattern is chosen for the problem at hand rather than trying to shoehorn something that doesn't fit due to some bias or other factor.  Opting to use design pattern A for part of a problem and design pattern B for another portion with some design pattern that marries the two is actually very common place in programming and in my experience generally carries considerably more benefits than it does consequences.   I prefer to consider that both a Map and the Player are stand alone classes pertinent to the game engine, core classes if you will.  I then give the Player class an unsigned integer property that is basically the identifier to some game object in the game object system that represents the actual player.  The benefit here is that if the engine operates on the exposed API of the Player class, the engine doesn't care if its an entity or not, nor does it care about the components which make up the player.  With all that abstracted, you can change implementation details of the Player with minimal impact to the engine/game itself.    And as you can see, such an approach does follow your idea of a Character class that knows everything about itself.  The only difference is that rather than the state of a Character being stored in that class specifically, the Character class queries various systems to get it's state based on the method call invoked.   One of the biggest reasons why these systems are great is the focus on data-oriented design.  You store data that you plan to operate on at the same time together in logical chunks, utilizing cache friendly operations, thus boosting performance.  Because you're grouping data oriented things together and decomposing state, you also gain the benefit that it becomes easier to piece features together and morph object state from A to B.  Of course, all this implies a different mindset to understand that you're updating game state in many many tiny stages throughout a single update tick for any given object.     But nothing of the above says you cannot use ECS in conjunction with OOP.  Both are wonderful tools that can and do work well together if you follow the simple rules of programming with separation of concerns, single responsibility principals and data-oriented design goals.  
  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!