Pulp tricks, maybe they help someone

Hi, I'm making a game in Pulp called The Fall of Elena Temple, more info in this
thread.

lvl3

Working in Pulp has been so much fun! I usually make these bigger games, in Unreal or Unity, that take a year or two to develop. It's been refreshing working on something so small and I love Pulp's limitations, they really get my brain working trying to find workarounds.

If you check the game and want to know how I've coded something, do let me know, I'd be happy to share. For now, here's a little trick I applied. This is likely basic for many of you, but maybe it comes in handy for beginners.

When designing the rooms, considering I have a selection screen in the main menu, I needed a way to place the player when the room starts. Since you can select any room from the menu, I couldn't rely on room connections, so I used:

goto 1,1 in "room{currentRoom}"

But, of course, that's not the position I want the player to start at in every room. At first, I had something like this in each room's script:

on enter do
	startX = 6
	startY = 2
	goto startX,startY
end

But manually writing the start position for each room quickly became a hassle, especially when I wanted to change it during design. So I needed a more automated way, by placing a specific tile that would indicate the player's start position and would move him there when entering the room. I ended up with this code placed in the tile's script:

on enter do
	startX = event.x
	startY = event.y
	
	tell startX,startY to
		swap "white"
	end

	goto startX,startY
end

Pretty basic stuff, but very useful. Note that I'm replacing the custom start tile with a white one, so I only see it during design, but the player doesn't see it at runtime.

I'll add more simple tricks in the next days. It's been fun discovering them.

1 Like

A new little trick about restarting the room when the game character takes damage. This might work for you or not depending on what you need to happen, but in my case, I wanted to reset the entire room. I store how many rooms you've completed and I want a room to always reset when you load it, so you can play the level over and over.

Here's my approach. In any tile that does damage, I have this event:

on pickup do
	tell event.game to
		call "takeDamage"
	end
end

You can have the call in a collect or interact event too, but since I'm working with config.autoAct = 0 I replaced all collect events with pickup. Maybe more on that in a future post. By the way, my damage tiles are all items. Now, in the game script, this is the takeDamage event:

on takeDamage do
	sound "damage"
	restart = 1
	store "restart"
	wait 0 then
		fin "Write your dying message here."
	end
end

I have to wait for the next frame (wait 0) before calling fin, otherwise the game would crash - not sure why. The fin call resets everything in all the rooms, but I still have the stored variables. The finish event also gets called and I make sure the currentRoom is stored there. I could've also stored it in the event above, but doing it in the finish is better since I also call fin from other places. Like when I exit the room from the pause menu and I use the stored variable to select the current room in the list from the main menu.

on finish do
	store "currentRoom"
end

Now in the load event, I just have to check if I'm restarting the room and place the player accordingly. Otherwise the game would reboot in the main menu, not in the current room. Here is the code:

on load do
	restore	
	if restart==1 then
		toss "restart"
		goto 1,1 in "room{currentRoom}"
	end
end

Quite simple stuff. It's important to toss the restart variable from storage so we don't keep on restarting this room every time the game loads. Hope this helps someone!

1 Like

How about making a teleporter that changes its destination every time we go in? Let's say there are four possible destinations, indicated by (portalX, portalY) coordinates.

lvl4

The code for our cycling (pretty sure this isn't the right word) teleporter looks like this:

on enter do
	cycle = teleCycleFirst
	call "updateTop"
end

on interact do
	sound "teleport"
	if cycle==1 then
		goto portalX1,portalY1
	elseif cycle==2 then
		goto portalX2,portalY2
	elseif cycle==3 then
		goto portalX3,portalY3
	else
		goto portalX4,portalY4
	end
	
	cycle++
	if cycle>teleCycleEnd then
		cycle = teleCycleStart
	end

	call "updateTop"
end

That if statement is one example where arrays are sorely missed in Pulp. If you have a trick how to replace such checks with something more elegant, I'd be grateful to hear it.

What about the updateTop event? That one changes a little rune above the teleporter which indicates where on the map it will teleport you next.

on updateTop do
	above = event.y
	above--
	tell event.x,above to
		swap "teleporter-top{cycle}"
	end
end

But that's not all, we also need to initialize all those coordinates and variables in the room enter event. Well, for the destination coordinates I use four tiles that automatically set portalX1, portalY1 and the rest, similar to the player start position described in the first post. As for the other three variables, they're commented below.

on enter do
	teleCycleFirst = 3 //the first destination the teleporter takes on every room restart
	//these two variables set the range of destinations, in this case it goes from 2 to 4
	teleCycleStart = 2 //the start index of the range of destinations
	teleCycleEnd = 4 //the end index of the range of destinations
end
1 Like

I've just finished a simple system to listen to a few tracks in sequence, looping them for different times and with pauses between. And I wanted to share my approach with you.

Let's start with the basics. I have 4 songs I wanted to play in sequence, at first each for the same amount of time - 72 seconds in this example, with a 15 seconds pause between them. Simple stuff handled in an event, which starts the next song in the list every time it's called. This means that when entering a room, restarting it or going to the main menu, the next song starts.

on playSong do
	if musicOn==1 then
		currentSong++
		if currentSong>4 then
			currentSong = 1
		end
		store "currentSong"
		loop "song{currentSong}"

		wait 72 then
			stop
			wait 15 then
				call "playSong"
			end
		end
	end
end

But what happens if I turn the music on and off? When turning it off, it stops the current song and when turning it on it calls this event, starting the next one. But! There is that nasty wait there that doesn't go away when stopping the previous song. Meaning that now there will be two wait calls. And if I keep toggling the music I will have even more wait calls. Not good.

To solve this, we use a lock. This makes sure that only the last wait call executes the code, stopping the current song and starting a new one, while the other waits do nothing. Here is the code:

songWaitLock++
wait 72 then
	songWaitLock--
	if songWaitLock==0 then
		stop
		wait 15 then
			if songWaitLock==0 then // checks a wait hasn't been called in the meantime
				call "playSong"
			end
		end
	end
end

Works beautifully! But at this point I decided I wanted different play times for each song. Which I set up like this:

if currentSong==1 then
	waitSong = 96
elseif currentSong==3 then
	waitSong = 72
else
	waitSong = 40
end

songWaitLock++
wait 72 then
	// same code as before inside the wait
end

This code works fine if I don't toggle the music on/off. But what happens if I toggle it after the first song starts, with a waitTime of 96? It starts the second song, with a waitTime of 40. But remember, the previous wait, which lasts for much longer hasn't yet stopped. When the second wait (the one for 40) ends, the songWaitLock will go from 2 to 1, meaning nothing happens. Only after the first wait (the one for 96) ends too, will the song be stopped. Meaning that instead of stopping the second song after the intended 40 seconds, it stops after much longer. Not our intended behavior.

This is where the wait limitation kicks in. I had to avoid calling wait altogether and track the time myself, to avoid stacked up wait calls. We know that Pulp calls the game loop event 20 times per second. So we multiply our waitSong with 20 to get the number of frames we need to wait.

In the game loop event, we call a new event handleWaitSong. In that event, which remember, is called every frame, we tick down the waitSong. When it reaches zero, we stop the current song. If by any chance a new song was started in the meantime, it means waitSong was reset to the value for that song, which is what we want. We handle pausing between songs similarly, with waitSongPause which is ticked down only if waitSong is zero - otherwise we're waiting to first stop the song. Here is the final code:

on playSong do
	if musicOn==1 then
		currentSong++
		if currentSong>4 then
			currentSong = 1
		end
		store "currentSong"
		loop "song{currentSong}"

		if currentSong==1 then
			waitSong = 96
		elseif currentSong==3 then
			waitSong = 72
		else
			waitSong = 40
		end
		waitSong *= 20
	end
end

on loop do
	call "handleWaitSong"
end

on handleWaitSong do
	if waitSong>0 then
		waitSong--
		if waitSong==0 then
			stop
			waitSongPause = 300
		end
	elseif waitSongPause>0 then
		waitSongPause--
		if waitSongPause==0 then
			call "playSong"
		end
	end
end
1 Like

I'm not sure what your use case is, but did you consider using once to play the songs rather than loop? You can run code after once and if you change what song is playing before then the code after the once will never trigger, so you'd avoid worrying about multiple waits and locks etc.

Loving this thread btw!

1 Like

Thank you! You can absolutely use once, but my songs are made of intros and little loops that I want to repeat for several times. I could've just cloned the loops for as many times as I wanted for each song, for sure. But why do that and be done in a minute when you can play around in code for a couple of hours? :smiley:

I like experimenting with different approaches and to keep things a bit more flexible - for example I wasn't sure how many times I wanted the loops to play and I also wanted to avoid recloning the loops each time I'd want to make a change.

But I will admit I didn't know that stopping a song started with once doesn't call the code inside. That's quite useful, thank you for the tip!

1 Like

I have to post this one fresh from the oven, as I struggled with it quite a bit today - mostly with trying to isolate the issue, create a scenario to reproduce it 100% of the time, then to find a workaround to how Pulp works.

I was implementing moving arrows, which use a wait between each movement step - simple stuff. BUT! When you change rooms or call fin, the wait calls for the tiles in the previous room do not get cancelled. So the code inside the wait can still execute in the new room. Or in the case of the fin call, it has a chance to execute on the same frame as the fin call, depending on the execution order (this one is a bit more complicated to get into). For this second case, which I use when restarting a room, I have this little bit of code:

restart = 1
store "restart"
wait 0 then
	fin "Restart message"
end

For exiting the room through a room connection, I use a collectible item placed on the exits, with the following code inside:

on pickup do
	nextRoom = currentRoom
	nextRoom++
	roomName = "room{nextRoom}"
end

This is vital! I also set the roomName inside the enter event of each room, but it's not enough, as between the player exiting the room and the enter event of the next room being called, depending on how those wait calls finish, you might have one exactly in between. The code above makes sure that roomName is already updated in case that happens.

Then, in my moving arrows script I have the following checks:

on move do
	wait arrowTime then
		if restart==1 then
			done
		elseif roomName!=event.room then
			done
		end
		// do move code here and call 'move' again
	end
end

Those checks make sure that we're not restarting the game and we're still in the same room as this tile. Why the second part? Well, I have to thank this nice thread where Scott is complaining that when calling a wait, the event.room variable remains set to what it was when calling that event, not to what it should be after the wait finishes. And one man's complaint is another man's solution! Since event.room after the wait will return the room name for my tile, I just compare it to the roomName as set by either the room enter event or the call from that collectible placed on the exit of the previous room.

I have no idea if all I've written above makes much sense, I'm way too tired, but if you need me to explain it better, please let me know. Alternatives solutions are also very welcome! Scott's approaches in the mentioned thread above are very useful and worth checking out too.

1 Like

Tip of the day: don't call an event break! It will work fine in the browser, but the compiler will fail when exporting the pdx. Call it obliterate or something. :slight_smile:

1 Like

Hah, haven't come across this but that's good to know! Lucky that I went with destroy when breaking pots then :smile:

1 Like