Mufaddal's Final Project Reflection

by mchakerautexas

08 Dec 2022

Final Project Reflection - Astroman

Introduction

My purpose for taking this class was two fold: To gain an essential skill that would help me in my career, and to learn programming so that I could develop games. Being a frequent video game player, I have always been fascinated by the skill and nuance that goes into creating games, and have wanted to learn how to make some of my own someday. So, I chose to build a game for my final project. The game I built is based of the same concept as Flappy Bird: A revolutionary game that changed the landscape of mobile gaming. It is a skill based game, where the task is to keep the bird afloat and not hit any poles on its endless journey. The concept was so enthralling, that they had the developer of the game take it down because it was generating overwhelming amounts of revenue. I wanted to create something similar, but not exactly the same, so I went with Astroman. The idea behind my game is to create a setting with an astronout floating in space, trying to avoid asteroids as he speeds faster into the abyss. I thought that this design was simple enough to be able to pull of, while also having enough to do, to keep me occupied while also letting me put the skills I gained this semester to test.

The Game

The idea for the game is pretty simple, creating an endless forward moving astronaut, and have asteroids be thrown at them with random speeds and random directions: As long as it is headed on a collision course, it would make the game challenging. I chose not to put any collectibles like coins in the game, as there was nothing the player could do with those coins, and also it would add extra complexity without making the game more enjoyable. The score then, would just increase proportiante to the amount of time you can survive in game. I also set up checkpoints within the game, that would increase the difficulty of the game exponentially the more you survive. With this initial design ready, I decided to embark on the journey for creating this game.

The Process

Right off the bat, I had no idea how games work (from a development point of view). I felt like I had set out on a mission without a plan. The easy way out would be to cheat, as apparently, this is not the most unique idea out there, but I wanted to test my intuition and challenge myself, so I limited myself to the number of code examples I would see online. I did feel like I had to understand the key concepts of game development to create something like this though, so I made a simpler game before instead. For my Turtle Game assignment, I created air hockey. It was a simple enough game, with the player capable of controlling two paddles, and have to prevent the puck from exiting from the two vertical ends of the screen. I did end up successfully creating a game, but I made some key mistakes in doing so, here are my main takeaways from it:

  • Games need to draw the same things over and over super fast (ideally upto 60 frames per second). To do this, everything needs to be an endless while loop, and the things that are being drawn need to be updated in terms of their position and shape, to give the effect of animation and interaction.
  • Using turtle to draw each one of the frames above, over and over causes weird stuttering. This ‘refresh’ as it is called in the game dev lingo, is not what turtle is made for. For my air hockey game I drew everything on the screen with a turtle, including the actors, actionable items and non-actionable items as well, which made in every iteration of my loop, I had to call turtle.clear() which led to a blinking effect in my game.
  • Advanced mouse controls like drag events seemed not to work for me, and I didn’t spend much more time debugging the issue, so I chose to restrict myself to keyboard type events to interact with the game.
  • The air hockey game I created used native shapes drawn in Turtle. This made the development process cubersome, it would have been cool if I could use images to draw stuff rather than have to make them with turtle (Spoiler alert, that’s exactly what I did).

Based on the learnings above, I made sure to:

  • Design the game keeping in mind the infinite loops. Designing modules that have functions that can be called over and over to draw key elements on the screen.
  • DO NOT DRAW everything. Instead use turtles themselves as actors. It is much easier to control a turtle (they already move like video game characters), and they don’t flicker when the loop updates over and over.
  • Leave out mouse controls. I never got around to figuring that out. Keyboard controls only.
  • Later in class we covered how to draw images in turtle. I used that knowledge extensively, and did not chose to draw a single native shape. Everything images.

With the above learnings, I set out to create my game.

Milestones

I settled on the following milestones for the game development process:

Milestone Status
Create a functional menu, with selectable menu items. Done
Create a shape for the actual astronaut, and have them animate flying up and down in the menu. Done
Create shapes for obstacles and background landscape objects(extra!) Discarded
Implement the start game, and have the person just float up and down based on the user input. Done
Implement obstacles populating on the screen|Done| |Implement motion. Done
Implement object collision Done
Implement scoring Done
Implement the high score section Done
Display score and game over splash Done
Implement saving scores to a file Done
Add increasing difficulty levels. Done

Challenges

As with any project (and especially software engineering ones), one comes accross many challenges. Here are some of the ones I want to call out:

Animating Motion

I had a hard time wrapping my head around the concept of having the player move. I kept thinking in the direction of wanting my player to move forward, but then also have the screen move forward with it with the same speed? But then how would anyone see any motion? The lightbulb moment was to have a background moving back instead. It gives the good illusion of the character moving forward. Relative motion!

Okay, but how do you infinitely scroll a backgroud backwards?

From one hurdle onto another, once I had figured out I had to scroll the background backwards, I got stuck on actually figuring out how to do it. The first problem was that (as far as I know), you could only draw images by making them shapes of turtles. To draw a background image (even a static one), I would have to set it to a turtles shape. As it turns out, a you cannot draw over turtle shapes, which meant if I had to use an image as a background, I could not draw anything over it. Then I remembered that turtles have a .stamp() function. So I just hid the turtle and called .stamp() every iteration of my while loop. This made a background appear which I could draw on. But how do I scroll it backwards? To do that, I created two copies of the background image, and had them scroll backwards one after the other. Once the first one scrolled out of the screen, I would place it in front of the second one again. This would go on and one, with the images getting swapped out everytime they go off screen. This created an endless scrolling background.

Changing game screens

The next thing was not a challenge, but was a big pain to deal with in terms of code organization. A game has many screens or scenes, and I needed a way to be able to switch off some components and swap them with other components. I came up with a nifty solution for that: Make each screen a Class of its own, with functions of the same name in each module. This ‘update’ function would be called from the main module, and depending on which module it is being called from, it would swap out the screens.

Game difficulty

Another thing that I struggled with was to figure out how to spawn the asteroids and increase difficulty. I experimented with many schemes, and settled on having the asteroids come from the top and bottom of the screen, at random angles gravitating towards the player. The difficulty of the game would then be scaled with the velocity of these asteroids. The faster the asteroids, the harder to avoid them, and the more difficult it becomes to survive.

Code Organization

As touched on above, I organized the code using Classes. Each screen of the game (Menu, Game, High Score Screen) were made of classes, that had the ‘Update’ function exposed. Then, it was as simple as just calling the update function of the correct module to display the screen.

Example of a Module:

class GameOver:
  astronaut_sprite = 'astronaut_small.png'
  background = 'bg.png'
  
  def __init__(self, screen, change_scene, score, high_score):
    self.screen = screen
    self.change_scene = change_scene
    self.score = score
    self.high_score = high_score
    
    self.bgs = [turtle.Turtle(), turtle.Turtle()]
    self.t = turtle.Turtle()
    self.pointer = turtle.Turtle()
    self.bgpos = (0, 0)
    
    self.bgwidth = 1000
    self.bgmove = 0
    for bg in self.bgs:
      bg.shape(self.background)
      bg.setpos(self.bgpos)
      bg.seth(90)
      bg.speed(0)
      bg.hideturtle()
    
    self.pointer.shape(self.astronaut_sprite)
    self.pointer.seth(90)
    self.pointer.setpos(-5, 100)
    self.pointer.penup()
    
    self.t.hideturtle()
    self.t.color('white')
    self.t.speed(0)
    
    screen.onkey(self.return_to_menu, "Return")

  def write_at_pos(self, text, pos):
    pos[0] -= len(text) * 2.5
    self.t.penup()
    self.t.setpos(pos[0], pos[1])
    self.t.pendown()
    self.t.write(text)
    self.t.penup()

  def draw_menu(self):
    self.t.clear()
    gameOverText = ""
    if(self.score == self.high_score[0]):
      gameOverText = "YOU SET A NEW HIGHSCORE!!! : " + str(self.score)
    else:
      gameOverText = "Game over, you scored :" + str(self.score)
    self.write_at_pos(gameOverText, [0, 10])
    self.write_at_pos("High Score " + str(self.high_score[0]), [0, -10])
    self.write_at_pos("Press Enter to Restart", [0, -30])

  def return_to_menu(self):
    self.change_scene('Menu')
  
  def update(self):
    speed = 10
    if(self.bgmove >= self.bgwidth):
      self.bgpos = (0, self.bgpos[1])
      self.bgmove = 0
      self.bgs = [self.bgs[1], self.bgs[0]]
    self.bgpos = (self.bgpos[0] - speed, self.bgpos[1])
    self.bgmove += speed
    self.bgs[0].setpos(self.bgpos)
    self.bgs[1].setpos(self.bgpos[0] + self.bgwidth, self.bgpos[1])
    for bg in self.bgs:
      bg.stamp()
    self.draw_menu()

I also passed in the change_scene function from main so that each screen could switch to a different scene. This is how the modules are then used:

def change_scene(which_scene, update_score=0):
  global scene, high_score
  screen.reset()
  del scene[0]

  if(update_score > high_score[0]):
    write_to_file(text = update_score)
    high_score[0] = update_score
  
  if which_scene == 'Game':
    scene.append(Game(screen, change_scene))
  if which_scene == 'Menu':
    scene.append(Menu(screen, change_scene, high_score))
  if which_scene == 'GameOver':
    scene.append(GameOver(screen, change_scene, update_score, high_score))
  
scene = [Menu(screen, change_scene, high_score)]

while True:
  if(len(scene) > 0):
    scene[0].update()

The Final Product

Final Thoughts

I can say with a lot of confidence that I am a better professional after having taken this course. The knowledge I gained, not just about coding, but about organizing and reflecting will surely help me further down the line. I have a newfound passion ignited within me to write software, especially games. No matter how frustrating this project was, it taught me valuable lessons, and I will always have this game with me. I have spent a considerable amount of time playing my own game, and it was awesome to see my friends try it out and actually struggle with it’s difficulty, while enjoying the gameplay. From here, it’s nowhere but up. I will continue to code, write better software and hope to be a capable software engineer someday!

Here's a little about mchakerautexas Find mchakerautexas on Twitter, Github, and on the web.