A Love Letter in Godot

Max Novak
8 min readJan 5, 2024

I bet you thought I couldn’t do another love pun for a title didn’t you? Well this time I implemented a card game called Love Letter! After spinning my wheels in my last project, I felt like I could use a palate cleanser where I just built something I already knew I liked. I figured that a card game would be a good project for exploring Godot.

Love Letter is a pretty simple card game, which can be summarized as: draw a card, play a card. You start the game by dealing every player a card and removing one from the game. Then the first player draws from the deck and chooses one of their two cards to play. Each type of card has a different ability, e.g. the “priest” lets you look at your opponent’s hand; the “prince” lets you choose a player to discard their hand and draw a new card; etc. You win by being the last player standing (some cards let you try to remove players) or once the deck is empty you compare cards to find the highest value (each card has a value from 1–8).

Right off the bat Godot really impressed me. From reading articles, I already thought I would like how the engine was structured. But it wasn’t until I dove into it that I realized: “oh, this just clicks like how I wanted other engines to”. While I really liked the code first approach of PixiJS and Love2D, having a simple editor to view things as I built each small piece was something I didn’t realize I wanted. Also things are structured so that it is just easier to build your game in a very object oriented way. I’ll get into Godot proper a bit later, but I’m not sure if I would have really appreciated a system like this without my journey through PixiJS and Love2D.

For this game, I took a different approach than I have for some of my other projects. I started by breaking down all the mechanics and setup on my whiteboard.

Maybe someday my handwriting will be more legible
White boarding out the basic game structure and the abilities of cards

Let me break down what we have in this picture, because I know my handwriting is atrocious. On the left is a list of all the card powers I would need to implement, with a rough order of what I thought made sense for implementation. Starting with the easiest: make the opponent’s hand visible to the player; and ending with the hardest: cards that need to check other cards in your hand for behaviors. The arrows pointing to some of the things on that list are small notes about general behaviors I would need to implement to get past that point. To the right of that list is a larger diagram breaking down what the board would look like and some ideas around how to organize code objects. For example, cards should be their own object, which can live within a hand or the deck. When cards are in the hand you should be able to hover over them to get some help text and make them bigger to just see better. Looking back I’m not sure why I used a different color for that final note, which says: “Dealing cards after played”. Maybe I was thinking of explaining what the animation was? ¯\_(ツ)_/¯

Also as a note, when I started this I built out the game for just two players even though Love Letter is designed for 2–4. I fully intended to build something that could support 4 players, but taking a lesson from last time I started out with an MVP for just two players to simplify a lot of problems.

For my next task I started looking for assets, I found some pixel art for most of a deck of 52 cards by Spicygames Studios. The free version of the assets came with 9, 10, & Jack missing, which actually was perfect for my use case where I only needed values from 1–8. (It actually turns out that was a mistake in their upload and they have a full deck of 52 cards up now!) From there I started building out the different types of objects I needed: the Card which had a value; the Hand which would hold player’s cards; and the Table which would manage the larger game state. I started out with adding some behaviors, like hover and click for cards. To do this I learned about an interesting pattern within Godot, Signals. Signals are how objects communicate upwards to their parent object. For example, my card object looked like this:

The left side is the collection of nodes and the right side is what the game will display

The Sprite2D is the sprite that the card itself looks like; the Card has some logic to it around behaviors; and the Area2D/CollisionShape2D is how a signal is emitted. When the mouse hovers over or clicks the collision object it emits a signal so that the Card node will do some behavior. This basically means that each object only is responsible for itself and signals it consumes. There is a little bit of code philosophy going on here. In Object Oriented Programming you set up everything in your code as blocks that allow you to build complex systems. Each block should only be responsible for itself and do actions related to itself. So a card only cares about a few things: the value it has; is it visible to the player; if it was hovered over; and if it was clicked. But there are behaviors around all of that which its parent will manage, but it isn’t responsible for.

Let me break this down with a feature I wanted: when the player hovers over a card I want it to show some helper text. Now, the Card itself just knows that it is a “Prince” and has a value of 5. It isn’t responsible for displaying text saying: “When you play this card…”. So, I add a signal to the Card so that when the player hovers over it it will say “I’m a ‘Prince’”. The Table is responsible for showing all text to the player, so when it receives the signal it will do a look up of what to display for a “Prince” and print it on the screen. This keeps each object responsible for its own things and allows other features to be added much more easily.

After having the hover behavior, I decided I wanted to animate playing cards to the table. This slows down the action in the game and lets the player know what they or their opponent did. Implementing this feature led to a very funny bug where after you played a card the opponent started throwing every card in the deck at you until there were none left.

First attempt at animations led to my opponent flinging cards at me

Now what happened here? I was actually misusing signals and firing a signal too often. When the opponent got dealt a card it would emit an event saying “I am playing this card” with the card type. When the table saw this event it would trigger an animation; would remove the card from the opponent’s hand; and would deal a new card to the next player in the turn order. However, I mistakenly had the opponent check each frame if it should play a card, instead of just when it got dealt a new card. This meant that by the time the card animation finished there had been 60 events saying play a card from the opponent (30 Frames per second and 2 seconds of animation). That meant that now 30 turns had passed and each player was holding 30 cards.

The eagle eyed might notice that after the player plays a 3 their hand shifts from a 2 to a 4. That’s because of the player receiving their new cards. But why did the cards start curving towards the player instead of getting added to the pile on the right? This was from using the number of turns to determine where a card should get placed on the right side. Because a turn was happening every frame that meant that the length of that card pile was way off the screen and made it look like the cards were getting thrown at the player as they tried to quickly reach the end of that huge pile. A much better way (and what I shifted to) was just to look at the number of cards and then make the next card get played 20px lower so you can see the feed of cards.

Correct behavior after fixing the bug

Once I had dealing and playing cards figured out it was time to implement the abilities of all the cards. I actually made a couple changes from my initial implementation list as I went. I had the Guard implemented as #3, but I realized that because you need to guess a card when you play it, I needed to figure out a new UI element for that. So the Guard actually became one of the last cards I implemented, but that is why having things plotted out ahead of time can be helpful. Because it meant I could add, remove, or reorder tasks as I needed.

Once I had the game mechanically sound I had a couple of other features I wanted to add. Such as the number of players. I mentioned before that I started with just 2 players as an MVP, but Love Letter supports up to 4 players. I always intended to make the game support up to 4 players in order to “finish” this project, but it was important to start simpler. Luckily even though I built the game with just two players I still built it with the level of scale it would need to grow into in mind. This meant I built out the opponent as its own object, so adding more was super straight forward. It just meant figuring out where I wanted them to live in the UI and everything kinda just worked!

Final version of the game with 4 players a few turns in

At the end of this I am loving working in Godot and I already have a new idea for my next project. I think I will talk a bit about my brainstorming process for it instead of having a new game for y’all to play, but until next time!

Check out the current build of the game here and the code here and hopefully I’ll have some other small games to write about soon.

--

--

Max Novak

Software Engineer that likes to futz about with games