devlog

I finished a big milestone in my Inchrevision this past Friday. The structure of the map is a hub attached to three dungeons, one of which is gated by completing the other two. My latest accomplishment is that I completed one of the dungeons, a boxy vertical one I called wetdungeon during development.

I’m pretty proud of this! It still took me forever to make, but I am realizing that is now because I have chosen some pretty hefty/large-scale targets to aim for. People always say start small when you’re learning, and I keep thinking I should have heeded that advice. And then I think ah who cares…

7 Likes


been making screens for my adventure game. making good progress although this part can be tedious for me. i’ve found just getting stuff down is important in the beginning. creation is the necessary first step, and improving/iterating is the step that makes it “good” but that can come later.

also started iterating on music for it. i noticed i get very different results when i make a song from scratch using different trackers/programs. the more complicated a program, the harder it is to get the initial idea down. so my new strategy involves making a song in two steps. i use a simple program to work on basic structure/melody (because it’s easier for me to do quick and dirty things there and also visualize it) and then i move to a program that has more knobs for expression once i’m happy with it.

i thought this two step thing might be onerous and extra work but it turns out nah it doesn’t really slow me down much and separating out the steps like that keeps me focused so i don’t go down rabbit holes too early.

this video gives an example of what both passes might look like.

7 Likes

Woah this looks great! What programs are you using to make these screens?

it’s godot – i’ve kind of hacked together a “map editor” by making each screen a “scene” and then i smush them all together in a “map” scene.

when the program starts it reads from this scene to get all the map data and connections and caches that info in memory. then when playing the game, each screen is loaded individually as-needed.

looks a little different in game, too, because i do layering to get some depth.

4 Likes

I really love the way that looks. Super impressive and cool how you made a tool to suit your own purposes here. Developing in Godoy seems scrappy.

yeah weirdly it works for me. i tend to be more creative within a framework than on my own. my previous attempt at making a game was to build all the tools myself from scratch (exactly the way i wanted them), and that turned out to be well maybe a useful programming exercise but not a good way to make a game.

funny enough, even when you have full control over the tools you make it somehow still doesn’t align to your ideal; you solve one class of problem just for an entirely new higher-level class of problems to emerge and potentially overshadow the problem you were trying to solve to begin with.

sure my editor in godot isn’t perfect but it took an order of magnitude less time to throw together, so i can actually get to the “making the game” part of making a game.

3 Likes

Yeah classic wisdom is probably do the thing everyone learns in hindsight… but you gotta internalize these hard lessons yourself. You’re probably way better off for having gone the really hard route and, crucially, lived to learn from the experience and do it differently this time.

I’ve been thinking about this…I feel like it’s not necessarily true in all cases although it probably varies by game and developer preference and so on, but I want to note what I feel like are at least a couple of potential exceptions just for the sake of keeping options open kind of…I feel like there can sometimes be something really special, maybe depending on how you like to work, about:

  1. really scrappy tools (even like, barely even real tools :stuck_out_tongue:) that are just enough to let you say what you want,
  2. a DSL (Domain-Specific Language, custom mini-language) you invent as you go that lets you describe the game in whatever syntax you please, and
  3. expressing the game as a SQLite database.

Of course all these techniques can be mixed.

the scrappy "barely-a-tool tool" approach

Theis approach can yield an extremely low “tool development effort to power+flexibiliy” ratio, where you can get a surprisingly fun and expressive uncluttered editing GUI trivially. An example of this to me is like, the way Loren Schmidt made the environments for their game Strawberry Cubes—they came up with a lot of different “tile types” with different cellular automata associated with them and things, and then composed them into fixed-size screens using MS Paint with a scale of 1 pixel = 1 tile, associating a different color of pixel with each tile type. Everest Pipkin and I made a screen for the game together at the time using this system (although I don’t remember if it actually went into the game or not :stuck_out_tongue:) and I remember it was really fun and easy, and very cool because the behaviors of each tile type were quite elaborate and varied. They had a “tile key” open in another window they would edit on the fly for their own reference as they added new tile types (you could also generate something like this as part of your build process using cairo+pango or the like, if you didn’t want to keep it in sync by hand); if I recall it was sort of like this:

with a single screen laid out in this sort of fashion:

leading ultimately to:

I’ve remembered that technique ever since then because it’s really easy adapt to like, any arbitrary thing you can think of as a “tile-based game map” or “grid of adjacent cells” or whatnot. With 24-bit color you have 16,777,216 available tile types so you can take this pretty far, and you can also use the different color channels to express different things (like R can index into a set of “bottom tiles,” G into a set of “middle tiles,” and B into a set of “top tiles” for 3-layer overlapping, or other things like that). You have a really nice “instant map editor” in Krita, GIMP, MS Paint, w/e that way, and you can also play around with procedurally generating maps/grids by generating images of the right size using the relevant pixel colors. Lots of different kinds of game data can be fit into this structure, especially if you start assigning meaning not only to individual tile colors but also to certain arrangements of tiles, and there’s a large family of amenable graph traversal algorithms that you can use to solve common problems like pathfinding.

So, that’s what I mean by a “scrappy tool” or “barely-a-tool tool”—assigning meaning to some data format you already have good powerful tools for authoring and which is easy to parse, and which is also amenable to elaborate procedural generation if you’re interested in that, like color data or audio. You could maybe call this an “out-of-the-box binary format” for game data.

The Domain-Specific Language approach

A DSL is more like a “custom text format” for game data. The best example of this I have on hand is actually not for a game per se, but rather a tool I wrote to diagram dependency relationships in the Vulkan API:

(full-size svg)

That’s much of the API all at once there, but it can also make little subset parts:

(svg)

In the SVG files the blue underlined parts are links to the Vulkan documentation you can click through. Green squares are for structs, black squares are for fundamentally-typed struct fields, and maroon ovals are for functions. Vulkan has a very twisty, baroque API as you can see, so this aid helps me keep track of where I am in it and what I need to do next.

This is what the DSL source text for the SVG just above looks like:

(raw)

I think this is nice if, in any situation where you might want to edit the data by hand, you would prefer to edit the data in question with a text editor as opposed to a paint or audio program. Writing a parser to consume this data is not as hard as you might think, because there are specialized programs designed for this purpose, lexers and parsers, and even development tools that can generate lexers and parsers for you. This Vulkan-diagramming doodad uses the lexer generator Flex (which generates a lexer in C or C++) and the Ruby standard library parser generator Racc. Those aren’t typically used together; I did this as an experiment when I writing a translation of some of the Japanese-only parts of the Racc docs (which I had totally forgotten about until just now and need to finish and submit a pull request for speaking of :fearful:). Other things you can use are Ruby’s StringScanner stdlib class for lexing which allows for an all-Ruby approach, pair Flex with its more traditional partner Bison for an all-C or all-C++ approach, use Haskell’s Alex and Happy which I haven’t actually tried but have heard really nice things about and maybe will try the next time I want a parser, etc.

If you’re curious, here’s the Flex source—this generates a shared object file which Ruby can consume and call into:

(raw)

and the Racc source:

class VkGraph::Parser
rule
  graph: cnctstmts
       |

  cnctstmts: cnctstmt
           | cnctstmts cnctstmt

  cnctstmt: id cnctlist { @ordered_nodes << val[0]
                          @current_cncts.each do |c|
                            @cnct_list << [val[0], *c]
                            if c[0].kind == :CMD
                              cmd_buff = Node.new('VkCommandBuffer', :STRC)
                              begin_cmd_buff = Node.new('vkBeginCommandBuffer', :FN)
                              @cnct_list << [cmd_buff, c[0], :none]
                              @cnct_list << [begin_cmd_buff, c[0], :none]
                              @cnct_list << [cmd_buff, begin_cmd_buff, :none]
                              @added_nodes.add(cmd_buff)
                              @added_nodes.add(begin_cmd_buff)
                            end
                          end
                          @current_cncts = [] }

  cnctlist: cnctline
          | cnctlist cnctline

  cnctline: cnct
          | cnct ret { @current_cncts[-1][-1] = :ret }
          | cnct opt { @current_cncts[-1][-1] = :opt }
          | cnct arr { @current_cncts[-1][-1] = :arr }
          | cnct msk { @current_cncts[-1][-1] = :msk }

  cnct: CNCT id { @current_cncts << [val[1], false] }

  id: raw_id { add_node(@current_node)
               result = @current_node }

  raw_id: STRC  { @current_node = Node.new(val[0], :STRC) }
        | CMD   { @current_node = Node.new(val[0], :CMD) }
        | FN    { @current_node = Node.new(val[0], :FN) }
        | NONVK { @current_node = Node.new(val[0], :NONVK) }
        | raw_id ANNOT { @current_node.annotation = val[1] }

  ret: RET

  opt: OPT

  arr: ARR

  msk: MSK

---- header
require 'set'

module VkGraph
end

require_relative 'lexer.so'

class VkGraph::Node
  attr_reader :id, :kind
  attr_accessor :annotation
  def initialize(id, kind, annotation: "")
    @id         = id
    @kind       = kind
    @annotation = annotation
  end

  def hash
    (id + annotation).hash
  end

  def ==(other)
    (id == other.id) && (annotation == other.annotation)
  end

  alias eql? ==

  ##
  # Turns a Vulkan identifier into an easier-to-read form.
  #
  # "vkCreateDescriptorSetLayout" -> "Create descriptor set layout"
  # "vkBufferView"                -> "Buffer view"
  #
  # "vkCmdPushConstants"          -> "Command: Push constants"

  def fmt_id
    id.sub(/^[vV]k/, '')\
      .sub(/^Cmd/, 'Command: ')\
      .gsub(/_/, ' ')\
      .sub(/^[a-z]/) {|c| c.upcase}\
      .gsub(/[0-9]/) {|c| ' ' + c}\
      .sub('KHR', ' KHR')\
      .split(/([^ ][A-Z])(?!([A-Z\s]|$))/)\
      .each_with_index.map do |s,i|
        if i % 2 != 0
          s.downcase.insert(1, ' ')
        else
          s
        end
      end.join
  end

  def label
    main = fmt_id + ' ' + annotation

    if kind == :NONVK
      "<#{main}>"
    else
      "<<u>#{main}</u>>"
    end
  end

  def shape
    if (kind == :STRC) || (kind == :NONVK)
      "box"
    else
      "oval"
    end
  end

  def color
    case kind
    when :STRC
      "green"
    when :CMD
      "orangered2"
    when :FN
      "purple"
    else
      "black"
    end
  end

  def fontcolor
    if kind == :NONVK
      "black"
    else
      "blue"
    end
  end

  def grphv_id
    id + annotation.gsub(/[^a-zA-Z0-9_]/, '')
  end

  def stmt
    href = ""
    unless kind == :NONVK
      href = "href=\"https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/#{id}.html\",\n"
    end

    <<-END
    #{grphv_id}[#{href}label=#{label},
          tooltip="#{id}",
          shape=#{shape},
          color=#{color},
          fontcolor=#{fontcolor}]

    END
  end

  def to_s
    "{id: #{id}, kind: #{kind}, annotation: #{annotation}}"
  end
end

---- inner

  def parse(fname)
    @lexer = VkGraph::Lexer.new(fname)
    @current_node = nil
    @annot = ""
    @added_nodes = Set.new
    @ordered_nodes = []
    @current_cncts = []
    @cnct_list = []
    nodes = ""
    @cncts = ""

    do_parse

    @ordered_nodes.each do |node|
      nodes += node.stmt
    end

    @added_nodes.subtract(@ordered_nodes).each do |node|
      nodes += node.stmt
    end

    @cnct_list.uniq!

    _cnct_list = []

    cncts_by_to_id = @cnct_list.chunk {|argl| argl[1].id}.to_h
    cnct_tos_by_id = @cnct_list.inject([]) {|arr,argl| arr << argl[0]}.uniq.chunk {|n| n.id}.to_h

    puts cncts_by_to_id['VkImage']
    puts cnct_tos_by_id['VkImage']
    cnct_tos_by_id.each_pair do |id, nodes|
        nodes.each do |node|
          if cncts_by_to_id[id]
            cncts_by_to_id[id].each do |argl|
              _new = argl.clone
              _new[1] = node.clone
              _cnct_list.append(_new)
            end
          end
        end
    end

    @cnct_list.concat(_cnct_list)

    @cnct_list.uniq.each do |args|
      @cncts += cnct_stmt(*args)
    end

    dot_fname = File.basename(fname, '.*') + '.dot'
    svg_fname = File.basename(fname, '.*') + '.svg'

    File.open(dot_fname, 'w') do |f|
      f.puts "digraph {"
      f.puts nodes
      f.puts "edge [color=\"#00000099\"]"
      f.puts 'rankdir="LR"'
      f.puts @cncts
      f.puts "}"
    end

    `dot -Tsvg #{dot_fname} -o #{svg_fname}`
  end

  def next_token
    tok = @lexer.lex
    tok
  end

  def add_node(node)
    @added_nodes.add(node)
  end

  MAX_COLOR_B10 = "777777".to_i(16)

  def rand_color
    sprintf("%06x", rand(MAX_COLOR_B10))
  end

  def cnct_stmt(from, to, attr)
    tail = " [color=\"##{rand_color}99\""
    case attr
    when :ret
      tail += ",arrowhead=empty"
    when :arr
      tail += ",arrowhead=box"
    when :msk
      tail += ",arrowhead=tee,style=dashed"
    when :opt
      tail += ",style=dashed"
    end
    tail += "]"

    "    #{from.grphv_id} -> #{to.grphv_id}#{tail}\n"
  end
---- footer

parser = VkGraph::Parser.new
parser.parse("#{ARGV[0]}")

Both are kind of in a “not intended for public consumption” state so my apologies for that. :stuck_out_tongue: Although they work, you can tell they were written in haste, they have little documentation, etc.; I’m just sharing them in the hopes that they might be useful or interesting somehow. Someday I need to make this into a “proper program,” with tidy code and a graphical UI…I feel like it would make a nice web application.

As a little bonus, too, here’s my custom Vim syntax highlighting file for the DSL—this is nice to have:

vkg_vim

(raw)

Aaaaanyway. Even though in this case we get diagrams of the Vulkan API, you can probably imagine how readily this could capture arbitrary game logic instead. It’s nice for anything sequential, also for like branching paths and things, any time you want something like a markup format, etc. etc. About half a year ago I started sketching out a format like that to describe a kind of '80s-Enix-adventure type of game, set on a generation ship and kind of inspired by experiences I’ve had working on Wikipedia among other things, which I started work on and then had to drop because Lily and I were getting really short on cash and had to pivot to doing freelance work for a while, but may return to once we’ve done enough:

---start

:Makrus

My question is addressed to
Tender and Sagar.

Both of you said in your bios
that you care particularly
about the idea of SRs being
compassionate.

:Tender

Yeah.

:Makrus

What does that mean to each of
you in practice, and do you
feel like there are ways the
SR role could be more
compassionate?

:Tender

Sagar, do you wanna start?

:Sagar

+ yes
> Sure, if you'd like.
- sagar-gofirst

+ no
> That's all right, you're
> free to go ahead.
- sagar-gosecond

---sagar-gofirst

:Tender

Sure.

:Sagar

Okay. So, that's something
Tender and I have actually
been talking about a lot
recently.

Um, I think, at least speaking
for myself...

+ SRs could be more
  compassionate, it's true
* more critical
> Not to point to anyone in
> particular, just overall
> reading the boards lately, I
> feel like there's been a bit
> of a tendency to scrutinize
> requests for imperfections
> and turn them down too
> readily.
- too-readily-rejecting

+ A request is like a cry in
  the dark
* more warm
> Something I've noticed is
> that, when any of us reaches
> out to an SR, it's often in
> a moment that feels like a
> bit of a personal crisis.
- request-crisis
the SQLite approach

SQLite is a very convenient library and utility which allows you to utilize a single file as a SQL database. A SQL database represents data as a set of tables, each of which consist of an ordered set of column headings and one or more ordered rows of typed data, with one row field per column. The format of a SQL database has certain special advantages; it’s designed to facilitate a very low-coupling way of expressing data, where the data in the different tables is very easy to pick out and select from, transform, recompose into new tables on the fly, etc. etc.

The most handy example I have is again not actually a game, so my apologies for that; rather it’s the database for a very simple program I wrote the other day to keep track of what I’m doing hour-to-hour, for when I need to keep track of how much time I’m spending on something or just want to help myself a bit with time management. I think this database will work nicely as a li’l introduction because it’s very concise.

Here is the tiny “schema,” as it’s called, for the database, which describes two tables, activities and entries:

create table activities (
    id          integer primary key not null,
    description text    unique not null default ''
);

create table entries (
    id               integer primary key not null,
    secs_since_epoch integer not null,
    activity         integer references activities not null,
    secs_taken       integer not null default 0,
    notes            text
);

Here’s an example of an activity:

id  description                                                 
--  ------------------------------------------
10  post 暴れん坊天狗 "non-gameplay music" on SB

and here’s an entry:

id   secs_since_epoch  activity  secs_taken  notes                                                       
---  ----------------  --------  ----------  ------------------------------------------------------------
84   1712631174        57        0           thankfully setting GTK_IM_MODULE and QT_IM_MODULE to `ibus` 
                                             seems to have fixed it like the maintainer suggested  

The keywords primary key in the schema means that the field in question holds the unique identifier for the row. The keyword references means that the column contains “foreign keys,” primary keys for rows in another table—a pointer to data elsewhere, basically.

The idea with this design is that often I want to reuse activities; having a special table for uniquely-named activities facilitates that, because then an entry can store the ID of an activity instead of the activity text verbatim. That allows us to easily do things like sum up how much time has been spent on that activity in total, just by dint of having decoupled activites and entries:

sqlite> select id, description, round(sum((select secs_taken from entries where activity=id)) / 3600.0, 2) total_hrs from activities where description='work on diary software';
id  description             total_hrs
--  ----------------------  ---------
1   work on diary software  2.14   

Although I’m using the SQLite console interface here, there are other ways of interacting with a SQLite database: you can use the official C library, bindings for many other languages if you want to generate the data, or third-party GUI tools if you want to edit the data by hand that way such as DB4S:

SQLite databases are very commonly used as save files or to store media for a game, but you can also use the database to describe behavior, game areas, etc. etc. just like the other two techniques. This works nicely when you want to impose as little intrinsic strucure on your data as possible, to make it very easy to arbitrarily rearrange and recompose and so on.

All of these techniques can pair nicely with SDL, with which you can take in data from any of these and present the resulting game to the player. That way, you get a very lean game, fast and with a small binary if you aim for that, which will run on all the major PC platforms and then some (including obscure Linux distros and the BSDs). For generating the data, of course, you don’t necessarily have to even think about SDL—you can use whatever tools or language you like because it can just be part of your build process. Something I always think is nice to note is that, although with all three of these you can work with the data by hand, they’re all amenable to procedural generation as well, so if you like you can procedurally generate a whole game using these techniques as a basis. Also, you may end up with trivially easy modding for free and that sort of thing just by allowing the client to run other game files aside from the built-in one.

You might be able to come up with other techniques similar to these; they all share the basic approach of finding (or inventing) some kind of flexible and expressive easy-to-work-with data format that can describe your whole game in essence, then writing a thin engine that can consume and present that data. For games that are really experimental, especially, I think these approaches are nice, because you can design your game’s data format to support a very wide number of possible games and even have fun feeding weird garbage into the engine and seeing what happens and that sort of thing. You can give yourself a very focused development environment this way, too, one which uniquely reflects the structure of your game.

None of this is necessarily to say that these kinds of approaches are better than using Godot or Unity or the like. Obviously it’s nice to have all the features those engines provide close at hand, and for some games I realize that will yield the quickest and easiest development process. I just think it’s nice to keep strategies like these in mind for the special advantages they have, even if they aren’t necessarily the right approach all the time.

As a side note, if anyone is interested in using these techniques and wants more info, feel free to ask—I know I’m presenting them all kind of sketchily. I have a mostly-written first draft of a “how to make a game in SDL using C++” tutorial I’m getting ready to post here soon, and now that I’m thinking about it that would probably also present a nice opportunity to demo one or more of these.

2 Likes

yeah i’m not saying this is some universal truth – rather, i have a lot of direct experience with how my projects tend to fail and what makes me the most productive, and these kinds of programming-first or programming-centric schemes typically aren’t compatible with how i want to work on a game for a couple reasons:

  1. it turns the project into a programming puzzle, which excites me in the beginning, and then once i solve the puzzle i drop it and don’t actually make the game part.
  2. i like to let an idea grow organically in a sort of feedback loop, where i analyze what’s fun, emphasize that, and potentially change what my game is “about” multiple times throughout development. maybe i’m not great at planning ahead or something but i find forcing myself into one of these schemes early hamstrings what’s possible for the game down the line or like forces me to lock down details about the game before i’m ready to do so.
  3. making my own tools means i have to solve most problems myself. for the types of games i make, engines are “good enough”. i don’t need a bespoke solution every time i want a new feature. ootb gets me close to what i want, and little tweaks get me the rest of the way there. tweaking ootb stuff takes me soooo much less time than doing things from scratch, bc i’m modifying something existing instead of creating a whole new universe to solve the problem.

i have noticed that many people that started w/ programming and then decided to make a game sort of bristle at my suggestion that engines are the way to go. but like, if my goal is to make a game, making the programming too interesting is actually antithetical to that. i have a lot of energy for programming and my brain likes to focus on concrete problems over loosely defined creative ones, so if i feed the programming side too much it actually steals time from the side i’m trying to exercise and improve on. like, mode switching is just a pain in the ass for me (maybe an ADHD thing) so if i’m trying to be creative but i have to keep stopping to solve some tricky technical problem, nothing really gets done and i burn out fast.

3 Likes

That makes sense! Yeah, I imagine what sorts of approaches work best depend a lot on your inclinations and tendencies as a developer. Like, Lily would never use any of the techniques I described on her own, for example, because she actually kind of hates coding and prefers to spend as little time doing it as possible; for her it’s pretty common that she’ll start making a game by making a 3D model of an environment, then bring it into Unity and add a collider and an FPS-style camera and controls, then build from there mostly just using like preexisting components and plugins and things. She’ll use tools like I’m describing if I set them up, but she wouldn’t want to put them together herself. So I guess like, speaking in general, in conversations like this, I just think it’s fascinating to compare different inclinations, since every developer has their own unique relationship to things like this.

One thing I do actually feel like you and I share is a sense that it’s good to get some kind of game going ASAP. When I first started out making games, there were several times I had the experience you’re describing of like, spending a lot of energy at first trying to figure out how to the engine going and then not actually doing the project afterwards. I also would say, kind of going along with that I guess, that I also feel like it’s nice to grow the game in an incremental, try-something-and-see-what-happens kind of fashion, and not try to bake too much in at the start.

One approach I often take in any programming project, which I think I actually first picked up doing web development, is to start by getting the “whole stack” going in a really minimal way at first—like, the interface, the “engine” whatever that might be, the output, etc.—so that you have a complete application or library on like day 1 of development, but just a very small one. That way you know that all the parts will work together, you get a feeling for the overall design of the codebase, and you’re less likely to be tempted to put too much of the program into any one layer at first. Sometimes I do think it’s a bit nicer to develop the interface or input part a bit far at first and then start on the rest, just to make sure you have a sufficiently-robust example to work from, but it depends.

I think, to some extent, it’s impossible to really plan ahead no matter what approach you take really, unless you have a crystal ball. :stuck_out_tongue: You never know what you or your collaborators or teammembers or other stakeholders or w/e will decide down the road. When I think about this I think of the Agile Manifesto, which I know has long since become a set of corporate buzzwords and things kinda but which I still think is a really great piece of text on its own by and large (although I always think it’s nice to remember that there are not inevitably businesspeople or a fundamentally commercial motive involved in software development, something that writing on both web and game development frequently has a hard time keeping in mind :stuck_out_tongue:). In any case though, one of things I like from “Agile” is the idea that you develop only in short-term intervals or “sprints,” like 2 weeks, and try to deliver an entire working piece of software at the end of each sprint, instead of trying to plan the whole thing at the outset. That way, if anyone involved wants to go in a new direction halfway through development, you don’t have a long-term plan to discard or reformulate, you can just pivot to the new approach. Sticking to the question of like, “What’s a complete working something we can develop in 2 weeks” (or a week, or a day, or an hour, etc.) helps you not get too ambitious at the start or bake in too much early-on.

You may well already be familiar with all this since I know these ideas are pretty popular (although I feel like they often get kind of misapplied in corporate environments etc.)—I’m kind of just noting this to give a sense of where I’m coming from. Even though I think this approach can work really well for like, any kind of software, I have observed that there’s something of an art to developing defensively under these conditions, because you can still easily back yourself into a corner as the days/weeks/months go by. I think, like, the risk of this isn’t as severe as if you plan the whole thing up front, but like, to speak in classical OO terms, if you get a set of classes together in the first sprint, and then just glom behavior into them in successive sprints, obviously after you’ve done this for a while you’ll have a few giant classes that are really hard to modify and it won’t be very easy to change direction anymore. That’s kind of an extreme example (although not unrealistic of course) but in practice there are lots of more subtle versions of this issue that are more insidious. I lean on the basic idea of “coupling” a lot when thinking about how to make a codebase futureproof, or even associated game media like 3D models/illustrations/music/level designs/etc.—I try to constantly ask myself “if I was to change (x) about the game what else would also need to change” and try to set things up so that the different parts of the program are mostly self-contained.

That’s actually a big part of why I personally am kind of averse by now to big heavy engines like Godot or Unity or Unreal or RPG Maker or w/e. I started out actually liking things like that more but by now I feel really sick of them kind of. I’ve had a ton of experiences involving Unity or RPG Maker, and somewhat more recently Godot, where I want to do something that I know is pretty simple in the abstract but the design of the engine makes it really awkward somehow, and I have to spend lots of extra development time and energy working around the parts of the engine that are in my way. To me that feels like it’s own taxing, frustrating, enthusiasm-killing sort of puzzle.

I often feel like every engine has its “ideal game” that is very easy to develop in it, and the more you deviate from that the more awkward it becomes to work in the engine. RPG Maker is very well-suited to making Dragon-Quest-3-alikes, Unity started out as the Gooball engine and is still well-suited to making 3D puzzle-platformers and stuff, Unreal has its genesis in late-'90s FPSs, etc. You don’t necessarily have to make games just like those to have a good experience with these engines, of course—it’s more just a matter of degree, I think, like if you try to something very different it may be frustrating, if you try to do something a bit different it will probably be fine, etc. The thing is though, as you say, it’s really hard to know at the outset what you’ll eventually decide you want to put in the game, so you can easily find out that far down the line you’re facing a really awkward problem with the engine you would never have seen coming at first. Sometimes I like to mix wildly incongruous elements in games or shock the player by abruptly pivoting to a completely different type of game out of nowhere or that sort of thing, and those sorts of impulses have lead me to frustrating places with all of the “premade” engines I’ve used over the years.

As kind of a side note, too, I’ve also found it can be really unfun to have a hard dependency on something volatile, especially as a solo developer or in a very small team or w/e. Unity is especially bad about this—I’m sure just about everyone here who’s worked with Unity for a while has had the experience of trying to move to a new Unity version and seeing everything in their game fall apart. Any kind of large, heavy framework will have this issue IME, game or otherwise—Rails has this problem too for example—and it really sucks if you’re the only programmer and you have to deal with the migration all by yourself, because it can entail days or even weeks or months of pointless-feeling work. Even if you’re not the only programmer, if you have a large application already it can be really bad (I’ve heard horror stories about Unity like this from people at larger commercial studios). On the contrary, things like C++, SQLite, the Autotools, and even SDL tend to change at a pleasingly glacial rate over time and are very careful to maintain backwards compatability and so on. I think that’s one of the big advantages of depending on small, focused, widely-used things, because once they do what they’re made to do effectively they don’t really need to constantly become something else, and they have a lot of motive not to break existing code.

I also like that like, those kinds of tools and libraries really impose only the mildest amount of structure on your codebase. It’s pretty easy to squirrel them away in their own corners and have totally free reign to do whatever you want otherwise. Then it’s just up to you to not back yourself into a corner; to me that’s so much nicer than having to take on a huge list of assumptions some distant group of people came up with that might not mesh well with whatever I decide I want to do. Of course like, if you find something that is largely based on the same assumptions you would make, you’re in great shape then, I just haven’t really found that to be true for me usually in practice.

Yeah, speaking of, I guess like, that’s great if you feel that way, it seems really fortunate honestly. I do kind of wish I felt like that I guess. Like I say, all of the ootb engines I’ve worked with a lot I’ve ended up feeling more and more frustrated with the more I’ve worked with and learned about them, which is kind of the opposite of what you would hope for…again I think it’s probably really personal—how you like to work, what things you want to do in your games, etc. etc.

One thing I do think is worth noting about coding just in general is that there are a lot of “premade” widgets and things just in the abstract, in the form of like common algorithms and data structures and the like. I never feel exactly like I’m just doing things from scratch because of that—like, lots of things in games are nicely expressed as directed or undirected graphs for instance, and most of the things you would want to do with those are already worked-out and have standard solutions (like searching for a certain node, measuring the distance between one node and the others, finding a path between two nodes, visiting each node in order of their connections, etc. etc.). That sort of thing still gives me the feeling that I’m putting the game together from “premade parts” even if they’re just conceptual—I don’t necessarily have to think much about how to code something from that place, it feels more like following a set of recipes and making little tweaks here and there to suit my purposes, kind of like you’re describing with your own process. A mature programming language will even tend to have all the common data structures and algorithms in its standard library, already optimized to death and so on, so sometimes all you have to do is plug them into each other (one of the things I like about C++).

It is definitely true, though, that there are ways to saddle yourself with a lot of extra work using something like SDL over something like Godot or Unity, and to some extent it’s almost inevitable that you’ll have to recreate at least small parts of what those sorts of engines provide. I readily concede that, it’s totally true. To some extent you can still use libraries and tools and things to save yourself a lot of the effort—like, SDL has built-in scaled 2D sprite blitting and simple 3D rendering support, you can use Ogre for 3D rendering if you want something more like Unity- or Unreal-style 3D, Bullet provides standalone physics, etc. Of course, the more dependencies you introduce the more problems you’ll have to solve in your build process, getting these different components to communicate well requires writing glue code, and you still have to learn how to use them which is not necessarily trivial.

Over time I’ve come to be more and more inclined to just use SDL and sometimes Vulkan, not only because that yields a nicely clean and focused codebase but also because you can do wild things that are way outside the norm of what engines or even more focused libraries tend to provide, but because of that I’ve spent a lot of time studying things like calculus and linear algebra, 3D rendering in the abstract, classical mechanics, etc. I know not everyone has the time or inclination for that kind of sciency stuff, and I also feel like I still have lots to learn about those topics honestly even given how much time I’ve spent so far. In my experience, once you understand a given thing about topics like that, it’s not too much work to actually apply it in your codebase a lot of the time, but learning about them in the first place does involve a huge amount of time and effort. You only have to expend it once, at least, but it’s still significant, and it is certainly time you’re not actually spending on development directly. I have to squirrel things like that away in little places in our schedule because we have to keep income coming in and so on.

That’s interesting. I have terrible ADHD actually (maybe that’s kind of obvious :sweat_smile:), to the point that it causes me serious problems in my day-to-day life just in terms of like shopping or other errands, keeping appointments, returning phone calls, staying on top of bills, keeping the house tidy, etc. etc., normal life stuff or w/e. With the aid of medication I’ve gotten better at those things over the years, but I still feel like the average person is way ahead of me with stuff like that despite how much I try, in a way that’s almost hard for me to imagine. Lily helps me a lot in various ways. I really feel the “mode switching” stuff too—I feel like it’s hard for me to keep track of more than one or maybe two goals over the course of a day. Despite this though for some reason, I don’t really feel like mixing programming and other forms of creative activity causes me that kind of problem—I don’t really know why but somehow it all feels kind of in the same vein to me, whether I’m writing code or dialogue or composing music or drawing or w/e—all of those activities are sort of different of course but I feel like they all kind of flow together and feed into each other somehow and I don’t really feel like I’m switching modes to go between them, they’re like different lenses on the same stuff to me or something. It’s more in the “material world” where I feel like that kind of mode-switching stuff really becomes a problem for me most of the time. Obviously this is intensely personal and it doesn’t surprise me that someone else would feel differently. Humans are interesting creatures :stuck_out_tongue:

4 Likes

Tabletop?

This is a layout for the map I’m working on. Drawing it out like this will hopefully let me build it in the level editor much quicker. But I learned to do this while running tabletop games, so it’s effectively the same exact practice actually. And Quake is really just a dungeon delving game in spirit, so!

Edit: But I really, realllllly want to play some tabletop games soon. Maybe that’s what I’m expressing through this as well :sweat_smile:

1 Like

I am really pleased to see how much faster I can build out the next wing of my map thanks to having a layout already designed. Not complete yet but this has just been like a few hours of work this week and there are lots of little successes to celebrate already. I found the scale of the place with maybe the least amount of friction I’ve experienced while building other spaces in this map and in others.

3 Likes