Orkn's Pulp Prototypes

Thank you!

Yeah for sure, this would work. What I don't like about doing that is it has no upper bound on how long it might take - if you have quite a small/busy area so the chances of clashing locations are high, or just get unlucky, you could be looping several times each time getting a new random coordinate and then checking if it is free. This could - theoretically - go on forever! That's probably not a particularly big concern, in most situations including this one I think the chances are very good you will very quickly find a free location, but the uncertainty bothers me. What I like about my approach is that you are guaranteed to only have to call random once for each object.

Yes, please endeavor to do so!

Forget the above - why write elegant code when you can just brute force things!

resonant_tale_warp

I quite like how this warp effect looks. The code on the other hand...

on collect do
	ignore
	wait 0.25 then
		pdx = 0
		pdy = 1
		wait 0.25 then
			pdx = -1
			pdy = 0
			wait 0.25 then
				pdx = 0
				pdy = -1
				wait 0.25 then
					pdx = 1
					pdy = 0
					wait 0.25 then
						pdx = 0
						pdy = 1
						wait 0.2 then
							tell event.room to
								call "warp"
							end
							pdx = -1
							pdy = 0
							wait 0.25 then
								pdx = 0
								pdy = -1
								wait 0.25 then
									pdx = 1
									pdy = 0
									wait 0.25 then
										pdx = 0
										pdy = 1
										listen
									end
								end
							end
						end
					end
				end
			end
		end
	end
end

I think we should coin a term for this kind of common pulpscript pattern - I like "apocalyptic", because at the bottom of the event is the end of all things :playdate_cry_laugh:

6 Likes

A new game "camera pan" intro (excusing the placeholder title text):

resonant_tale_intro_scroll

This uses 3 rooms (title screen, full screen beach, beach with black HUD background) - the "pan" is done with config.follow and changing the center Y position, with the water tile as the overflow. Much simpler than using draw (which would probably have performance issues) or creating many rooms. I quite like the effect :playdate_happy:

(The pan in the gif looks slightly choppier because of how I captured it, but you get the idea)

What a great idea! I was wondering how do to something like this.

A couple of techier (for Pulp) bits I thought might be interesting to share:

Implementing simple stealth

I wanted to make some simple line-of-sight stealth mechanics - think being spotted by Pokemon trainers. I thought through a few different ways I might achieve it.

The naive approach would be to loop over each guard (or whoever is trying to spot the player) every frame and then look out along their line-of-sight until you find the player, hit a solid wall or reach the edge of the screen. With a bit of code this could work but it'd mean adding code to (or called from) the game's loop event and it could be quite impactful if I have several guards on screen and am needing to check each guard's line of sight every frame.

My first "trick" was to flip around the way of thinking about line-of-sight and instead start from the player. That means only worrying about four lines-of-sight in the cardinal directions from the player.

My second trick was to decide to use chained events to propagate along and check the lines-of-sight rather than having some code do it from the loop event. Every frame I already call an event on the tile at the player's location called "playerPresent" (I use it for hiding the player and taking damage on spike tiles). I realised I could add a "playerPresent" event handler to my floor tiles that would trigger four events, one for each cardinal direction, that would call the same directional event on the next tile in that direction. Provided the next tile in that direction similarly implements that directional event the event will propagate outwards along that line of sight. If it reaches a guard, the event will be called on the guard, and I can then check if the guard is facing in the right direction (from their current frame) and have them react accordingly. Even better, when laying out rooms I only need to use floor tiles implementing these events if they will potentially be in a guard's line of sight (so while I could have the events on every floor tile, it's cheaper not to). All the code ends up contained in the relevant tiles and there is zero overhead otherwise as I already had the playerPresent event.

That implementation looks a little like this (the "v" tiles are placeholder floor tiles implementing the directional events):

pulp_simple_stealth

One neat advantage of this approach is that doing something like pushing a block into the guard's line-of-sight will block their sight without me coding for that specifically!

Randomising on which tile something happens from a set of tiles

Or in other words - whack-a-mole!

pulp_resonant_tale_randomised_enemies

This mole enemy will popup from a random mole hole and throw a stone in the cardinal direction closest to the player.

I wanted to randomise which hole the mole would pop up from but without hardcoding the locations of all of the mole holes on the screen, and also without using loads of variables to store the number of holes and their locations.

After a bit of thinking, the solution suddenly occurred to me using only one variable. When I want to spawn a mole I first set this variable to be 0. I then emit a "spawnable" event. The mole hole tile implements this event to simply increment the variable, so when the emit completes I have a count of the spawnable tiles. I then reassign the variable using the random function to get a number between 1 and the variable itself i.e. the count of spawnable tiles. This is effectively the index of the list of spawnable tiles where I want to spawn the mole. I emit a second event, "spawn", again implemented by the mole holes. This event checks the variable - if it is greater than 1, it decrements it and finishes. If it is equal to 1, it spawns the mole and then decrements. If it is equal to 0 it does nothing. You can think of emit as looping through a list, and my randomised variable decides at which index in the list something should happen.

Now I can add, remove and move mole holes around a room and know that the spawning will always be randomised correctly, no further effort required!

2 Likes

Here's how that line-of-sight stealth is shaping up in-game:

resonant_tale_jail_stealth

The screen wipe transitions are implemented using crop. Unlike other approaches, crop doesn't have to be called from the player's draw event, so I could keep the code for the screen transitions all self-contained.

6 Likes

Hey, curious if I can use this demo code in a zelda-like project I'm currently working on. Would be sure to include your name in the credits and identify your portions of code in the comments of my own :playdate:

Sure, go ahead and use the movement code from the demo, I'm glad to be of help! Just please don't use the player tiles/character art itself :slight_smile:

It's been a while again since I've posted anything here, but I have been steadily working away on Resonant Tale. The big news is that I now have a collaborator for music and sound! My own efforts at them (even with Pulp's simple interface) proved woefully inadequate so I'm pretty excited. Good sound really is transformative! It's great being able to get an insight into that side of things by working with someone who knows their stuff :slight_smile:

On the subject of smooth movement, my approach here in some ways already feels outdated since people have found draw can accept non-integers, which can free you from the grid constraints entirely (with a bit of effort). I'm already tied in to my approach, which is fine as it works for what I want it to do, and the last thing I did was extend it so that the player can be smoothly animated while descending a ladder - like this:

resonant_tale_ladder

(While on a ladder you also can't turn to face left or right, and you can't place bombs or shoot arrows.)

2 Likes

Absolutely, thank you! And of course not- I'm doing all my own art :playdate:

1 Like

Over the last couple of days I revisited my tile-based pulp platformer prototype to shape it into a little "arcade cabinet" for inclusion in another soon-to-be-revealed project I was approached about.

My goal was to:

  • Make a proper level
  • Crop the screen to an arcade cabinet feeling aspect ratio
  • Implement coins to collect with a saved high score
  • Implement a timer with a saved high score
  • Give the game some polish with little things like a title screen
  • Refactor all of it to be self-contained and as easy as possible to drop into a larger pulp project!

Here is what it looks like right now:

tile_hill_pits_and_spikes

tile_hill_springs_and_saws

Implementing coins

The code is simple, but it's a lot of effort!

Coins are an item tile with this basic script:

on collect do
	
end

on pickup do
	thill_coins++
	tell event.room to
		call "pickupCoin{event.x}x{event.y}y"
	end
	swap "white"
end

collect is overriden to do nothing because of the non-standard player movement. Instead every frame I call the pickup event on the player's location.

The implementation of screen scroll (by having overlapping rooms with inset edge exits stitching them together) means that the "same" coin will be placed in multiple rooms. When the player picks up a coin in the level it not only needs to be swapped out of the current room, but any other room in which it is visible. For this reason every coin has its own variable to track whether it has been collected, and when picking up a coin the room script has a matching event to set the correct coin variable:

on pickupCoin2x10y do
	thill_coin_1 = 1
end

There is an event like this for every coin that can be picked up in that room.

When entering a room the coin variable needs to be checked to see whether each coin visible in the room should be swapped in or out. Swapping in as well as out means the level can easily be reset without restarting the entire game:

on enter do
	if thill_coin_1==0 then
		thill_tile = "thill coin"
	else
		thill_tile = "white"
	end
        tell 2,10 to
		swap thill_tile
	end
end

That's it for the concept - but multiply up for every coin (each appearing in multiple rooms) and it's a lot of code and a lot of copy/paste while changing the numbers!

Making it portable

I refactored extensively to try and make it as easy as possible to just drop in to another project:

  • "Portable" means "small". The game uses 11 rooms (10 for the level and 1 for the title screen). In addition to the default black/white world tiles the game only uses 10 player tiles, 11 sprite tiles, and 4 item tiles (cut down from the original prototype).

  • Every variable starts with thill_ so there shouldn't be any conflicts despite pulp having only global variables.

  • All of the rooms and tiles similarly start with thill so they will be grouped together in the pulp editor's alphabetically sorted lists

  • All of the code is self-contained within tile and room scripts, primarily a tile hill code tile (that doubles up as the coin HUD icon, just to save on tiles!). The player and game scripts have the minimum hooks to relay the required pulp events to the Tile Hill code. Essentially to start Tile Hill from within another pulp game someone just needs to call the one boot event to start the game and ensure those events are relayed for the duration (probably just with a flag that is set when the game is running).

  • Pressing "B" while playing will return the player to the title screen. Pressing "B" on the title screen will call a quit event which is intended to quit the player out of Tile Hill and back to the containing pulp game. This requires a little bit of tidying of config changes and resetting the screen crop.

Hopefully that's mildly interesting!

2 Likes

ART& was just released on catalog - check it out and head to the in game arcade if you want to give Tile Hill a play. It's very cool to have contributed something to a catalog release!

1 Like

I still think what you did with it is magic

Resonant Tale is finally approaching a complete game, and while it's not quite finished yet, yesterday I spent a surprisingly short amount of time trying to make the game more accessible. I thought my experience might be interesting to share :slightly_smiling_face:

Accessibility in Resonant Tale

Previously I have implemented some cheat codes in Resonant Tale that can be entered on the title screen, partly for fun and partly for making testing easier. One of those cheats was player invincibility, but it occured to me this would really be better as an easily discoverable option in some assistance settings. That prompted me to consider accessibility in the game in general.

I was a bit trepidatious about exploring accessibility. It's not something I'm familiar with or have consciously addressed before and I was worried about how much work the additional functionality would be, especially using Pulp. Obvious accessibility features that sprung to mind having seem them in other games, like adjustable font sizes, are not readily supported by Pulp (which I think is a fair trade off with Pulp's intended simplicity as a starting point into game development). Nethertheless I started reading up on Accessible Player Experiences and Game Accessibility Guidelines, both great resources. I geared my mindset to thinking about what can be easily done rather than getting hung up on what can't!

More details below, but this is the assist menu as it now looks in game:

resonant_tale_assist_menu

Settings persistence

Up until now my approach to save data has been really simple - if the player selects Continue from the title screen I use restore while if the player selects New Game I use toss. With assistance settings however I want them to persist even if a new game is started. I replaced my toss call with a custom game event (also called "toss") that looks like this:

on toss do
	toss
	// Re-store persistent vars
	store "assist_damage"
	store "assist_bosses"
	store "assist_crank"
	store "assist_timing"
	store
end

This works great and is going to be useful beyond these assist settings as I'm planning on keeping a record of whether the game has ever been beaten so I can offer a New Game Plus!

Player damage

I already had a version of this implemented in my invincibility cheat so this was easy. From an accessibility perspective being able to turn off player damage is a maybe heavy handed but simple solution to a host of potential issues.

I already had all player damage implemented through a player event takeDamage so this was as easy as adding this one conditional to the start of the event handler:

on takeDamage do
  if assist_damage==1 then
    done
  end
  // code handling player damage
end

Boss battles

I'd describe Resonant Tale as "low combat" - there are numerous hazards but few traditional enemies. Bosses are the exception as tests of player execution and as barriers to progress. While a valuable aspect of the game, making them optional prevents them blocking someone from enjoying the rest of the game which is not as challenging or demanding of execution.

Again I already had logic in place for triggering bosses, previously there to prevent bosses from being fought multiple times if returning to the boss room for example. This made implementing a simple skip almost trivial as (for the most part) I only needed to alter a few conditionals. The first dungeon's boss, for example, has a conditional changed from this

on enter do
  if has_bell_of_vitality==1 then
    // code toggling the boss
  end
end

to this

on enter do
  flags = has_bell_of_vitality
  flags += assist_bosses
  if flags>=1 then
    // code toggling the boss
  end
end

(The flags>=1 pattern is a pulpscript version of an or)

Crank puzzles

Input, especially analogue input, is an obvious consideration for accessibility. Resonant Tale has minimal use of the crank, used only in a couple of instances, only one of which is required to beat the game. A setting to remove those instances to prevent a small aspect of the game stopping someone from playing it entirely seems an obvious win!

With the assist option enabled, for an optional puzzle I simplify the puzzle to no longer require the crank. For the required puzzle simplifying it was not an option so I just made it pre-solved (although I may revisit this to make an alternative solution instead).

Timing puzzles

Timing puzzles refers to anything (outside of bosses) where precise timing is required from the player. This covers a relatively significant minority of Resonant Tale and was the most work to implement as I took different bespoke approaches depending on what content I was changing.

Take this room as an example - it has retracting spikes that the player must time passing over to not take damage:

resonant_tale_retracting_spikes_without_timing_assist

With the assist option enabled I replace the retracting spikes with static spikes and add a path through for the player to navigate:

resonant_tale_retracting_spikes_with_timing_assist

Not every timing puzzle has quite as nice a replacement as this and in multiple places I simply removed the challenge entirely, but I like being able to offer an alternative like this where I can!

2 Likes

Really excited that a reveal trailer for Resonant Tale just featured in the Playdate Community Direct! Here it is if you missed it :smile:

2 Likes

This looks wonderful! Amazing it's in Pulp too!
:trophy::crossed_swords: :shield:

1 Like

Something completely different for a change, a little toy called Paint with Pulp!

paint_with_pulp

Drawing at double the room tile resolution! I made 16 sprite tiles that represent all permutations of a 4 sub-tile grid, and these get swapped in as you draw.

The player is hidden (trapped in the middle of the room) and instead I draw the cursor using fill , with the position updated through the sprite interact event that's mimicked across the tiles.

Anyway, at the moment a lot of games have a nice launcher card but the default list view icon, so I decided I wanted to make a joke game which has a nice list view icon but where the launcher card looks like a card version of the default icon. I think it came out pretty well (read: terrible)!

paint_with_pulp_cursed

I added a second canvas to draw on, you can switch between them by cranking - this allows for some very basic animation. Also note that the canvas is saved (I just reused my room saving code from Pulpino :smile:)

If you want to try it out (and ruin the look of your card view launcher), here's the pdx:

Paint-with-Pulp!.pdx.zip (60.0 KB)

4 Likes

I realise I haven't posted about it here, but Resonant Tale is coming to Catalog! It launches September 12th on both Catalog and Itch, which is almost exactly a month away, so please look forward to that!

Anyway, I actually came here to share a concept called Travelling Balloon:

travelling_balloon_movement_concept

The player controls a hot air balloon that moves automatically with the wind, but the crank moves the balloon between different air layers with different wind directions (see HUD on right). The top layer is the prevailing wind for that room; the middle and lower layers vary by tile (which means 16 permutations for every tile to cover all wind direction combinations). The little arrows on the tiles are for ease of concepting only - the intention is that you'd only see the wind direction on the HUD and would have to learn the hidden wind patterns across the map.

One of the "tricks" here is that every map tile is a sprite, not a world tile. For one the tiles being solid means the dpad won't trigger any movement without me having to ignore input, and second it means each map tile can define its wind directions in its script.

1 Like

So many end lines. :woozy_face:

How about calling it a "stairway to heaven", or maybe a "pass" because when you look at it, you want to look away. :sweat_smile:

Personally I would have made the player automatically step off the warp tile right after warping, instead of continuing to spin around after warping and save myself from some grief, but we all do things differently and there's no "right way" when making your own game.

Keep up the great work!

1 Like