Log In  


Cart #55337 | 2018-08-17 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
2

This was my entry for #LOWRESJAM2018.

I am posting it here to share it with the pico8 community, and effectively "open source" the result. Feel free to remix and poke around. I've had to remove most of the comments to make it fit in the cart limits. But most of the code should be pretty self explanatory, there's nothing super fancy besides the generator and some efficient reuse of "render object" functions under the hood. The cart is nearly full, so you might need to do some serious memory hacking or make some code somehow more efficient in token use to make new AI/features. But graphics and tile layouts are all your playground too.

Since the chunk generators are based on parts of the tileset map data, even a newbie can have fun changing those and playing the results.

Let me know if you remix it into anything awesome, and enjoy! :)

2


Digging the premise. Not too far off from a thing I was working on once, actually...

I did hit a code block with the world generation (too much data) before I got finished with it, though.

As for feedback... the premise is fun enough, and you nailed the "weird music style" of the original pretty well. Only thing right now is that there seems to be no functional difference between the Electric Shots and the Missles - both of them effectively two-shot the spawn-shooting things, and that's the only real applicable property of them. About the only other change I'd consider is spiky/lava floors, to force the player out of that comfortable space on the ground, and to use the higher platforms. It's also possible to get stuck because some upper inclines have full-tile collision for some reason (even though the ground-level ones don't?).

I suppose the enemies per depth/area could HP creep a little higher than they currently do.

Still, the simplistic approach to world design has me reconsidering a revisit, but with less code creep... :)


Still not 100% sure what I'm doing with "sand temple;" but the other areas look legit enough. The bright green spiky area is poised to be flooded and traversed with a swim/fly upgrade. Or maybe wall-jumps and air-dashes if we wanna be really dicky about it? No, this isn't MegaMan X6 endgame here.


That looks to be quite a nice start actually! A bit more structured then my crazy "chunk id" setup, for sure.

The ground ramps literally push the player up, and I ran out of tokens to make the roof sprites do the same. The game has no concept of slopes actually, I had trouble finding a solution that didn't involve a lot of dead reckoning or multiple collision passes. I had literally about 500 tokens total for the player movement, so lots of things had to suffer. ;)

Electric shots go through walls, but I will admit it doesn't come up often enough to matter. They were originally supposed to also "stun" enemies, but the enemy types planned for the gun never made the token cut. For what it's worth, the shots DO deal more damage then missiles, just not enough to be noticed on anything besides the boss fight. :p

I had a lot more plans for additional enemy types (classic bug pipes, greemer-like wall crawlers, basically every missing metroid staple really).

I also originally had a bit more advanced room generation, but was having trouble teaching the game to generate appropriate height jumps for the power up levels. For token space, I eventually had to "flatten" the floor gen to fit into a single room type. I also had to generate the map an entire row at a time using a deck/draw system to place the powerups. With that new system I lost the ability to "teach" the generator about the order of things on a floor. Sadly the current generator doesn't know much other then the Y depth being built at the time.

Lava was another concept that had to be removed to fit the boss into the token space. I did manage to fit the heat suit, but yes, it's not really the same. ;)

Doors got the cutting floor as well. The p8 file actually still has the sprites for the doors, and some concepts of other boss attacks using sprites that haven't been colored in yet. Fun little hacker treasures from before I slammed into the token/cart compression wall.

Were I to revisit the concept again, I'd probably go for a more structured layout of room data like you have above. Then the code could build each room out of corner chunks instead of this crazy 4x4 chunk/prop system I created for this version.

Technically, if I could make the generator use the same flags as some of the player/enemy/status variables, I guess that could shave off a good amount of tokens. I had ended up too far into the code before I had thought of that solution though.

Maybe I can take a stab at doing Infinitroid 2, and structure the game better this time. Would be a good opportunity to see what I could do if the game wasn't 64x64 res as well. Maybe I can fit "morph ball" that way. :)


Really neat game. Too bad the Konami code doesn't do anything special... Pretty neat.


For slopes, I usually do a 3-point bottom-up conditional and move accordingly. (+1*facing) as the constant x, and then y+1, y, y-1 sequencially.

There's a couple potential sticky points where you can go up slopes, but if there's a wall neighboring it, you can't go back down (even though it feels like you should). Again, I typically stick to the floor because there's literally no incentive to go higher yet.

I do have to find a way to make my redux "jump" from one scene to another, preferably using a table or string for each story to determine where to go. I'd totally be down for a collab sometime. Also, I don't think you teach it so much about "in order" for the floor, so much as you have the variance carry through to the FOLLOWING floor. For instance, if there's two jump upgrades, I'd make the floor after the second one start the possibility of requiring at least one to be collected; and at least two after all four of them.

My layouts on my thing do include some utility for a small high-jump upgrade, but it's totally optional to the layouts and really just makes navigation more streamlined as opposed to "necessary;" which I think works better with the unpredictable PCG approach anyhow. That's the secret with Isaac - none of the items are necessary, but they can make things easier (or harder)! FTR, I'm actually starting off with a default 2.5 tile jump height (that extra .5 gives the player some margin of error, but not necessarily additional clearance).

Originally I had these big, open "space rooms" where I tried to use code to write tilesets dynamically, make feedback loops with items and occasional boss rooms that connected hubs... and was just WAAAAY beyond PICO-8 scope. The scope really is a challenging thing to deal with!

In retrospect, I think it was the same kind of thing you were trying to do with your "chunk ID thing," and it might just be better to use a collection of intentionally-designed spaces. I did these newer subrooms to make the ball utility and vertical space important; and doing something more patchwork like this (maybe include a few "probablistic" tiles/objects?) might free up more code space that you can use for that enemy variance; which would 100% be my priority in its place. If nothing else, the greemers with some kind of HP creep... and really, there's a lot of potential with very vanilla enemies (like vertical/horizontal space controllers, or a standstill enemy that shoots a pattern every n frames; since they're really all about controlling screen real estate more than "threatening" the player).

The Konami code does the coolest thing. The same as the other codes, determine the world around you! I really like that as a seed input, btw. I might steal that for my procedural DDR-in-progress thing.


--gentroid is a pgc metroid fangame
--made in 2015 by tony "the tgr" wolverton
--inspired from "gentrieve" by phr00t and self

--note:working on tiles/collision
--and worldgen/doorid handling.
mainpower={ball,bomb,grenade,double,dash,unltd,missile,supmiss,icemiss,hook,scuba,jet,win}
subpower={spread,burst,bank,laser,boost,warp,blade1,blade2,blade3,bombup,grenup,missup,smisup,imisup,shotup,battery}
direction={left,right,down,up}
gravity=1
jumpheight=3.5
jumpvel=-2*sqrt(gravity*jumpheight*8)
maxdrop=8
maxrun=8
weapon=0
scenetype=0
topblock={32,33,34,35,36,37,38,39,40,41,42}
botblock=topblock+16
biome=biome%8
tile={air,water,lava,solid,dests,desta,links,linka,fake,damage,pushl,pushr,sticky,zerog,door}
hallway=1 --length of v/h corridors
hwrap=0
vwrap=0

function shufflepower()
add(sortpower,shot)
for n=0,count(mainpower)
get = rnd count(mainpower)
add(sortpower,get)
del(mainpower,get)
n+=1
return
for n=0,count(subpower)
get=rnd count(subpower)
add(sublist,get)
if get=bombup or grenup or shotup or missup or imisup or smisup or battery then 
n+=1 return else
del(subpower,get)
n+=1
return
end

--room drawing functions
--rooms are 12 wide, mget xs are all multiples of 1.5
--rooms are 9 tall, mget ys are all multiples of 1.125
function makestart(biome) 
--starting room, 2x2 hub
 mget(8,0)
end

function maketrans(biome,sortpower)
--single room, 1x1
 mget(0,2)
end

function makeprize(biome,perlin,prize)
--single room, 1x1
 mget(0,2)
end

function makevcorr(biome,hallway,playlevel)
 mget(2,0)--top
 for hallway>0--middle
  mget(4,0)
  hallway-=1
  return
 mget(6,0)--bottom
end

function makehcorr(biome,hallway,playlevel)
 mget(2,2)--left
 for hallway>0--middle
  mget(2,4)
  hallway-=1
  return
 mget(2,6)--right
end

function makehub(biome,playlevel)
--2x2 room again
mget(8,0)
end

function makesolve(biome,playlevel,sortpower)
--2x2 room, past weapons make obstacles
else mget(8,0)
end

function makeboss(biome,playlevel,boss)
else mget(8,0)
end

--the game has a list of sceneid
--it uses sceneid to copy map
--and edit copies of map for content
function drawroom(sid)
return sid{biome}
return sid{scenetype}
return sid{playlevel}
return sid{doorid}
if scenetype=0 then 
 makestart(biome)
 --move player to door id
if scenetype=1 then 
 return sid{sortpower}
 maketrans(biome,sortpower)
if scenetype=2 then 
 return sid{perlin}
 return sid{prize}
 makeprize(biome,perlin,prize)
if scenetype=3 then 
 return sid{hallway}
 makevcorr(biome,hallway,playlevel)
if scenetype=4 then 
 return sid{hallway}
 makehcorr(biome,hallway,playlevel)
if scenetype=5 then 
 makehub(biome,playlevel)
if scenetype=6 then 
 return sid{sortpower}
 makesolve(biome,playlevel,sortpower)
if scenetype=7 then
 return sid{boss}
 makeboss(biome,playlevel,boss)
end

function nworld() --new world
shufflepower()
sid=1 --scene id, 1=start
hubdoor={u1,u2,l1,l2,d2,d1,r2,r1}
did=1

biome=rnd(0,6) --first biome
scenetype=0
playlevel=0 --start does not generate enemies/hazards
hubid=0
hubmax=0

sid+=1 --give one main weapon
direction=rnd(0,3)
hubdoor=rnd(0,7)=did
scenetype=2
return sortpower{1}
did+=1

sid+=1 --give one subweapon
direction=rnd(0,3)
hubdoor=rnd(0,7)=did
scenetype=2
return sublist{1}
did+=1

sid+=1 --make one hallway, start enemies
playlevel+=1
direction=rnd(0,3)
hubdoor=rnd(0,7)=did
biome+=rnd(1,5)
hallway=rnd(1,4)
if direction=left or right then
 scenetype=4
 else
 scenetype=3
did+=1

sid+=1 --first hub, main cycle begins after this
hubid+=1
hubmax+=1
hubdoor+=4=did
scenetype=5
did+=1

for playlevel=1 to 5 --main loop
do
 for p=1 to 3 do --sub loop
 sortpower+=1 --bump up a power
 sublist+=1 --bump up a secret
 hallway=rnd(1,3)
 direction=rnd(0,3)
 hubid=rnd(2,hubmax)
 hubdoor+=direction*2+rnd(0,1)=did
 did+=1

 sid+=1 --transition room
 scenetype=1
 did+=1

 sid+=1 --hallway
 if direction=left or right
 then scenetype=4
 else scenetype=3
 did+=1

 sid+=1 --turnaround hub
 hubid=hubmax+1
 hubmax+=1
 hubdoor+=4
 scenetype=5
 did+=1

 sid+=1 --hallway back
 if hubdoor%2=1 then hubdoor-=1 else hubdoor+=1
 if direction%2=0 then direction-=1 else direction+=1
 door=(door+1)%2
 if direction=left or right
 then scenetype=4
 else scenetype=3
 did+=1

 sid+=1 --prize room
 scenetype=2
 perlin=direction
 prize=sortpower
 did+=1

 sid+=1 --secret room (from hallways)
 scenetype=6
 solve=sortpower
 did+=1

 sid+=1 --prize for secret
 scenetype=2
 perlin=direction
 prize=sublist
 did+=1

 p+=1
 return

sid+=1 --transition room
direction=rnd(0,3)
scenetype=6
solve=sortpower-2
did+=1

sid+=1 --hallway to boss
biome+=rnd(1,5)
playlevel+=1
hallway=rnd(1,5)
if direction=left or right then
scenetype=4 else scenetype=3
did+=1

sid+=1 --boss room/new hub
hubid=hubmax+1
hubmax+=1
scenetype=7
bosslives=1 --deactivate 'boss' once beaten
boss=sortpower-1
did+=1
return

--now, playlevel=6, endgame
--insert endgame, this is final item
sid+=1
scenetype=2
perlin=direction
prize=win
did+=1
--make a boss at the start room

save world.sav
end

function hiprob(prob80,prob20,prob5,prob3 or =prob5,prob2 or =prob3,prob1 or =prob2)
 local roll=rnd(0,100)
 if (roll=1) return prob1
 if (roll=2) return prob2
 if (roll=3) return prob3
 if (roll=4 or 5) return prob5
 if roll<20 and roll>5 then return prob20
 else return prob80
end

function loprob(prob60,prob30,prob9,prob1)
 local roll=rnd(0,100)
 if (roll=1) return prob1
 if roll<10 and roll>1 then return prob9
 if roll<40 and roll>10 then return prob30
 else return prob60
end

function _init()
px=92
py=64
pstate=1
paspr=0 --top sprite
pbspr=8 --bottom sprite
pdir=right
pat=0	--state timer
function _draw()
--draw the world

--draw the player
if pstate=6 and scuba=1 then
spr(paspr,px+(8*pdir),py,1,1,pdir)
spr(pbspr,px,py,1,1,pdir)
else
spr(paspr,px,py-8,1,1,pdir)
spr(pbspr,px,py,1,1,pdir)
end

px+=xspeed
py+=yspeed

--finite state machine for player
function cstate(n)
pstate=n
pat=0
end

function groundck()
--check position under the player
v=mget(flr((px)/8*gravity,(py+8)/8*gravity)
w=mget(flr((px+7)/8*gravity,(py+8)/8*gravity)
return not fget (v,0)
return not fget (w,0)

function wallchk()
v=mget(flr((px+xspeed)/8,py+12)
return not fget (v,0)

function _update()
b0=btn(0)--l s      left
b1=btn(1)--r f      right
b2=btn(2)--u e      up
b3=btn(3)--d d      down
b4=btn(4)--z tab				jump
b5=btn(5)--x q      shoot
b6=btn(6)--n shift  select
b7=btn(7)--m a      start

pat+=1
if hwrap=1 then (px+96)%96
if vwrap=1 then (py+72)%72

if pstate=1--standing
 if groundck=1 then cstate(4)
 if xmove<0 then
  pbspr=18
  for xmove to 0
  do xmove+=1 else
 if xmove>0 then
  pbspr=18
  for xmove to 0
  do xmove-=1 else
 pbspr=16
 if b0=1 then cstate(1)
 if b1=1 then cstate(1)
 if b2=1 then --aim up
  paspr=1
  shotdir=up
  else
  paspr=0
  shotdir=pdir
 if b3=1 then cstate(2)
 if btnp(5)=1 then shoot(weapon,shotdir,4)
 if btnp(4)=1 then
  yspeed=jumpvel
  cstate(4)
 if btnp(6)=1 then weapon+=1
 end

if pstate=2--crouching
 paspr=nil
 pbspr=19
 if xspeed<0 then xspeed+=1
 if xspeed>0 then xspeed-=1
 if groundck=1 then cstate(4)
 if b1=1 then pdir=left
 if b2=1 then pdir=right
 if b3=0 then cstate(1)
 if btnp(4)=1 then 
  if ball=1 then buzz+=1
  paspr=nil
  pbspr=83
  if b4=0 then 
  spindash(buzz)
  cstate(7)
  else
  yspeed=jumpvel
  cstate(4)
 if btnp(5)=1 then shoot(weapon,pdir,0)
 if btnp(6)=1 then weapon+=1
 end

if pstate=3--walking
 if groundck=1 then cstate(4)
 pbspr=16+(pat/2)%3
 if boost=0 then maxrun=4 else maxrun=8
 if b0=1 then
  pdir=left
  for 0 to -maxrun
   do xspeed-=1
   if wallchk=1 then xspeed+=1
 if b1=1 then
  pdir=right
  for 0 to maxrun
   do xspeed+=1
   if wallchk=1 then xspeed-=1
 if b2=1 then cstate(2)
 if b3=1 then
  paspr=2
  shotdir=up
 if b3=0 then
  paspr=1
  shotdir=pdir
 if b4=1 then
  yspeed=jumpvel
  cstate(4)
 if btnp(5)=1 then shoot(weapon,shotdir,4)
 if btnp(6)=1 then weapon+=1
 end

if pstate=4--airborne
 if yspeed<-2 then pbspr=17
 if yspeed>2 then pbspr=18
 if -2>yspeed>2 then paspr=19 and pbspr=nil

 if boost=0 then maxrun=4 else maxrun=8

 if groundck=1 then
  if yspeed>6 then cstate(3) else cstate(1)

 if b0=1 then
 --walljump check (add)
 if wallchk=1 and pdir=right and yspeed<2 and b4=1 then
  pdir=left
  xspeed*=-1
  yspeed=jumpvel
  paspr=nil
  pbspr=66
 else
  pdir=left
  for 0 to -maxrun
   do xspeed-=0.5
   if wallchk=1 then xspeed+=1
 if b1=1 then
 --walljump check
 if wallchk=1 and pdir=left and yspeed<2 and b4=1 then
  pdir=right
  xspeed*=1
  yspeed=jumpvel
  paspr=nil
  pbspr=66
 else
  pdir=right
  for 0 to maxrun
   do xspeed+=0.5
   if wallchk=1 then xspeed-=1
 if b2=1 then
  shotdir=down
  paspr=67
  pbspr=nil
  if b2=0 then 
  shotdir=pdir
  cstate(4)
 if b3=1 then
  paspr=66
  pbspr=nil
  shotdir=up
  if b3=0 then
  shotdir=pdir
  cstate(4)
 if b4=0 then
  if yspeed<-1 then yspeed=-1
 if btnp(4)=1 then
  if double>0 then
   double-=1
   yspeed=jumpvel
 if btnp(5)=1 then shoot(weapon,shotdir,4)
 if btnp(6)=1 then weapon+=1
 end

if pstate=5--recoil
 yspeed=-3
 for pat=0 to 8
 do
  if pdir=left then xspeed=2
  if pdir=right then xspeed=-2
  if wallchk=1 then xspeed=0
 return
 if pat=9 then
 cstate(4)

if pstate=6--liquid
	if scuba=1 then
	 if b0=1 then
	  xspeed+=-2
	  pdir=left
	  if b0=0 then xspeed=0
	 if b1=1 then
	  xspeed+=2
	  pdir=right
	  if b1=0 then xspeed=0
	 if b2=1 then
	  yspeed+=2
	  if b2=0 then yspeed=0
	 if b3=1 then
	  yspeed-=2
	  if b3=0 then yspeed=0 

	 if btn(5)=1 then shoot(weapon,pdir,0)
	else--no scuba equipped
	 pat-=0.5
	 if xspeed>0 then xspeed-=0.5
	 if xspeed<0 then xspeed+=0.5
	 if yspeed<0 then yspeed+=0.5
	 if yspeed>0 then yspeed-=0.5
	 if yspeed>6 then yspeed=6
	 if wallchk=1 then xspeed=0
	 if groundck=1 then yspeed=0

	 if btn(5)=1 then shoot(weapon,pdir,4)
	end 

if pstate=7--rolling
 paspr=0
 pbspr=2+pat%2
 if buzz>4 buzz=4
 if facing=left then
  xspeed=buzz*-2 else
 if facing=right then
  xspeed=buzz*2
 if wallchk=1 then xspeed=0 and yspeed=3 and cstate(5)
 if btnp(4) then yspeed=jumpvel
 if btnp(5) then
  if bomb=1 then shoot(bomb,down,0)
 if btnp(6) then weapon+=1

if pstate=8--dashing

if pstate=9--dead

end

I literally whittled all of that worldgen code out because of this!

The big sticking point that really changed my approach was the realization that procedrual != random. I'm not gonna get away with teaching the code how to be the designer - I have to plug the design in, and just have the code shuffle the cards. Procgen will never do the heavy lifting part FOR you, it just allows you to do more things WITH it.

Could "Electric Shot" be a triple shot, since the boss uses that firing pattern? Then just normalize the damage - now you have a power utility for the damage sponges, and a space utility for the crowd control!



[Please log in to post a comment]