Inspiration

I took the inspiration to make this game by going back to the fact that when I had my first contact with video games it was from the Atari, SEGA, Nintendo NES, N64, and Super Nintendo consoles. Reminding me of good titles such as Sonic, Mario, Zelda, Contra, Mortal Kombat, and many others. Also, now that I want to show people that making video games is an art at the same time. Two fields to which I feel attracted to continue developing video games on different platforms.

What it does

The player is presented with a series of coins in the playing area, which must be collected by a wizard. For this, the magician must take different paths trying to avoid any collision with the thorns that are in motion. Also, if he touches the ground where there are some flames the magician will immediately die. To beat the game, the player must manage to collect all the coins that are in the entire window.

Every time the wizard dies, a message appears to the player telling him that he failed to beat the challenge and that he must try again. For this, he must click the reset button again to start a new game.

Whenever the game starts, the wizard appears in the air so the player must be careful to press a directional arrow or the space bar on the keyboard if they want to avoid the character dying. Otherwise, he has to restart the game by clicking the reset button.

How we built it

Different elements were used to create the video game:

It has a background image that helps give the game an atmosphere. It has a start menu that is made up of two buttons: the first button is to load the game and the second button is to quit the game immediately.

#Creating Buttons for the Main Menu
start_button = Button(screen_width // 2 - 350, screen_height // 2, start_img)
exit_button = Button(screen_width // 2 + 350, screen_height // 2, exit_img)

Different images were selected to build the scene. Among these, we can mention: platforms, coins, thorns, buttons, and gravity. The game also has sounds such as the start of the game, when the character walks, when the magician takes a coin, jumping sound every time the user presses the space bar on the keyboard to move the character around the game area. It has collisions between the enemy objects and the main character, if there is a collision then it's game over. Appearing to the player a message and a reset button.

Regarding the technical aspects of video programming, we have the following:

The installation of pygame was done on the work computer. It was verified that there was a version of python installed on the computer. Visual Studio Code was used for programming.

Once the main file of the video game was created, the following was done

Import the necessary libraries to work with Pygame

import pygame
import sys
import os
from pygame.locals import *
from pygame import mixer

Define the dimension of the window in WIDTH and HEIGHT that will be used to present the video game. In this case, the dimension used is 1800 x 1000

screen_width = 1800
screen_height = 1000
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("Fantasy Pygame 8 Bits")

4 global variables were declared to be used to control operations within the source code.

tile_size = 50
game_over = 0
main_menu = True
score = 0

In order to know where to place the elements that will be part of the game area, a special function was created that draws a grid taking into account the dimensions of the window.

def draw_grid():
    for line in range(0, 40):
        pygame.draw.line(screen, (255, 255, 255), (0, line * tile_size), (screen_width, line * tile_size))
        pygame.draw.line(screen, (255, 255, 255), (line * tile_size, 0), (line * tile_size, screen_height))

The control of the character of the video game is done by means of the keyboard. In this case, to control its movement, use the directional keys --> to go to the right and <-- to go to the left. With the spacebar, you control the jump of the character. If the player presses the space bar several times in a row then continuous jumps are generated providing a kind of gravity or the character can fly on the Y axis of the window. And by doing the combination of the directional keys plus the space bar then you can reach the coins that are in different positions in the player window.

if game_over == 0:
            #When the user get keypresses
            key = pygame.key.get_pressed()
            if key[pygame.K_SPACE] and self.jumped == False:
                jump_fx.play()
                self.vel_y = -15
                self.jumped = True
            if key[pygame.K_SPACE] == False:
                self.jumped = False
            if key[pygame.K_LEFT]:
                dx -= 3 #If we increase the number, the sprite will move "more fast"
                self.counter += 1
                self.direction = -1
            if key[pygame.K_RIGHT]:
                dx += 3 #If we increase the number, the sprite will move "more fast"
                self.counter += 1
                self.direction = 1
            if key[pygame.K_LEFT] == False and key[pygame.K_RIGHT] == False:
                self.counter = 0
                self.index = 0
                if self.direction == 1:
                    self.image = self.images_right[self.index]
                if self.direction == -1:
                    self.image = self.images_left[self.index]

To control the animation of the character, a class named Player was created. In it, an arrangement of images is loaded that in this case will provide the effect that it walks when the directional keys are pressed from the keyboard.

class Player():
    def __init__(self, x, y):
        self.reset_game(x, y)

Code snippet that controls character animation

#Handling animation into the character
            if self.counter > walk_nice:
                self.counter = 0
                self.index += 1
            if self.index >= len(self.images_right):
                    self.index = 0
            if self.direction == 1:
                    self.image = self.images_right[self.index]
            if self.direction == -1:
                    self.image = self.images_left[self.index]

To put the elements that are part of the game in the player's window, a class called World was created. This class is in charge of containing everything that is displayed to the user.

class World():
    def __init__(self, data):
        self.tile_list = []

Part of some images that are loaded into the World class to be displayed in the main window of the video game.

#Loading images to be put it into the world
        dirt_img = pygame.image.load('img/dirt.png')
        dirt1_img = pygame.image.load('img/dirt1.png')
        dirt2_img = pygame.image.load('img/dirt2.png')
        ground_img = pygame.image.load('img/ground0.png')
        ground1_img = pygame.image.load('img/ground1.png')
....

These images were placed using a grid as a reference. For this, use was made of tile and Sprite which can be used within Pygame. Each image is randomly assigned an integer number that must be within an array-type list that basically represents the position of the image that we want to place within our window for the game.

                 if tile == 4:
                    spike = Enemy(col_count * tile_size, row_count * tile_size - 15)
                    spike_group.add(spike)
                if tile == 100:
                    spike_invert = Enemy_invert(col_count * tile_size, row_count * tile_size - 15)
                    spike_invert_group.add(spike_invert)
                if tile == 101:
                    spike_invert_y = Enemy_invert_y(col_count * tile_size, row_count * tile_size -15, 0, 1)
                    spike_invert_y_group.add(spike_invert_y)
                if tile == 102:
                    spike_invert_y_opposite = Enemy_invert_y_opposite(col_count * tile_size, row_count * tile_size - 15, 0, 1)
                    spike_invert_y_opposite_group.add(spike_invert_y_opposite)

The class that contains the numbers that represent the grid is named world_data and contains the following information

world_data = [
[0, 0, 0, 100, 0, 0, 0, 100, 0, 0, 9, 9, 9, 0, 0, 100, 0, 9, 9, 0, 100, 0, 9, 9, 0, 100, 0, 0, 9, 9, 0, 100, 0, 0, 0, 0], 
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 9, 4, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 102], 
[101, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 2, 2, 3, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
[0, 0, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 100, 100, 100, 0, 0, 0, 0, 0, 0, 0, 44, 45, 46, 0, 0, 0, 9, 9, 9, 9, 9, 0, 0], 
[0, 0, 9, 0, 0, 44, 45, 46, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 333, 334, 335, 336, 337, 0, 0, 102], 
[0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 776, 9, 0, 0, 0, 0], 
[0, 0, 9, 0, 0, 0, 0, 0, 0, 29, 9, 9, 9, 9, 30, 0, 0, 0, 0, 0, 9, 9, 9, 0, 0, 0, 0, 0, 0, 9, 776, 9, 9, 0, 0, 0], 
[101, 1, 2, 3, 0, 0, 0, 0, 0, 29, 776, 776, 776, 776, 30, 0, 0, 0, 0, 333, 334, 335, 336, 337, 0, 4, 4, 4, 0, 0, 776, 776, 776, 0, 0, 102], 
[0, 0, 776, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 776, 9, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0], 
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 776, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
[0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 776, 776, 776, 9, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 102], 
[0, 9, 0, 0, 0, 0, 0, 1, 2, 2, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 1,2,3, 0, 0, 0, 0, 0, 0, 0, 0], 
[101, 2, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0], 
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 9, 9, 29, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 776, 776, 776, 0, 0], 
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 776, 0, 28, 0], 
[0, 9, 0, 0, 9, 0, 0, 0, 0, 4, 9, 0, 4, 0, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 776, 776, 776, 776], 
[9, 29, 9, 9, 29, 9, 9, 1, 2, 2, 2, 2, 2, 2, 3, 8, 8, 8, 1, 2, 3, 9, 9, 9, 333, 334, 335, 336, 337, 9, 9, 9, 0, 0, 0, 0], 
[8, 8, 8, 8, 8, 8, 8, 775, 776, 776, 776, 776, 776, 776, 776, 776, 776, 776, 777, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8],
]

If the character suffers a collision with some enemy element located in the player's window, then a button appears with the possibility of restarting the game. The function that controls this part is the following

def reset_game(self, x, y):
        self.images_right = []
        self.images_left = []
        self.index = 0
        self.counter = 0
        for num in range(1, 24):            
            img_RIGHT = pygame.image.load(f'img/hero/hero{num}.png')
            img_RIGHT = pygame.transform.scale(img_RIGHT, (60, 90))
            img_LEFT = pygame.transform.flip(img_RIGHT, True, False)
            self.images_right.append(img_RIGHT)
            self.images_left.append(img_LEFT)

        self.image = self.images_right[self.index]
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y
        self.width = self.image.get_width()
        self.height = self.image.get_height()    
        self.vel_y = 0
        self.jumped = False
        self.direction = 0

This is the code fragment that validates when the character had a collision with an enemy within the game area. A message appears to the user plus an image of the character that he represents who died and a button to restart the game also appears.

#Checking for collision with the enemies in the game
            if pygame.sprite.spritecollide(self, spike_group, False):
                game_over = -1
            #Play sound when the player died
                game_over_fx.play()

            if pygame.sprite.spritecollide(self, spike_invert_y_group, False):
                game_over = -1
            #Play sound when the player died
                game_over_fx.play()

            if pygame.sprite.spritecollide(self, spike_invert_group, False):      
                game_over = -1
            #Play sound when the player died
                game_over_fx.play()

            #Checking for collision with the fire
            if pygame.sprite.spritecollide(self, dang_group, False):
                game_over = -1
            #Play sound when the player died
                game_over_fx.play() 


            #For update the player coordinates
            self.rect.x += dx
            self.rect.y += dy

        elif game_over == -1:

            for num in range(1, 15):
                self.dead_image = pygame.image.load(f'img/hero_death/hero_death{num}.png')
                #dead_image = pygame.transform.scale(dead_image, (60, 90))
                self.image = self.dead_image


            draw_text('You cant beat the Pygame Challenge. Try again..!', font, blue, (screen_width // 2) - 700, screen_height // 2 )


            if self.rect.y > 50:
                    self.rect.y -= 1


        #Draw the player onto images
        screen.blit(self.image, self.rect)
        #pygame.draw.rect(screen, (255, 255, 255), self.rect, 1)

        return game_over

This is a code snippet that allows us to work with the Sprite type classes provided by Pygame to add elements to the game window. Here we present some sprite groups that were created

spike_group = pygame.sprite.Group()
spike_invert_group = pygame.sprite.Group()
spike_invert_y_group = pygame.sprite.Group()
spike_invert_y_opposite_group = pygame.sprite.Group()
dang_group = pygame.sprite.Group()
coin_group = pygame.sprite.Group()

Then those Sprite groups should be drawn in the window. This is a code snippet that performs that action.

        spike_group.draw(screen)
        spike_invert_group.draw(screen)
        spike_invert_y_group.draw(screen)
        spike_invert_y_opposite_group.draw(screen)
        dang_group.draw(screen)
        coin_group.draw(screen)
        world.draw()

A button named "Credits" was put in the upper right corner. As its name implies, it displays the name of the person who created the game. When the user presses the button by clicking on it, a message appears with the name of the developer. In this case, the name of Danny Chavez appears. This message lasts for 3 seconds on the screen and then fades away to give the player the chance to continue the game. The code snippet that does this is as follows:

# create the credit button rectangle
        credit_button_width = 100
        credit_button_height = 50
        credit_button = pygame.Rect(screen_width - credit_button_width - 10, 10, credit_button_width, credit_button_height)

    # set up the font for displaying the message
        font = pygame.font.SysFont('Arial', 30)

    # draw the credit button
        pygame.draw.rect(screen, (0, 255, 0), credit_button)
        credit_message = font.render('Credits', True, (255, 255, 255))
        credit_message_rect = credit_message.get_rect(center=credit_button.center)
        screen.blit(credit_message, credit_message_rect)    

    # check for mouse clicks on the credit button
    mouse_pos = pygame.mouse.get_pos()
    mouse_clicked = pygame.mouse.get_pressed()
    if credit_button.collidepoint(mouse_pos) and mouse_clicked[0]:
        # display the credit message
        message1 = font.render('This game was created by Danny Chavez', True, (0, 0, 0))
        message2 = font.render('using Pygame.', True, (0, 0, 0))
        message1_rect = message1.get_rect(center=(screen_width/2, screen_height/2-20))
        message2_rect = message2.get_rect(center=(screen_width/2, screen_height/2+20))
        screen.blit(message1, message1_rect)
        screen.blit(message2, message2_rect)
        pygame.display.update()
        pygame.time.wait(3000) # wait for 3 seconds before clearing the message
        screen.fill((0, 0, 0)) # clear the screen

    # draw the credit button again (in case it was covered by the credit message)
    pygame.draw.rect(screen, (95, 150, 179), credit_button)
    screen.blit(credit_message, credit_message_rect)

And finally the game loop part looks as follows

run = True

while run:

    clock.tick(FPS)

    screen.blit(bg_img, (0, 0))

    if main_menu == True:
        if exit_button.draw():
            run = False
        if start_button.draw():
            main_menu = False
    else:

        if game_over == 0:
            spike_group.update()
            spike_invert_group.update()
            spike_invert_y_group.update()
            #Update the score of the player
            #Checking if a coin has been collected
            if pygame.sprite.spritecollide(player, coin_group, True):
                score += 1
                coin_fx.play()

            draw_text(' ' + str(score), font_score, white, tile_size - 10, 10)

        spike_group.draw(screen)
        spike_invert_group.draw(screen)
        spike_invert_y_group.draw(screen)
        spike_invert_y_opposite_group.draw(screen)
        dang_group.draw(screen)
        coin_group.draw(screen)
        world.draw()

        draw_grid()    

        game_over = player.update(game_over)


        #If the player has died, which I expected happen :=)
        if game_over == -1:
            if restart_button.draw():
                player = Player(100, screen_height - 500)
                game_over = 0
                score = 0

        #If the player completed the level
        if game_over == 1:
            draw_text('YOU ARE THE MASTER OF THE PYGAME CHALLENGE', font, blue, (screen_width // 2) - 140, screen_height // 2)




    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False

    # create the credit button rectangle
        credit_button_width = 100
        credit_button_height = 50
        credit_button = pygame.Rect(screen_width - credit_button_width - 10, 10, credit_button_width, credit_button_height)

    # set up the font for displaying the message
        font = pygame.font.SysFont('Arial', 30)

    # draw the credit button
        pygame.draw.rect(screen, (0, 255, 0), credit_button)
        credit_message = font.render('Credits', True, (255, 255, 255))
        credit_message_rect = credit_message.get_rect(center=credit_button.center)
        screen.blit(credit_message, credit_message_rect)    

    # check for mouse clicks on the credit button
    mouse_pos = pygame.mouse.get_pos()
    mouse_clicked = pygame.mouse.get_pressed()
    if credit_button.collidepoint(mouse_pos) and mouse_clicked[0]:
        # display the credit message
        message1 = font.render('This game was created by Danny Chavez', True, (0, 0, 0))
        message2 = font.render('using Pygame.', True, (0, 0, 0))
        message1_rect = message1.get_rect(center=(screen_width/2, screen_height/2-20))
        message2_rect = message2.get_rect(center=(screen_width/2, screen_height/2+20))
        screen.blit(message1, message1_rect)
        screen.blit(message2, message2_rect)
        pygame.display.update()
        pygame.time.wait(3000) # wait for 3 seconds before clearing the message
        screen.fill((0, 0, 0)) # clear the screen

    # draw the credit button again (in case it was covered by the credit message)
    pygame.draw.rect(screen, (95, 150, 179), credit_button)
    screen.blit(credit_message, credit_message_rect)

    pygame.display.update()

pygame.quit()

Challenges we ran into

Detect when the character has a collision with an enemy within the area of the game window.

Creation of Sprite to provide a better development of the algorithm.

Read about which sound file is best to put in a game.

When I put the background image, it covered all the elements that I already had inside the grid. So I had to reorder all the source code to follow the drawing sequence in the game window.

In the beginning, I thought that different windows could be created to guide the player. I had to rethink how to present the user with the menu option to then load the game.

Accomplishments that we're proud of

Create a 2D game with a moving character inspired by the movement of the Flappy Birds character.

Design a simple game mechanism, easy to understand and entertaining for the player

Put certain elements in the video game window taking some games from the 80s and 90s as inspiration. For example, the sounds are inspired by the time

Create the game installer using the tool called pyinstaller

What we learned

How to design a window for a video game using pygame

How to create classes and functions to control the elements and the character of the video game.

How to present a 2D figure that contains an animation by using Sprite

How to create groups of Sprite

How to define keyboard input or reads to be read by pygame source code

How to add sound inside pygame. Because a game without sound is a boring game.

How to define or draw a grid in a 2D view that serves as a reference to place elements with which the player will interact.

Add images within the defined window that will contain the video game.

Define collisions to provide a better user experience.

Create an executable using pyinstaller that allows me to distribute my game to my friends for testing on their computers

What's next for Fantasy Game

Create a series of levels for the game. Maybe 5 levels to start

Create an AI to introduce new enemies

Distribute my game to my acquaintances, family and friends so they can try it out. I could also share it with gaming communities on different sites such as Reddit, itch, twitch, Twitter and others.

Incorporate new object animations.

Incorporate new challenges for the player

During the time that I was dedicating the development of this video game, I came across the information that you can create video game controllers using Arduino. And since pygame has the option to add a function that controls a joystick then it would make a version of the game available that has a game mode with a joystick. This would be a great update.

Incorporate the use of a camera within the game window.

Define if I want it to be a 2D-Scrolling type game or to be completely level by level.

Maybe I would add a timer so that a level is completed in a certain time.

Built With

Share this project:

Updates

posted an update

This is game was tested only using Windows as OS. The game has not been tested using IOS or Linux. If you can not see the full screen you need to change the resolution size dimensions of your screen/monitor. Otherwise, you can not see where the character of the game starts. To control the character you only need to use the arrow keys LEFT and RIGHT and to jump use only SPACEBAR. For a jump like gravity, you can repeatedly press SPACEBAR in order to reach the platform.

Log in or sign up for Devpost to join the conversation.