Love Letter: A Game to Propose to my Partner (In Development)

Hey Playdate developers,

New to the forums and developing for the playdate!

Very recently I made the decision to make my first Playdate game using Pulp.
The game I intend to make for the handheld will assist me in asking my partner to marry me! <3

My day job is game development for an AA studio, currently working in VR/MR as a Designer. Using tools like Unity and previously Unreal, this is the first time I'll be working with such design constraints from a hardware level and honestly I can't wait for the challenge.

The issue though - is I'm an absolute coding novice, and so I suspect I'll be constantly posting to these forums to get some assistance from people way more clever than myself :smiley: :pray:

So if you want to follow along in this journey where I ask the love of my life to marry me via the medium of video games - follow this thread!


Okay! So the game... the working title is Love Letter and so far development progress has been:

  • Reading Pulp documentation + testing the interface.
  • Brushing dust off of my pixel art in Photoshop.
  • Working out how to slice PS pngs into tiles via the Import function.
  • Concepts for the gameplay.
  • Searching through forum posts, looking at test projects posted to see how they are built.

The Concept

  • The player will start the game by inputting their name (to use for the proposal).
  • The player will then be tasked with getting a Love Letter safely to a delivery point by using the crank to move the letter along conveyor belts
  • (possibly needing to avoid obstacles - this game isn't meant to be challenging so probably not)
  • Each love letter delivered will open up and give a little note to the player and the final letter (level) will pop "the question" with a YES or NO selection.

Here's an image of the title card made in PS and imported.
(I really need to clean up the repeated dithering tiles)

7 Likes

I've made 8 frames so that the conveyor belt mid/end caps animate.
Currently, my conveyor belt is set up as a sprite but I'm thinking that may be a mistake and I should make it a world tile instead... more reading is required.

The hope is that I can hook these frames up to the directional rotation of the crank so if the crank is not moving the frame is held and then progresses +1 or -1 through the frames on the rotation/direction.

I've been doing a bit of reading on how to hook these up to the crank rotations.
conveyor belts (1)

3 Likes

After a bit of tinkering this morning and discussing with some developers on Discord I was able to get my conveyor belt cycling through frames when the player presses [A] for clockwise and [B]
for anticlockwise.

beltAnims

I've re-read a couple of forum posts regarding how to use the cranks ra but I'm not fully grasping where I should place the logic etc.

Current Goal
My goal is to replace the inputs of [A] and [B] with the directional rotation of the crank.

This is what I have on the Player script.

on confirm do
// Press A to tell conveyor belts to
emit "beltClockwise"
end

on cancel do
// Press B to tell conveyor belts to
emit "beltAnticlockwise"
end

I understand that I need to replace the confirm and cancel events with something but I'm not sure where to start. Do I first need to define what it means to "rotate clockwise" or "rotate anticlockwise" or does pulp have that shorthand available?

1 Like

The crank rotation is handled in the crank event in the player script. You might need something like this:

on crank do
	crankDelta = event.ra
	if crankDelta>0 then
		emit "beltClockwise"
	else
		emit "beltAnticlockwise"
	end
end

If you want to move the belts only when the crank is turned a certain amount (10 degrees in my sample below) and not on every minor rotation (which I recommend, as emit calls are expensive and you likely don't need them every frame), you could do this:

on crank do
	crankAngle += event.ra
	if crankAngle>=10 then
		crankAngle = 0
		emit "beltClockwise"
	elseif crankAngle<=-10 then
		crankAngle = 0
		emit "beltAnticlockwise"
	end
end
1 Like

Amazing, that worked perfectly! Thank you so much.
I also appreciate you confirming the cost of using emit.
So yeah, I've used your second example and bumped the number up slightly from 10 degrees and it's feeling pretty good.

Awesome, happy to help! Let me know if you hit any other bumps. Love your idea for a proposal! :slight_smile:

1 Like

Project update! (+ need code help).


Sorry for the delayed update, had some busy weeks and trying to keep this project away from "prying eyes" aka my partner has proven difficult haha

Here's what I've now got.

  • The conveyor belt is now attached to Crank and works in both directions.
  • The letterbox at the bottom will become the Goal of the level.
  • Three letters are floating, they are placeholders for the 3 ask functions I will attach to the letterbox.
  • Was trialling some visuals like the buttons and how they might function ON/OFF.
  • Using a few of the platformer posts on these forums I've been able to get the player (envelope) to have gravity on an interval of 10 ticks until it hits a solid tile.

gravity_Crank


Now I need help with the letter moving left and right when on the conveyor belt and it detects the direction of the crank. (Please and thank you!)

I attempted to reuse code from this project/post which worked somewhat, but I removed the code as I'm not sure it is what I needed.

For example, originally the frameIndex for the conveyor belt was moving 1 frame per 15 or so degrees of the crank rotation. But it meant that the envelope and the conveyor belt were moving at two different speeds visually. I've since bumped the frameIndex to 2 frames per 15 degrees which slightly helped.

This is how I envision my player movement working;

  1. Check if the tile below the player is solid (or a conveyor belt).
  2. If crank rotation (ra) is greater than 15 move in that direction (to match belts).
  3. If crank rotation (ra) is greater than -15 move in the opposite direction.
  4. Stretch goal, the envelope moves at a believable pace relative to the conveyor belt anims.

Here's a quick mock-up of what I mean in case I'm not making much sense haha

  • red squares and arrows to indicate player and direction.
  • purple arrows, the player can't detect a solid (or belt) under it so the gravity ticks kick in.
  • green == solid tile checks as example.

If required I could upload the project to this post to help come up with a solution.
I appreciate everyone's help with this! :smiley:

1 Like

I figured something out to get the player moving left/right with the crank. Basically I do the following steps:

  • I have an envelope for every possible offset, ranging from "env_-7" to "env_7" (tiles in picture are in this order)
  1. I have a variable offset that stores the offset of the envelope. Depending on the movement of the movement of the crank I change that offset.

  2. Depending on the offset I draw the envelope as a combination of two tiles. This results in smooth movement of the envelope

This is the code:

on draw do
	rotation += event.ra // Get rotation
	letterSpeed = 15 // Change as needed
	hide // preferaby don't call this here as it only needs to be called once at the start of the game/level
	
	// Update offset
	// Check movement in positive direction
	while rotation>letterSpeed do
		offset++
		rotation -= letterSpeed
	end
	
	// Check movement in negative direction
	while rotation<letterSpeed do
		offset--
		rotation += letterSpeed
	end
	
	// Do stuff depending on offset
	px = event.px
	py = event.py
	
	if offset==0 then
		draw "env_0" at px,py
		
	// Letter moving to the right
	elseif offset==1 then
		draw "env_1" at px,py
		px++
		draw "env_-7" at px,py
	elseif offset==2 then
		draw "env_2" at px,py
		px++
		draw "env_-6" at px,py
	elseif offset==3 then
		draw "env_3" at px,py
		px++
		draw "env_-5" at px,py
	elseif offset==4 then
		draw "env_4" at px,py
		px++
		draw "env_-4" at px,py
	elseif offset==5 then
		draw "env_5" at px,py
		px++
		draw "env_-3" at px,py
	elseif offset==6 then
		draw "env_6" at px,py
		px++
		draw "env_-2" at px,py
	elseif offset==7 then
		draw "env_7" at px,py
		px++
		draw "env_-1" at px,py
	elseif offset==8 then
		px++
		draw "env_0" at px,py
		offset = 0
		goto px,py
	elseif offset>8 then
		px++
		draw "env_1" at px,py
		px++
		draw "env_-7" at px,py
		offset = 1
		goto px,py
		
		// Letter moving to the left
	elseif offset==-1 then
		draw "env_-1" at px,py
		px--
		draw "env_7" at px,py
	elseif offset==-2 then
		draw "env_-2" at px,py
		px--
		draw "env_6" at px,py
	elseif offset==-3 then
		draw "env_-3" at px,py
		px--
		draw "env_5" at px,py
	elseif offset==-4 then
		draw "env_-4" at px,py
		px--
		draw "env_4" at px,py
	elseif offset==-5 then
		draw "env_-5" at px,py
		px--
		draw "env_3" at px,py
	elseif offset==-6 then
		draw "env_-6" at px,py
		px--
		draw "env_2" at px,py
	elseif offset==-7 then
		draw "env_-7" at px,py
		px--
		draw "env_1" at px,py
	elseif offset==-8 then
		px--
		draw "env_0" at px,py
		offset = 0
		goto px,py
	elseif offset<-8 then
		px--
		draw "env_-1" at px,py
		px--
		draw "env_7" at px,py
		offset = -1
		goto px,py
	end
	
end

You can change letterSpeed to match the speed of the envelope to the speed of the belts. Also it's better to call hide somewhere else, as it only needs to be called once, not every frame.

In the two while-loops offset is decreased/increased depending on how much the crank was moved.

Then I get the players position and after that it's just a long if-else-statement checking every possible offset and drawing one or two tiles corresponding to the offset.

If the offset is equal to 8/-8 I reset the offset, as the envelope has reached a new tile. The player is moved there with the goto-command.

I also included the case of the offset being greater than 8, because the player could crank in a fast speed, resulting in the offset increasing/decreasing by few numbers at a time. More cases could be added here (equal to 9, equal to 10, ...) if greater speeds need to be supported.

If you only want this to work only while the envelope is on a belt, you can wrap the whole thing in an if-statement checking the tile below the player.

Hope this helps :smiley:

1 Like

Amazing! Your suggestions worked perfectly!

Here's a few things I did differently:

  1. As suggested, I've placed the hide inside a different function, I chose enter.
  2. To simplify the rotation portion of the code I've added the offset inside my crank function.
    That looks like this:
on crank do
	crankAngle += event.ra
	if crankAngle>=10 then
		crankAngle = 0
		emit "beltClockwise"
		offset--
	elseif crankAngle<=-10 then
		crankAngle = 0
		emit "beltAnticlockwise"
		offset++
	end
end

And here is a gif of your draw code in action with:
playermovement_smooth


Remaining Tasks

  1. Clean up the artefacted pixels left over from the movement/draw.
    (I've seen this done a few times in forum posts so will try those methods).

  2. Remove the players ability to move left or right while falling.

  3. Fix the collisions with walls and objects such as the button.
    (Currently, the player can just draw right through obstacles which is breaking the level flow).

Here's the build in its current state if anyone wants to dive into it further.
LNBLOYZMEFplaydate-Game_20231023092045.zip (57.9 KB)

1 Like

Solve for Task 2

"Remove the players ability to move left or right while falling"

After a bit of failed experiments using ignore and listen I had the realisation that I'm better to nest my offset++ and offset-- inside a conditional event.

That looks like this for the right moving direction (left if the similar but with offset++)

on crank do // What happens when the crank is turned.
	crankAngle += event.ra
	if crankAngle>=10 then
		crankAngle = 0
		emit "beltClockwise"
		call "canMoveRight" // the event to see if player can move.
	elseif crankAngle<=-10 then
		crankAngle = 0
		emit "beltAnticlockwise"
		call "canMoveLeft"
	end
end
on canMoveRight do
	if isGrounded==1 then
		offset--
	elseif isGrounded==0 then
		offset = 0
	end
end

I'm not really sure the elseif in the canMoveRight event is doing anything... I added it in an attempt to stop the player from over shooting the edge of the conveyor belt. I attempted a few other combinations of wait then's but the results remain the same.

The results:
canMoveLeft_issue

You can see in the gif that;

  • !! When the player cranks quick enough the envelope can be almost an extra tile away from the conveyor belt before falling.
  • The falling logic works! WAHOO!
    While the envelope is falling, the player will no longer move with the crank.

:playdate_question: Any ideas how I can stall the envelope immediately as it leaves the belt?

So I've attempted to fix the collision issue with a solid object but unfortunately, I can't seem to get it working.

I'd appreciate it if anyone had time to investigate into the build and let me know how I could fix it?

Inside the Player script I have two events called canMoveRight and canMoveLeft where I check if the player is currently grounded and if so they can update the offset to draw the new sprites.

So I wanted to tap into this by checking "isGrounded" and "isSolid" is true then do nothing... but again, after a few attempts I've made no progress toward the goal.

isGrounded == checks if the tile below is solid.
isSolid == checks if tiles left and right are solid (I think... this could be broken).

Note - When you hit play, use the crank to move the player and not the D-Pad.
Thanks in advance to anyone who can help me shed light on the issue.

Also feel free to suggest any further refactors or improvements as I'm sure the code is horribly hacky and not efficient at all haha

v0.9MMPSKJVXFSLove-Letter_20231028044356.zip (59.5 KB)
(Hope this build is the actual files I need to upload, never shared a game package like this before)

Update - Collision left and right.

Got this fixed after tinkering with some new events and variables.
Now the player can bump up against solid tiles and not continue through!

Feels good to get these things fixed by general understanding and learning.

1 Like

Congrats!!! :champagne::tada:

2 Likes

Oh wow!! That's crazy hahaha
Crazy to see the positive reception of my proposal outside of the playdate community.

3 Likes

Congrats from Germany as well!
https://www.golem.de/news/playdate-entwickler-programmiert-spiel-extra-fuer-heiratsantrag-2404-184245.html

Love the idea and the perfectly matching style of the game!
You inspired me to do something similar now. :smiley: