My first Godot game: FlyTrap - let me know what you think

Hey all, I’m new here :slight_smile: I’ve been following Godot from a distance for a few years, and I finally had an opportunity to make a game with it, so I jumped on it. [Edit: I used Godot 4.2.1]

It’s free on the iOS and Android store. Steam and itch.io coming sometime soon.

If you just want to see it, I’ve got some videos here:
https://www.youtube.com/playlist?list=PLlLSzBng4c2xGwWDp0EQo6Dq9yaXPTJjX

… and if you want to play it (I would love that), grab it from one of these links:

Now for the backstory … I allowed my Google developer account to sit mostly dormant for too many years, so Google notified me that if I don’t publish a product within 30 days, my account would be closed. “Challenge accepted!”, I thought. So I immediately planned out a project I was sure I could achieve in the time I had. And that’s how “FlyTrap” was born. I got it done in 27 days. Not 27 actual days though. I am fully committed to my day job, and like most of us, I have many family-related responsibilities, haha. Anyway, so I spent my spare time here and there over a 27-day period getting it done and submitted to Google. Success! Account saved. A little while later I submitted an update with some polish items I didn’t have time for. Then I submitted it to Apple a short time later with all the polish items of course included. So, that’s how it all came to be.
I have Google to thank, for motivating me to release another game. Until FlyTrap I hadn’t released something since 2020. It was about time!

For this project I was learning Godot as I went. On a technical level, I have not even scratched the surface of what Godot can do. I’m sure that there are a ton of things I did the hard way, because I didn’t know about all the built-in mechanisms that provide out-of-the-box functionality equivalent to what I was building. Having said that, if you’re curious about how I achieved any of the things you see in the game, I would be happy to share. Just let me know!

I think Godot is awesome, and I intend to delve deeper into it.

Cheers,
Paul Glinker

4 Likes

Nice work, that looks absolutely awesome, and you clearly were highly productive during your spare time for 27 days. Shudder to think how much you produce during your working hours…

Coming to the end of my own first Godot project, I think it’s natural to do things so they work the first time, learning how to do them better, faster, cheaper the next time.

For me I stayed away from AnimationPlayer for a long time, only to realise I’d made things so much harder on myself. Is there one particular thing you learned while cranking out FlyTrap?

1 Like

Hey Ubiguchi, thank you, I appreciate the feedback!

The one that stands out the most in my mind is the tweening system. I was writing functions like this …

func ease_in_quad(x:float):
	return x*x

func ease_out_quad(x:float):
	return 1.0 - (1.0 - x) * (1.0 - x)

… and using the delta to decrement timers for manual tweening calculations.
When I stumbled upon the tweening system I started to do all of the non-physics-based movement with it, but still I looked for ways to integrate my own curve functions into the built-in tweening, because getting the curves I wanted from the built-in tweening was proving difficult. Then, I found this cheat sheet!


… I’m using Godot 4.2.1 but the cheat sheet still applies. Here’s the link where I found it…
https://www.reddit.com/r/godot/comments/frqzup/godot_tweening_cheat_sheet/
… with the cheat sheet in hand, I found I could do everything I wanted with the built-in curves :sunglasses:

Next, I built on my use of the tweener by learning to chain tweens (so I didn’t have to write my own code for that either, sweet!), loop tweens, and trigger callbacks from tweens. That last one was really handy with the pipes. When they are doing that chomping motion they play a sound at the right moment because of a callback that is received from the tweener. For example …

func setChompMode():
	if tween != null:
		tween.stop()
		tween = null;
	tween = create_tween() 
	tween.set_parallel(true)
	tween.tween_property($lowerPipeNode, "position:y", calc_lower_pipe_y()+chomp_dist, chomp_duration*0.5).set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_QUAD)
	tween.tween_property($upperPipeNode, "position:y", calc_upper_pipe_y()-chomp_dist, chomp_duration*0.5).set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_QUAD)
	tween.chain().tween_property($lowerPipeNode, "position:y", calc_lower_pipe_y(), chomp_duration*0.5).set_ease(Tween.EASE_IN).set_trans(Tween.TRANS_QUART)
	tween.tween_property($upperPipeNode, "position:y", calc_upper_pipe_y(), chomp_duration*0.5).set_ease(Tween.EASE_IN).set_trans(Tween.TRANS_QUART)
	tween.chain().tween_callback(on_pipe_chomped)
	tween.set_loops()

func on_pipe_chomped():
	$sndChomp.play(0)

Another thing I might have used more extensively if I had to start over would be the signals.
There are some things I did before knowing about signals, that I didn’t bother to change. For example, when I want the screen to fade to black I do this…

var root = get_tree().root.get_child(0)
var fader = root.get_node("fader")
fader.fade_to_black()

… which I would guess might make some of you cringe, haha. When I stumbled upon the signal stuff, it was when I was working on adding points for things happening in the game. I started with this type of scenario …

emit_signal("enemy_killed", 200, position)
emit_signal("pipe_passed", score, position)

… and later reworked the main menu to use them to talk to the global game state…

func _btn_newgame():
	emit_signal("sig_newgame")

func _btn_instructions():
	emit_signal("sig_instructions")
	
func _btn_leaderboard():
	emit_signal("sig_leaderboard")

func _btn_credits():
	emit_signal("sig_credits")

A reworked version of the fader would likely be invoked by something simple like emit_signal("fadeToBlack").

Anyway, I guess I rambled on a bit there. Hopefully that wasn’t excessive detail :slight_smile:

That’s really interesting, as it was the tweening system that kept me away from the AnimationPlayer for so long.

Having previously written my own tweening system before landing on Godot, I ended up using it a lot, and created a lot of complex tweens with numerous callbacks and the ilk, but so much so that I convinced myself you never really needed the AnimationPlayer.

However, when I got to cutscenes I realised I needed a graphical tool to play with the timings, and as soon as I’d got AnimationPlayer working I realised you can over indulge with tweens.

And getting signals right makes a big difference to the overall code tidiness, which then helps speed up changing code. I luckily stumbled on the GDQuest event bus article which gave a great starting point.

Anyway, you’ve made an awesome little game while hitting those hurdles, so I’m very keen to see what you come up with next.

1 Like

Thanks for sharing about the AnimationPlayer … that will definitely come in handy at some point :slightly_smiling_face: (probably sooner than later)

If anyone is curious about how I kept myself on track under the given time constraints (deadline of 30 days, start to ship), my process was as follows:

  • make a list of every feature I want in the game
  • do a rough sort of all the items in order of importance/impact (for each feature, ask myself, “if I could only do one more of the remaining features, which one would bring the most value to the player”)
  • when I implement a feature, I don’t move on until it is solid. I do not (intentionally) leave lingering bugs.
  • every time I’m ready to move on to another task, I quickly re-assess the order of the remaining items, in case my opinion has changed
  • if I decide I’m not doing a task, I leave it in the list with a special mark, so it doesn’t find its way back on the list due to memory lapse
  • as I work through the list, I insert any new ideas (inspiration-driven) for features that would have significant impact. this can lead to scope creep, but, it’s all ordered in the list by impact.
  • when I got a couple of days from the deadline (which I had set at 27 days to give me some safety buffer), I stopped implementing features and switched to making screenshots etc. as required by the app store. these things always take me longer than I think they will, so I’m glad I gave myself lots of time.

At the point where I stopped, there were still a lot of features and polish items on my list that did not make it into the game, so I put those into the first update. A pretty significant one was the leaderboard (local and online). The backend for the online leaderboard was already built for a previous game release so there was nothing to do there. To build the UI for it in Godot took me as long as the entire game took to build up to that point (just under a month). It shows you local scores, the top global scores, and the 29 scores closest to your global rank. The little icon beside each online score tells you what kind of device it was submitted by.

One of the gotchas I encountered post-release on iOS was that some older devices (specifically 7th-gen iPads) could not run the game. They got a message about an unsupported GPU feature. I then set out to re-release in compatibility mode. My GPU particle systems caused instantaneous crashes in compatibility mode, so I remade them as CPU particles and that did the trick. That version just went live this evening on iOS and on Google Play (the fastest set of approvals I’ve ever had … was about an hour for each store).

Anyway, that’s probably enough out of me. I’ll end it by saying Godot is pretty awesome, and I plan to stick with it for the long term.

Oh, and here’s a copy of my actual TODO list.

Cheers!

Todo:

[Initial Launch]
- [x] create full game logic loop
  - [x] determine all the application states to flow between. create array of names.
  - [x] add variable to track app state and function to switch to next state
  - [x] create the conditions that trigger each state transition and call nextState() from each.
- [x] add minor semblance of start screen (title, and press SPACE to start)
- [x] add some semblance of game over screen (game over message, score, and Press SPACE to continue)
- [x] add pipe manager for doing pipe sequence. make sure it's deterministic, based on seeded random number generator.
- [x] add score sensor to pipes, which increments score.
- [x] add enemy birds
- [x] add giant saw blades
- [x] add center obstacles (hexagons)
- [x] replace graphics
  - [x] pipe
  - [x] sky
  - [x] mountains
  - [x] trees
  - [x] ground
  - [x] bird
- [x] add full-screen fader
  - [x] from solid white to transparent
  - [x] from solid black to transparent
- [x] animate main menu logo into view
- [x] animate in-game hud into view
- [x] animate gameover logo into view
- [x] score popups
- [x] particle effects on player death
- [x] particle effects on enemy death
- [x] particle effects on orbs/coins
- [x] fix upward force sensitivity
- [x] app-store assets
  - [x] marketting copy
  - [x] icons
  - [x] screenshots
  - [x] video
- [x] upload and set live


[1st Update (polish items)]
- [x] internet permission flag for Android
- [x] pipes slam closed when you pass through
- [x] add pipe movement styles
  - // [ ] stationary
  - // [ ] shifting
  - [x] chomping
- [x] sawblades take more space as time goes by
- [x] sawblades animate up and down (not all of them. randomly chosen)
- [x] instructions screen
- [x] 3-2-1-GO! start sequence
- [x] more orbs!
  - [x] arc around sawblades
  - [x] line of them leading straight across, through pipe opening
  - [x] circle of them around enemy bird
  - [x] above and below the moving hexagons, like chevrons
- [x] leaderboard post and get flow
  - [x] save name to local config
  - [x] get and register, for first time launch with internet
  - [x] get with existing leaderboard id
  - [x] post score with leaderboard id
- [x] add high scores screen
  - [x] local best
  - [x] global top
  - [x] global around me
- [x] add initials input screen
- [x] fix leaderboard rank
- [x] fix leaderboard 'near' you
- [x] Leaderboard polish
  - [x] "your scores", "global top scores", "your global rank"
  - [x] gameover screen has to hide "tap to continue" message when initials input shows.
  - [x] button to reset local scores
  - [x] initials input shows "ENTER INITIALS FOR THE LEADERBOARD"
  - [x] initials input transition in
  - [x] initials input transition out
  - [x] leaderboard transition in from initials
  - [x] leaderboard transition in from main menu
  - [x] leaderboard transition out from having submitted a score
  - [x] leaderboard transition out from having viewed scores from main menu
  - [x] sounds for every button press
    - [x] initials character scrolling
    - [x] initials done button
    - [x] leaderboard close button
    - [x] leaderboard escape key
- [x] main menu
  - [x] new game
  - [x] leaderboard
  - [x] instructions?
  - [x] credits (click Glinkie Games Logo)
  - [x] audio blip for navigation and selection
- [x] credits screen
- [x] instructions screen

[2nd Update (compatibility mode + cluster scoring)]
- [x] enable compatibility mode for rendering (to support older mobile devices)
  - [x] test on 7th gen iPad
  - [x] test on Android
  - [x] remake all GPU particle systems as CPU particle systems, to eliminate crash
- [x] implement cluster scoring for the cluster types
  - [x] ring around enemy bird
  - [x] ring around sawblade
  - [x] chevrons around hexagon
  - [x] line through pipe

1 Like

That insight on how to organize the workflow is very interesting. As a noob gamedev, I’ll definitely try to implement a similar system for my future projects.

Oh, by the way, I have been tryharding the game for 15 minutes and got 7195 points (top 20 in the leaderboard), it was a funny experience :saluting_face:

1 Like

@wender Nice, I see your score in the leaderboard. Pretty good for 15 minutes :sunglasses: It’s a tough game.

I’m glad you found the workflow method interesting. It works well when you have a pretty strong idea of what you want (e.g. production phase of development).

In more exploratory phases of development it’s harder to follow a strategy like that because you’re trying new ideas and seeing where they take you, although you can use a similar approach at a more granular level to break down goals for a specific idea/experiment. You stop working on a particular experiment when you feel it is a success, or there is nothing left to be gained (rather than when you hit a deadline … although you could give yourself a deadline for each experiment of course, which is not a bad idea really).

In this particular case I had to skip the exploratory phase entirely, so I conjured everything in my head first, and then began writing out that list, expanding it here and there as I went along.

1 Like