Log In  
Follow
jasondelaat
Follow

Had a video on ray casting pass by in my Youtube feed the other day and thought I'd play around with it a bit.

There are the four outer walls and then four randomly generated interior walls. It's casting 27 rays so, when moving, that's 216 intersections calculated per frame at 60 fps. Either more walls or more rays forces it down to 30 fps so it's a bit of a performance hog (okay, a huge performance hog.) I'm sure it could be optimized somewhat, though I'm unlikely to do it.

Edit: Okay, so apparently I lied. I realized that I could have my rays do double duty by calculating two intersection points each, one in each direction along the line. So I get effectively twice as many rays for virtually no extra cost.

Cart #yirijahiwe-1 | 2021-09-27 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
2

Here's a quick little modification I made which uses the same logic to handle actual collisions with the walls. It's only casting one ray in the direction of movement so you could add a lot more walls before you notice performance issues. It's not perfect. Every once in a while the red dot will zip right through a wall. I suspect this is just because of decimal precision errors and in an actual game could be fixed by checking more than one point for collisions: the four corners of the sprite for instance. But, again, I probably won't bother.

Cart #tewetaripi-0 | 2021-09-25 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
2

Oh, and the video:

P#97805 2021-09-25 20:00 ( Edited 2021-09-27 15:33)

State Machines

A wrote (a couple variations of) a simple state machine manager. The code is on github under an MIT license. There are a couple demos down at the bottom of the page.

  • state-machines-du (107 Tokens): each state has its own draw and
    update methods
  • state-machines-st (111 Tokens): each state has setup and teardown
    methods which are run only when the state is entered and exited
    respectively

Usage

Creating a state machine

To create a new state machine use the 'new' method:

    sm = state_machine:new()

Adding states

After creating the machine itself you need to add states. A state
consists of four things:

  • A unique name or identifier: Most likely a string but can be
    anything as long as it's unique.
  • A transition function: This function is called once per cycle
    and should return the identifier of the state the machine
    should switch to.
  • An update (or setup) function: The update function is called
    once per cycle and should update variables, etc. associated
    with the state. For state-machine-st.lua, this is instead a
    setup function which is only run once each time the machine
    enters this state.
  • A draw (or teardown) function: The draw function is called
    once per cycle and should draw everything relevant to the
    state. For state-machine-st.lua, this is instead a teardown
    function which is only run once each time the machine exits
    this state.

Add a state to the machine using the 'add_state' method:

    sm:add_state(
       -- identifier
       'a state',

       -- transition function
       function()
          if btnp(5) then
         return 'some other state'
          else
         return 'a state'
          end
       end,

       -- update function
       function()
          if timer then
         timer += 1
          else
         timer = 0
          end
       end,

       -- draw function
       function()
          print(timer)
       end
    )

Using the state machine

  1. state-machine-du.lua

    Once you've created a state machine and added some states using it
    is simple: Set the initial state then call the update and draw
    methods.

        function _init()
           sm:set_state('a state')
        end

        function _update()
           sm:update()
        end

        function _draw()
           cls()
           sm:draw()
        end

The update method calls the current state's transition function
and changes the current state if necessary and then calls the
current state's update function. The draw method calls the current
state's draw function.

  1. state-machine-st.lua

    The setup/teardown version is basically the same except there is
    no draw method and the update method does a bit more work, so all
    you need is this:

        function _init()
           sm:set_state('state 1')
        end

        function _update()
           sm:update()
           -- whatever other update stuff you need to do.
        end

        function _draw()
           cls()
           -- whatever draw stuff you need to do.
        end

The update method in this version also calls the transition
function. If a state change is necessary, then the current state's
teardown function is called, then the current state is changed,
and finally the new state's setup function is called.

Demos

basic-sm

A very basic state machine with two states. Press X/V to switch from state 1 to state 2, Z/C to switch from state 2 to state 1.

Cart #basic_sm-0 | 2021-08-04 | Code ▽ | Embed ▽ | No License

platform-tut

A simple platformer demo with 5 states: intro, tut_movement, tut_jump, play, and gameover.

Cart #platform_tut_sm-0 | 2021-08-04 | Code ▽ | Embed ▽ | No License

P#95647 2021-08-04 09:55

I've finally started working on an actual game. The programming is not a problem but art, on the other hand, well...there be dragons.

I'm not an artist and it doesn't come naturally to me but pixel art, 8x8 pixel art in particular, seems much more approachable than more traditional types of art. I think because the feedback cycle is shorter and more obvious: with only 64 pixels to play with, it's pretty obvious when you put one in the wrong spot and there are a limited number of choices available for how to fix the problem. So I spent a few days poking around the sprite editor doing a bit of an "art study" to see what I could figure out. Comments, tips, tricks, etc. very much welcome.

This is very much not a tutorial. I'm not and artist and I'm not qualified to teach anybody anything about doing art of any kind. If people find it interesting or useful, great! But mostly I'm just using this as a place to record observations for future me to think about when I'm trying to figure out what the heck my game should actually look like. I'll add the full sprite sheet at the bottom if anyone's interested in having a look at it.

Basic proportions

One of the things I remember from my many failed attempts to learn how to draw is that people are generally about 8 heads tall, which seems ideal for an 8x8 box. One for the head (duh), three for the torso, and four for the legs, with the hand hanging just below the waist. Sketching that out as a sort of measuring stick, I got this:
[8x8]

Trying to make that into a front view though presented immediate problems:
[8x8]

The shoulders are a bit too broad compared to the head but the main problem is the pogo-stick leg. So obviously I had to mess with the proportions a bit to get something that looked acceptable.
[8x8]
[8x8]

The legs had to be shortened to allow for the larger head but at least now I get hair! This is, I think, probably about as close to "proper" human proportions as it's possible to get inside and 8x8 box.

Messing with proportions

Alright, so how does it change if I mess with the proportions further? Maybe other people will read these differently than I do but here's what I've got:

  1. Extending the torso an additional pixel gives him a solid, football player-ish kind of feeling.
    [8x8]

  2. Making the legs one pixel longer, on the other hand, gives a more prim and proper, possibly slightly
    stuck-up vibe.
    [8x8]

  3. Lowering only the shoulders gives him a "stylish" turtle neck.
    [8x8]

  4. While making the head larger makes the character feel a bit more heavy-set. Especially if the torso is
    also extended an extra pixel making the legs shorter.
    [8x8]

    [8x8]

  5. I did a couple characters at 8x16 with the same basic proportions where each pixel in the 8x8 becomes a
    group of 4 pixels in the 8x16 which can then be edited to keep the proportions more in-line. Didn't do
    many of these though
    [16x16]

  6. And then, of course, with a bit of understanding of the proportions and how to balance them, I went for
    the big-head, small-body look. I tried a few variations:
    [8x8]

    [8x8]

    But eventually ended up with this cute little guy. With a whole pixel of headroom to spare!
    [8x8]

Animation

Okay, so I'm getting the hang of things but now I need them to move. I went back to my original "measuring stick" guy and tried a walk cycle.

Not too bad. The torso stays straight up and down so I also tried angling it forward and backward to see how that changed the feel of the walk.

The forward tilted one feels like it moves faster, even though it doesn't, and feels like it has more of a sense of purpose about where it's going. The backward tilted one feels slower and like it's ambling, lazily, almost clumsily from place to place. Honestly, I feel like I could do almost anything with the legs and as long as I've got that bit of a head bob and the arm swinging, it'll pretty much read like a walk.

Tried the little cute guy next and even added an idle animation and played with the position of his head.

Feet don't do much. Still looks like a walk. Nice.

And finally, having just watched a video on sub-pixel animation I'd thought I'd give that a try in a small way by making a couple guys with weapons.

1-bit

I decided to try making 1-bit black-and-white characters because I'd have to figure out how to suggest shape and separation of body parts with as few pixels as possible since I couldn't use colour to distinguish the sections. Mostly just rehashed everything I did above but in black-and-white only but a few things I learned: The colour stick-guy walking looked okay in all three versions. In 1-bit, the forward tilt of the body was essential to suggesting the direction of motion. Everything else just looked vaguely off no matter how I tweaked the frames.

For characters with faces, outlining the head but leaving it open on one side worked best and, depending on the size of the head, eyes can go right up into the hairline or not. Also, I found for some reason that a black pixel behind the hand really helped to sell the idea of the hand hanging by the waist even though that black pixel didn't really represent a part of the character's body. More like just a shadow which helped to sell the idea of a hand there.
[8x24]

And then, once I've got a general character I'm happy with I can always colourize the sprite if I want to.
[16x24]

Textures

How do you make something look hard or soft? Rough or smooth? I have literally no idea. I started by just trying to sprinkle some random pixels around and try to figure out if it looked rough or smooth. What it looked like was a bunch of random pixels.

So instead, I created a few regular patterns, copied them and then made some tweaks trying to change the texture somehow. What I mostly ended up with were just a bunch of different patterns. I guess some looked rough and some looked smooth but mostly they just looked either more or less regular.
[48x32]

As individual tiles they don't look like too much but some of them made nice patterns when tiled together. And I played around and found a few others that I liked as well:
[24x24]

[24x24]

[24x24]

[24x24]

[24x24]

Didn't really get me closer to texture though. So here's the process that I figured out, basically applying a manual noise filter, that's giving me fairly decent results. I think, anyway.

I take copy of the "clean" tile and overlay some other pattern with a different colour. Usually diagonal lines but sometimes other patterns too, it takes some trial and error to find a good one. Anyway, once I've done that, I apply some rule like, "all grey pixels in the top half of the tile get coloured white while all the grey pixels in the bottom half get coloured black." That gives me a "dirty" tile which still has some recognizable structure to it but is a bit messed up.
[16x8]

Tiled together on their own the dirty tiles are a bit too messy but mixing a few clean tiles in with them gives the eye enough to pick out the underlying pattern and make sense of the whole thing.
[24x24]

And then, just like with the characters, I can colourize them later. Here are a couple I quite like. Though I make no claims that my colour choices are any good or that these are great or anything. But, I think, not a terrible starting point, at least.
[40x32]

[40x32]

[24x24]

So that's the beginning of my pixel art journey so far. Still much to learn but I'm starting to get a handle on it.

P#94449 2021-07-04 23:44

I hate fiddling with meaningless magic numbers trying to get a behaviour right. I prefer a bunch of knobs with predictable effects. So I made this little tool for fine tuning variable-height jumping behaviour for platformers.

Note: As written, it's possible for the cart to get stuck in an infinite loop for certain values so read below on how to, hopefully, avoid that issue.

Cart #jump_tuner_1-0 | 2021-06-25 | Code ▽ | Embed ▽ | No License

Usage:

  • Up/Down to select a value
  • Left/Right to modify the value
  • X to test the jump, tap for small jumps, hold for higher jumps

There are three tunable values and a fourth value which is calculated for you and can't be changed manually. Once you get something you're happy with, note down the values, and use or modify the jumping code from this cart in your own project. I think I've clearly indicated all the parts of the code that can be deleted and which parts should be modified. If you do use it, attribution is appreciated but not necessary.

The tunables are pretty self-explanatory. They are:

  • height
  • gravity
  • initial acceleration

The fourth value, alpha, is calculated based on the other three.

Height:

This should be a negative number (upwards direction) in pixels. It's actually more like "requested height." Depending on the other values there is a certain amount of error in the calculations so the maximum height of the jump won't be exactly the height you specify but it should be close.

Gravity:

This should be a positive number (downwards direction). Technically, the units are pixels per frame per frame but that doesn't really matter. As you'd expect, this controls how fast you fall. Higher values make you fall faster; smaller values, slower.

Initial acceleration:

This should be a negative number (upwards direction). This is the upwards acceleration of the character when you press the jump button. While you're holding the jump button, this acceleration slowly decreases until the character is once again only under the influence of gravity. If you let go of the jump button before the jump reaches its full height, the acceleration is cut immediately and the character starts to fall sooner.

Alpha:

Alpha is calculated from the other three values and controls how quickly the initial acceleration decays.

Issues:

Given the specifics of the physics, it turns out that alpha can't be determined analytically: There's no nice simple formula for it. Or maybe there is and I'm just not patient enough to grind through the equations to figure it out...Anyway, it can be solved computationally, essentially using a binary search algorithm. Which is fine except that PICO-8's limited floating point precision as well as the way lua handles division by zero (turns out 0/0 = 32768, take that math!) means that the algorithm sometimes gets stuck in an infinite loop because it can't find a good enough solution.

Avoiding problems:

Height error

Gravity and initial acceleration should have similar magnitudes, just in opposite directions. The greater the difference between them, the more error creeps into the requested height. Basically, if the difference is too great then once the upward acceleration cuts out, it takes longer for gravity to slow the character down and it over-shoots the target height. The closer the two are in magnitude, the closer to the target height you'll get. However...

Infinite loops

Though it is by no means obvious, when gravity and initial acceleration have the same magnitude the code which calculates alpha will try to divide by zero and the program won't be able to find a suitable solution. But it'll keep trying. Forever.

PICO-8's limited floating point precision means that this actually become a problem when gravity and initial acceleration are even close to being the same magnitude. If gravity = 0.3 and acc=-0.3, that's going to cause problems.

In general, gravity and acceleration should be close to the same size (but in opposite directions) if you want accuracy in height but not too close or the cart will freeze up.

This is only a problem when calculating alpha based on the other values. When you copy/paste the code you'll just assign alpha and can tweak the other values to be as close as you want.

P#94022 2021-06-25 19:05

I've been playing around with various procedural generation techniques and wrote a little tool for creating generative string grammars. It's 104 tokens and the code is on github under an MIT license.

Demos

This first one generates side-scroller/platformer levels. Each character of the generated string represent a four tile wide column of the map. I didn't add a player because I just wanted to showcase the level generation itself so the map just scrolls automatically from the start to the end. Reload the cart to generate a new level. It's just an example so the levels aren't particularly interesting but you could add a few "post-production" passes over the generated strings to clean things up, generate enemies, add additional rules for pre-made features, etc.

Cart #jdelaat_stringgram_scroller-0 | 2021-06-21 | Code ▽ | Embed ▽ | No License

The next one is a vastly simplified version of this process to generate names of things/places in a made-up language. The grammar constructs some syllables then makes words of either two or three syllables. A final pass over the generated word cleans up some messy, hard-to-pronounce double consonants. For a quick, not very sophisticated implementation it comes up with almost but not quite decent sounding place names.

Cart #jdelaat_stringgram_words-0 | 2021-06-21 | Code ▽ | Embed ▽ | No License

API

string-gram provides a number of functions to help you build a grammar and it's easily extensible via custom rule functions. Grammar rules are just functions which take no arguments and return a string.

  1. lit

    The lit function takes a string as input and produces a rule which will generate that string when called:

h = lit('hello') -- h is a rule, aka a function of 0 arguments.
print(h()) -- prints 'hello'
  1. seq

    The seq function takes any number of rules as input and produces a new rule which outputs the result of each rule in sequence.

h = lit('hello')
c = lit(', ')
w = lit('world')
hw = seq(h, c, w)
print(hw()) -- prints 'hello, world'
  1. choice

    The choice function takes any number of rules as input and outputs a new rule which outputs the result of one of those rules chosen at random.

a = lit('a')
b = lit('b')
ab = choice(a, b)
print(ab()) -- prints either 'a' or 'b' at random
  1. copy

    The copy function takes a single rule and an integer, n as input and outputs a new rule which applies the given rule n times.

a = lit('a')
aaa = copy(a, 3)
print(aaa()) -- prints 'aaa'
  1. sym and register

    Grammars are often defined recursively so you may find yourself needing to include one rule, which you haven't yet defined, inside the definition of some other rule. sym and register solve this problem by allowing you to insert a 'symbolic' rule which will be looked up at a later time when it's actually called.

one = lit('1')

-- This won't work!
-- many_ones = choice(one, seq(one, many_ones))

-- Instead, we use a symbolic rule...
many_ones = choice(one, seq(one, sym('1s')))

-- ...and then use register to insert the rule into a lookup table.
register('1s', many_ones)

print(many_ones()) -- prints random number of 1s
                   -- ex: 1, 111, 1111111111111, etc
  1. Custom rules

    Rules are just functions which take no arguments and return a string so you can easily create your own rules or functions which create rules. For instance, suppose you want to create a rule that randomly returns the output from another rule or else returns an empty string. One way to do that would be like so:

function zero_or_one(rule)
   return function()
      if rnd() > 0.5 then
     return rule()
      else
     return ''
      end
   end
end

a = lit('a')
a_or_not = zero_or_one(a)
print(a_or_not()) -- prints either one 'a' or nothing with 50/50 probability
P#93825 2021-06-21 17:54

This is the second of the two little utilities I've made, the first being stream-ecs (in a separate post.)
The github project page.

prot-oo

This one's a bit more straight-forward. Prototype based inheritance/OOP. It basically just defines the base object and a few methods.

Use create to use an object as a prototype for some other object:

dog = object:create() -- use the base object as prototype
dog.sound = 'woof!'
function dog:talk()
  print(self.sound)
end

cat = dog:create() -- use the dog object as prototype
cat.sound = 'meow'

dog:talk() -- woof!
cat:talk() -- meow

You can create classes (well, not really, because prototypes, but sort of) by defining a method called init and using the new method.

vec2D = object:create()
function vec2D:init(x, y)
  self.x = x
  self.y = y
end
function vec2D:length()
  return sqrt(self.x^2 + self.y^2)
end

v = vec2D:new(3, 4)
v:length() -- 5

The new method creates a new object with vec2D as the prototype and then passes its arguments to init which initializes the object.

And sub-classes:

vec3D = vec2D:create()
function vec3D:init(x, y, z)
  self.proto.init(self, x, y) -- calls vec2D's init
  self.z = z
end
function vec3D:length() -- overrides vec2D's length
  return sqrt(self.x^2 + self.y^2 + self.z^2)
end

v3 = vec3D:new(1, 2, 3)
v3:length() -- 3.74-ish

And that's about it. Enjoy!

P#93466 2021-06-15 20:39

I picked up a copy of PICO-8 a few months ago on a whim and have been playing around with it, following a tutorial here and there, etc. Really enjoying it and I love seeing the creativity of people in the community.

I have yet to try my hand at an actually complete game and, as I'm basically infinitely distractable, may never actually accomplish it. In the meantime, to learn some of the ins and outs of Lua, with which I was not previously familiar, I've combined two of my favourite software writing pastimes: re-inventing wheels and making tiny utilities.

In that spirit, I give you:

  • stream-ecs (267 tokens), a (sort of) reactive stream based entity-component-system with automatic entity queue management, and
  • prot-oo (72 tokens), prototype based object-oriented programming (in a separate post)

I'll describe them a bit below and there's more info at the github project page. Each tool has its own README describing it in detail.

stream-ecs

I know there are a few different ECS frameworks floating around the PICO-verse. This one's larger than some and smaller than others but should, I hope, save you tokens in the long run by managing queues of game entities for you.

You start by creating a "world" and then spawning systems off of it:

world = ecs()
world
   :system({'timer'}, increment_timer)

world
   :system({'timer', 'position'}, move_character)

Each system creates and manages its own queue and when you add entities to the world...

world:insert(character)

... the world passes it to each system which either stores it in its queue, if it matches the selectors, or discards it.

You can also chain systems together to create a sort of filter. This example is functionally identical to the one above:

world = ecs()
world
   :system({'timer'}, increment_timer)
   :system({'position'}, move_character)

Entities with a timer component go into the first system's queue then, of those entities with a timer, those which also have a position component go into the second system's queue.

You define components like so:

position = component('position', {'x', 'y'})

Which returns a constructor function:

p = position(1, 2)
p.x -- 1
p.y -- 2

And you create entities like so:

character = entity()
   :add(position(1, 2))
   :add(timer(0))

There's a pre-defined draw component and draw system so you don't have to manage those manually either, you can just do this:

function _draw()
  cls()
  world:draw()
end

And updating all your entities is this easy:

function _update()
  world:run()
end

Here are a couple demos:

Simple particle system

Spawns a new particle at the source each tick. Particles are removed from the system after 200 ticks so there are 200 particles on screen at any given time.

Cart #jdelaat_streamecs_particles-0 | 2021-06-15 | Code ▽ | Embed ▽ | No License
1

Simple platformer

Collisions are a bit wonky: the character occasionally falls into the floor some reason but that is, presumably, because of how I wrote collisions and not the ECS itself. It should be enough to give you an idea of how it works, anyway.

Cart #jdelaat_streamecs_platform-0 | 2021-06-15 | Code ▽ | Embed ▽ | No License
1

P#93530 2021-06-15 20:39

Follow Lexaloffle:        
Generated 2021-09-27 20:00:20 | 0.127s | Q:35