Log In  

Hey guys, I'm stuck on trying to make an endless runner with procedurally generated platforms.
I was originally going to try and use the map sheet to make different 'rooms' and then try to cycle through them and draw the 'next room' randomly, but decided to abandon that idea in favor of just procedurally generating the platforms. (someone please let me know if that's a better way to do this type of game)

If you try the game you'll see the issue - the tiles are generated all over (I was going off the LazyDevs roguelike tutorial and trying to modify it for my purpose), they aren't jumpable despite having the right collision flags, and for some inexplicable reason they fall as if they had gravity, which I just can't figure out.

I've been tearing my hair out and really need assistance. Any help at all, or a better method would be really appreciated! I basically want to generate the runner platforms per 8 map screens, and ideally be able to generate powerups, holes in the floor, eventually enemies/traps etc.

Thank you for your time.

Cart #kabopowabi-0 | 2021-12-29 | Code ▽ | Embed ▽ | No License
1

P#103744 2021-12-29 05:42

1

There's two major problems with your cart from what I can see, and those problems cause more problems throughout.

  1. You really shouldn't be making changes that are relevant to gameplay during a draw function unless you absolutely have to. For the map_gen() function especially, you should be calling that during _update() or _init() so that the number of times it gets called will be consistent and also so it will take effect at the correct time. I would also recommend not calling it on sections that the player can already see, but that's more out of preference.
  2. You need to pay more attention to which parts of the map you're using for each part of the game. map_gen() is setting tiles in the rectangle with y values of 16 to 48, but your gameplay takes places in 0 to 15. That's why the collisions aren't working. The reason the platforms are falling sometimes is because they're being placed in the portion for the stars.

Regarding other ways to generate platforms, I think something more based on the player abilities would be better. Judging by eye, it looks like the starting jump range is 6 tiles and the starting jump height is 4. You could probably add or subtract 2 tiles or so going u or down a row, with 3 being the base distance (since the jump is symmetrical). From there you could just generate 1 platform at a time, moving the position over each time using a random position among the ones that would fit the above characteristics. For variety you could also occasionally generate decorative platforms that are irrelevant or optional platforms that may or may not be better choices for next platform. A bit of testing or math could also find the amount the jump range increases as the range increases.

Regardless of method, I would recommend putting in some code that will let you adjust the rate at which the platforms are generated so that you can tweak it.

P#103747 2021-12-29 07:55

Hi @kimiyoribaka, thank you so much for your response. I've been working on it and came to the same conclusions re map_gen() in the init() function. How do you mean calling it on sections the player can already see? As in, it should already be generated for the current screen?

As for 2 - thank you, I finally got that part figured and adjusted accordingly.

This is what the cartridge looks like now. I'm curious as to how you would generate one platform at a time - currently I'm just running map_gen() 30 times in my init() function, but that seems inefficient? I'd like for it to generate a new 30 platforms everytime the scroll goes past the 8th screen, so it effectively can scroll forever. Also, how would you go about generating decorative or other types of platforms, use different sprites etc? Really appreciate your input.

Cart #sofudipiho-0 | 2021-12-29 | Code ▽ | Embed ▽ | No License

P#103750 2021-12-29 08:08 ( Edited 2021-12-29 08:09)

Yes, I did mean the current screen should already be generated. Generating the map in the _init() function already solves that.

Going more in-depth on the one-at-a-time idea, here's a way to do it (the first that comes to mind). You make variables platx and platy, then set them to the first x position where platforms that the player should be encouraged to jump on would be and y position of the floor. Using those, you then randomly roll an x and y position that is within comfortable jumping range of the player if the player were at platx and platy. If the values rolled are out of the gameplay range, roll again. Next, Make a platform at the x and y that was just rolled, then set platx and platy to the position at the end of the platform that was just generated. You can then repeat the process as many times as needed until the end of the stage.

By "decorative" I meant platforms that can't be reached by the player. The idea would be to make the trail of platforms look more natural and less like it's been generated. If you go with that idea, it could just mean sometimes adding a 2nd platform with a position that's completely random rather than being within the constraints of where the player could jump.

P#103753 2021-12-29 08:37

@kimiyoribaka interesting, I like that idea. Would definitely bring some challenge to the game, I'll give it a shot.

Any advice on how to keep generating the level after resetting the player to screen one again? I'm currently basically resetting the position to start after hitting the rightmost edge. I'd like for the game to just continue infinitely until death.

P#103754 2021-12-29 08:42

There's a couple ways. I think you should still try to use the map for the platforms, simply because you've already got the system set up for that. However, you will need some way to reuse the map with different platforms. Here's what I suggest: choose a positions around 2/3 or 3/4 of the way through. Then when the player reaches that point, copy 2nd half of the map onto the section where the first half of the map was and move the player and any other object that isn't part of the map 64*8 pixels to the left (including the camera, and platx and platy). If done carefully, that should give the illusion that the player is in the same spot they were before, since everything else the player can see will be identical. Then you can clear out the platforms from the second half of the map and replace them with newly generated ones.

This technique is used in modern games too btw. That's actually how Half-life 2 and some areas of Portal 2 appear seamless despite the world being way too big to fit in memory all at once.

P#103755 2021-12-29 09:05

@kimiyoribaka okay, cool. That makes sense to me intuitively but I'll have to figure out how to make it work haha. Thank you so much for your help!

P#103756 2021-12-29 09:40

hey @kimiyoribaka I had some a few more questions if you have the time -

Cart #zgugoziti-0 | 2021-12-30 | Code ▽ | Embed ▽ | No License

I'm still working on the map generation using the midpoint method, hopefully that'll be the final touch on this.

I can't seem to figure out why my collisions don't work on the up-down enemy. I have 2 enemies, up-down and side-side (currently static), and the side-side guy kills ya but the up-down guy doesn't, despite them both having the same flags and all.

Secondly I've generated the side-side guys in the map_gen() so that they can be placed on the platforms, but as a result I can't animate their x-positions this way to make them move side to side. Do you have any advice on how to do this properly?

I want to add coins for extra score and small speed boost, as well as maybe having 3 lives before the final death. Any advice for these mechanics would be appreciated as well.

Thanks!

P#103828 2021-12-30 17:49 ( Edited 2021-12-30 17:56)

I'm also not sure how to copy the 2nd half of the map back to the 1st half.. as well as then call map_gen() for just the second half of the map again, if you can provide any pointers

Here's what I have so far:

Cart #moyaziyeta-0 | 2021-12-30 | Code ▽ | Embed ▽ | No License

P#103838 2021-12-30 20:05 ( Edited 2021-12-30 20:06)

The reason the up-down enemy doesn't kill the player is because your code doesn't check for a collision with it. The enemy_collide() function wouldn't do anything, because it's setting some local variables and not using them. Beyond that, your map_collide() function only checks for collisions with static objects that have been embedded in the map. Your up-down enemy appears to be implemented by created a table of its values and drawing that.

To clarify, since I can't tell if you would already know this based on your posts, mget() doesn't check flags for things that appear on screen. It only checks the flags in the current map data.

Having looked through your code, I think what you need for both types of enemies is more generic handling. You already have the up-down enemy in a table, so you already have a working representation of the enemy as an object. What you can do to expand that is to instead use that table as a prototype to make a list of enemies (as a table) instead. Here's what that would look like:

    enemy_list = {}
    for i=1,10 do
      local enemy={
          sp=16,
   -- this or something similar so they don't end up in the same spot
          x=120 + i * 300 - flr(150*rnd()),
          y=30,
          w=8,
          h=8,
          dx=1,
          dy=1,
          dir=.02
      }
      enemy_list[i] = enemy
    }

If you then add a parameter "enemy" to your existing functions for dealing with the up-down enemy, you hand them individual enemies from the list. If done in the _update() function, here's how that could look:

        for i=1,#enemy_list do
          enemy_update(enemy_list[i])
          enemy_animate(enemy_list[i])
        end

(there appears to be functions in pico-8 to make this exact method faster, but I haven't tried them)

Then the last bit to make enemies work the way you've described them would be a table-based collision function. Your existing function map_collide() does a good job at checking the space that the object is about to move into. It'd be a good idea to use the same code up until the part that translates from pixels to tiles. For the test of whether the space overlaps with an object in a table, a simple axis-aligned bounding box test can be used. Using the names x3, y3, w2, h2 for the second object's values, that would look like this:

  -- the same code as map_collide for x1,y1,x2, and y2
  -- then some code for getting x3,y3,w2, and h2 from the enemy 
  -- to check against
  local x4 = x3 + w2 - 1
  local y4 = y3 + h2 - 1
  if (not (x1 > x4 or y1 > y4 or x2 < x3 or y2 < y3)) then
    return true
  end

For why that would work, mboffin posted a cart that visually demonstrates it here: https://mboffin.itch.io/pico8-overlap.

As for the coins, you could use the above method for those too, but if they're not moving it might be overkill. If you just want coins that stay still but animate, then I would instead recommend keeping track of where the coins ended up using a table, then changing each one based on their current sprite. That way you could still use map_gen as normal for them.

For having 3 lives, that depends on what you want to happen on death. If the player's speed just stop and they get to be invincible for a bit, then that'd be as simple as setting the player's speed and checking if the player has moved enough before checking any further interactions with enemies. If death means restarting, then placing all the initial setup in a function that can be redone would help. That way you could effectively restart the game but have the score and start stay the same.

For copying the map, you can use mget and mset in a pair of loops if you want to use the straight-forward way:

  for _x=32,32+46 do
    for _y=4,11 do
      mset(_x,_y,mget(x+47,y))
    end
  end

(47 is half the range you currently have tiles being set at, rounded down)

If that feels too inefficient, I believe this would be equivalent using memcpy instead:

  for _y=4,11 do
    memcpy(0x2000 + 32 + _y * 128, 0x2000 + 32 + 47 + _y * 128, 47)
  end

I'm not confident about that, though. Also, if you choose to use the method of storing objects in a list of tables as described above, you would need to go through the tables, preferably backwards, and check which objects are still relevant. If still relevant, their x-positions would need to be changed. If not, they would need to be deleted (which is the reason to go through the table backwards).

For generating tiles on just the second half of the map, you could just set a global variable to indicate that the initial generation was done, then have place_tiles() check it to see what the range for placement should be.

P#103841 2021-12-30 21:24

Cart #yonijoyuwi-0 | 2021-12-30 | Code ▽ | Embed ▽ | No License

@kimiyoribaka thanks a bunch, your help has been invaluable.

I've paused on the map generation as I have to submit this game tomorrow, but I will revisit it. For now I've just let it repopulate the tiles per 8 screens.

I've tried to implement the enemy collision as you've described but I keep running into errors. Specifically "attempt to call global enemy_collide a nil value".
I made the enemy1 and 2 objects and got them moving on the screen, all good, then I made an enemy_collide() that takes player obj, aim, flag, and enemy obj data but I don't think I'm doing it right.

P#103855 2021-12-30 23:41

A nil value means it doesn't exist at the moment. There's 3 reasons that could occur:

  1. the code reached the call before the function is defined (which won't happen using the structure that you're using)
  2. the function was set to nil
  3. a typo

In this case it appears to be #3. You put ememy_collide, and it's hard to tell (took me a while to notice too) due to the font.

P#103861 2021-12-31 00:20

Cart #pisebusiya-0 | 2021-12-31 | Code ▽ | Embed ▽ | No License

Lol I've been agonizing over the past hour and turns out the issue the whole time was emeny_collide() instead of enemy, jfc. I think I've got it working in it's basic state now! Please let me know if you have any more ideas/thoughts! I got the pots working as 'coins' now but I'm still trying to figure out how to make them a little more animated/disappear after being hit

P#103862 2021-12-31 00:21

Also, as I was messing around trying to figure it out I ended up putting the enemy_collide() calls in enemy_update(), is that a good way to go about it? Previously I had it in the draw function, which seems like maybe a less efficient way to do it.

P#103863 2021-12-31 00:22

As I said before, nothing that affects gameplay should be in a draw function. The reason is that depending on the platform, pico-8 may need to change how often things are drawn, which can cause unintended differences in player experience.

As for where exactly to put it, remember that what works is more important what's ideal. The actual most efficient spot or method for anything would probably require benchmark testing to figure out, and could be the result of nuances that aren't documented.

For the pots, animating them is just changing the sprite. I would advise putting their positions on the map in a table during generation, then just going through the table using mget to check the current animation sprite and mset to change to the next one. If you use this method, though, make sure the code doesn't change map positions that don't have a pot anymore. Alternatively you could also just check all the map positions that are currently visible and change any that have the pot flag.

For making them disappear, I think the easiest way to code would be to have the map_collide() function store the map positions that actually got checked so that if a pot is hit, you can have the 4 possible map positions be checked against the flag for pots. If any still have that flag, you can just use mset() on that position to change it to empty (sprite 0 usually). There's more elegant ways like having a variable return value, but they require knowing the nuances of lua. Alternatively, you could also figure out the map positions again based on the player position.

P#103865 2021-12-31 00:43

Is your jump measurable?
Like, a left jump, neutral jump, right jump kind of way where you can predict in tiles what will happen, relatively?

I think there's a fair bit of map design you can work with these metrics in mind... and even if you have precise mid-air control, it's still just a minor deviation from these.

P#103869 2021-12-31 01:25

Cart #juwotedusi-0 | 2021-12-31 | Code ▽ | Embed ▽ | No License

[edit: just updated it again so enemies dont appear until 10s in]

super grateful to those who have responded and helped out.

Here's where I'm at with it for now - working around the constraints of my abilities I added a flappybird-esque jump and it feels pretty fun. I'm definitely going to keep working on it though and figure out how to store the generated objects in tables, and then on to real map procedural gen.

@TonyTheTGR I was trying to figure out how to put in a double jump, and then make the actual momentum controllable as otherwise the game would put you in impossible situations, but then I accidentally did the flappybird thing and I feel like it compensates for the issue for now.

P#103871 2021-12-31 01:41 ( Edited 2021-12-31 01:44)
:: dw817

I've been watching your progress on this, @ssarkar - your improved coding each time. Looking good ! Keep it up ! :)

Here's a gold star for your encouragement.

P#103872 2021-12-31 01:41 ( Edited 2021-12-31 01:41)

@dw817 :D i <3 pico, it's so much fun

P#103875 2021-12-31 01:45

[Please log in to post a comment]

Follow Lexaloffle:        
Generated 2022-05-04 03:56:25 | 0.062s | Q:56