Table of Contents

  1. Overview
  2. Preqreusits, Downloading stuff
  3. Create new project
  4. Create new Scene
  5. Create Bird
  6. Create Ground
  7. Linking Bird and Ground to Scene
  8. Create Pipes
  9. Linking Pipes and Ground
  10. Changing Draw Order and Score
  11. Do top and ground checks
  12. Game Over
  13. Final Scripts (TO UNLEASH YOUR ULTIMATE TECHNIQUES)
  14. YIIPEEEE!!1!11!!!!

Finished Project

Flappy Bull finished

Download Godot

Click Here to Dowload Godot

Download our Assets

Click here to Download our Assets

Run the correct Godot

Run the exe

newproject

newproject

Resize Game Window (Width: 864, Height: 936)

Resize Game Window

Create new Scene with Node (NOT Node2D) and a child with Sprite2D

Scene Setup 2

Drag background image to Sprite2D and add offset (X: 432, Y: 384)

Scene Setup 3

Create new Scene (name it Bird)

Add Character Body2D

Animate the Sprite, and Adjust the Collision shape

Bird Setup 1

In AnimationSprite2D add new Animation and Drag bird to animator
(if using Rocky, adjust scale by 1.618)

Bird Setup 2

Add Capsule Colider to bird

Bird Setup 3

Create a new Script

extends CharacterBody2D

# Global Variables
const GRAVITY: int = 1800
const MAX_VEL: int = 600
const START_POS = Vector2(100,400)
const FLAP_SPEED: int = -500
var flying: bool = false
var falling: bool = false


func _ready():
	reset()
	
func reset():
	falling = false
	flying = false
	position = START_POS
	set_rotation(0)

# Function that runs while game is running (Game loop)	
func _physics_process(delta):
	if flying or falling:
		velocity.y += GRAVITY * delta
		
		if velocity.y > MAX_VEL:
			velocity.y = MAX_VEL
		if flying: 
			set_rotation (deg_to_rad(velocity.y * 0.05))
			$AnimatedSprite2D.play()
		elif falling:
			set_rotation(PI/2)
			$AnimatedSprite2D.stop()
		move_and_collide(velocity * delta)
	else:
		$AnimatedSprite2D.stop()

func flap():
	velocity.y = FLAP_SPEED

Adding Area2D node

Add Sprite and collision shape

Ground setup 1

Offset X by 864, Position Transform Y by 852

Ground setup 2

Add Rectangular Collision

Ground setup 3

Script for Ground (ground.gd)

extends Area2D

signal hit

func _on_body_entered(body):
	hit.emit()

Linking Bird to Main

Link Bird to Main

Linking Ground to Main

Link Bird to Main

Coding main.gd

extends Node

var game_running: bool
var game_over: bool
var scroll
var score
const SCROLL_SPEED: int = 4
var screen_size: Vector2i
var ground_height: int
var pipes: Array
const PIPE_DELAY: int = 100
const PIPE_RANGE: int = 200


# Called when the node enters the scene tree for the first time.
func _ready():
	screen_size = get_window().size
	ground_height = $Ground.get_node("Sprite2D").texture.get_height()
	new_game()

func new_game():
	game_running = false
	game_over = false
	score = 0
	scroll = 0
	$Bird.reset()
	
func _input(event):
	if (game_over) == false:
		if event is InputEventMouseButton:
			if event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
				if game_running == false:
					start_game()
				else:
					if $Bird.flying:
						$Bird.flap()
					
func start_game():
	game_running = true
	$Bird.flying = true
	$Bird.flap()

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
	if game_running:
		scroll += SCROLL_SPEED
		
		if scroll >= screen_size.x:
			scroll = 0
			
		$Ground.position.x = -scroll
		

Add two Sprite2D and four Collision2D

Pipe Setup 1

Add Image to upper Sprite2D, and offset by -380 and Flip Vertical

Pipe Setup 2

Add Image to lower Sprite2D, and offset by 380

Pipe Setup 3

Set collisions to Rectangles and place them over pipes

Pipe Setup 4

Add Score Area (HAS TO BE CALLED "Score" TO USE SCRIPT BELOW)

Pipe Setup 5

Script for Pipes (MAKE SURE IT'S CALLED "Score") (pipe.gd)

extends Area2D

signal hit
signal scored

func _on_body_entered(body):
	hit.emit()

func _on_score_body_entered(body):
	scored.emit()

Add new Timer to main scene, rename it "PipeTimer" and change delay to 1.5 seconds

Pipe Timer 1

Create a new variable and reference the Pipe scene

Pipe Timer 2

Modifying main.gd

Changes at:

extends Node

@export var pipe_scene:PackedScene

var game_running: bool
var game_over: bool
var scroll
var score
const SCROLL_SPEED: int = 4
var screen_size: Vector2i
var ground_height: int
var pipes: Array
const PIPE_DELAY: int = 100
const PIPE_RANGE: int = 200

func scored():
	pass
	#score += 1
	#$ScoreLabel.text = "SCORE: " + str(score)

# Called when the node enters the scene tree for the first time.
func _ready():
	screen_size = get_window().size
	ground_height = $Ground.get_node("Sprite2D").texture.get_height()
	new_game()

func new_game():
	game_running = false
	game_over = false
	score = 0
	scroll = 0
	$Bird.reset()
	# NEW CODE HERE
	generate_pipes()
	
func _input(event):
	if (game_over) == false:
		if event is InputEventMouseButton:
			if event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
				if game_running == false:
					start_game()
				else:
					if $Bird.flying:
						$Bird.flap()
					
func start_game():
	game_running = true
	$Bird.flying = true
	$Bird.flap()
	# NEW CODE HERE
	$PipeTimer.start()

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
	if game_running:
		scroll += SCROLL_SPEED
		
		if scroll >= screen_size.x:
			scroll = 0
			
		$Ground.position.x = -scroll
		
		for pipe in pipes:
			pipe.position.x -= SCROLL_SPEED
			

func stop_game():
	$PipeTimer.stop()
	$Bird.flying = false
	$GameOver.show()
	game_running = false
	game_over = true

func bird_hit():
	$Bird.falling = true
	stop_game()

func _on_pipe_timer_timeout():
	generate_pipes()

func generate_pipes():
	var pipe = pipe_scene.instantiate()
	pipe.position.x = screen_size.x + PIPE_DELAY
	pipe.position.y = (screen_size.y - ground_height) / 2 + randi_range(-PIPE_RANGE, PIPE_RANGE)
	pipe.hit.connect(bird_hit)
	pipe.scored.connect(scored)
	add_child(pipe)		# Add new pipe object to scene main
	pipes.append(pipe)	# Add new pipe object to pipes array, to keep track of them

Changing ground draw order (Do the same to Score Label)

Draw Order

Back in main, add a Label Control Node, rename it ScoreLabel
(HAS TO BE ScoreLabel)

Score 1

Inside Control -> Theme Override -> Font Size, set it to 40, and also change Font Color to #cbbd93

Score 2

Coding (edit main.gd)

Add new function to main.gd

func check_top():
	if $Bird.position.y < 0:
		$Bird.falling = true
		stop_game()

Call the function in inputs when flapping

func _input(event):
	if (game_over) == false:
		if event is InputEventMouseButton:
			if event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
				if game_running == false:
					start_game()
				else:
					if $Bird.flying:
						$Bird.flap()
						check_top()

Adding Ground check

func _on_ground_hit():
	if $Bird.flying:
		$Bird.flap()
	$Bird.falling = true # Set to false if you dont want to let rocky drown
	stop_game()

Create new Canvas Layer Scene, add Button Child node, and Override Themes with New StyleButtonTexture

Restart Btn 1

Select the StyleButtonTexture (not the arrow) add the button Texture

Restart Btn 2

Repeat for Hover and Pressed

Restart Btn 3

Change the Transform, Control -> Layout -> Transform

Restart Btn 4

Restart Button Script

extends CanvasLayer

signal restart

func _on_restart_button_pressed():
	restart.emit()

Create new pipes group in Pipe scene

Pipes group

main.gd

extends Node

@export var pipe_scene:PackedScene

var game_running: bool
var game_over: bool
var scroll
var score
const SCROLL_SPEED: int = 4
var screen_size: Vector2i
var ground_height: int
var pipes: Array
const PIPE_DELAY: int = 100
const PIPE_RANGE: int = 200


# Called when the node enters the scene tree for the first time.
func _ready():
	screen_size = get_window().size
	ground_height = $Ground.get_node("Sprite2D").texture.get_height()
	new_game()

func new_game():
	game_running = false
	game_over = false
	score = 0
	scroll = 0
	$ScoreLabel.text = "SCORE: " + str(score)
	$Bird.reset()
	$GameOver.hide()
	get_tree().call_group("pipes", "queue_free")
	pipes.clear()
	generate_pipes()
	
func _input(event):
	if (game_over) == false:
		if event is InputEventMouseButton:
			if event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
				if game_running == false:
					start_game()
				else:
					if $Bird.flying:
						$Bird.flap()
						check_top()
					
func start_game():
	print("start game")
	game_running = true
	$Bird.flying = true
	$Bird.flap()
	$PipeTimer.start()

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
	if game_running:
		scroll += SCROLL_SPEED
		
		if scroll >= screen_size.x:
			scroll = 0
			
		$Ground.position.x = -scroll
		
		for pipe in pipes:
			pipe.position.x -= SCROLL_SPEED
			

func _on_pipe_timer_timeout():
	generate_pipes()

func generate_pipes():
	var pipe = pipe_scene.instantiate()
	pipe.position.x = screen_size.x + PIPE_DELAY
	pipe.position.y = (screen_size.y - ground_height) / 2 + randi_range(-PIPE_RANGE, PIPE_RANGE)
	pipe.hit.connect(bird_hit)
	pipe.scored.connect(scored)
	add_child(pipe)		# Add new pipe object to scene main
	pipes.append(pipe)	# Add new pipe object to pipes array, to keep track of them
	
func scored():
	score += 1
	$ScoreLabel.text = "SCORE: " + str(score)

func stop_game():
	$PipeTimer.stop()
	$Bird.flying = false
	$GameOver.show()
	game_running = false
	game_over = true

func check_top():
	if $Bird.position.y < 0:
		$Bird.falling = true
		stop_game()

func bird_hit():
	$Bird.falling = true
	stop_game()

func _on_ground_hit():
	if $Bird.flying:
		$Bird.flap()
	$Bird.falling = true # Set to false if you dont want to let rocky drown
	stop_game()

func _on_game_over_restart():
	new_game()

bird.gd

extends CharacterBody2D


const GRAVITY: int = 1800
const MAX_VEL: int = 600
const START_POS = Vector2(100,400)
const FLAP_SPEED: int = -500
var flying: bool = false
var falling: bool = false

func _ready():
	reset()
	
func reset():
	falling = false
	flying = false
	position = START_POS
	set_rotation(0)
	
func _physics_process(delta):
	if flying or falling:
		velocity.y += GRAVITY * delta
		
		if velocity.y > MAX_VEL:
			velocity.y = MAX_VEL
		if flying: 
			set_rotation (deg_to_rad(velocity.y * 0.05))
			$AnimatedSprite2D.play()
		elif falling:
			set_rotation(PI/2)
			$AnimatedSprite2D.stop()
		move_and_collide(velocity * delta)
	else:
		$AnimatedSprite2D.stop()
		
func flap():
	velocity.y = FLAP_SPEED

✅ Intro to Godot

✅ Introduction to Game Engines

❓ Checked in?!?

Soly Image Caption

Solly thanks you for checking in and wanted to thank you guys personally. But Solly made a mistake and was forced to fight a galatic death god in a form of an alligator. But as one would expect from fighting a death god, he stood no chance. Fortunately it was an alligator and Solly grew up in Australia and came to Florida so he was able to escape with his life. But in return, he arrived on last Friday missing the workshop.

After missing the workshop Solly went undercover and went to an esport event at USF and there he found something shocking. Other than how bad gamers smelled, there was food. The image below is the amount of food AFTER everyone ate, although they woould probably eat more after the event finished. But luckily there was a kind gamer girl who shared some food with Solly and he had his first meal in 3 days. So be remember to sign in so food can come to both Solly and us.

Pizza 1

Pizza 2

Pizza 3

Disapointly Solly did not make it today, for that he was only able to take 5 days off from a Triple A gaming studio per year. Thus he had left us with an gif of him waving at us.