Aug-14-2019, 09:45 AM
import pygame from pygame import QUIT from sys import exit from time import time from random import randint, choice class Entity: """A structure that holds the properties of an entity""" def __init__(self, rect, x, y, x_sign, y_sign): """Initialize entity properties""" # Collision rectangle with integers self.rect = rect # x coordinate in floating point self.x = x # y coordinate in floating point self.y = y # x and y direction signs self.x_sign = x_sign self.y_sign = y_sign class Model: """Rectangles move about on the screen bouncing off the screen edges and other rectangles""" def __init__(self, screen, number_of_entities=25, speed=150.0): """Initialize state, populate model with entities""" self.screen = screen self.screen_width = screen.get_rect().width self.screen_height = screen.get_rect().height self.image = pygame.image.load('images/entity.png').convert_alpha() self.speed = speed self.entities = [] for _ in range(number_of_entities): self.entities.append(self.add_entity()) def add_entity(self): """Return a new entity object with randomized properties""" # Repeat loop until there is no collision with an existing entity done = False while not done: rect = self.image.get_rect() # rect object x and y coordinates must be integers # so floating point values must be stored separately # for the purpose of variable frame rate rect.x = randint(0, self.screen_width - rect.width) rect.y = randint(0, self.screen_height - rect.height) collided = False for entity in self.entities: if entity.rect.colliderect(rect): collided = True break if not collided: done = True # x and y coordinates must be floating point for variable # frame rates. Otherwise the delta will truncate to 0 each cycle x = float(rect.x) y = float(rect.y) # Choose a random direction, in x and y axis x_sign = choice([-1, 1]) y_sign = choice([-1, 1]) return Entity(rect, x, y, x_sign, y_sign) def update(self, delta): """Apply the model's logic to go from one state to the next""" # delta is the time elapsed and multiplying it by speed # gives the amount of pixels travelled delta = self.speed * delta for entity in self.entities: # Check to see if entity contacts a screen edge and # if so reverse direction in the respective axis if (entity.rect.left - delta < 0) or \ (entity.rect.right + delta > self.screen_width) or \ (entity.rect.top - delta < 0) or \ (entity.rect.bottom + delta > self.screen_height): if (entity.rect.left - delta < 0): entity.rect.left = 1 entity.x_sign = 1 elif (entity.rect.right + delta > self.screen_width): entity.rect.right = self.screen_width - 1 entity.x_sign = -1 if (entity.rect.top - delta < 0): entity.rect.top = 1 entity.y_sign = 1 elif (entity.rect.bottom + delta > self.screen_height): entity.rect.bottom = self.screen_height - 1 entity.y_sign = -1 else: # Entity did not hit edge so test a potential move potential_move = pygame.Rect.copy(entity.rect) potential_x = (entity.x + (delta * entity.x_sign)) potential_y = (entity.y + (delta * entity.y_sign)) # rect x and y must be integers potential_move.x = int(potential_x) potential_move.y = int(potential_y) commit_move = True # Test for collision between potential move and all # existing entities for other_entity in self.entities: if not (entity is other_entity): if potential_move.colliderect(other_entity.rect): # There is a collision commit_move = False # Reverse directions of both entities entity.x_sign *= -1 entity.y_sign *= -1 other_entity.x_sign *= -1 other_entity.y_sign *= -1 if commit_move: # No collisions detected so commit potential move entity.rect = potential_move entity.x = potential_x entity.y = potential_y def render(self): """Render the entity image to all entity rectangles""" for entity in self.entities: self.screen.blit(self.image, entity.rect) class Controller: """Controller for a model""" def __init__(self): """Initialize pygame, load resources, and create the model""" pygame.init() pygame.display.set_caption('Entities') pygame.mouse.set_visible(0) self.screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN) self.background = pygame.transform.smoothscale( pygame.image.load('images/background.jpg').convert(), ( self.screen.get_rect().width, self.screen.get_rect().height)) self.model = Model(self.screen) def run_controller(self): """Main loop. Uses variable frame rates. The elapsed time between each iteration of the loop is passed to rendering methods""" delta_now = time() while True: delta_last, delta_now = delta_now, time() elapsed_time = delta_now - delta_last self.check_events() self.model.update(elapsed_time) self.update_screen() def check_events(self): """Handle the window close event and keyboard commands""" for event in pygame.event.get(): if event.type == QUIT: # Window close icon clicked exit() elif event.type == pygame.KEYDOWN: # A key was pressed if event.key == pygame.K_ESCAPE: # Quit exit() self.check_keydown_events(event) def check_keydown_events(self, event): """Control the model through keyboard commands""" if event.key == pygame.K_UP: # Increase speed self.model.speed += 50.0 if self.model.speed > 500.0: self.model.speed = 500.0 elif event.key == pygame.K_DOWN: # Decrease speed self.model.speed -= 50.0 if self.model.speed < 0.0: self.model.speed = 0.0 elif event.key == pygame.K_LEFT: # Delete an entity if len(self.model.entities) > 0: del self.model.entities[0] elif event.key == pygame.K_RIGHT: # Add a new entity self.model.entities.append(self.model.add_entity()) def update_screen(self): """Composite the background image with the model rendering""" self.screen.blit(self.background, self.screen.get_rect()) self.model.render() # Flip the new image to the graphical display pygame.display.flip() if __name__ == '__main__': # Create an instance of a Controller and call its entry point controller = Controller() controller.run_controller()This code bounces rectangles off of each other and the screen edges. Substitute your own entity and background images as you see fit.