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.

18687889092

Posts

  • Golden YakGolden Yak Burnished Bovine The sunny beaches of CanadaRegistered User regular
    Oh nooo... I wanted to learn Unity, now it's too evil.

    H9f4bVe.png
  • ScooterScooter Registered User regular
    I don't make enough money to be hit by this, but I feel like if I ever manage to make 100k in a year I'm going to need to immediately look at switching to Godot before I hit 200k. And I shouldn't be afraid of being successful.

  • LD50LD50 Registered User regular
    And who knows what Unity will do once they start losing even more money after losing their own customer base to this idiocy.

  • asofyeunasofyeun Registered User regular
    yeah, unity is dead to me now

    i've tried out unreal before but i think it's a little too much for me

    guess i'll try out godot, anyone have good introductory videos for that?

  • GlalGlal AiredaleRegistered User regular
    Their own documentation is pretty good at introducing the basics. Note, they changed major versions about a year back, so you might encounter cases where an example doesn't match the current state of the engine; hopefully they've polished over all of those by now, but just something to keep in mind, so you don't think you're going insane.

  • KupiKupi Registered User regular
    Kupi’s Apparently Semi-Annual Friday Game Dev Status Report

    Well, here I am again, thoroughly off the wagon but still lurching along the dusty trail. I am still in the middle of what has turned into an extended sabbatical from the concept of work; I have lost all faith that I can accomplish any good for the world in a corporate environment where the c-suite and shareholders will arbitrarily decide to cut costs by laying people off and I always seem to be the last one in the door. Meanwhile I also lost almost all my motivation for game dev work largely out of a depression that hasn’t totally cleared. But I have managed to make some steps forward and/or things have happened, so I’m documenting them here.

    The Nimsters project is either on indefinite hold or abandoned. When last I posted here, the next thing was to try to make a vertical slice of the regular gameplay loop, allowing the player to move from one town to another through a series of randomized events, roguelite-style. I wound up stalling out creatively in the introductory sequence, where I had planned to rub the player’s nose in the idea that there is an in-game manual by forcing them into at least the table of contents. The prospect of writing entries for each little mechanic pretty much stalled me out in multiple attempts to get it out of the way, and combined with the comparatively recent loss of my job at the time, I just kind of dropped off the whole project. Maybe I’ll return to it someday and discover that I’d actually created a fucking brilliant monster-tamer game and all the time I spent doing data entry and building the battle engine was worth it in the end, rather than being another endless pit into which I threw a bunch of useless effort because I went broad instead of deep yet again.

    Then there was several months of absolutely nothing, at least in terms of creative efforts.

    When I finally regained the desire to open Visual Studio and code again, I indulged myself in another rearchitecting exercise on my action-game engine. This is the one that involves trying to implement a “pure” ECS system, expressing everything in the architecture as a sequence of Systems operating on streams of Components arranged according to which Entity they belong to. Sacrificing purity for practicality, I’ve added the notion of “Step” as a sub-element of System; each Step represents a separate operation on the “Query” (the Component stream), be it single-threaded or a work-sharing multi-threaded operation. And I’ve been trying to find a way of defining Steps that I like (one that, ideally, requires the least typing and especially the least repeating of boilerplate code). Rather than spend too much time directly describing the specifics, I’m just going to post some sample code that demonstrates the principles involved.

    This is what a simple movement System looked like in my very first attempt:
    class MovementSystem : System
    {
        [QueryBuffer(QueryBufferTypes.Required)]
        Position[] position;
    
        [QueryBuffer(QueryBufferTypes.Required)]
        Velocity[] velocity;
    
        [QueryBuffer(QueryBufferTypes.Optional|QueryBufferTypes.ReadOnly)]
        Acceleration[] acceleration;
        bool[] hasAcceleration;
    
        [Step(0)]
        void Move(int index)
        {
            if(hasAcceleration[index])
            {
                velocity.Value += acceleration[index].Value;
            }
    
            position[index].Value = velocity[index].Value;
        }
    }
    

    The constructor for the base System class uses reflection to examine its own fields and methods, finds ones that have been labeled with the appropriate Attributes, and then in a method called Process() goes and gets arrays of the matching components from a database and assigns them to the matching fields. If they're marked as ReadOnly, the values aren't copied back to the database after processing; if they're marked as Optional, then the matching hasComponent buffer is filled with an array indicating if the entity has a value for that component. The awkward bit being how you have to have to access the buffers by index.

    This is the second revision:
    class MovementSystem : System
    {
        protected override Queries => new Queries
        {
            new Query
            {
                new QueryBuffer<Position>(QueryBufferTypes.Required),
                new QueryBuffer<Velocity>(QueryBufferTypes.Required),
                new QueryBuffer<Acceleration>(QueryBufferTypes.Optional|QueryBufferTypes.ReadOnly)
            }
        };
    
        protected override Steps Steps => new Steps
        {
            new Step(Move)
        };
    
        void Move(ref Position position, ref Velocity velocity, bool hasAcceleration, in Acceleration acceleration)
        {
            if(hasAcceleration)
            {
                velocity.Value += acceleration.Value;
            }
    
            position.Value += velocity.Value;
        }
    }
    

    This version does away with the attributes, and uses a comparatively recent C# feature where a method can be coerced into a delegate. The constructor of the Step class uses reflection to tease apart the method signature of the delegate provided and maps it to an executor class that invokes the delegate using references to positions in the matching Component array from the Query. This means you don't have to append "[index]" to everything. The problem is in that concept of the "step executor" class. Unfortunately, to avoid certain garbage-producing boxing operations, you need a step executor for each individual possible method signature. I did some codegen to support it, but ultimately this design meant leaving 1000+ micro-classes around, to account for the possibility that I might eventually need a step method with e.g. six required components and three optional ones. Also, the method signature has to match the definition you declared in the Queries property, which you'll only discover at run-time. I didn't like that, and so we move on to today's revision:
    class MovementSystem : System
    {
        class MovingEntity : QueryEntity
        {
            public Required<Position> Position { get; set; }
            public Required<Velocity> Velocity { get; set; }
            public ReadOnlyOptional<Acceleration> Acceleration { get; set; }
        }
    
        protected override Queries Queries => new Queries
        {
            new Query<MovingEntity>()
        }
    
        protected override Steps Steps => new Steps
        {
            new Step(Move)
        }
    
        void Move(MovingEntity movingEntity)
        {
            if(movingEntity.Acceleration.Component.HasValue)
            {
                movingEntity.Velocity.Component.Value += movingEntity.Acceleration.Component.Value;
            }
    
            movingEntity.Position.Component.Value = movingEntity.Velocity.Component.Value;
        }
    }
    

    This version re-introduces a vexatious required magic word ("Component") when accessing component values, but it can be bypassed by capturing the value in a variable like this:
    void LongerMethodWhereBrevityMightBeDesirableDueToRepeatedAccess(MovingEntity movingEntity)
    {
        ref var position = ref movingEntity.Position.Component;
        ref var velocity = ref movingEntity.Velocity.Component;
    
        // TODO: rest of this function
    }
    

    ... whereupon all access to the component requires only typing "position" or "velocity". Though this structure is slightly more verbose than the previous one, it only requires a small set of backing classes instead of an arbitrarily large set that might not even be sufficient for future purposes. And unlike the previous rendition, there are certain compile-time limits on what you can do with the various values. The ReadOnlyOptional<> component cursor, for instance, will only return a readonly version of the struct reference, so not only shouldn't you modify the component value (because it won't write back to the database), you literally can't. So I'm happier with it.

    Still with me? ... really? Well, on to the second half, then.

    So far I've been using JSON serialization as the primary means of handling custom content files in my engine. It's an enormously useful format, being relatively compact (for plain text), human-writeable, and human-readable. Granted, thanks to the Nimsters project I've learned that hand-editing game content files is an enormous pain in the ass and you should make an editor for anything with more than one or two properties, but that easy parseability is a huge boon. However, since my early game projects where I used handspun binary formats, I've always wanted to take revenge on my stupid younger self by creating a serializer that makes serialization to a binary format easy, gaining the file-size advantage of binary formats with the same ease as you can just call JsonSerializer.Serialize<T>(value). I've tried to do that four or five times by now, each time running into some conceptual roadblock that I just didn't have the energy to deal with.

    Well, this time I stuck with it. The details are, frankly, not that important (and I feel like I'm already at the bottom of the well for word and attention budget here), but the short version of it is that I've succeeded in creating a binary serializer that takes an arbitrary object, and writes a recursive description of its public properties followed by the data contained in those properties to a file, using various caching mechanisms to prevent repeat definitions, infinite loops when objects self-reference, and so on. It doesn't beat JSON for very small objects, but in one benchmark test involving 30,000 objects with complex randomized state, the resulting binary-format files were about 55-60% of the size of the matching (minified) JSON from the same objects. Unfortunately, the JSON format made up most of the ground when the files were zipped; though the binary files were still smaller, the zipped binaries format was only about 90-95% of the size of the zipped JSON. And for highly regular data (where the 30,000 objects had numerical values in order instead of randomized), the JSON actually wound up being smaller after zipping (but not before). Still, I think the binary serializer accomplishes what I set out to have it do-- produce data files that are as small as I can get them without sacrificing convenience. And they don't have to be my day-to-day version of the files: XNA/MonoGame/FNA's publish pipeline lets you emit a different format than they take in, so I can work in human-readable JSON, then convert to the binary format during publish. Huzzah for small game file sizes!

    Where am I going from here? In circles, probably. But if I somehow manage to make forward progress, it's going to be in the field of exploring FNA (another XNA alternative with, so far as I know, better support for publishing to consoles), and ironing out the edge cases in my binary serializer.

    See you next... whenever.

    My favorite musical instrument is the air-raid siren.
  • KupiKupi Registered User regular
    Kupi's Game Dev Status Report Is Clearly Not Anchored To Any Kind of Schedule Any More, So Why Not Give It A Day Early Since Nothing Else Is Going To Happen This Week Anyway?

    I have covid................. vaccine in my veins, so my head isn't completely clear. But that also means I'm probably not going to accomplish anything more than I was going to this week, so... the week's report!

    I focused on one project this week: trying to integrate my ECS game engine into FNA. As of this writing, I now have a separate project in the same solution that runs the test project (with Sonic running around in a small level made of brick tiles), running on FNA instead of MonoGame. (I've got a chain of DLLs that encapsulate increasingly specific elements of the engine, a "utilities" library with my math, serialization, and other functions, the ECS library that has the actual game-engine parts, an "engine" library that imports the surrounding runtime engine (FNA or MonoGame), and finally the actual game executable.) Here are my findings on FNA vis a vis MonoGame.

    FNA's stated raison d'etre differs from that of MonoGame. The MonoGame philosophy is to pick up the ball where XNA dropped it and continue to run with it. The MonoGame project wants to be XNA carried into the future, adding new features where possible, revising the APIs to make them more usable, integrating with modern dev pipelines, and so on. By contrast, FNA has just one goal: make XNA, as it was, continue to be usable. The project goal is 1:1 compatibility with XNA in service of source-code compatibility, as more of a preservation system than an ongoing game engine.

    However, that doesn't mean that FNA has no changes, nor that it has no enhancements, over XNA. Because my engine is written for .NET Core instead of .NET Framework, I originally thought that I was more or less up the creek without a paddle because the FNA documentation leads with a statement that .NET Framework is just fine for the engine's purposes. However, there is a .NET Core version of the project, since early 2022, which seems to work just fine. So FNA is perfectly viable for new projects, though its API is, by design, more exactly what XNA offered than with MonoGame.

    There is, however, one big deviation from XNA, somewhat ironically: the content pipeline. In FNA, the Content Pipeline does not exist. The team lead / primary developer on FNA appears to be one of those prickly open-source types, the kind who will write a several-thousand word essay on the myriad reasons the XNA Content Pipeline was not, is not, and will never be a good idea, and therefore he cannot in good conscience replicate it in FNA, only provide code support for importing existing XNA content files. Now, fortunately for my specific case, I fully agree; the Content Pipeline is cumbersome, confusing, failure-prone, and does not at all operate in the same idiom as the rest of the engine, so I'm more than happy to be rid of it.

    But on the topic of developer prickliness and... interesting opinions, the documentation goes out of its way to emphasize, before warning you that any NuGet distributions of the FNA code are counterfeits, that nobody should use NuGet in general. FNA is distributed as a solution file from its GitHub repository, with public updates being pushed to the main branch on the first of every month. As above, I'm not going to say it's bad reasoning-- NuGet packages are prone to security failures by way of pulling in their various dependencies, you can't debug into the libraries you import through NuGet or modify or fork the codebase if you need to. However, I get a small chuckle out of the admonition not to use NuGet (a Microsoft-owned distribution platform) in favor of the GitHub repositor (a Microsoft-owned distribution platform).

    As for actually building an executable for multiple targets, FNA and MonoGame have differing philosophies there as well. MonoGame has separate build targets for Windows, MacOS, and Linux, embedding the required libraries for the target platform at build time. The philosophy behind multi-targeting with FNA is to ship libraries that multiplex to different targets, so one compiled executable runs on every (PC) platform. The documentation makes it very clear that this approach will not be changing any time soon and you will get an extremely curt response if you submit a pull request trying to add build-time switches to the code. There's an awkward wrinkle to this approach, though: the libraries that FNA uses for this multiplexing don't ship with FNA. They aren't imported or built by the main project; you have to download an "fnalibs.tar.gz2" file from somewhere else and copy those into your build output (either manually, or by putting them in your own repository and setting them up to copy into the build as content files). I believe that this is how the developer achieves console support; if you want to port to e.g. the Nintendo Switch, you sign your NDAs, buy a license, and aside from changing a configuration setting to say "The platform is Nintendo Switch", none of your other game files have to change; you just submit them with the Nintendo Switch platform DLLs in the right spot. It felt weirdly manual, but I think it makes a sort of Linux-y kind of sense.

    The only other problem I encountered is that XNA's implementation of sound effects throws an exception if you try to change the looping status of a sound effect if it has ever been started, even if it was subsequently stopped and reset to the beginning. So one of my instance-reuse systems that worked fine in MonoGame wound up exploding and had to be slightly rewritten. I also happened to discover that some of my content-management code was in disarray, so I fixed it up while I was in there.

    Ultimately, it felt like a worthwhile exercise. I'm not sure whether I prefer FNA or MonoGame, ultimately; they function almost identicially. The biggest potential problem I see is how you compile Effects (since FNA has removed the content pipeline); unfortunately, I haven't done any real work with shaders yet, so I don't know if FNA makes it easier or harder to import them. Say, that sounds like a great topic for next week's research. See you then! Or some other time! Kupi's Game Dev Status Report Is Clearly Not Anchored To Any Kind of Schedule Any More.

    My favorite musical instrument is the air-raid siren.
  • PhyphorPhyphor Building Planet Busters Tasting FruitRegistered User regular
    Okay, I took a whole bunch of time off. Did some Vulkan stuff, wrote a serialization library, designed a novel garbage collector. But it's time to stop slacking and get back to the logic mines

    First up: PLLs. Spoilered for boring
    So a PLL allows generalized clock signals to be generated from a source clock, so you can have a clock that runs at 3x, and you can divide a clock by arbitrary amounts too, so you can have a clock at 0.5x and these can coexist. This is necessary because in a NES there are two clocks, the CPU runs at one third the rate the pixel chip does. In a SNES this gets worse, I'm not actually sure it's even possible to handle that well

    In modern circuits at high speeds you need explicit synchronization of these clock domain crossings but at 70s/80s clock rates you could just yolo it and it's probably fine. A simply gated clock without huge fanout or distance is probably synchronized enough to not cause problems, so for my purposes I will assume precise synchronization; clock skew tends to be on the order of nanoseconds and these clocks are hundreds of nanoseconds. Gates were slower back then, but wires were not

    So multiple clocks looks like this
        0   1   2   3   4   5
        v   v   v   v   v   v
    3x  /‾\_/‾\_/‾\_/‾\_/‾\_/‾\_/
    1x  /‾‾‾‾‾\_____/‾‾‾‾‾\_____/
    .5x /‾‾‾‾‾‾‾‾‾‾‾\___________/
    
    Each rising edge causes all clocked components to (nearly) simultaneously latch and the tricky bit is that there are sets of clocks rising simultaneously, for example all 3 clocks rise at T=0, the 3x clock rises at each T, the 1x clock at T=0,3 and the 0.5x clock at T=0. Over the entire period this creates a distinct sequence {0+1+2, 0, 0, 0+1, 0, 0} which repeats every 6 ticks (which actually represents 2 toplevel cycles)

    I was originally planning on simulating this in an ad-hoc way, just evaluating the PPU at 3x rate but that doesn't actually work well. The issue is a full clock simulation consists of latching all clocked components, writing registers and memory and such, followed by propagation of new values through the combinatorial network. However if you introduce multiple clocks you need to latch simultaneous edges atomically, you can't fully evaluate one half and then the other half because that can change what the other side's clocked components see. So ultimately I decided to bite the bullet and do it properly, so now for each unique clock combination (0+1+2, 0+1, 0) I compute each element that is evaluated on a clock edge, plus each component that needs evaluation after that clock edge and also the clock sequence

    The other real problem is that in this era everything was asynchronous read, so I'd have to calculate the CPU's address, go into the PPU, read the register, pull it out and feed it back into the CPU during CPU evaluation anyway and having explicit clocks fixes that as well, the graph walk will pick out all the correct bits from everywhere, but only if it's all one big mess

    I was also initially implementing things like pixel output and cartridge read as generic callbacks because it was simpler, but CPUs hate indirect calls and well I'm going all-in on codegen anyway and implementing them in the graph probably yields at least as optimized code since the compiler can see the implementations of the functions and can optimize and possibly avoid spills. It was also unclear which ones had to evaluated because of visible side-effects and which ones did not, inlining them makes the rule simple - children of clock edge nodes must be evaluated

    As a side effect of this change all toplevel modules are now defined to only take a single clock input and any asynchronous inputs like controller data will be fed via a special register tied to a specific clock. This kills ports off at the simulation boundary which were always weird and special cased anyway and it lets me work with a well defined interface, so that's a nice bonus. During testing/editing I generate a special toplevel module that feeds the ports from properly clocked ROMs/RAMs/registers so everything works nice and consistently

    At this point the only remaining theoretical questions are
    - are you allowed to construct your own flip-flop? I cannot resolve combinatorial loops but I could detect them and see if they match a known flop design or try to prove the feedback loop is stable. The problem is things like a SR flop have invalid states so I likely can't even allow that without just pretending the special case doesn't exist
    - will transparent latches exist? It's awkward to have a node be conditionally combinatorial but it might be possible in this new design where the sequence is calculated, and it is more true to the technology used at the time - reading a register activates the pass transistors and writing it allows the latch to update, it's why the illegal instructions sometimes have wonky behaviour, reading and writing the same register continually. I'm also still planning on hiding the details of clock wires and forbidding logic on clocks and latches will require that, though I could special case them like I do flops
    - can I expand up to the SNES? Between it's clock weirdness and increased speed probably not. Definitely not for the expansion chips but I could implement those. I suffer from Moore's Law here, each circuit is several times bigger, there are more components and I have fewer clocks to compute each one in. I could maybe do individual components instead of a full system. I think NES is within the capabilities of a desktop though

    On the more gamedev side of things, It's time I actually get something done so I've started writing up an actual set of features and a... task list. I really like the conceit of Shenzhen I/O and Last Call BBS where the entire game is pretending it's a machine running a fake OS so I'm going to do that, windowing and tasks and all. I might even emulate a filesystem and have a terminal and a text editor, just for fun. But first things first, get the core game working (again) and build out the "desktop" / main menu and windowing

  • KoopahTroopahKoopahTroopah The koopas, the troopas. Philadelphia, PARegistered User regular
    Been watching a lot of Thor on YouTube since he blew up on YT Shorts. He's an ex-dev from Blizzard and has been making his own game called Heartbound in GameMaker and streams the whole thing.
    https://www.youtube.com/@PirateSoftware

  • ThegreatcowThegreatcow Lord of All Bacons Washington State - It's Wet up here innit? Registered User regular
    Yeah his short about how much WoW's unicorn mount made more than the entire run of Starcraft 2 was particularly soul crushing.

  • KupiKupi Registered User regular
    Kupi's At-Will Friday Game Dev Status Report

    I'll be the first person to admit that my game engine is overdesigned in a lot of respects, and I'm working on getting my ass in gear to actually make games with it. But I took one little diverge this week in the service of taking some of the overdesignedness out of it, because I realized that I'd made a gigantic blunder in how I handled object pooling. The main thing I did was bake object pooling into the class hierarchy; any object capable of being pooled had to inherit from a specific base PooledObject class. This was because of a number of assumptions I made, most of them embarrassingly obviously wrong in retrospect so I'm not going to dwell on them. The upshot is that I've completely removed the PooledObject base class, which obviated the need for a huge amount of supporting code in the serialization systems and elsewhere, and the new object pooling system can handle instances of any type, not just the ones I control. I also determined, through testing, that the built in ConcurrentQueue collection slightly outperforms my custom lock-free stack, and won't actually contribute to GC pressure until you're storing 1,048,576 instances (1024*1024) or more in it. I... very much doubt that I'm ever going to make a game where there are over a million of anything sitting around unallocated, so I'm in the clear there.

    My next goal is to revise how terrain is placed into the world. Right now, absolutely everything is a "character": a placeable object that's fit onto a placement grid. That means that each individual tile has to be hand-placed, which I've already found to be obnoxious even in my one-screen test scenarios. So I plan to build something that lets me draw the desired outline of the terrain with a tilemap, that then automatically places entities with appropriate collision volumes and display sprites. That's a big enough lift that it's the only thing I'm going to commit to before next Friday whenever.

    My favorite musical instrument is the air-raid siren.
  • GlalGlal AiredaleRegistered User regular
    I've been working on a little "you control a remote robot" game, inspired by Cholo and Iron Helix. The idea is that you're controlling it remotely, so your screen is split between the robot camera, the map of the area, and any other camera feeds you might currently have access to.

    For the past week I've been working on the minimap. I figured I'd make it dynamically generated based on the GridMap (3d tile map) of the environment around you. Take 2d tiles that symbolically represent the 3d tiles, draw them on a texture, done. And with it being dynamic I can then handle stuff like height, where we draw the floor you're on, and can then dynamically transition to higher floors as you go up ramps, or colour grade tiles to indicate they're above or below.

    What I did not realise was that even today, in the year of our lord 2023, operations that deal with raw 2d image manipulation are still comparatively slow. So imagine my surprise when I get the code working, plug it in to run every frame, and the game's framerate plummets to 30fps, on a machine I normally play non-RT games at over 100fps on.

    So, now I'm deciding what the best way to handle this going forth is. Do I just generate the map once and then scroll the texture around? Do I generate a flat tilemap and scroll around that instead? Unfortunately, any static solution will preclude doing stuff like rooms-over-rooms dynamically. Oh well, time to think.

  • PeewiPeewi Registered User regular
    Glal wrote: »
    I've been working on a little "you control a remote robot" game, inspired by Cholo and Iron Helix. The idea is that you're controlling it remotely, so your screen is split between the robot camera, the map of the area, and any other camera feeds you might currently have access to.

    For the past week I've been working on the minimap. I figured I'd make it dynamically generated based on the GridMap (3d tile map) of the environment around you. Take 2d tiles that symbolically represent the 3d tiles, draw them on a texture, done. And with it being dynamic I can then handle stuff like height, where we draw the floor you're on, and can then dynamically transition to higher floors as you go up ramps, or colour grade tiles to indicate they're above or below.

    What I did not realise was that even today, in the year of our lord 2023, operations that deal with raw 2d image manipulation are still comparatively slow. So imagine my surprise when I get the code working, plug it in to run every frame, and the game's framerate plummets to 30fps, on a machine I normally play non-RT games at over 100fps on.

    So, now I'm deciding what the best way to handle this going forth is. Do I just generate the map once and then scroll the texture around? Do I generate a flat tilemap and scroll around that instead? Unfortunately, any static solution will preclude doing stuff like rooms-over-rooms dynamically. Oh well, time to think.

    I don't know what you're making this in, but my first guess for why image manipulation would be slow is that you're doing it on the CPU and not the GPU.

  • GlalGlal AiredaleRegistered User regular
    edited November 2023
    I'm using Godot, and I wouldn't really expect that it would be using software as a default if hardware acceleration was an option. I'll ask on their forums, see if I'm doing something wrong.

    Glal on
  • PeewiPeewi Registered User regular
    All I really know for certain is that there has to be some other way of generating your minimap without tanking performance.

  • GlalGlal AiredaleRegistered User regular
    Yeah, as said, I could generate the minimap once, and then scroll the generated texture around. Or, I could use Godot's built in tilemaps (2d tile type used for drawing tile-based levels), and then scroll it inside a viewport.
    I just thought it was weird that basic 2d operations would be so comparatively taxing.

  • agoajagoaj Top Tier One FearRegistered User regular
    You'll need a different solution, drawing each tile separately will need the cpu and gpu to talk to each other, and that back and forth on every tile will be a bottleneck.
    To be fast you'd want to reduce it to 1 drawcall, where you build the mesh of the tilemap and draw that mesh with an atlas texture of all your tile graphics.
    There is probably a godoty way to do this by spawning a small TileMap node that will do the rendering optimization for you.

    ujav5b9gwj1s.png
  • GlalGlal AiredaleRegistered User regular
    Thanks for the tips! I'll experiment with using tilemaps for the purpose after I brush up on how exactly (sub)viewports work, so I can display it as intended.

  • PhyphorPhyphor Building Planet Busters Tasting FruitRegistered User regular
    edited November 2023
    So there are a variety of reasons why this could be slow

    You could be using a texture abstraction and not a dynamic texture one. Generally you deal with dynamic texturing by actually using multiple textures and having frame N-2 being displayed, frame N-1 being drawn and frame N being issued each with their own buffer. If you just have a single backing texture it has to flush & wait. If you're using opengl or older d3d it's on the driver to do it's best to make this fast, but modern apis make it the app's problem to deal with and the slow case becomes much slower. This can also happen if using the wrong kind of update function or no invalidation was issued

    CPU->GPU transfers can also be somewhat slow, the GPU doesn't like reading texture data from host memory so it has to transfer it and wait for everything to finish

    If you are loading this via mapped memory beware that reads - even implicit ones that you might not notice - from GPU memory are extremely slow (excepting certain situations like host coherent device local memory, not relevant here)

    Particularly since generating it on the CPU is also slow, especially if you do this every frame. The GPU is very good at doing bulk data manipulation and the CPU kind of isnt, so it's possible that you are spending so much time in the generate that your whole frame is late

    Depending on how you are hooking into the engine you may be blocking large parts of the frame from being issued too

    The canonical ways to do a basic minimap would be
    - produce in very large chunks and scroll frame to frame, do it asynchronously before it is needed so it's ready for draw
    - produce small chunks as needed and tile
    - per frame render live from top down perspective, ideally from a reduced complexity scene
    - preprocess the entire thing in advance

    But the main thing is to avoid producing pixels on the CPU and reuse as much work as possible frame to frame

    Phyphor on
  • VontreVontre Registered User regular
    I think the phrase "basic 2D operation" belies a misunderstanding of the actual heavy workload that computers are doing. Your cut and dry 2048x2048 texture has over 4 million individual pixels. Iterating through them with a for loop would be an extremely basic operation, that would also utterly obliterate the fastest gaming PC. Any type of texture manipulation or display needs specialized algorithms and graphics hardware to function at speed, it cannot be approached naively.

    The very simple answer here is that you should never generate raw textures during runtime unless there is literally no other way to accomplish your goals. Add a step to your build process that automatically generates the required textures at build/compile time. If you absolutely need to generate minimap textures at runtime because of random permutations or something, you'll want to chunk this down to the smallest workload possible and figure out some deeper graphics optimization, which could be a lot of work. None of this is particularly easy or free, but runtime is going to be the way harder and riskier option.

  • PhyphorPhyphor Building Planet Busters Tasting FruitRegistered User regular
    edited November 2023
    Well, it depends. The problem isn't that you can't iterate through 4 million things it's you are iterating through 240 million things per second constantly and doing meaningful work and probably not playing nice with the GPU or maybe even CPU while doing that (in practice the image here is likely much, much smaller though). A CPU is really, really fast when you give it the right workload but in practice spends most of it's time stalled waiting for things - hence hyperthreading

    Windows' UI was done entirely in software pre-vista and even after while compositing is on the GPU if possible, many apps still render in software (the task manager will show you which applications have a GPU context active). But a significant portion of making that work was focusing on only re-painting the dirty region and reusing the old stuff - not doing unnecessary work

    Like assuming it's the painting that's slow and not the texture usage/upload all you'd have to do is determine the delta from the previous minimap position and just paint the new stuff over the now hidden stuff and that's probably fine, just not ideal. You can do some clever math with texture coordinates and wrapping to not even have to shift things

    Phyphor on
  • KoopahTroopahKoopahTroopah The koopas, the troopas. Philadelphia, PARegistered User regular
    edited November 2023
    GameMaker just went free to use for non-commercial use.

    $100 one time license fee for commercial use and console exports. All Asset bundles are free too: https://gamemaker.io/en/bundles

    KoopahTroopah on
  • GlalGlal AiredaleRegistered User regular
    Everyone scooping up the rats fleeing USS Unity.

  • KrathoonKrathoon Registered User regular
    I am learning Godot right now. Screw Unity!

    It is not that hard to pick up if you are an experienced programmer. It is like a GUI for object oriented programming.

    The real challenge is making the content. You have to learn animation and make artwork.

    GameMaker is more useful for 2D games. It has all the tiling tools. I don't think Godot has that.

  • KrathoonKrathoon Registered User regular
    Ah! Godot has tile maps. Brilliant!

  • KrathoonKrathoon Registered User regular
    I like how Godot is all in one small exe. Easy to carry around. I will try out their scripting language.

    I also downloaded the .NET one just in case I would rather use C#.

  • templewulftemplewulf The Team Chump USARegistered User regular
    Krathoon wrote: »
    I am learning Godot right now. Screw Unity!

    It is not that hard to pick up if you are an experienced programmer. It is like a GUI for object oriented programming.

    The real challenge is making the content. You have to learn animation and make artwork.

    GameMaker is more useful for 2D games. It has all the tiling tools. I don't think Godot has that.

    Godot's sprite tools are a little less sophisticated than Unity's, but as of 4.x they have most of the same features

    I'm still skeptical of it in some areas, but I *really* like it in others. Autoload singletons, for example, make it easy to see their intended design patterns

    Twitch.tv/FiercePunchStudios | PSN | Steam | Discord | SFV CFN: templewulf
  • GlalGlal AiredaleRegistered User regular
    edited November 2023
    Krathoon wrote: »
    Ah! Godot has tile maps. Brilliant!
    Yeah, you have TileMaps for 2d, and GridMaps for 3d. Also look into auto tilemaps, they allow for very quick level prototyping once you've set them up.

    Speaking of, I've now rewritten my minimap code to generate and scroll around a tilemap (instead of drawing 2d Images onto a texture like Back In The Day) and, unsurprisingly, it was like 10% of the code to implement and runs lightning fast.

    Glal on
  • KupiKupi Registered User regular
    Kupi's Still At It Despite Everything

    Hey, speaking of tile maps, adding a terrain painter to my engine is what I'm working on right now. I am still working out the actual implementation details, so instead I mocked up a basic tile set in Asesprite that I could use to test whatever tools I wind up creating. I started with a brown background and added some random scribbles and shading, aiming for something stony/earthen/dirty and after I zoomed out realized that I'd ended up with a convincingly goopy mud instead. The greenery I added around the edges doesn't look like anything, but getting anything aesthetically pleasing out of the exercise felt like a win.

    369r74la4nvw.png

    Then I patched up the vertical tiling and made it loop:

    nzyhe2gpkcad.gif

    Looks like something Willy Wonka would be happy to drown a child in!

    ... or something like that.

    This week I'm going to continue my design effort on terrain painting in my engine. And/or, look into how Godot handles it and see if I want to consider leaving the engine development to the experts and try actually making something playable for once.

    My favorite musical instrument is the air-raid siren.
  • templewulftemplewulf The Team Chump USARegistered User regular
    Kupi wrote: »
    Kupi's Still At It Despite Everything

    Hey, speaking of tile maps, adding a terrain painter to my engine is what I'm working on right now. I am still working out the actual implementation details, so instead I mocked up a basic tile set in Asesprite that I could use to test whatever tools I wind up creating. I started with a brown background and added some random scribbles and shading, aiming for something stony/earthen/dirty and after I zoomed out realized that I'd ended up with a convincingly goopy mud instead. The greenery I added around the edges doesn't look like anything, but getting anything aesthetically pleasing out of the exercise felt like a win.

    369r74la4nvw.png

    Then I patched up the vertical tiling and made it loop:

    nzyhe2gpkcad.gif

    Looks like something Willy Wonka would be happy to drown a child in!

    ... or something like that.

    This week I'm going to continue my design effort on terrain painting in my engine. And/or, look into how Godot handles it and see if I want to consider leaving the engine development to the experts and try actually making something playable for once.
    I haven't tried tilemaps in a couple of releases, but I think this is still accurate: https://foosel.net/til/how-to-create-an-animated-tile-in-godot-4s-tilemaps/

    Twitch.tv/FiercePunchStudios | PSN | Steam | Discord | SFV CFN: templewulf
  • ContentContextContentContext Registered User regular
    On a related note, I've been working on a game that's kinda Desktop Dungeons inspired, so I've been making my way through a Minesweeper tutorial. It's the most coding work I've done since around this time last year, when I went through a basic platformer tutorial, watched the Harvard cs50 lectures and, in lieu of having access to the problem sets, played the syntax-focused Zachtronics games I had previously bounced off of. I then stopped and didn't touch Godot or anything coding related, until recently.

    It's not like I haven't been doing things that require programming logic. In recent years I've played plenty of the more visual Zachtronics games, launched rockets in Factorio, and built a lottery system and display in Minecraft. Interfacing with walls of text is just intimidating. On this project, I spent 2 nights agonizing over a problem that ended up boiling down to improper indentation. 2 nights fixed with 2 backspaces.

    I'm about a 3rd of the way through this one, but I managed to take what I've learned so far and get random tile generation working on my project, where the correct wall tiles are drawn, based on the neighboring wall tiles. It's not much, but it is a decent first step towards getting everything else I want in there. Just generating and viewing a bunch of maps got me thinking about the next steps, like what will constitute a valid map and what rules I'll need for enemy placement. Exciting times!

    I also need to/get to make more art for the next bit, which my strong suit.

  • KupiKupi Registered User regular
    Kupi's Intended To Write This In The Morning But Advent Of Code Ran Long So It Is Now The Afternoon, If Only Technically Friday Game Dev Status Report

    My intent has been, for a while, to implement terrain painting in my engine, where you can plop down a tile and it updates both itself and adjacent tiles to display the right sprite and trim the collision volumes so that they surround the aggregated terrain tiles instead of having a bunch of surfaces pointing toward each other on the internal side where (one would hope, and yet speedrunners exist) game objects would never interact with them. Instead, rather like that first sentence, I've found up somewhere entirely other than where I started as more pressing matters present themselves.

    Rather like the case of my object pooling implementation, upon coming back to it after several months away I've discovered that the way I handle content files in my engine was either or both of massively overdesigned, and just generally mismanaged, on account of my original assumption that I'd be handling any custom data objects in JSON serialization. Since canonical JSON doesn't handle object references well, I had set up most references to content objects as indexes into some array that was presumed to exist elsewhere in memory or at the head of the file. This resulted in a bunch of cases where code would do a lookup in a dictionary to get an index which it would then take back to a dictionary stored in a completely different component, look up a list by object type and finally wind up with the loaded content object. Because I've relented on the idea of my content files being human-writeable JSON, I've got room to use the not-entirely-standard referential-integrity-preserving settings in .NET's JSON serializer, which means that while I'm still slightly overcomplicating the idea of managing content files, it's a hell of a lot simpler than it used to be and doesn't require half the vigilance in remembering what place you expect a content file to land in to put together a new character that it used to.

    Instead of the old "friendly name to index in typed list" system that I refuse to elaborate further on because it's gone, instead anything that wants to use a content file just stores a property of type ContentReference<T>. ContentReference<T> stores the name of the file the content is in, and the actual loaded instance (which is not serialized). When a ContentReference<T>'s file name property is assigned, it goes to the main content manager in static space and requests the instance. The main content manager maintains a ref-count to the file and the loaded instance, so multiple attempts to load, e.g. the same spritesheet, all get just the same shared instance. When a ContentReference is deliberately disposed or finalizes, it alerts the main content manager, which decrements the reference count and unloads the file when the reference count hits zero. Because ContentReference is a reference type and tends to be stored in the template that active game objects are copied from, deleting an active game character won't unload content files, nor will spawning the first instance of that character cause a reload. The short version is that content files are all loaded in a controlled manner, no more than necessary, and a large number of game objects can share their content references without consuming a lot of memory.

    ---

    On the terrain-painting front, I had a pretty major breakthrough on how exactly to implement it; I knew there was a simple approach, but it took me a while to find it. (For now,) The goal is to determine which line segments on a fixed-size grid should be solid. The interior of each grid node is either solid (having terrain in it) or empty. So the question is how to map whether a tile is solid onto whether there should be collision volumes next to it.

    The answer: XOR, or binary addition with a single bit.
    0 + 0 = 0
    1 + 0 = 1
    0 + 1 = 1
    1 + 1 = 0

    Every time a terrain tile changes state (either becoming solid from empty or becoming empty from solid), all adjacent line segments assume their current state XOR 1. That's it! It's that easy!

    Let's start with an entirely empty universe, and drop a terrain tile into it. The solidity state of each line segment adjacent to the tile we just painted is XORed with its previous state. In this case, that means 0 (empty) + 1 (solid)
    +-+
    |N|
    +-+
    

    Now, we'll paint another tile next to the first one. The three line segments on the outside become solid (0 + 1 = 1), but the solid segment between the two tiles disappears (1 + 1 = 0).
    +-+-+
    |  N|
    +-+-+
    

    Painting another tile below the first one produces a similar effect: the segment above it becomes empty (1 + 1 = 0) while the other surrounding segments become solid (0 + 1 = 1).
    +-+-+
    |   |
    + +-+
    |N|
    +-+
    

    Just to round out the square, we'll put one more tile on the bottom-right. The segments to its left and above it disappear (1 + 1 = 0), while the ones to its right and below become solid (0 + 1 = 1).
    +-+-+
    |   |
    + + +
    |  N|
    +-+-+
    

    Finally, we'll delete the top-left tile. This makes the segments to its left and below become solid again (0 + 1 = 1), while the ones to its right and above become empty (1 + 1= 0).
    +-+
    | |D
    + +-+
    |   |
    +-+-+
    

    There's going to be more to it than that, since I want to combine adjacent line segments into longer collision volumes and tiles will need to change what sprite they display based on the surrounding terrain states, but in terms of identifying the required perimeter volumes it works and that's what matters.

    ---

    ... all of which I'd love to be working on, but I've realized that my change to content management is going to blow up all of my existing content files (it's okay, I only have test projects so far, no real progress on an actual game), and since I'm going to have to redo them all anyway I'm addressing some other low-level problems that are going to change how the content files are written. I will probably not bore you with a report on that; hopefully (ha ha ha) the next thing I decide to report will involve a gif of the terrain painter in action.

    My favorite musical instrument is the air-raid siren.
  • CornucopiistCornucopiist Registered User regular
    edited December 2023
    Hello there!

    it's been ages since I posted here, and I'm kind of afraid to find my last post and check the progress. This year has been nothing but middleware and refactoring.
    Basically, I figured out how to draw hexes properly. These are specific hexes with bundled outlines representing transport, houses, etc.
    The secret is that I check pixels for their place on a tile that starts at the left side of the horizontal lines, and includes the 'fork' on the right. I therefore need type data for each of the three hexes in such a tile. With that I can calculate the offsets, bundle thicknesses, line types...
    All of which went through several iterations of classes.
    I also had to introduce triangles wedged into lost space as each 'hex' deforms to create randomness.
    Then I added CSV input and new class handlers going from human-readable to runtime classes.
    Spaghetticode got refactored and respaghettified for expediency, porting most of the code into the creation of the 'startnode' class that now basically generates all the variables needed to draw the entire tile.
    All in all, a solid two (maybe three) week's work crammed into a whole year, without much to show for it nor any idea if the other middleware modules I created last year will still work.
    My personal life has been dire, there's been work stress, etc. etc. but floundering in the refactoring wastelands has, I have to admit, not been conductive to progress either.
    This week I'm pushing through a few especially boring refactoring droughts, hoping to end up with something close enough to the desired end result that I can go test the whole shebang...
    Or, at least, move on to incorporating interior canals, roads, and bridges.

    86u4ich17zmj.png

    Cornucopiist on
  • KupiKupi Registered User regular
    Kupi's Abrupt Monday Game Dev Status Report To Share A Joke To Comfort Cornucopiist

    I had an extremely productive week last week. I deleted so much code.

    (I have discovered four or five paved paths to nowhere as a result of my content-management refactor, and wiped out ten to fifteen code files that wound up unreferenced by the end of it. Refactoring does feel productive, but sometimes it can be good to lighten your code, either by jettisoning stuff or just rearranging it.)

    My favorite musical instrument is the air-raid siren.
  • LD50LD50 Registered User regular
    As one of my favorite YouTube channels says: Delete Your Art!

  • KupiKupi Registered User regular
    Kupi's Friday Morning Distribution of His Ongoing Autobiographical Light Novels

    We're back from the holiday hangout, and that means the Game Dev thread is once again subject to my drifting ramblings on the subject of what I’ve been up to lately in terms of game development. I have been unusually disciplined lately, largely on the back of the novelty in making a major change of direction, so there’s plenty to discuss.

    In the week prior to the holiday forum, I decided that since my own ECS architecture was facing a gigantic overhaul that was going to render pretty much all the extant content files I had made for test projects useless, I might as well see what things looked like on the other side and experiment with Godot. I went through the public documentation, reading and following along with the “getting started”-type material up through their 3D game tutorial. Here’s the main takeaways I came away from the exercise with.

    Godot has the design philosophy you’d expect from an open-source competitor to Unity. While it comes with a great many tools out of the box, the Godot team is extremely cautious about adding new features to the feature set. One specific example they gave was AI (in the “video game” sense, not the “the current blight on our society” sense); Godot does not currently have anything like Unreal’s behavior trees and pawn-controller system, because 1) these are extremely heavyweight features that the team can’t provide appropriate care for as open-source developers, and 2) they aren’t so generally applicable as to be something that every game might employ. These more esoteric pieces of a game engine are left to be provided as extensions. Extensibility is considered a critical feature of the engine, with delegation to high-performance code written in C or C++ being the expected path to handling most things that are beyond the core engine’s capabilities.

    Godot is object-oriented. A sidebar in the tutorial pages linked to a blog post addressing ECS architecture and why the Godot team has consciously chosen not to employ it. My cynicism directs me to say that the article was written in the sort of polite tone that carries a hint of exasperation at having been forced to answer the question one too many times, but that’s irrelevant. The core premise is that ECS is an extremely heavyweight, data-oriented approach designed to manage incredibly large data sets-- things like Cities: Skylines where you might have tens of thousands of characters moving, perceiving their surroundings, and colliding with each other on-screen. Godot is not designed for such AAA game processing; it’s designed for rapid iteration and enabling quick game design. There are various mechanisms for eliding objects in play, such as a built-in “is this object on screen” component that can be used to detect when something is no longer visible to the player and disable or delete it. The Godot philosophy to performance is that objects you aren’t processing have no cost, so be smart about what actually exists instead of throwing more hardware at the problem. I can dig it.

    The primary languages Godot supports are GDScript (as a first preference), C and C++ through the extension library (which I did not explore), and, distantly, C#. I avoided C# because C# is what I have always worked with and I wanted to step out of my comfort zone; besides that, the Godot pages note that C# support was developed with the support of a generous financial grant from Microsoft, which sort of scans to me as Microsoft trying to buy their way in on the ground floor of the next big indie game engine and that feels… skeevy. I dunno, as much as I love C# the language I’m leery of Microsoft having too much cultural control of anything. Anyway, GDScript: it’s Python-like, with many syntactic similarities (you can create a new variable on anything at any time by performing the assignment; scopes are controlled by indenting; you use the word “and” instead of “&&” as the and operator; and so on). However, it’s (very explicitly) not actually Python or any other scripting language. The various existing scripting languages all had various deficiencies that made them less useful for driving a game engine, in particular that they tend to be garbage-collected, and nondeterministic performance loss is not something the Godot devs are willing to accept. (Godot-controlled objects are reference-counted instead; indeed, the highest entry on the inheritance tree for Godot classes from which all else derives is “refcounted”.) Consequently, GDScript is built into the editor at all levels, which means it has better auto-complete and context-sensitive suggestions than it might otherwise. The class documentation is even built into the editor, so if you need to look up a function of a particular class you can just pop it open in a tab instead of having to open your web browser and risk winding up somewhere in Wikipedia.

    Speaking of integration, the Godot editor is itself a Godot application. It uses the same Button classes, callback engines, object management APIs, and so on that the engine makes available for building games. This ties back to the point about extensibility-- because there’s no real difference between “user” code and “engine” code, it’s far easier to connect the two if you need to.

    As for concepts within the actual engine, Godot structures the world as a tree of Nodes. Everything with a game existence is a Node, and the minimum responsibility of the Node is to link to its parent (unless it’s the root Node), and to provide access to its child Nodes, if it has any. Nodes are arranged in Scenes, which take the place of both Unity’s own concept of a “Scene” and the “prefab”. A Scene is just a pre-arranged set of Nodes that can be instantiated and assigned into some Node tree. That could be at the root, to set off the main gameplay, or it could be as a leaf node to represent the player or an enemy. This tree structure enables and encourages you to assemble the game out of independent pieces. You can build the player and a few types of enemies, then create a gameplay scene that instantiates them as necessary, then connect that whole “gameplay mode” scene to a “game type management” node that swaps between the main menu and gameplay scenes. The tree structure is also what enables that “perf by reducing the active set” concept I mentioned above-- when you disable a Node, you also disable all of its children, so you can calculate what’s relevant to the player and shut off processing for everything else.

    One feature I was absolutely delighted to see was that sound players have support for a “loop from” position on the sound file they’re playing. That means you can use just a single file for background music that has an introductory phrase on it that then loops from a different point.

    There is at least one practical matter on which I’m not real fond of what I’m seeing: the way sprites are handled. At least from the tutorial material, it appears that Godot can import sprites either from full files, or as completely regular grids on a file, but not as directed subsets of a given image file. Sprite atlasing is an important component of graphics performance in 2D (or, at least, it is for XNA), so it’s disappointing to see sprite import lack the facilities I was kind of hoping to see. That could easily be a matter of me not knowing exactly which class to look for; I have only barely begun to read the manual. It could also be another instance of the Godot philosophy-- if you have less than a hundred enemies in play at once, and their sprites are all 64x64 or less, does it really matter how many times you upload their image data to the GPU? You’ll be coming in well under your render time budget anyway.

    I may or may not continue to explore in this direction; it’d be a better use of my time than a lot of what I’m doing these days.

    Such as my home field boondoggle, the ECS game engine. Recently I started a major restructure that changes how content files are handled by my engine; this, plus a few other revisions due to going “oh my god that could be so much simpler than I made it originally” wound up knocking down the entire tower. I spent several days with the entire codebase unable to build because key classes and methods were missing, but it was more of a matter of certain support structures being missing than all the bricks coming unmortared. The short of it is that I have most everything building again, and the project editor (the thing that edits content files) builds and runs, even if all the old content files are untouchable.

    So, with the new content management system in place, I created a new editor for spritesheets. Originally I had had a Sprite component that itself stored the Texture (image), subset, and offset-from-center information, which could be fed values from anywhere, but normally a SpriteAnimation content object that determined how to change the information on each frame. I’ve changed this so that a Sprite component stores a reference to a Spritesheet and the name of the sprite to display, and the spritesheet contains the texture information and a dictionary of named subset-offset information objects. Ultimately this means that Sprite components store less information (two references) instead of duplicating information. It does mean that I’ve lost the ability to have the Sprite component display arbitrary subsets of a given texture, but I’m having trouble coming up with instances where it would be necessary-- it could be used to make sprites “shatter” like how Axiom Verge does when enemies die, or a sort of “dissolve” effect by scrolling the top of the subset downward (like how I’m sure some enemy somewhere in Castlevania dies), but even that could be supported through a shader or just a series of overlapping sprite entries. Don’t mind my mumbling.

    Anyway, I wrote a spritesheet editor. Here’s a demo:
    c4y2nul12b5o.gif

    Since sprites and animations are done, the next barrier to getting my test projects back up and running is the Character editor. I’m currently revisiting the “arbitrary object editor” Windows Forms control I created to support modifying component data. I had been experimenting with using the Windows Forms Table Layout Panel to control where the various controls that let you edit component properties were arranged, but I have discovered that it’s a cantankerous beast that routinely puts things in weird places. I don’t envy UI framework designers, who are trying to solve the difficult problem of certain elements wanting to size up infinitely while other elements are trying to constrain them to a maximum size, but I do wish that more of what they produced actually worked. I am finding that just manually instantiating the controls, reading their Size properties, and calculating where the fuck they’re supposed to be myself is producing far more reliable results.

    Restricting my outlook to the coming week only, I expect to stay mired in the component editor for the duration, as it needs to support editing collections and dictionaries, which are both their own beasts.

    My favorite musical instrument is the air-raid siren.
  • GlalGlal AiredaleRegistered User regular
    Regarding getting sprite frames from a larger image, I believe what you want is an AtlasTexture. I've not needed to use it yet (no 2d project ever got to the point where I needed the optimisation, and I'm currently messing with a 3d one), but this lays out pretty well what you can use it for.

    https://gamedevacademy.org/atlastexture-in-godot-complete-guide/

    Not sure how much you can do in-editor, but you can define the regions programmatically, or have&load a JSON that contains that information.

  • KupiKupi Registered User regular
    Yep, that's what I found when I went looking for sprite atlassing in Godot out of curiosity, but I didn't get deep into it since I was trying to stay on the rails as far as the tutorial material went. Good to know the capacity's there, even if the approach is a bit more manual.

    My favorite musical instrument is the air-raid siren.
  • KupiKupi Registered User regular
    Kupi’s Weekly Friday Game Dev Status Report, This Time Without Any Silly Modifications To The Title Oh Wait God Damn It

    I did a lot of work this week in terms of hours, and in terms of actual outputs to describe it’s fairly thin; I could go into a great amount of detail on the particulars of what I accomplished, but a short description suffices.

    The next major step on the way to having my test projects running again is the “character” editor; in my engine, a “character” is an instantiable object that you can put in a Level’s placement grid. Originally this encompassed everything from terrain tiles to actual moving objects, but with the terrain editing facilities on the horizon a Character is really more just about the moving objects in practice. In any event, Characters have an Entity Storage object representing their moving parts, which are then poured into the Entity Storage in the main game when the Character is created. Editing a Character therefore necessarily entails being able to edit Components that exist within the Entity Storage, which means using a Form to edit arbitrary objects. I had a previous implementation of that concept of “present a control to edit an object’s public properties”, but I didn’t like how it worked in practice, so I started over from first principles.

    Or, to put it another way, as so often happens, I spent a week putting together a tangibly worse version of what you’d get out of a professionally- (or, as with Godot, volunteer-but-professional-feeling*-)developed game game engine. Oh well! It’s who I am.

    Here’s a screenshot:
    tn67nmk1w8u9.png

    The interface handles C# intrinsics like ints and strings, wraps reference types in a control that manages instantiation, nullification, and selective investigation of the instance (this prevents circular references from blowing the stack), and provides facilities for editing lists, dictionaries, and hash-sets. It automatically resizes as controls are added and removed using hand-coded sizing and layout because I simply could not get the built-in FlowLayout and TableLayout controls to arrange in ways I could predict. (The TableLayout documentation even begs you not to try to nest them because they know stuff lands wherethefuckever it wants.)

    After I felt satisfied with the object input controls, I realized that I’d forgotten to support Enum types. Creating a control for editing those properties was trivial, but it reminded me that I’d also forgotten to revise my Enum type support in my binary serialization system. So I spent most of yesterday bringing that up to speed, and now my binary file format supports saving and loading enum types.

    My next goal is updating the Character editor to use the new object input system, which should allow me to remove the old implementation (huzzah for deleting code). After that, I’ll resurrect what I can of my old test project’s character files, and once I can compile and run the test project again, I’ll break ground on the terrain painter.

    Or maybe I’ll give up and just use Godot (/s a real engine) like I should have years ago. Who knows.


    * This was one of those stray thoughts that didn’t make it to print in my last post because I just forgot to include it— my positive impression of Godot is that it is the least open-source-feeling open-source product I’ve ever used. It feels like it was built to be used by people other than the programmers who built it.

    My favorite musical instrument is the air-raid siren.
  • GlalGlal AiredaleRegistered User regular
    Godot feels like Blender, where it keeps developing into a better and better tool for the target audience.

    Unlike GIMP, which still feels like its developers had never used a photo manipulation tool in their life and develop features based on stories they heard of what they were for.

Sign In or Register to comment.