Skip to content

Instantly share code, notes, and snippets.

@ectrimble20
Created May 24, 2020 02:13
Show Gist options
  • Save ectrimble20/066c23599c7aee19d412632810f0481e to your computer and use it in GitHub Desktop.
Save ectrimble20/066c23599c7aee19d412632810f0481e to your computer and use it in GitHub Desktop.
alient_invasion_revision
class Entity(object):
def __init__(self, game_ref):
self.game = game_ref
def update(self, dt):
raise NotImplemented
class Ship(Entity):
def __init__(self, game_ref):
super().__init__(game_ref)
self.image_key = 'player'
self.rect = self.image.get_rect().copy() # always make a copy when you're going to mess with a rect
self.speed = 150
self.move_right = False
self.move_left = False
self.fire = False
self.fire_counter = 0
self.fire_interval = 0.15 # how fast we can shoot in seconds
self.abs_x = 0.0
# now we can position everything
self.rect.midbottom = self.game.screen.get_rect().midbottom
self.abs_x = float(self.rect.x)
# and lastly we make our 'last position' rect
self.last_position = self.rect.copy()
# just a flag to note if we're still alive
self.alive = True
@property
def image(self):
return self.game.images[self.image_key]
def update(self, dt):
# this logic can mostly stay the same
if self.move_right and self.rect.right < self.game.screen.get_rect().right:
self.abs_x += self.speed * dt
if self.move_left and self.rect.left > 0:
self.abs_x -= self.speed * dt
self.last_position = self.rect.copy()
self.rect.x = int(self.abs_x)
# handle pewpew here
# increment our fire counter, note that there's no need to update the counter if it's past the interval
# since it will trigger when fire is activated
if self.fire_counter < self.fire_interval:
self.fire_counter += dt
# if 'fire' and the counter is past our interval, we can shoot
if self.fire and self.fire_counter > self.fire_interval:
# inject a new bullet into the entities
self.game.entities.append(Bullet(self.game, self))
self.fire_counter = 0.0 # reset our counter
class Bullet(Entity):
def __init__(self, game_ref, parent):
super().__init__(game_ref)
self.image_key = 'bullet'
self.parent = parent # should be a ship
self.rect = self.image.get_rect().copy() # always make a copy when you're going to mess with a rect
self.speed = 350
self.abs_y = 0.0
# we want this to be positioned at the center of the ship and in front, we can adjust this as needed
self.rect.midbottom = self.parent.rect.midtop
self.abs_y = float(self.rect.y)
# and lastly we make our 'last position' rect
self.last_position = self.rect.copy()
self.alive = True
@property
def image(self):
return self.game.images[self.image_key]
def update(self, dt):
# all we care about right now, is if we've exited the screen
if self.rect.bottom < 0:
self.alive = False
if self.alive:
self.abs_y -= self.speed * dt
self.last_position = self.rect.copy()
self.rect.y = int(self.abs_y)
"""
This is the main game controller class, I kept the name for simplicity.
"""
import pygame
from alien_invasion_revision.settings import *
from alien_invasion_revision.entities import Ship
class AlienInvasion(object):
def __init__(self):
# we don't init pygame here, we do that prior to this being launched, we come into this object presuming
# that PyGame has been initialized, so first we need the display, since Surfaces don't function until the
# display is initialized
self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
# we can initialize our images here, so we can ensure they don't try to load prior to our screen being setup
self.images = {
'player': pygame.image.load(os.path.join(IMAGES_PE_DIR, 'playerShip2_blue.png')).convert_alpha(),
'bullet': pygame.image.load(os.path.join(IMAGES_PE_DIR, 'laserBlue07.png')).convert_alpha(),
'background': pygame.image.load(os.path.join(IMAGES_BG_DIR, 'Background.png')).convert_alpha()
}
# I didn't know what square image was being used for, so I didn't load it
# we also don't pass around a settings object, it's easier, and a bit clearer to just have a settings module
# that can be imported wherever, this is just a personal preference really.
# we don't need to blit the BG here either, nor do we run flip, that only needs to happen when we're ready to
# draw the screen to the buffer, we can set the caption here, that isn't super important, but I'm going to set
# it below so I can add a FPS meter to it
self.player = Ship(self) # we can initialize the player here, that's perfectly fine
self.clock = pygame.time.Clock() # the clock and time delta as before
self.time_delta = 0.016
# well use an entity manager here, in this case, just a simple list. You can use Sprite.Group if you'd prefer
# I never use Sprites so I always just roll my own. This list will just contain entities in the world, we'll
# go ahead and add the player to this.
self.entities = []
self.entities.append(self.player)
# this is the control flag, it'll be used by the game loop, this is for cleaner shutdowns then using sys.exit
self.running = True
# these are a couple helpers for our event checks, we have a tuple (immuntable and cheaper on memory) of keys
self._move_left_keys = (pygame.K_a, pygame.K_LEFT)
self._move_right_keys = (pygame.K_d, pygame.K_RIGHT)
# joining two tuples can be done with concatenation
self.move_keys = self._move_left_keys + self._move_right_keys
def run_game(self):
# the game loop will be about the same, with some tweaks to names
# draw our main bg first
self.screen.blit(self.images['background'], (0, 0))
while self.running:
self.process_events()
self.handle_logic()
self.handle_render()
# update the time-step
self.time_delta = self.clock.tick(60) / 1000
# let's set our caption here
pygame.display.set_caption(f"Alien Invasion - FPS: {int(self.clock.get_fps())} - Entities: {len(self.entities)}")
# refresh the screen here
pygame.display.flip()
def process_events(self):
for event in pygame.event.get():
# handle quit events and K_q (key Q)
if event.type == pygame.QUIT or (event.type == pygame.KEYUP and event.key == pygame.K_q):
self.running = False
# handle movement keys, bundling them into tuples isn't required, but IMO makes it more read-able
# key-pressed
if event.type == pygame.KEYDOWN and event.key in self.move_keys:
if event.key in self._move_left_keys:
self.player.move_left = True
if event.key in self._move_right_keys:
self.player.move_right = True
# handle pewpew
if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
self.player.fire = True
# key-released
if event.type == pygame.KEYUP and event.key in self.move_keys:
if event.key in self._move_left_keys:
self.player.move_left = False
if event.key in self._move_right_keys:
self.player.move_right = False
# handle pewpew
if event.type == pygame.KEYUP and event.key == pygame.K_SPACE:
self.player.fire = False
def handle_logic(self):
dead_entities = [] # handle entities that can go away
for entity in self.entities:
entity.update(self.time_delta)
if not entity.alive:
dead_entities.append(entity)
# we handle it like this because you never want to modify a list while you're iterating it
if dead_entities:
for entity in dead_entities:
self.entities.remove(entity)
def handle_render(self):
# There's some issues with how this is being done, we should have to draw our entity unless it moved, but that's
# a challenge for another day, this handles clearing their position and rendering them separate, this is in case
# entities overlap, if we cleared then drew, any entities below (rendered before) would be cleared and we'd get
# some weird looking images.
# walk the entities, clear their last position
for entity in self.entities:
self.screen.blit(self.images['background'], entity.last_position, entity.last_position)
# walk them one more time, this time drawing the entities
for entity in self.entities:
self.screen.blit(entity.image, entity.rect)
import pygame
from alien_invasion_revision.game import AlienInvasion
pygame.init()
game = AlienInvasion()
game.run_game()
pygame.quit()
Okay, so basically just copy down the four files and then just copy the same image directory you were using before and it should work
I put this in a subdirectory in a testing project I use for messing with reddit code, so you'll need to change the imports if you
put the files your base directory, so it'd be:
from entities import *
instead of
from alien_invasion_revision.entities import *
Adding whatever directory structure you might use in front of entities.
import os
# Note we don't keep any images here, just configuration options
SCREEN_WIDTH = 1200
SCREEN_HEIGHT = 800
BG_COLOR = (230, 230, 230)
BULLET_COLOR = (0, 0, 255)
# we'll setup our directory structure here because this file is in the top level of our project
ROOT_DIR = os.path.dirname(__file__)
IMAGES_DIR = os.path.join(ROOT_DIR, 'images')
IMAGES_BG_DIR = os.path.join(IMAGES_DIR, 'background')
IMAGES_PE_DIR = os.path.join(IMAGES_DIR, 'player-enemies')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment