[Pygame] Organizing Code

[Pygame] Organizing Code

Postby metulburr » Fri May 10, 2013 4:46 pm

This may be more organizing code rather than pygame specfic.

I was trying to organize the code a little better. Mainly removing so much random code in Control.update(), in which i moved a section of it out and created Player.fire() for it. However i was trying to removve the section of enemy fire out of Control.update(), but then i thought that wouldnt make sense to be in Enemy(), as that is each enemy, and the enemies list is a list of those objects. So now i am not sure where to put the enemies_list check for fire> It seems horrible just plopped in Contorl.updataE(). I really just had it there to just get the program layed out.

Also i was thinking should i have all the drawing in one Contorl() method, or each class draw its own object, upon the method executing?

So i guess i cant really shorten up Contorl.update() any more really, but it just seems so "all over the place", not right, sort of.

Code: Select all
import pygame
import sys
import math
import random

def image_from_url(url):
    try:
        from urllib2 import urlopen
        from cStringIO import StringIO as inout
    except ImportError:
        from urllib.request import urlopen
        from io import BytesIO as inout
    myurl = urlopen(url)
    return inout(myurl.read())

class Projectile:
    limit = 3
    def __init__(self, loc, color):
        self.surf = pygame.Surface((5,10)).convert()
        self.surf.set_colorkey((255,0,255))
        self.surf.fill(color)
        self.rect = self.surf.get_rect()
        self.rect.center = loc
        self.mask = pygame.mask.from_surface(self.surf)
        self.speed = 8

class Enemy:
    count = 3
    def __init__(self, start_loc, orig_image):
        self.score_value = 100
        self.orig_image = orig_image
        self.orig_image = pygame.transform.scale(self.orig_image, (40,80))
        self.orig_image.set_colorkey((255,0,255))
        self.image = pygame.transform.rotate(self.orig_image, 180)
        self.mask = pygame.mask.from_surface(self.image)
        self.rect = self.image.get_rect()
        self.rect.center = start_loc
        self.bullets = []
        self.speed = 2
        self.is_hit = False
        self.range_to_fire_degree = 15 # degrees to fire, if player below
        self.timestamp = pygame.time.get_ticks()
        self.distance_above_player = random.randint(100,300) #enemy to go above player to shootat player
        self.bullet_color = (255,0,0)
       
    def pos_towards_player(self, player_rect):
        '''get new coords towards player'''
        c = math.sqrt((player_rect.x - self.rect[0]) ** 2 + (player_rect.y - self.distance_above_player  - self.rect[1]) ** 2)
        try:
            x = (player_rect.x - self.rect[0]) / c
            y = ((player_rect.y - self.distance_above_player)  - self.rect[1]) / c
        except ZeroDivisionError:
            return False
        return (x,y)
       
    def update(self, player):
        if player.active:
            if not self.is_hit:
                #move enemy towards player
                new_pos = self.pos_towards_player(player.rect)
                if new_pos: #if not ZeroDivisonError
                    self.rect.x, self.rect.y = (self.rect.x + new_pos[0] * self.speed, self.rect.y + new_pos[1] * self.speed)
       
        if self.is_in_range_to_fire_at(player): 
            diff = pygame.time.get_ticks() - self.timestamp
            if diff > 1500.0:
                self.timestamp = pygame.time.get_ticks()
                if player.active: #if player is not dead
                    self.bullets.append(Projectile(self.rect.center, self.bullet_color))
       
    def is_in_range_to_fire_at(self, player):
        '''check if player is in range to fire of enemy'''
        #if player is lower than enemy
        if player.rect.y >= self.rect.y:
            try:
                offset_x =  self.rect.x - player.rect.x
                offset_y =  self.rect.y - player.rect.y
                d = int(math.degrees(math.atan(offset_x / offset_y)))
            except ZeroDivisionError:
                #player is above enemy
                return
            #if player is within 15 degrees lower of enemy
            if math.fabs(d) <= self.range_to_fire_degree:
                return True
            else:
                return False

class Player:
    def __init__(self, start_loc, orig_image):
        self.score = 0
        self.damage = 10
        self.active = True #check for end of game
        self.bullet_color = (255,255,255)
        self.speed = 4

        self.orig_image = pygame.transform.scale(orig_image, (40,80))
        self.orig_image.set_colorkey((255,0,255))

        self.image = pygame.transform.rotate(self.orig_image, 180)
        self.mask = pygame.mask.from_surface(self.image)
        self.rect = self.image.get_rect()
        self.rect.center = start_loc

        self.bullets = []

    def move(self, x, y):
        if self.active:
            self.rect[0] += x * self.speed
            self.rect[1] += y * self.speed
           
    def fire(self, screen, enemies):
        '''blit player bullet
        check if bullet hits enemy and adjust score accordingly
        '''
        if self.bullets:
            for obj in self.bullets[:]:
                screen.blit(obj.surf, obj.rect)
                obj.rect[1] -= obj.speed

                #did obj move off screen
                if obj.rect[1] < 0:
                    self.bullets.remove(obj)
               
                for enem in enemies[:]:
                    #did obj hit enemy rect
                    if obj.rect.colliderect(enem.rect):
                        offset_x =  enem.rect.x - obj.rect.x
                        offset_y =  enem.rect.y - obj.rect.y
                        #did object hit enemy mask
                        if obj.mask.overlap(enem.mask, (offset_x, offset_y)):
                            self.bullets.remove(obj)
                            enem.is_hit = True
                            enemies.remove(enem)
                            self.score += enem.score_value
                            break
           
    def update(self, screenrect, screen, enemies):
        #make sure rect does not go outside of screen
        self.rect.clamp_ip(screenrect)
       
        #blit,check for hit, player firing
        self.fire(screen, enemies)
        '''is this actually removing objects from the enemies'''
       
        #check for player death
        if self.damage < 0:
            self.damage = 0
            self.active = False

class Control:
    def __init__(self):
        pygame.init()
        self.screensize = (800,600)
        self.screen = pygame.display.set_mode(self.screensize)
       
        enemy_link = 'http://i1297.photobucket.com/albums/ag23/metulburr/spaceship_enemy3_zpscfeb13bf.png'
        self.enemy_image = pygame.image.load(image_from_url(enemy_link)).convert()
        player_link = 'http://i1297.photobucket.com/albums/ag23/metulburr/spaceship2_zps095c332a.png'
        self.player_image = pygame.image.load(image_from_url(player_link)).convert()

        self.screenrect = self.screen.get_rect()
        self.clock = pygame.time.Clock()
        self.player = Player((0,600), self.player_image)
        self.score_loss_per_shot = 25
        self.enemies = []

        self.mainloop()
       
    def write(self, displaytext, color=(0,0,0), size=15, ul=False, bold=False,
            ital=False, font='timesnewroman'):
        font = pygame.font.SysFont(font, size)
        font.set_underline(ul)
        font.set_bold(bold)
        font.set_italic(ital)
        label = font.render(displaytext, 1, color)
        label_rect = label.get_rect()
        return label,label_rect

    def mainloop(self):
        run = True
        while run:
            self.screen.fill((0,0,0))

            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    run = False
                elif event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_SPACE:
                        if len(self.player.bullets) <= Projectile.limit:
                            if self.player.active:
                                self.player.score -= self.score_loss_per_shot
                                self.player.bullets.append(Projectile(self.player.rect.center, self.player.bullet_color))

            self.update()
            pygame.display.flip()
            self.clock.tick(60)

    def update(self):
        print(len(self.enemies))
        self.keys = pygame.key.get_pressed()
       
        self.player.update(self.screenrect, self.screen, self.enemies)
       
        if len(self.enemies) <= Enemy.count:
            self.enemies.append(Enemy((random.randint(1,800),-100), self.enemy_image))

        for enem in self.enemies:
            if enem.bullets:
                for obj in enem.bullets[:]:
                    self.screen.blit(obj.surf, obj.rect)
                    obj.rect[1] += obj.speed
                    offset_x =  obj.rect.x - self.player.rect.x
                    offset_y =  obj.rect.y - self.player.rect.y
                    #did object hit player
                    if self.player.mask.overlap(obj.mask, (offset_x, offset_y)):
                        self.player.damage -= 1
                        enem.bullets.remove(obj)
                        break

        if self.keys[pygame.K_UP]:
            self.player.move(0,-1)
        if self.keys[pygame.K_DOWN]:
            self.player.move(0,1)
        if self.keys[pygame.K_RIGHT]:
            self.player.move(1,0)
        if self.keys[pygame.K_LEFT]:
            self.player.move(-1,0)
       
        for obj in self.enemies:
            obj.update(self.player)
            if not obj.is_hit:
                self.screen.blit(obj.image, obj.rect)
           
            #if player collides with enemy
            offset_x =  obj.rect.x - self.player.rect.x
            offset_y =  obj.rect.y - self.player.rect.y
            if self.player.mask.overlap(obj.mask, (offset_x, offset_y)):
                obj.is_hit = True
                self.player.damage -= 1
                self.player.score -= obj.score_value
                self.enemies.remove(obj)
                break


        self.screen.blit(self.player.image, self.player.rect)
        scoreboard = self.write('Score: {}'.format(self.player.score), color=(255,255,255), size=30)
        self.screen.blit(scoreboard[0], (10,10))

        if self.player.active:
            lifeboard = self.write('Damage: {}'.format(self.player.damage), color=(255,255,255), size=30)
            self.screen.blit(lifeboard[0], (self.screensize[0] - 150, 10))
        else:
            game_over = self.write('GAME OVER', color=(255,255,255), size=50)
            game_over[1].center = (self.screensize[0] // 2, self.screensize[1] // 2)
            self.screen.blit(game_over[0], game_over[1])
       

app = Control()
pygame.quit()
sys.exit()
New Users, Read This
version Python 3.3.2 and 2.7.5, tkinter 8.5, pyqt 4.8.4, pygame 1.9.2 pre
OS Ubuntu 14.04, Arch Linux, Gentoo, Windows 7/8
https://github.com/metulburr
User avatar
metulburr
 
Posts: 1103
Joined: Thu Feb 07, 2013 4:47 pm
Location: Elmira, NY

Re: [Pygame] Organizing Code

Postby Mekire » Sat May 11, 2013 2:40 am

Think about the game on larger scale. What would it have if it was complete? Well probably a title, a menu, a game phase, and maybe a highscore list. So these would be states. The current one you are working on would be the game state obviously. You want to create a class for this phase of the game. Your lists of enemies or obstacle information would be kept track of there. I recently rewrote this guys game to try to show this concept.

This is what the Control class looked like:
Code: Select all
import pygame as pg
import os,sys

from . import setup as su
from .states import splash,title,game

class Control:
    def __init__(self):
        self.screen = pg.display.get_surface()
        self.done = False
        self.Clock = pg.time.Clock()
        self.fps = 50

        self.state_dict = {"SPLASH":splash.Splash(),
                           "TITLE" :title.Title(),
                           "GAME"  :game.Game()}

        self.state_name = "SPLASH"
        self.State = self.state_dict[self.state_name]

    def update(self):
        """If our current state has finished, change state.  Then update
        current state. All states have a function called update()."""
        if self.State.done:
            self.state_name = self.State.next
            self.State.__init__()
            self.State = self.state_dict[self.state_name]
        self.State.update(self.screen)

    def event_loop(self):
        """This should be the only place in the entire game where you see:
        for event in pg.event.get():. This is THE event loop, not A event loop.
        Events are instead passed to the current state."""
        for event in pg.event.get():
            if event.type == pg.QUIT:
                self.done = True
            elif event.type == pg.KEYDOWN:
                if event.key == pg.K_ESCAPE:
                    if self.state_name in ("SPLASH","TITLE"):
                        self.done = True
            #Pass the event along to the current state.
            self.State.get_event(event)

    def main(self):
        """This should be the only place in the entire program where you see
        display.update() (except for the moment the display was initialized).
        This should also be the only place you ever see pygame.Clock.tick()"""
        while not self.done:
            self.event_loop()
            self.update()
            pg.display.update()
            self.Clock.tick(self.fps)
        pg.quit();sys.exit()
I think the above structure is pretty general so you should be able to use it almost as is.

If you want to take a look at the organization of the project in general, take a look at this (just keep in mind that I didn't rewrite most of the game logic; I just changed what was necessary to restructure it into this paradigm).
http://code.google.com/p/lunar-panda-restructuring/downloads/list
Also if you read the readme I included with it it details why I changed certain things.

-Mek
User avatar
Mekire
 
Posts: 816
Joined: Thu Feb 07, 2013 11:33 pm
Location: Amakusa, Japan


Return to Game Development

Who is online

Users browsing this forum: Google [Bot] and 2 guests