Jump to content
  • Advertisement

DrDeath3191

Member
  • Content Count

    61
  • Joined

  • Last visited

Community Reputation

118 Neutral

About DrDeath3191

  • Rank
    Member

Personal Information

  • Role
    Programmer
  • Interests
    Art
    Programming

Recent Profile Visitors

The recent visitors block is disabled and is not being shown to other users.

  1. Sorry for taking so long to get back to you guys; real life got in the way. Adding and removing components is quite important in my case, as I intend to use them to implement character states and status effects. Ad-hoc scripts feels like a band-aid solution in comparison to the system I have now. Speaking of the system I have now, I have found a way to store the different component types in the World; I went all-in on template metaprogramming and implemented a type list, I template the world class based on this type list's contents and store a tuple. All the component tables are in one place, they have the same access speed as though they were members of the World class (which is how I implemented it before), and thanks to using C++17 I can access the elements of the tuple by type, making it quite easy to use! And if I want to apply an operation to all tables (for example removing dead entities), I can just use std::apply. Compile times are probably going to get rough, but I knew that going in. Here's the code for interested parties: template<typename...> struct list_register; template<template<typename...> class List, typename T> struct list_register<List<void>, T> { typedef List<T> type; }; template<template<typename...> class List, typename T, typename... Types> struct list_register<List<Types...>, T> { typedef List<Types..., T> type; }; template<class... Types> struct type_list { typedef World_Templated<Types...> worldType; }; template<> struct type_list<void> {}; #define REGISTERED_COMPONENTS type_list<void> //example of registering a class to the list typedef typename list_register<REGISTERED_COMPONENTS, TransformComponentTable>::type updated_list; #undef REGISTERED_COMPONENTS #define REGISTERED_COMPONENTS updated_list //example of constructing the world with all registered tables REGISTERED_COMPONENTS::worldType testWorld; //very basic World class that just shows that the tables are contained in the tuple as desired, I plan on adding more functionality later //but this is for demonstration purposes template<typename... RegisteredComponentTables> class World_Templated { public: World_Templated() {}; ~World_Templated() {}; private: std::tuple<RegisteredComponentTables...> tables; }; As you can see, it uses a macro. Not a huge fan, but it works. If anyone else has a better implementation of a type list I can register to, I'd love to know. Or if you have any more comments on the ECS design I'm working with in general.
  2. So your approach is similar to this one? Doesn't this version make it difficult to add and remove components at runtime? If your entire system is based on these archetypes, I can't imagine it being particularly tolerant of adding and removing data. That's a pretty big advantage to lose. And if entities share common functionality but are in different archetype groups, you would need to iterate on each group separately, which seems a little bit silly. Also, if multiple systems were to require access to the transform component, you would need to reference it by pointer anyway so that your data is accessed correctly, which is what I would have to do in my version anyway. The relational database like system I have here handles these cases a bit more elegantly as far as I can see. If I need to add or remove functionality, I can add or remove components by just accessing a single table without having to shuffle around anything else. Yes I may have additional lookups to do, but entities with a component signature that match a system's needs will be operated on without hassle. Although I am not primarily concerned with multi-core performance at the moment, I can also easily divvy entities up for processing. That's just my take. I am more than willing to have my perspective changed, though.
  3. Alright, so after a bit of fiddling and an ill-advised attempt to implement my own SOA vector instead of just using a tuple of vectors like a rational person, I have a basic table implementation. It uses CRTP, so no virtual calls are made, and using templates I can create different collections of vectors, add and remove stuff, seems great. I'm going to handle the enum case later. Here's the source for anyone curious: template<typename Derived, typename... ComponentArgs> class ComponentTable { public: std::vector<Entity> entities; std::tuple<std::vector<ComponentArgs>...> componentData; size_t getEntityIndex(Entity e) { return derived().get_index_implementation(e); } void registerEntity(Entity e, ComponentArgs... args) { derived().register_implementation(e, args...); } void unregisterEntity(Entity e) { derived().unregister_implementation(e); } size_t getNumberOfComponents() { return numberOfComponents; } protected: Derived& derived() { return *static_cast<Derived*>(this); } std::unordered_map<Entity, size_t> entityToIndexMap; size_t numberOfComponents = 0; }; template<typename... ComponentArgs> class DefaultComponentTable : public ComponentTable<DefaultComponentTable<ComponentArgs...>, ComponentArgs...> { public: size_t get_index_implementation(Entity e) { auto search = entityToIndexMap.find(e); if (search != entityToIndexMap.end()) { return search->second; } return numberOfComponents; } void register_implementation(Entity e, ComponentArgs... args) { size_t index = getEntityIndex(e); if (index >= entities.size()) { entities.push_back(e); //push_back into componentData std::apply([&](auto&... vecs) { (vecs.push_back(args), ...); }, componentData); } else { //set elements in componentData std::apply([&](auto&... vecs) { ((vecs[index] = args), ...); }, componentData); } if (index == numberOfComponents) { entityToIndexMap.insert(std::make_pair(e, numberOfComponents)); numberOfComponents++; } } void unregister_implementation(Entity e) { size_t removedIndex = getEntityIndex(e); //Entity not present in this table if (removedIndex == numberOfComponents) { return; } size_t last = numberOfComponents - 1; Entity lastEntity = entities[last]; std::swap(entities[removedIndex], entities[last]); //remove data from SOAVector std::apply([&](auto&... vecs) { (std::swap(vecs[removedIndex], vecs[last]), ...); }, componentData); entityToIndexMap[lastEntity] = removedIndex; entityToIndexMap.erase(e); numberOfComponents--; } }; Now however comes a new logistical problem: how should I handle putting them into a World class or similar? As I am using CRTP, I can't just shove all of them into a vector, as they are all completely different classes. I could define some sort of wrapping interface to work around that, but it seems like a hack and would introduce pointers in order to access the tables which would seemingly defeat the purpose of this exercise. I'm not a fan of the World being a singleton, so I'm against using the registration trick usually done for Automatically Registering Factories which seem to depend on such a thing. Perhaps I could have the World class defined as a giant tuple of tables? Should I even worry about having a single repository for all my component tables?
  4. Yes, this was hinted at above. I think that will be the best strategy going forward, but I don't have much experience with template meta-programming. I will hack away at this and let you guys know how I'm progressing. Such as? Mostly for curiosity, since I think I'm on the template train with everyone else in the thread.
  5. Not at all! Enums can essentially just be thought of as integers, so I keep track of the indices where each value of the enum's segment ends in an array. Cast the enum to an index into that array, insert the entity's data into that index of the table, and then increment all later indices by 1. Easy peasy, constant time, no comparisons. I do need to take care that order is maintained when removing entities or they change their enum value, but that just means rotating the vector (std::rotate for people who may be confused) and patching up the index values. Yeah, its the elegant part that tends to be an issue. If I'm reading the two of you correctly (and I'm very likely not), are you perhaps suggesting using the curiously recurring template pattern to create tables with variadic templates, one template per component type? And providing accessors, registers and removers in similar fashion? I just want to be sure, as I'm a little rusty with template magic. Okay, but it's really not the actual form of the data I'm concerned with right now. The reason I was planning on changing the structure was to facilitate creating new tables, possibly without having to write any code. I don't care if the data is a series of separate vectors, a binary blob, a fixed-size array, etc. Hell, if I understood you two correctly, that could just be another templated thing as well. I just want to make adding new component tables easier, since the underlying functionality is mostly similar, without having to lose too many of the advantages of Data Oriented design. You're telling me. Thankfully I can rely on people who have gone before me to provide some useful direction. Thanks again, by the way.
  6. Ehh, I'm not too sure about that. I also have some other methods to the components like accessors, registry and removal. These may need to be slightly different based on the specific needs of the data, meaning I have virtual function pointers to worry about which are not so great performance wise, and definitely to be avoided in data-oriented stuff. No. There's no need for this to be a singleton. Not sure I follow you here. What mechanisms are you talking about? Pretty much, I guess. I can look into it, although I could easily replace the functionality with pointers instead of vectors. Would probably increase my debug build's performance, too. By its very design. Let's say for example I have an attack originating from Team 1 that can hit only teams 2, 3, and 4. I could store an enumeration in the hitbox table, loop through all of them and ask "what team are you?". This would result in a bunch of branches by necessity. However, if the table is sorted in such a way that teams are contiguously aligned, I don't actually need to ask; I know by the index that this is a valid target to check. No branches, and cache friendly to boot. I suppose putting it off to a follow-up step is how I would have to do it, if I pursue this idea further. Ah well, I suppose it isn't too terrible. Well the idea behind DOD is to maximize the efficiency of data traversal. Just because stuff is 'data' doesn't make it DOD. However, I think you may have a point in regards to the systems; a function pointer is not that much more expensive that a direct function call, and it would be rather convenient to add systems that way. If you might be wondering where I'm getting some of my ideas, I modeled my ECS after this article. I of course stopped following before doing the binary blob, but am currently rethinking that decision. Thanks very much for your input!
  7. Hello everyone. I've been working on my game for a while now, and I am beginning to dislike my means of adding new component tables and such to my game. My components at the moment are hard-coded classes formatted as structures of arrays; you can basically think of it as a relational database. Each variable of the component has its own dedicated vector in the table, meaning I can loop through them all in a cache-friendly manner. When it comes to the function in-game, it works nicely. Development, on the other hand, is a little annoying. As I am trying to remain data-oriented, I am avoiding inheritance as much as possible. So each component table is an entirely standalone class. However, the underlying idea of the component table is the same; some tables do have some other tricks (which I will cover later), but the general idea is similar. So I essentially have to copy a lot of my code, add it to my giant World class that has all the component tables, make sure its garbage collection gets called, etc. I feel like this should be made easier. Especially if I wanted to support modding in the future. I do have an idea as to how to possibly improve things, but I figured I should bounce it off people who might have more experience in this field. Essentially my plan is to replace all these component table classes I've made with a single class that stores a contiguous array of bytes with some metadata. So if I have a component table that was modeled something like this: //... std::vector<Entity> entities; std::vector<vec3> positions; std::vector<quat> rotations; std::vector<vec3> scales; //... I would instead model it something like this: //... std::vector<uint8_t> data; size_t entitiesIndex; size_t positionsIndex; size_t rotationsIndex; //... Essentially, I would flatten my structure to a single array, with indexes into that array indicating where each variable can be accessed. Why would this confer any sort of advantage? Because I could then theoretically create a file format that creates these tables based on my specifications. All data fundamentally can be represented as a contiguous array of bytes; all I need to know is the size of the datatype and how many are present in order to get those offsets to point to the correct memory. It would not need any sort of inheritance based structure; its an array of bytes, and an array of offsets. I can just create them and store them in an array in the World class. If I want to get a specific table, I can just index into that array. If I want to use the data in that table, I can just do a memcpy or a reinterpret_cast and process as desired. I've been mulling over this idea for a little while, but I still have some reservations. This has to do with the tricks I mentioned earlier. I don't use enums or bools in the component tables. I use the indices of the components to indicate what their condition is (if the index is less than x, the condition is false, for example). This allows contiguous processing with minimal branching. I would need to make the format aware of this sort of trick. Theoretically, not that hard; I just need to indicate a number subdivisions for the component. However, if I were to create an entity in this fashion, I would need to be aware of runtime information in order to put it in the right place. I suppose I could have some intermediary tables that I put those sorts of components into and then handle it as a follow-up step, but that just seems sloppy. The way I've handled that runtime problem in the past was to have a series of factory classes for each component type that would specifically be passed in that information. I am also hoping to get rid of these; if I am thinking of completely redoing my ECS, this would be the time to lose them. I've yet to think much on how systems would be implemented better; Right now they are implemented as classes that have references to the component tables they are interested in, and a single method. I suppose I could just pass the World a series of function pointers and have them be executed in some given order. But seeing as I am going for something data-oriented, function pointers seem a bit off the path. But maybe I'm being silly. Has anybody here implemented an ECS in a similar fashion? Is it worth my time to try to rewrite my entire ECS structure in this way? Is what I want even feasible or wise? How would you tackle this problem? I hope I expressed myself clearly enough; the thought of this has wracked my brain for a bit, so I might be a little caught in my own world here. Thanks for the help.
  8. It wasn't so much about it looking better so much as it was looking different: I like the appeal of 2D, and thought this would be a novel way to approach it, and I could get a painterly character that appears 3D. Perhaps the reason it's so novel is because it isn't a particularly good idea. As for the performance side of things that would depend on the complexity of the model and rigging, but I think you may be right on general principle. Certainly the texture size would be a concern if I go for the painterly look. Yeah, hearing from 2 different voices has convinced me to shelve the idea for the moment. Maybe I'll play around with idea more later, but I think looking into painterly NPR is going to be my best bet.
  9. I wasn't too concerned about the alpha blending. I'm not developing for phones, and most desktop GPUs can handle a large number of sprites with minimal issue. As for lookup, the vector rotation method works, I just thought I could do it smarter with quaternion comparison instead. But the sheer amount of texture work required is the major issue. With 45 degree increments in both axes, after removing redundant sprites, my test sprite has 26 sprites. If I wanted to move to 22.5 degree increments for smoother motion, I would need to almost quadruple that. And that would be for a single body part! Granted, my current test is not symmetrical on either axis for exactly the purpose of testing that. I could also reduce the rotational definition in certain sprites and axes (you won't often see the bottom of a character's foot, after all). I was hoping to use my 45 degree increments to cheat my way to the answer. I'll look more into imposters, but it seems to me I'll need to abandon this enterprise for now. Sometimes you just need another person to confirm. Thanks!
  10. Hello everyone; I had a bit of trouble deciding where this needed to be posted; although there is quite a bit of math involved, in the end this is about graphics, and some aesthetic concerns need to be taken into account. If this is in the wrong place, I apologize. There's quite a bit to this topic and quite a few questions, so please forgive the length. So, what I am attempting to do is take a 3D rotation represented as a quaternion and use that to pick an appropriate sprite from a sheet. Think of it as a more complicated form of what was done in the original DOOM: I want to use the y rotation and x rotation to pick the corresponding sprite, then use the remaining z rotation to orient that sprite appropriately. I'm not sure if this has been done before, but if someone has, I'd love to see it. I currently have a system that works, but I think it could be better. I currently have a simple sprite that is rotated in the x and y axes in 45 degree increments. I calculate the appropriate quaternion, and rotate the unit Z vector by it, and store this away. When I render the sprite in question, I get its current rotation, rotate the unit Z vector by it and compare the dot products of it and the vectors I stored away. I use the texture coordinates corresponding the the vector with the highest dot product. I then take the difference between the closest rotation quaternion and the actual rotation and find the component around the respective stored vector using swing twist decomposition. I extract the angle of this quaternion from its w component, giving me the z rotation of the sprite. As I said, I think it could be better; it seems kind of silly to me to need to store those rotated Z vectors when I should just be able to compare the quaternions directly. There is a reason for them however; certain rotations are completely equivalent barring a rotation around z, which I'm hoping to extract separately regardless. Thus, I actually want to find the closest rotation irrespective of Z. However, I am struggling to find a way to make this work with only quaternions, as different rotations could wind up with the same orientation. I'm sure I just need to take the portion of the actual rotation around a certain axis before comparing with the stored ones, but I'm struggling to know what axis that is. No matter what I seem to do, it seems to pick the wrong sprite at certain times. Another concern I have is smoothness. Obviously, 45 degree angle changes, even with a completely fluid Z axis, would still look a little jarring in motion. My game runs at a 60 Hz timestep, so my rendering rate would be at least that high. How small a rotation angle would I need to have an appealing turnaround at that rate? Or, perhaps better, is there some clever math or perspective trick I can do to fake it? I render with orthographic projection, but maybe I can pull a clever math trick? The reason I'm doing this at all is because I was hoping to combine this with skeletal animation. Typical 2D skeletal animations look sort of like paper dolls, mostly because the bones can only turn on a single axis. I was thinking I could leverage the bone animation in something like Blender and use those rotations with this sort of sprite high resolution character sprites that animate fairly smoothly and look good from literally any angle. But my fundamental question, at the end of the day, is this: is this actually worth my time to pursue further, given how much of a headache it has already been? I mean, I still think its a neat idea, but I might just be better off modelling everything in Blender in the first place. Even if I were to reduce the number of orientations to the bare minimum, removing symmetrical cases and orientations in which a sprite would never be seen, that would still be a lot of sprites for a single character. My time might be better used elsewhere. I've been thinking about these issues in a vacuum, so I was hoping to get input from other people before I drive myself to the brink of insanity trying to do something that may not be as clever as I thought. Hopefully I explained everything satisfactorily. Thanks for your help.
  11. DrDeath3191

    Projectile Motion not behaving as expected

    RK4 seems to get me to the same result as well. That settles it; integration is the issue. I think I'll stick with semi-implicit Euler with the subtraction for now; RK4 is kind of excessive, and with that initial subtraction I get similar results anyway. I will reevaluate my decision if I continue to have physics issues. Thank you very much for your help!
  12. DrDeath3191

    Projectile Motion not behaving as expected

    No I didn't. As previously mentioned, I am using a fixed timestep. My unit of time for these calculations is logical ticks, so dt is always 1.
  13. DrDeath3191

    Projectile Motion not behaving as expected

    I overshoot significantly. Explicit Euler has a tendency to add energy, so this is not too surprising. You and me both, and specifically by one frame as well. Makes me think there is some sort of logic or math error I'm not seeing. However: This solution works as well. So I guess there was an integration problem after all? It wasn't mentioned on the page that this could be an issue. But then again it's aimed at Unity developers, and I don't know how Unity integrates its physics.
  14. DrDeath3191

    Projectile Motion not behaving as expected

    Explicit Euler treats velocity and position as independent variables, whereas semi-implicit Euler calculates the position using an updated velocity value. In simpler terms; in explicit Euler you calculate your position then your velocity, semi-implicit is the other way around. Many game engines use this integration method because it is more accurate than explicit Euler, but not as resource intensive as something like RK4. Not to mention dead simple to implement.
  15. Hello, So I've been looking into the kinematic equations for the purposes of projectile motion. I specifically needed to hit a specific point at a specific time, so I am using the formula listed under "lateral speed" on this page. I am solving for the initial velocity and acceleration values. My game uses a fixed timestep of 60 ticks per second, and I am integrating my physics using semi-implicit Euler. By all accounts this should work fine on its own, yet it does not. If I were to plug in the found values into the kinematic equation x = x0 + vt + 1/2at^2 (I apologize for not using LaTeX; it doesn't show a preview when writing this comment), it would land precisely where I want it to. However, if I evaluate it per frame, I get significantly off. Here's the code for how I launched the projectile, including the code I was using to test where it would be during its flight time: float t = 15.0f; // t is in frames float targetHeight = -250.0f; float yAcceleration = 0.0f; glm::vec3 diff = targetPosition - initialPosition; glm::vec3 airDisplacement = glm::dot(diff, WORLD_UP) * WORLD_UP; glm::vec3 groundDisplacement = diff - airDisplacement; glm::vec3 xVel = groundDisplacement / t; //got the following from https://www.forrestthewoods.com/blog/solving_ballistic_trajectories/, //with slight adjustment to account for a negated y coordinate float a = glm::dot(initialPosition, WORLD_UP); float b = a + targetHeight; float c = glm::dot(targetPosition, WORLD_UP); yAcceleration = -4 * (a - (2 * b) + c) / (t * t); float yVal = ((3 * a) - (4 * b) + c) / (t); glm::vec3 yVel = WORLD_UP * yVal; glm::vec3 newVelocity = xVel + yVel; glm::vec3 accelerationVector = WORLD_UP * yAcceleration; //Testing code to see what the position should be at each frame //The first loop is how it would look according the equation of motion, //the second is pretty much how it would work in-game glm::vec3 priorPrediction = initialPosition; float testEnd = t; for (float testT = 1.0f; testT <= testEnd; testT++) { glm::vec3 predictedPosition = initialPosition + (newVelocity * testT) + (0.5f * accelerationVector) * (testT * testT); glm::vec3 predictionDiff = predictedPosition - priorPrediction; printf("Predicted Position in %f frames: (%f, %f, %f)\n", testT, predictedPosition.x, predictedPosition.y, predictedPosition.z); priorPrediction = predictedPosition; } priorPrediction = initialPosition; glm::vec3 v = newVelocity; for (float testT = 1.0f; testT <= testEnd; testT++) { v += accelerationVector; glm::vec3 predictedPosition = priorPrediction + v; glm::vec3 predictionDiff = predictedPosition - priorPrediction; printf("Predicted (Step) Position in %f frames: (%f, %f, %f)\n", testT, predictedPosition.x, predictedPosition.y, predictedPosition.z); priorPrediction = predictedPosition; } When the step integrated solution was evaluated, the ground position was always where it was supposed to be (barring some floating point error, not even all that noticeable). The position in the air, however seemed as though it was too low; specifically as though it was arriving one frame prior to when it should arrive. So, I edited the calculation of yVal above as follows: float offsetT = t + 1; yAcceleration = -4 * (a - (2 * b) + c) / (offsetT * offsetT); float yVal = ((3 * a) - (4 * b) + c) / (offsetT); And what do you know, now it works exactly how I want. It arrives at the target position in exactly 15 frames as I intended. I should be happy with this, but I am hesitant to accept a solution if I don't understand why it works, especially since it actually doesn't line up in the kinematic equation. So that's my question; why is it that the y velocity calculation seems to be one frame too fast in this implementation?
  • 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!