As was foretold, we've added advertisements to the forums! If you have questions, or if you encounter any bugs, please visit this thread: https://forums.penny-arcade.com/discussion/240191/forum-advertisement-faq-and-reports-thread/

[Game Dev] I don't have a publisher. What I do have are a very particular set of skills.

1606163656692

Posts

  • KupiKupi Registered User regular
    Kupi's Weekly Friday Status Report

    My goal from two weeks ago was somewhat ambitious: get to the point where I could run a program that, using my own game files, jumps straight into gameplay defined by content files. And thanks to having two weeks to do it, I managed to accomplish that goal! ... sort of. Technically.

    See, I kind of forgot to update my project editor to support the new paradigm for content loading, and then edit my test project files to actually load that content. So, I've confirmed through a debug session that my framework correctly loads a level file given by the command line or the configuration file, extracts a context, populates that context with system instances, and then runs the systems in the contexts ad infinitum. The problem is that my test set currently loads a base-class context instance with no systems in it, and none of my "batteries included" components have required systems defined. So the data is all there, but the game doesn't actually do anything with it. It just renders an open field of Cornflower Blue.

    Suffice it to say, my task for the coming week will be to patch up the Project Editor so that I can make an honest test of the content loading pipeline.

    ---

    In unrelated game-development news, my sister is currently in the process of making a pretty severe career swing from zoology (in which she has her degree) to programming (which she fell into after turning out to be the most intelligent person at a major multinational company's finance department and self-tutoring herself to proficiency in SQL). She's currently exploring job opportunities in data analysis, most of which ask that you work with Python. To that end, she's been learning Python from various tutorials online, and has started to build a basic choose-your-own-adventure game. She walked me through the first couple decisions, most of which involve typing a (l)etter that indicates your choice. After choosing to equip myself with a (f)lashlight, turn (b)ack when I heard a strange noise coming from up ahead, and then (r)un away as it got closer, I was faced with a woman on a motor scooter demanding that I guess what her favorite programming language is. I said "Python". My sister typed "python".

    And it crashed.

    It never fails~

    My favorite musical instrument is the air-raid siren.
  • eelektrikeelektrik Southern CaliforniaRegistered User regular
    In my senior year at university, and for my two-quarter long capstone game class we have a team of 4 making this game idea I had before of making a twin-stick shooter inspired by tabletop RPGs where you control a D20 rolling around the level by tilting it.

    Short clip of our current progress
    https://youtu.be/3Gi_lGRbxIU

    (She/Her)
  • KupiKupi Registered User regular
    Kupi's Weekly Friday Status Report

    My goal was to get to the point where I could load up a level from a file at program start, basically breaking through to the point where I'm doing game programming rather than architectural work, and, well, the old saw about the last 10% being 90% of the work applies as well as ever and also I'm lazy. There turned out to be a bunch of small tasks I'd left unfinished that had to be resolved, and I didn't get through them all. These included:

    - I started working on a "buffered component query" where the Components are returned in an array (which lets you do repeat random access without going through several layers of dictionary lookups each time). I stopped working on that when I realized that it wasn't necessary to what I was trying to accomplish (rewriting the sprite drawing system), so right now there's a NotImplementedException sitting in a deep stratum of the code waiting to be rediscovered at an inopportune time.
    - I rewrote the sprite drawing system, because it turned out that my original one assumed a completely static camera. The new system actually takes the position of a camera component into account, so you can display objects that are further than a screen's width from the origin.
    - I applied some class attributes that make sure that, for instance, when you have an entity with a sprite component, you always get a sprite drawing system to draw them.
    - Some work in the level editor to editor some attributes of a level that I forgot to give you the ability to edit before.
    - And numerous other one-liner edits not worth your or my time to document.

    On the design side, I think I'm going to wind up abandoning my attempt to have just one single "Project Editor" that I could use to edit content files for games made with my architecture. The idea was to load the binaries with the game code in them so I could display the classes they defined in the editor, but so far all attempts to load code from a different directory than the one I'm currently in have resulted in access denial errors. I'm sure there's some way around it through code signing or whatever, but it's far, far easier to simply turn my project editor into a library and create a custom editor application for each game that references the same libraries as the game. I lose the ability to let people mod my games in terms of behavior, but at the same time having my code load up code from a third party that runs on someone else's machine sounds like more than I want to be responsible for, and console makers hate it when you become an arbitrary code execution vector. It's not as though I'm going to be making a hundred games on this platform, so giving each of them a separate editor project isn't a huge tax.

    This week's goal is the same as the last three weeks: hit "debug" and make a game show up.

    My favorite musical instrument is the air-raid siren.
  • RoyceSraphimRoyceSraphim Registered User regular
    Trying to make incremental progress and tossed in a stock command.

    Said command worked, and bouncing is working normally?

    Progress?

  • GlalGlal AiredaleRegistered User regular
  • IzzimachIzzimach Fighter/Mage/Chef Registered User regular

    Since I actually got some things done this week I"ll write a little end-of-week post, Kupi-style.

    I set up the "world map" which is just a bunch of randomly arranged hexagons really. Dimmed out hexes are currently unavailable. Each hex is basically a level with a little mini-quest; when you complete the quest for that hex all the adjacent hexes are made available. In this way the player gets some choice about where to go next without getting overwhelmed. The map doubles as a quest log since you can see which hexes are "done"

    8uu1d554w9fr.jpg

    The second thing I did was set up objects that the player can interact or talk with. The screenshot below is a spirit that grants the player the ability to shapeshift into an animal. (a bear in this case).

    The different animal shapes are used to complete stages of puzzles. I made this concept on a smaller scale in a game called Chartreuse Warden, the second game in this list here

    1qbhbz3x8rqi.jpg

  • KupiKupi Registered User regular
    oh god name recognition

    Kupi's Weekly Friday Status Report

    Apropos of that "perfectionism" video, I am now further from my goal of loading into a playable level than I was when I started, because I realized that the way I handle level scrolling was philosophically flawed and had to be replaced entirely. Without going into too much detail, I realized that when my level-scrolling system detects movement on a particular axis, even if it's operating on a small subset of objects that could potentially spawn in, it's still performing a sweep that scans all the way across the entire world on that axis. Worse, there was no way to reasonably initialize the set of spawners that were already within the window without, again, scanning across potentially the entire world.

    Fortunately, I've already devised a system for fast detection of objects in a specific window in 2D space for my collision detection system, and it's no trouble to adapt it for the purpose of determining which entities ought to spawn in. Unfortunately, I realized that my sliding-window system has another problem, which I've dubbed the "Flaky Goomba Problem". Consider this situation, expressed in the recognizable terms of Super Mario Brothers: on a particular level, there's a recess one block deep, in which a single Goomba plods back and forth, bouncing against the walls and reversing course, as Goombas do. An inquisitive player discovers that if they stand on a particular block for several seconds before advancing, the Goomba doesn't appear in the pit. But why?

    Here's a picture to help visualize it:

    c0ekid4pka6k.png

    In this situation, the scrolling window (the dotted line) has advanced far enough to spawn in the Goomba, but not far enough to spawn in the wall on the other side of the pit. The Goomba, now in play, ambles forward, bounces off of the wall, and walks back, off the side of the world, and eventually hits the point where the game decides to delete it from play. Since the window never crosses back over the spawner, the pit is just mysteriously empty when Mario gets to it.

    I'm considering multiple solutions to this. The first one that occurred to me was to have two different level scrolling windows, one of which is further out from the camera and spawns in "passive" objects like terrain, while the other is closer and spawns "active" objects like enemies. That way I can be reasonably certain that solid objects on which moving objects rely will be loaded before the moving things need them. Another possibility is the kind of "area gates" that Valve's Source Engine uses that associate all objects in a particular area with a common bounding box. In that situation, the Goomba and all of the blocks in the pit load in at the same time, or something close to it. And a third possibility occurred to me while I was making dinner just before writing this, as the phrase "the way Unity does it" floated through my head, which is not to bother with all this "spawn/despawn" nonsense, and instead have an "enabled/disabled" switch on Entities, wherein Components associated with an Entity that's marked as disabled won't be returned in queries targeting those components. All entities start disabled, and are selectively enabled as the window crosses over them. (This suggests the existence of a component query that can disregard the enabled/disabled flag.) We still save on the computation involved in any Component that isn't actually in play, but this also means that far more objects will be in memory at the same time, and Component queries will mostly consist of scrolling past disabled entities. That sounds... not great, performance-wise, but I genuinely don't know how many entities I'm actually going to have in a single level.

    Napkin math! Let's say that we're using 32-pixel tiles. According to Microsoft Paint, this map of Ice Cap Zone Act 2 built with the original Sonic 3 sprites is 16832 pixels wide and 3072 pixels tall, which is 526x96 in tile scale. If we utterly saturate the map with one-tile objects, that's 50496 entities. I have no idea how to feel about that number. That feels like a large number of things to 95% disregard on each individual query, but that also sounds like a small number to a computer.

    This is going to take more thought than I want to put into it tonight. My goal for this week is to think about ↑↑↑ all that stuff.

    My favorite musical instrument is the air-raid siren.
  • CornucopiistCornucopiist Registered User regular
    Kupi wrote: »

    I'm considering multiple solutions to this. The first one that occurred to me was to have two different level scrolling windows, one of which is further out from the camera and spawns in "passive" objects like terrain, while the other is closer and spawns "active" objects like enemies. That way I can be reasonably certain that solid objects on which moving objects rely will be loaded before the moving things need them.

    That just reduces your problem to any system depending only on passive objects. Any 'active' interaction (say a goomba bucket chain) still won't work properly.
    Kupi wrote: »
    Another possibility is the kind of "area gates" that Valve's Source Engine uses that associate all objects in a particular area with a common bounding box. In that situation, the Goomba and all of the blocks in the pit load in at the same time, or something close to it.

    This is again only a reduction but it makes more sense. If you never design a completely open world level (say with a group of wandering traders that really exchange items across the map) you won't have issues.

    Kupi wrote: »
    And a third possibility occurred to me while I was making dinner just before writing this, as the phrase "the way Unity does it" floated through my head...
    ...This is going to take more thought than I want to put into it tonight. My goal for this week is to think about ↑↑↑ all that stuff.

    This is a bit of a mix and I'm pretty sure I don't understand all the ramifications.
    Unity does allow you to spawn ridiculous amounts of entities and then only enable the render component of any that are in your view window (or by distance to camera) . However they were not done porting their physics to DOTS (ex-ECS) it seems until like literally a month ago. I've not implemented it yet so no idea if physics are turned off in the distance or not. I will say one thing; Unitys non-DOTS physics will laugh at your 50496 entities even on a phone. 50496 is for babies! So I'm wondering what functions you're trying to optimize here?

  • IzzimachIzzimach Fighter/Mage/Chef Registered User regular

    Ok, this week I roughed out a little intro area with three starting animals to choose from: Bear, Badger, and Raccoon. They are the glowing blobs surrounded by particles off in the distance. Meanwhile I'm trying to talk to this winged man-lion (a Lamassu)

    bmg6pzn467fb.jpg

    I have a dialog system with the standard "vertical list of responses" layout that so many games use. It works so far but the default UE4 text box only supports one font and no inline bitmaps. I'm going to investigate using the Rich Text widget instead.

    6dy25hekiwb6.jpg

  • KupiKupi Registered User regular
    edited November 2019
    Kupi wrote: »

    I'm considering multiple solutions to this. The first one that occurred to me was to have two different level scrolling windows, one of which is further out from the camera and spawns in "passive" objects like terrain, while the other is closer and spawns "active" objects like enemies. That way I can be reasonably certain that solid objects on which moving objects rely will be loaded before the moving things need them.

    That just reduces your problem to any system depending only on passive objects. Any 'active' interaction (say a goomba bucket chain) still won't work properly.
    Kupi wrote: »
    Another possibility is the kind of "area gates" that Valve's Source Engine uses that associate all objects in a particular area with a common bounding box. In that situation, the Goomba and all of the blocks in the pit load in at the same time, or something close to it.

    This is again only a reduction but it makes more sense. If you never design a completely open world level (say with a group of wandering traders that really exchange items across the map) you won't have issues.

    Kupi wrote: »
    And a third possibility occurred to me while I was making dinner just before writing this, as the phrase "the way Unity does it" floated through my head...
    ...This is going to take more thought than I want to put into it tonight. My goal for this week is to think about ↑↑↑ all that stuff.

    This is a bit of a mix and I'm pretty sure I don't understand all the ramifications.
    Unity does allow you to spawn ridiculous amounts of entities and then only enable the render component of any that are in your view window (or by distance to camera) . However they were not done porting their physics to DOTS (ex-ECS) it seems until like literally a month ago. I've not implemented it yet so no idea if physics are turned off in the distance or not. I will say one thing; Unitys non-DOTS physics will laugh at your 50496 entities even on a phone. 50496 is for babies! So I'm wondering what functions you're trying to optimize here?

    There are two things I'm trying to accomplish:

    1) Maintain performance by continually operating on small data sets. Yes yes premature optimization blah blah, but doing it the right way the first time lets you write your program one time instead of three times. (As a reminder, I'm building my own ECS architecture on top of MonoGame, not working with Unity.)
    1a) I am admittedly probably excessively skittish after my collision detection engine gave me frame-skips because I did an assignment through an array accessor (which is the most bizarre performance hiccup I've ever seen).
    2) Stabilize both the act of designing and playing the game's levels by making individual segments of the game react predictably when they're approached the same way, which is harder to do when everything is moving from the start of play.

    ***

    Kupi's Weekly Friday Status Report

    Can you load into play from a level file yet?!

    Not for want of trying! Somewhere around Wednesday evening I realized that all the design work I was doing on the level scrolling system had absolutely nothing to do with my stated goal of loading into gameplay from a level file, so I drew up a "rush plan" to get that done in my Thursday evening. I changed everything in my test project to load globally and spent several hours debugging random small things that had fallen out of alignment, until I hit the big showstopper-- a while back I decided to revise how I was handling my camera logic (using a transform matrix rather than applying the camera's position as an offset to the position each sprite was drawn at. As a consequence, I have no idea where my sprites are. They could be just offscreen. They could be somewhere in orbit around Pluto. They could be exactly where they're supposed to be except flipped over the y-axis because XNA uses CRT pixel coordinates instead of the way any reasonable human being considers how positional coordinates are supposed to work.

    The data is all there, things are happening the way they're supposed to, but I can't see it happening, so no points for this week. If and only if I can find where I put Sonic wagging his finger back and forth will I move on to trying to figure out if my current design for level scrolling is actually worth pursuing.

    UPDATE: After having just a teeny little anxiety attack that I worked through with the help of a friend, I did some early-morning coding and got the sprites to show up:

    eob9j5ojw1e4.png

    Sonic is completely out of position and those blocks are supposed to be on the ground instead of the air, but fucking whatever, man, the sprites are there. I can load into gameplay from program start.

    Kupi on
    My favorite musical instrument is the air-raid siren.
  • HandkorHandkor Registered User regular
    Epic was doing their 5th annual Megajam and again this year I participated.
    I had some anime SFX that I'd been wanted to use for something and figured this was a good time. Too bad I ran a bit shot on time and did have time to actually implement all the attacks like the rocket punch, sword and shield.

    Here is a video of my son playing the game
    https://www.youtube.com/watch?v=oqm-vPVNKc4

  • eelektrikeelektrik Southern CaliforniaRegistered User regular
    Week 9 of my senior capstone game project. Coming to the end of the first 10 week quarter, and have another 10 weeks to work on it in winter quarter.

    https://www.youtube.com/watch?v=kPAWIRsqNpw

    (She/Her)
  • KupiKupi Registered User regular
    Kupi's Weekly Friday Status Report

    Duly chastened by Cornucopiist's frame-challenge of my level scrolling system, this week I set out to create a stress-test for my architecture and see if all that goomba moistening business was really necessary. The results were... not encouraging.

    With the same thoughtlessness that got me into this mess originally, I went with the first idea I had, which was to create randomized mazes that little ants would bounce around it, which would provide a nice dense saturation of entities trying to collide with one another without being unnaturally dense (like, placing all the matter in the universe in the same gameplay tile would not be a fair test of the system's capabilities). I managed to knock the maze generation out in an hour using "Prim's algorithm", which is basically "until you run out of walls, pick a random tile already in the maze and knock out one of its walls that doesn't connect to a tile already in the maze until you run out of walls that qualify". The result looks something like this:

    7wxuldh02s0l.png

    I discovered two things when I placed one ant in each tile that didn't have a wall tile in it:

    1) The engine managed about 12 FPS.
    2) I made a hilarious blunder in the depenetration phase of the collision and movement system.

    The second one is more fun to talk about, so let's go into that first. While I do my level best to keep collision volumes with a "blocking" interaction type from intersecting, mathematical tricks and forces beyond the collision system's control can conspire to leave them overlapping. There are various strategies for dealing with this; the one I've chosen is to shift one or both of the volumes involved in an illegal overlap by a few pixels until either they don't overlap any more or we hit an iteration cap, at which point one or both of the volumes involved is marked as "crushed" and stops participating in further collision for that frame. This movement, like other blocking interactions, is subject to what I've called "push priority". Rather than use a mass-based physics system (which is above my pay grade), I've gone with a simpler concept of weight where the object with a higher push priority pushes around those with lower ones. If a gnat and a shipping tanker collide in my engine and the gnat has a push priority of 2 and the tanker has a push priority of 1, the tanker gets pushed backward. There is one special rule involved, which is that entities without an associated velocity have an effectively infinite push priority. Whatever their defined push priority is ignored; if they lack a velocity, they win.

    ... well, they win during the main physics phase. It turns out that I forgot to implement the "infinite push priority for no velocity" rule in the depenetration phase. So it's possible to shift a completely immobile object by slamming into it with enough velocity, meaning every time my ants bounce off of a wall, they move by one pixel. Given enough time...

    as6rlv8cih3d.png

    ... the ants dig their way out. Appropriate.

    As for the chugging framerate, I've discovered and fixed a number of performance blunders like using a really bad collision test for one particular combination of volume types and completely neglecting to perform a bounding box test before doing more detailed collision, which wasn't quite as catastrophic as it sounds because I was already subdividing the problem space significantly, but there was still a visible improvement by adding the bounding box text to individual volume pair checks.

    My goal for this week is to keep knocking down tall poppies until I either get an acceptable framerate back or I hit the New Year and declare 2019 yet another wasted year of my life, give up, and go back to either Unity or Unreal as long as it takes to get frustrated with them and move to Minnesota to live as a hermit.

    My favorite musical instrument is the air-raid siren.
  • CornucopiistCornucopiist Registered User regular
    Kupi wrote: »
    Kupi's Weekly Friday Status Report
    As for the chugging framerate, I've discovered and fixed a number of performance blunders like using a really bad collision test for one particular combination of volume types and completely neglecting to perform a bounding box test before doing more detailed collision, which wasn't quite as catastrophic as it sounds because I was already subdividing the problem space significantly, but there was still a visible improvement by adding the bounding box text to individual volume pair checks.

    My goal for this week is to keep knocking down tall poppies until I either get an acceptable framerate back or I hit the New Year and declare 2019 yet another wasted year of my life, give up, and go back to either Unity or Unreal as long as it takes to get frustrated with them and move to Minnesota to live as a hermit.

    So, one reason why I'm rolling my own landscape generator (now nearly featuring A*!) is that I wanted to have arrays (coming down from lists which are supposedly faster but that's another topic) of stuff like for example blocks. Loads of blocks!
    Partially this is so I wouldn't have to do physics at all except for anything directly around the player, but still have things happening at various levels of granularity. Though of course Unity has crazy fast physics to I've yet to see if my concept/implementation is going to be faster.
    Would it be useful to have good-enough grid based interactions (with perhaps some randomizing thrown in for certain cases i.e. slopes and irregular edges and goomba interaction) across your whole map, and physics only where it's (nearly) visible and you need better shape/edge detection?
    The roaming goombas etc all seem to be plausibly possible to simulate. Especially in an ECS system where you operate on entities anyway- oh my god I'm going to create Conways infinite rule set aren't I?



  • eelektrikeelektrik Southern CaliforniaRegistered User regular
    Another progress build video, this is the week before finals week. Still more we could add or fix before we present next Wednesday to end this quarter but I am happy with the progress so far.

    https://youtu.be/p-i7k5Cp5E4

    (She/Her)
  • CornucopiistCornucopiist Registered User regular
    habxph0qo3hp.png

    #Gamedev
    Finally got A* to work in the RPG project... for the moment this is just a diversion. I'd love to use this to lay pipelines and monorails and whatnot in my FNS cityscape and later on Turbinehead), but I'm far from that sort of stuff, and I want to focus FNS development to get gameplay in and release early Q2 2020.
    Probably the first part of this that will see the light of day is a follow-up to ChooChoo after FNS is done.
    Still, it's a good tool to get under my belt, and a few things I picked up are probably going to still get into FNS. Vector2Int, for one, which I can't believe no tutorial uses, but also using my own classes combining Vector2Ints with ints for terrain type etc.
    The cities are really too regularly distributed at the moment, so the next step is probably going to be to figure out how to get them to seek and settle on the closest geographical features such as lakesides, river outlets,...
    After that I have to look into city generation. I have a neat set of functions that make nice labyrinths, but they're a bit too random for city gen.

  • IzzimachIzzimach Fighter/Mage/Chef Registered User regular

    So I got the Twine import kind of working. I exported the Twine data as JSON and imported it into Unreal Engine as a data table. That wasn't too bad.

    The hard part was parsing the Twine "code" with macro and such and ALSO using the RichTextBlock in UE4 to display the text. There is a huge pile of C++ macros and templates to wade through. But at long last, I made it so that clicking on a link calls a blueprint function, yay!

    Below is the text in Twine and the corresponding display in a UE4 RichTextBlock. Not sure if I will keep the link inline with text or just shove them all to the bottom.

    kclmavlazbyj.png

  • KupiKupi Registered User regular
    edited December 2019
    Kupi's Weekly Friday Status Report

    It seems like every week I piss and moan about how ugh it was such a bad week I'm so sorry everyone, but this week, oh man, I fixed so much broken stuff aaaaaaaand the performance is still nowhere near acceptable but we're gonna start out with the good news.

    For a while I've been noticing that the memory profiler would log a garbage collection about every ten seconds or so. Nothing I could actually see affecting the framerate, so I let it slide, figuring that it was just an incidental GC that happened because the runtime demands that GCs happen every so often no matter what. (That I believed this is in direct contradiction to the truth that I'd already internalized that every extant CLR GCs on allocations only is an embarrassment.) Well, when I ramped my stress test up to the next order of magnitude (a 100x100 maze instead of a 20x20 maze), all of a sudden the garbage collector went berserk. But, weirdly, it only went berserk with a very specific combination of systems active: collision detection, sprite animation, assign-sprite-from-animation-to-sprite-displaying-component, and make-ants-that-hit-walls-bounce. Only if all four were in play. Remove any one of them, and the GC went back to every ten seconds or so. I also determined that if I removed two very specific lines from the sprite animation system (the simplest one to use individually), I could get the GC to stop running entirely. I had a bit of a despair attack when I realized that the "only" thing those two methods had in common was the use of the typeof() operator. And if I can't use typeof() without making garbage, then the whole system falls apart and that "hermit in Minnesota" plan starts looking really attractive. BUT! "Select() isn't broken", as they say, and while typeof() isn't contractually obligated to give you a cached instance of a Type object every time you run it, allocating a new instance each time is performance stupid in a way that the CLR engineers aren't prone to being, so that couldn't have been the problem.

    At a loss, I reached deep into the well of black magic and went and got a tool called the CLR Profiler, which is one of those things a Microsoft engineer wrote because he had to chase some kind of infuriating obscure memory leak, realized it would be useful to other people, and published it, and then Microsoft stopped supporting it because ¯\_(ツ)_/¯. But it still works, and it hooks into the CLR and logs every goddamned memory allocation and function call that happens for the duration of the program. It took a bit of doing to move my exact situation into a form that the CLR Profiler would accept, but I ultimately got a histogram out of it. 3 kb of integer buffers, 7 kb of Tile instances, 5 kb of query tracking objects... and 25 megabytes of GSTypes.

    Found the issue.

    Now, I've said before that I used Newtonsoft.Json for my serialization purposes. JSON restricts you to basic data types for dictionary keys: integers, floats(?), and strings. So when you serialize a dictionary that uses a non-basic type as its key, Newtonsoft.Json uses the object's ToString() method to generate its string representation. That's... not great for my purposes, because I use Types as keys and those expand to a whole bunch of information in their ToString(). So I wrote a tiny wrapper struct for Type called GSType (the "GS" derives from my game studio name) that emits much smaller type names when serialized. I created an implicit conversion from Type to GSType and vice-versa because calling the constructor every time you go back and forth is a pain. I'm not sure why or how GSType instances got boxed or if the CLR just decided to stick them on the heap, but either way a bunch of low-level methods down in my tight loops were creating garbage, and it only reached the level where the garbage collector really got affected once four or five systems were issuing queries and antagonizing the things that used GSTypes heavily. Fortunately, I was able to gently re-tune my approach to serialization and bypass the need for GSType entirely.

    From there, I've been doing a whole bunch of performance-tuning changes. For the purposes of CPU tracking I've turned to using Visual Studio's CPU profiler, because it turns out that the amateur benchmarking library I wrote to get something off the ground quickly (and helped me diagnose a bizarre performance hit associated with a single line) will measure itself if you use it too broadly, which led me down a rabbit-trail where every time I tried to figure out why this one function was being such a performance sink, it got worse.

    Unfortunately, one of the major hotspots I discovered was iteration time in my "query cursors". The short version of this is the way you get a stream of Entities (which is actually just multiple streams of Components delivered in parallel) is you put them all in a Query object and hand that Query object a ComponentStorage to query against. Then, every time you turn the crank on the Query, each Cursor gets a new value, and any changes you made to the last value it held get queued to be written back to the ComponentStorage when the query completes. This involves a whole bunch of individual struct value copies, which I've discovered C# doesn't like at all. If you want to get a lot of data from one array that you don't want to write back onto until later, the most efficient way to do it is to copy everything at once into another buffer using Array.Copy(), which translates into the C intrinsic memcpy(), which you really aren't going to get faster than. So I went ahead and finished the work I started on being able to run a Query using Component "Buffers" rather than Cursors; now you can do a query that returns every result at the same time. This consumes more memory, but right now I'm not constrained on memory, I'm constrained on CPU, so it's a worthy trade.

    During the unit-testing of my buffered query system, I discovered that the code that finds the "archetypes" (combinations of Components) that qualify to be returned by a Query had a serious bug that could cause entire Archetypes to just never be returned. Whoopsy-doodle! I replaced that code with code that doesn't fail to do what it's supposed to. The old code is in a better place and will always be remembered in the Git commit history just beneath the log entry disparaging it for being terrible.

    The result of all of this perf work is... my current stress-test still clocks an average frame time around 20 ms, which is still over-budget and especially embarrassing because I took the ants out of the maze, which means nothing's moving. I'm spending 20 ms of CPU time to track the action of a bunch of completely immobile blocks. Fortunately I've already identified a few spots where I can avoid recalculating values that I know in advance aren't going to change, but damn.

    ADDENDUM: At 11 PM last night I finally realized what was up with GSType. GSType is a struct, but its one value was a heap-managed reference type. So GSType has to have a reference handle in the garbage collector that keeps the Type instance it wraps alive. And every time you make a new handle, you potentially trigger a GC.

    Kupi on
    My favorite musical instrument is the air-raid siren.
  • eelektrikeelektrik Southern CaliforniaRegistered User regular
    Working on improving movement in our game, and made a short demo with some different versions of our d20 rolling around. Threw it into a google form and any feedback would be appreciated

    https://forms.gle/2xsG2SiqqrAHRUZd8

    (She/Her)
  • CornucopiistCornucopiist Registered User regular
    Kupi wrote: »
    Kupi's Weekly Friday Status Report

    I'm not sure why or how GSType instances got boxed or if the CLR just decided to stick them on the heap, but either way a bunch of low-level methods down in my tight loops were creating garbage, and it only reached the level where the garbage collector really got affected once four or five systems were issuing queries and antagonizing the things that used GSTypes heavily. Fortunately, I was able to gently re-tune my approach to serialization and bypass the need for GSType entirely.

    Now I feel insecure. How often should I be serializing? Am I writing to JSON enough?

    Chr*st, man, I'm only doing that stuff when the user hits the *save* button... am I wrong?!

  • KupiKupi Registered User regular
    Kupi wrote: »
    Kupi's Weekly Friday Status Report

    I'm not sure why or how GSType instances got boxed or if the CLR just decided to stick them on the heap, but either way a bunch of low-level methods down in my tight loops were creating garbage, and it only reached the level where the garbage collector really got affected once four or five systems were issuing queries and antagonizing the things that used GSTypes heavily. Fortunately, I was able to gently re-tune my approach to serialization and bypass the need for GSType entirely.

    Now I feel insecure. How often should I be serializing? Am I writing to JSON enough?

    Chr*st, man, I'm only doing that stuff when the user hits the *save* button... am I wrong?!

    I'm not doing serialization in a tight loop, I'm operating on a type (that I wrote to solve a particular serialization problem) in a tight loop that turned out to have an unexpected performance behavior.

    ... though round-tripping through the serialization engine would be an extraordinarily wasteful way to deep-clone an object.

    My favorite musical instrument is the air-raid siren.
  • CornucopiistCornucopiist Registered User regular
    edited December 2019
    this is really exciting you guys! What you see is my 10*6 interface chopped up. First of all, I made it 8*6.
    Does that ring a bell?
    It's the same as 4:3, you know, the aspect ratio of iPads!
    Then, to work on any device with a wider aspect ratio, I split it into two pieces. The clever bit is that the pieces are inside an invisible rectangle. This is first scaled equally in all dimensions so its pixel height matches the pixel height of whatever screen it's on.
    Then that invisible rectangle is widened (but not scaled) to match the screen width. The left and right parts of the UI just slide along as they are attached to the left and right border of the rectangle.
    The gap in the middle is calculated, and the touch interface simply returns touches on an 8*6 grid. Any tile in that grid can have functions called depending on which 'UIstate' the interface is in.
    It still needs some finishing up, but the heavy lifting is done. And it won't take much extra work to make a portrait interface to match!
    And it uses no Unity canvas functionality (because AAAAAARGH)

    (as this was a facebook post I'm adding more details, just for you)
    There's one invisible rectangle for each UIState. The UIState int sets their setActive once the game is running...
    The invisible UIstate rectangles are all in a 'landscape' gameObject, so I can simply go over all children (one level) to do the resize on Awake.

    9df2bo1g9xmt.png

    Oh and remember, any iOS release gets exactly *one week* of free visibility and the best week of all the weeks is Christmas to new years so that's your release date right there sleep be damned!

    Also, obviously the red/white grid is for editing only :D

    Cornucopiist on
  • CornucopiistCornucopiist Registered User regular
    so it literally took me only 13 minutes to update my landscape UI to this new system, just dragging the images into the left of right halve.
    The only bad thing about this is that it now makes my flight UI look like crap, but guess who just perfected a UI resizing system?

  • CornucopiistCornucopiist Registered User regular
    So my UI wasn't working right. It worked perfectly in the editor, scaling to meet any screen. It scaled perfectly when opening the scene.
    But if I ran it on my iPhone and flipped the phone from portrait to landscape or back *while in the scene*, it just didn't scale right.
    I ended up spending faaaaar too much time figuring out that I needed to recalculate all the scale bits. And then later I figured out that I don't need to pull height and width every time, I just need long and short side and apply it depending on orientation, but all right that's another story.
    So, anyway, Unity resets UI elements to their default size and scale, I think when rotating the canvas. That bug really took it out of me, too; at one point I wrote a function that called itself.
    Yay crunch time!

  • KupiKupi Registered User regular
    Kupi's Weekly Friday Status Report

    Delivering this early this week because 1) I had occasion to be out of bed early, and 2) there isn't much to report. Owing to a minor catastrophe at work, I had less free time than usual this week and only got a few things done.

    Been playing whack-a-mole with various pieces that are taking up too much CPU. For now, I've run out of "structural" problems, the kind of thing where a choice I made in an iterator somewhere didn't scale, and now the biggest glowing target is the actual code that checks for collisions between collision volumes. I had two theories on how to fix that, and recalling recent lessons about measuring before acting, I checked just how many collision detection checks are being done in each phase; one had 3,000 per frame, and the other had about 200. So I focused on the 3,000 case! I have some future work slated to do even finer-grained bounding box checks where possible, but in the meantime I decided to see if I could lean into one of the reasons you use ECS in the first place: multi-threading. I built myself a budget thread pool, batched out the work, each frame, and let it rip-- aaaand my average frame time for the stress test went from 130 milliseconds to 110 milliseconds. It's an improvement, but still a far cry from the 16 millisecond target.

    This is with 5000 static tiles that don't interact with each other and 5000 moving objects that check for collision against themselves and the tiles, or about 10,000 objects total out of my target somewhere in the 50,000 to 100,000 range.

    Plans for the coming week involve more perf work. Going to be doing that until the end of the year, then looking real hard at how much more time I want to put into this science project. (Trying to get it to run better has been immensely entertaining, though.)

    My favorite musical instrument is the air-raid siren.
  • IzzimachIzzimach Fighter/Mage/Chef Registered User regular

    So I've started mapping out much of the dialog in Twine. You can see in this screenshot that I have two clusters of dialog right now, the Introduction and the "Mainline" game dialog.

    ltpcrh443ar4.jpg

    It works pretty well so far, but I need a way put Metadata into the Twine script. Basically, I need a way to tell the not-Twine part of the game that something should happen (close the dialog window, give the player an item etc.)

    I tried putting "MetaCommands" in comments and that sorta works. So this code gives the player an item and then closes the dialog window.
    <!-- METACOMMAND Dispense -->
    <!-- METACOMMAND Quit -->
    

    But I think a better route is to put that stuff into Twine local variables which begin with a "_". Local variables can be inspected but get cleaned up when the passage ends, so they are good for holding quick, transient data. The code above would look like this instead:
    (set: _Dispense to 'ThingtoDispense')
    (set: _Quit to true)
    

    The good thing about this approach is that I can specify WHAT to dispense. I'll see if this method pans out.

  • CornucopiistCornucopiist Registered User regular
    Cruuuuunch news! Iiiiiitsssss... MARKETING time!

    So I managed to get the UI to work, and I managed to remap UVs on 4 out of 5 of the planes. Which is all I need for screenshots and whatever Apple calls the game trailer. I'm ramping up my meager promotion efforts on the Instagram, which is still the best free bang for the buck it seems.
    (I'm going to add twitter in, which I long thought was useless, but it's right there in the Insta repost options so free.)

    I've also switched on 'Search ads' in the app store, for my old game ChooChoo. It was a pretty painless affair to set up, so worth a try. Worst case the promotion there eats its own long tail revenue, but it might boost my visibility on the other games? I got a 100 euro (or dollar?) bonus for starting 'Search ads' promotion, and I might use that to push Endless Azure on release.

    I was hoping for a Christmas boost and a 'new apps' visibility boost, but it seems the store interface is now dominated by 'best of the year' lists... Again, Endless Azure is aimed at older dudes who like planes, so the search might make up for that.

    We'll just have to see how that all pans out; it's been laying around for three years now so it was time to ship regardless.

    -Then there's also still some stuff to be fixed: small visual issues, but also I want to have paint colors come from 'pickups' instead of being instantly available. That's the main one I need to add before release so coding that tonight.
    I want to add another plane in an update right after Apple has vetted my submission, but after that the game will have to earn its upkeep.

    -I'm also planning to do a postmortem and make a project flowchart on all this for the next game (FNS, action sci-fi) which I want to release in April... (After which there might be a reskin of EA in June, etc. etc.)

  • CornucopiistCornucopiist Registered User regular
    edited December 2019
    Whelp, looks like I'm missing that Christmas release.
    On OSX the game has bugs because it's trying to instantiate a non-present prefab (which I can fix, but it'd need doing for every instantiate call because Xcode doesn't give me code line numbers) and also I need to redo the camera in one scene to adjust to the various screen heights in landscape/portrait. I do have that code somewhere, but even digging it up will probably take me more time than I have tonight. I don't know if a submission tomorrow will still be cleared before Christmas even if I do find the time to get the various screenshots and movies done...

    Cornucopiist on
  • KupiKupi Registered User regular
    Kupi's Weekly Friday Adventures In Performance Optimization

    A week or two ago I created a budget thread pool; this week I put it into practice for the initial collision detection phase that examines every pair of potentially-colliding volumes and throws them all into a big priority queue. Using all four processor cores I have, this brought my average frame time for the 10,000 object test case down from 130 ms to 110 ms. Not fixed by any means, but looking at the profiler, the time spent in generating the initial collision set was about a third of what it was originally. 25% is the theoretical optimum for work divided over four cores, so I'm okay with losing 8% to friction.

    Reading around the code led me to realize that I was unnecessarily recalculating the bounding boxes at several points. I moved the bounding box into a value on the collision volume (originally it was stored in a separate structure because otherwise there's a cache coherency problem between the collision volume's actual data and its bounding box) and decided to just accept that some idiot using my library may update a collision volume's data without updating its bounding box. When the time comes and that idiot mouthily declares that the author must be stupid, I will be prepared with numerous arguments why this change was necessary and also to tell them that very carefully reference the identity of the code's author with that of the mouthy idiot.

    I observed that essentially every collision test I have degrades to repeated use of one of two helper functions that either tests for intersection between two line segments, or intersection of a line segment and a circle. I added a bounding box test to both of these methods and instrumented them to track how often they were called and how many times they resolved in particular ways; overall, about 60% of the calls into these methods are short-circuited by the bounding box test, which consists entirely of basic arithmetic and comparisons, saving on the vector magnitude operations that wind up taking square roots and getting into all kinds of loops. That change took another 10 ms or so off my average frame time.

    I made another change that didn't work out so well, performance-wise. I've tried to explain this to two different people by now and it never seems to come out comprehensibly, so let's see if a laconic approach works. The root of the problem is that when a collision volume collides with another collision volume as is blocked/pushed by it, that volume's velocity changes. The fact of its velocity changing negates all the remaining collisions that we'd found involving that volume that happen at a later time in the frame. So we delete those from the queue and then re-test with the volume's new velocity.

    What I used to do was this:
    1) Update the volume's velocity to its new value.
    2) Change the volume's position at the start of the frame to be such that, given its new velocity, it reaches the impact point at the time of the collision.
    3) When we re-test for collision with other volumes, any collision we detect before the time of the collision is ignored.

    That approach produces correct results, but my intuition was that the system would wind up doing a lot of collision tests that wound up being bogus. In particular, the volume being pushed would be almost guaranteed to have a second intersection with the volume that pushed it which gets thrown out. So I changed to this new system:

    1) Update the volume's velocity to its new value.
    2) Store the time of the collision as the volume's "local time".
    3) Find the volumes that might need to be tested for collision, with the volume sweeping an area across its velocity * (1 - localTime).
    4) For each volume tested against, if its local time is less than the collision time, move it by its velocity * (localTime - itsLocalTime), which brings their local time back in sync.
    5) Perform the collision test and project the time of collision, if any, back to "objective time".

    This let me get rid of the "ignore collisions before time" logic entirely, but I discovered that performance actually got worse (about 10 ms average frame time) when I put this into effect. The reason is that a collision test has two phases: an overlap test and a moving-volume test. (Several of my moving-volume equations don't work if the moving volume starts inside the target one.) However, an overlap is collision time t=0, so if the "ignore collisions before time" is nonzero, the overlap test can be elided. Now all of my collision tests run from t=[0,1], so the overlap test is always performed. I think, however, that might actually have been a bug in my original implementation, and you don't get less optimal than wrong.

    Speaking of "wrong", the last thing I discovered this week was that there's a flaw in my collision logic somewhere. I'm observing that my ants (represented as moving circular volumes) are bouncing at weird angles (they're supposed to just reflect over the surface normal) when they strike the intersection point between two blocks that are flush with one another. Mathematically speaking, it should be impossible for the ant to strike a normal with anything other than a unit vector, but it seems like the collision test is falling into a literal "corner case" where it thinks that it's striking the corner of one of the blocks rather than the face of it. So, before I do any more perf work this week, I'm going to head back into my collision library with a wrench and tighten some nuts.

    I suspect my family from out of state is going to consume most of my time this weekend, so I might wind up skipping next week's report. Don't worry, you aren't going to be able to avoid hearing about how I've determined that a linked list is going to outperform a binary heap for my particular circumstances in the end...

    My favorite musical instrument is the air-raid siren.
  • PhyphorPhyphor Building Planet Busters Tasting FruitRegistered User regular
    Most operations involving vector lengths are also valid for the squares of vector length and it's much faster to square something else to compare it with. If you don't have a LengthSquared on your vector classes (also just the dot of the vector with itself) I recommend adding it

  • KupiKupi Registered User regular
    edited December 2019
    That's a good point. My reflex is to say that I'm testing both "whether" and "when exactly", which means most of the magnitude operations are unavoidable, but until I actually assess the code involved I can't rule out that it would help (especially if, say, the sqrt can be held off until as late as possible). Thanks for the reminder.

    Kupi on
    My favorite musical instrument is the air-raid siren.
  • PhyphorPhyphor Building Planet Busters Tasting FruitRegistered User regular
    edited December 2019
    Well there's still a workaround usually if you are willing to rearrange things
    A typical simple line segment-circle intersection looks something like (see https://stackoverflow.com/questions/1073336/circle-line-segment-collision-detection-algorithm)
    // compute the triangle area times 2 (area = area2/2)
    area2 = abs( (Bx-Ax)*(Cy-Ay) - (Cx-Ax)(By-Ay) )
    
    // compute the AB segment length
    LAB = sqrt( (Bx-Ax)² + (By-Ay)² )
    
    // compute the triangle height
    h = area2/LAB
    
    // if the line intersects the circle
    if( h < R )
    {
        ...
    }        
    

    However, you can instead rearrange and square both sides, which works because you only have positive numbers here anyway
    area2/LAB < R -> area2 < R * LAB -> area2² < R²*LAB² ->
    area2*area2 < R*R*( (Bx-Ax)² + (By-Ay)² )

    If you need the exact collision point you do need the root, but you can defer to only where there is a collision

    Phyphor on
  • KupiKupi Registered User regular
    Kupi's Weekly Friday Status Report
    is proceeding as planned, evidently

    I wasn't planning on writing a report this week, expecting that the holiday forum would last a little longer, but that didn't stop me from doing some coding, so here I am with what I have to offer. In no particular order, just listing things off as I see them in the git diff (because I haven't done a commit in a while due to struggling to get back into a stable state):

    - I removed the "ignore collisions before" parameter that flows through 80% of my collision test code, now that I no longer use it.
    - Following on from the reminder by Phyphor, I reviewed my use of vector magnitudes and replaced them with square-magnitude comparisons where possible. I haven't been able to test it in isolation, but analytically speaking there's no reason why this should have made things any worse.
    - I fixed some errors in my revised circle-segment collision test that made it claim there was a collision when there wasn't, which is why the ants were bouncing off of contiguous blocks at weird angles.
    - Moved some more systems to use buffered component queries and threaded work batches instead of the old cursor-based approach.

    Since I didn't get around to fixing my priority queue implementation this week, you get a stay of execution on that until next Friday. Look forward to it! Or don't.

    My favorite musical instrument is the air-raid siren.
  • IncenjucarIncenjucar VChatter Seattle, WARegistered User regular
    edited January 2020
    It took me way too long but my voice game''s first update is finally live.

    I added a third character class, unique hit sounds, dramatically improved visuals and support for different screens, multiple saves so you can play each character, a character skin system, and a daily XP bonus for your first win.

    As long as I didn't miss any major bugs, everything after this is comparatively simple and doesn't need to be rushed, so I will be able to relax and experiment or move on without guilt.

    Incenjucar on
  • KupiKupi Registered User regular
    Kupi's Weekly Friday Status Report

    Two line items:
    - I fixed a bug in my line segment intersection test that would incorrectly declare parallel lines as intersecting. You'd think this would be a rare case, but with the way I handle impacts with surfaces, it was actually extremely common for an object to be moving exactly parallel to the surface it had just struck, resulting in all kinds of wackiness when volumes collided with one another.
    - I wrote a special function to handle the calculation of vector magnitudes which can dip into higher decimal precision before rounding off the answer. Since I'm working with three-digit fixed point (because I'm a loon), normalized vectors could sometimes wind up with actual magnitudes of .998 or 1.002; with this change they're usually actually magnitude 1 and sometimes vary to .999 or 1.001.

    Now, the actual major change I made this week. For a little background, the goal of the collision detection engine is to create a record of which collisions happened in the frame that can then be interrogated by other systems later in the pipeline. This is in contrast to the callback- or event-based paradigm more familiar from Unity; writing to arbitrary components isn't impossible, but it is awkward and somewhat against the ECS principles I'm trying to stick to. However, that's beside the point. To create the list of events, the collision detection engine takes the following steps:

    - Every collision volume pair indicated as likely to collide by the broadphase tests is tested for collision, and any collision events discovered are placed into a priority queue by the time they occurred.
    - Until the priority queue is empty, the next collision event is taken off the front of the queue. If the event is a "trigger" interaction, meaning it is only meaningful for the fact of its occurrence, then it's considered resolved immediately and we go back to the start of this step.
    - If the event is a "blocking" interaction, then we have to revise our projected future. All collisions involving the collision volume(s) that got blocked are deleted from the priority queue.
    - The blocked/pushed collision volume(s) have their position and velocity updated. A broadphase test is used to determine their new likely interactions, and then the precision tests are used again to find a new set of future collisions.
    - These future collisions are added to the priority queue and we go back to step 2.

    My original implementation of the priority queue used an array-based binary heap implementation. In an array-based binary heap, the items at indexes 2n and 2n+1 relative to some index n (1-based) are the children of the array, and both compare unfavorably to their parent in whatever the priority queue's ordering is. When you add a new item to the binary heap, you initially place it at the end of the array, and then compare it to its parent and swap their positions until the heap ordering is restored. In the worst case, it takes O(log(n)) operations to get the value to its proper position. Taking the minimum value from a binary heap is similarly O(log(n)); you move the value at the last index of the array to the head position, and then compare-and-swap it down until the heap ordering is restored.

    When the PriorityQueue.TakeMin() function finally got to the top of my list of performance issues, I realized that while the performance characteristics of a binary heap are good for priority queues in general, for my specific case it was inordinately wasteful. The thing about blocking interactions is that it's actually incredibly rare that they result in new collisions; when the blocked object changes to a new velocity, it doesn't collide with the thing that blocked it again-- I specifically assign its velocity so that it doesn't collide with it. So, napkin-mathing some numbers, in my stress test, 95% of the time a collision resolution is nothing but just a PriorityQueue.TakeMin(), 4.999% of the time we have to delete a collision that we thought was going to happen and then didn't, and we reveal a new collision basically never (but sometimes).

    So, I realized that I needed a data structure with very fast deletion from the head and in-place, which sounded a lot to me like a linked list. Now the priority queue is a linked list of collision events. When new collision events have to be inserted, I use the MergeSort strategy to add them in. The insertion is now O(n), but the overall performance is more like O(n) *.000001 + O(1) * .999999, whereas my previous binary heap approach was O(log(n)) * .000001 + O(log(n)) * .999999. In any event, frame times measurably improved when I could actually get the damned thing running between my other stability and correctness fixes.

    I'm currently in an uncomfortable spot regarding performance, in fact; I'm running out of legitimate hotspots to hammer down, and the ones I'm finding don't have huge contributions to the runtime themselves (meaning the performance impact is highly diffuse and therefore tough to squeeze further optimization out of). There are a few things I can still multi-thread, but it looks like I'm getting to the maximum my engine can handle, at least when every object in existence is allowed to run riot. The next couple weeks may see me put some effort into the "things far away from the camera are declared irrelevant and don't get processed" system, along with an investigation of just how the numbers shake out with regard to how many fixed-position and moving objects the system can handle. I originally made 57,600 or so my target, but that's a fully saturated Sonic 3 map; there's actually a lot more empty space available, so I could actually be where I need to be. If so, I'm going to go back to my level editor and patch it up a bit, then see if I can get a microgame (like Asteroids-in-a-maze or something) thrown together.

    My favorite musical instrument is the air-raid siren.
  • IzzimachIzzimach Fighter/Mage/Chef Registered User regular

    Usually I end up moving away from linked lists, so it's interesting to see a situation where they improve performance.

    Can you sort of "loop unroll" by grabbing and processing two or three collisions at once? Sometimes the 2nd/3rd collision will be rendered invalid but it sounds like that happens infrequently.

  • PhyphorPhyphor Building Planet Busters Tasting FruitRegistered User regular
    Linked lists are typically one of the worst performing structures for modern processors - every node involves a cache miss

  • CornucopiistCornucopiist Registered User regular
    Kupi wrote: »
    Kupi's Weekly Friday Status Report
    *Collision stuff*
    I'm going to need to work on collision for my next game (and have worked a bit on it for another one) so I just would like to ask a few things about high level triage.
    Mind you, my goal is not to run actual collision calculations but collision avoidance. This was done in Unity using raycasts.

    -The first thing I did was to exclude all non-moving opponents.
    -Then I lowered the framerate for slower moving objects figuring they don't get into trouble as quickly.
    -I then created raycasts along possible trajectories (straight or deviating) and simply set the first non-intersecting deviation as course change.

    -What I didn't get round to was to create (or morph) a bounding box the shape of the object traversing the next update, i.e. extruded along its path. This would have improved my collision avoidance between opponents.

    -I used the built-in physics to have the opponents bounce off scenery.

    That imho was the big problem with the setup; having the scenery collision set up made for a lot of unneccessary calculations. Using a collission matrix fixed this though I moved on to another project before I had it all worked out.

    For my upcoming game I will have a lot of flying vehicles (opponents) which I'll try to rip straight from the Unity MegaCity demo. Iirc they already have collision avoidance set up, but the big question is whether it still works after many ECS > DOTS updates, as they've been shifting the collision paradigm around.

    The other issue is going to be scenery. While I don't count on having to simulate player/scenery interactions except of the high energetic kind, I do have to trigger those explosions. I've no idea how this will work, as, again, they've been shifting the collision paradigm around.

  • KupiKupi Registered User regular
    edited January 2020
    Izzimach wrote: »
    Usually I end up moving away from linked lists, so it's interesting to see a situation where they improve performance.

    Can you sort of "loop unroll" by grabbing and processing two or three collisions at once? Sometimes the 2nd/3rd collision will be rendered invalid but it sounds like that happens infrequently.

    I keep mulling this over in my head and I can't really come up with a way to make parallel-processing work for this situation. I've already multi-threaded the initial pass, of course, but once the timeline is established, there's no future operation that I can ever guarantee will actually happen. Every time I think I've got something that could apply, such as "scan the list for all blocking collections and erase the subsequent collision events for volumes involved", I wind up with a counter-example like...

    - Volumes 1 and 2 have a blocking collision, then
    - Volumes 2 and 3 have a blocking collision, then
    - Volumes 3 and 4 have a trigger collision.

    ... and in that case, it would actually be inappropriate to erase the 3&4 event, because the 1&2 event causes the 2&3 event to not happen, which means volume 3 won't actually change direction, so it will impact Volume 4.

    I've spent what parts of the day I have between watching runs on AGDQ workshopping possibilities for some kind of branch-prediction mechanism where the collision system runs multiple possible resolutions in different threads and then combines them afterward, and it's running into problems with the fact that resolving a blocking collision involves some non-reversible transformations on collision volumes to set up the new position and velocity.

    This is not to say it's impossible, just that the way I have things set up now pretty well locks me in to processing it on a single thread, which is going to put a pretty hard limit on what my system can accomplish. I might be able to get it to handle more volumes if I reworked it entirely to act like a more traditional overlap-detecting engine-- move everything by whatever the frame substep is, check for overlaps, depenetrate and update velocity for blocking interactions, and repeat until all of the time has been consumed for the frame. But that would be a pretty close to total rework, and one of my goals here was the kind of absolute precision that my current system provides. I'm going to look into the last couple things that aren't the main collision phase that I can improve, and then, since it's the New Year, shift gears to seeing if any further improvement on the collision system's performance is really necessary.

    EDIT: Especially because, I'm realizing, my stress test really is a stress test for the system. My "ants bouncing around in a maze" produces on the order of 780+ blocking collisions per frame. In actual practice, objects on the ground will be moving such that they don't actually contact anything, and most objects that do collide with the ground will be projectiles that destroy themselves as soon as they make contact. So I think it's definitely time I move on to something new.

    Kupi on
    My favorite musical instrument is the air-raid siren.
  • nervenerve Registered User regular
    Unity question: For a top-down 3D game with a map size similar to stardew valley or even as something like league of legends, would you just have one single large 3D file for the terrain or would you break it up into pieces and load them as the player moves nearer to each area of the map?

Sign In or Register to comment.