def __init__(self, screen): super().__init__(screen) self.rect = pg.Rect(0, 0, 0, 0) self.x1 = 50 self.y1 = 50 self.x2 = 50 self.y2 = 85 self.spacing = 30 self.p1Text = TextLabel("P1", 30, "orange", self.x1 - 35, self.y1 + 4, self.screen) self.p2Text = TextLabel("P2", 30, "navy_blue", self.x2 - 35, self.y2 + 4, self.screen) self._p1HP = None self._p1MaxHP = None self._p1Lives = [] self._p2HP = None self._p2MaxHP = None self._p2Lives = [] self._audio = AudioComponent(self, isAutoPlay=False, isRepeat=True) self._audio.add("healthy", SFX_RESOURCES["menu_heartbeat_healthy"]) self._audio.add("injured", SFX_RESOURCES["menu_heartbeat_injured"]) self._audio.add("danger", SFX_RESOURCES["menu_heartbeat_danger"])
def __init__(self, screen): super().__init__(screen) fontSize = 22 fontColour = "white" x, y = 230, 155 dx, dy = 0, 50 self.backgroundSetting = _SettingsLabel("Orientación: ", ["Vertical", "Horizontal"], fontSize, fontColour, x, y, 130, screen) self.fullscreenSetting = _SettingsLabel("Full Screen: ", ["Desactivar", "Activar"], fontSize, fontColour, x, y + dy, 130, screen) self.arrow = _Arrow(x - 40, y - 10, dx, dy, 2, screen) self.escapeImage = ImageLabel(MENU_RESOURCES["assets"]["esc"], 25, 440, screen) self.escapeText = TextLabel("Esc para volver", 14, fontColour, 50, 445, screen) self.effect = FadeEffect(self.screen) self.effect.timeStartDarken = float('inf') self.effect.timeEndDarken = float('inf') self.dt = 2 background = MENU_RESOURCES["screens"]["options"][0] self.render = RenderComponent(self) self.render.add("background", background) self.render.state = "background" self.audio = AudioComponent(self, isAutoPlay=False) self.audio.add("exit", SFX_RESOURCES["menu_exit"])
def __init__(self, settingName, settingChoices, size, colour, x, y, spacing, screen): """ :param settingName: String, the name of the setting. :param settingChoices: List, containing string options choices. :param size: Integer, the size of the font. :param colour: 3-Tuple, containing the RGB values of the colour. :param x: Integer, the x-position of the text. :param y: Integer, the y-position of the text. :param spacing: Integer, the gap between the setting name and choice. :param screen: pygame.Surface, representing the screen. """ self.rect = pg.Rect(x, y, 0, 0) self.screen = screen self.index = 0 self.optionChosen = None self.name = TextLabel(settingName, size, colour, x, y, self.screen) self.options = [ TextLabel(choice, size, colour, x + spacing, y, self.screen) for choice in settingChoices ] self.audio = AudioComponent(self, isAutoPlay=False) self.audio.add("switch", SFX_RESOURCES["menu_option_switch"]) self.audio.state = "switch"
def __init__(self, scene): """ :param scene: Scene Class, representing a level. """ self.scene = scene self.audio = AudioComponent(self, isAutoPlay=False) self.audio.add("explosion", SFX_RESOURCES["cat_coop_jump"])
def __init__(self, x, y, switchNum, screen, zoneArtwork=1): """ :param x: Integer, the x-position of the wall. :param y: Integer, the y-position of the wall. :param switchNum: Integer, identifying the number of the button. :param screen: pygame.Surface, the screen to draw the wall onto. :param zoneArtwork: Integer, choosing the image based on zone. """ self.screen = screen self.num = switchNum self.isOn = True # REFACTOR: Clumsy implementation to change image of the renderer # - Causes instantiating argument to be needlessly long. # - The code below does not scale well. if zoneArtwork == 1: button = ZONE1_RESOURCES["buttons"] if zoneArtwork == 2: button = ZONE2_RESOURCES["buttons"] self.render = RenderComponent(self, enableRepeat=False) self.render.add("on", [button["switch"][0]]) self.render.add("off", button["switch"], 500) self.render.state = "on" self.rect = pg.Rect(x, y, 0, 0) self.audio = AudioComponent(self, isAutoPlay=False) self.audio.add("click", SFX_RESOURCES["scene_switch"])
def __init__(self, x, y, num, screen, zoneArtwork=1): """ :param x: Integer, the x-position of the wall. :param y: Integer, the y-position of the wall. :param num: Integer, identifying the number of the button. :param screen: pygame.Surface, the screen to draw the wall onto. :param zoneArtwork: Integer, choosing the image based on zone. """ self.screen = screen self.num = num self.switchesWaiting = [] self.isClosed = True # REFACTOR: Clumsy implementation to change image of the renderer. # - Causes instantiating argument to be needlessly long. # - The code below does not scale well. if zoneArtwork == 1: door = ZONE1_RESOURCES["doors"] if zoneArtwork == 2: door = ZONE2_RESOURCES["doors"] self.render = RenderComponent(self, enableRepeat=False) self.render.add("open", door["open"]) self.render.add("closed", door["close"]) self.render.state = "closed" self.rect = pg.Rect(x, y, 0, 0) self.audio = AudioComponent(self, isAutoPlay=False) self.audio.add("open", SFX_RESOURCES["scene_door"])
def __init__(self, screen): super().__init__(screen) self.fontSize = 50 self.fontColour = "orange" self.x = 270 self.y = 225 self.pauseText = TextLabel("Pause", self.fontSize, self.fontColour, self.x, self.y, self.screen, isItalic=True) image = MENU_RESOURCES["screens"]["fade"][0] self.render = RenderComponent(self) self.render.add("background", image) self.render.state = "background" self.audio = AudioComponent(self, isAutoPlay=False) self.audio.add("pause", SFX_RESOURCES["menu_pause"]) self.audio.state = "pause" # Need to draw once only otherwise it overrides what is already drawn # by the Scene Engine self.render.update() self.pauseText.update() self.pauseText.draw() self.render.draw()
def __init__(self, screen): super().__init__(screen) self.totalOptions = 4 self.fontSize = 22 self.fontColour = "white" self.x = 250 self.y = 155 self.dx = 0 self.dy = 38 self.option1 = TextLabel("1 Jugador", self.fontSize, self.fontColour, self.x, self.y + 1 * self.dy, self.screen) self.option2 = TextLabel("2 Jugadores", self.fontSize, self.fontColour, self.x, self.y + 2 * self.dy, self.screen) self.option3 = TextLabel("Opciones", self.fontSize, self.fontColour, self.x, self.y + 3 * self.dy, self.screen) self.option4 = TextLabel("Salir", self.fontSize, self.fontColour, self.x, self.y + 4 * self.dy, self.screen) self.title = ImageLabel(MENU_RESOURCES["assets"]["title"][0], 60, 55, self.screen) self.arrow = _Arrow(self.x - 40, self.y + 28, self.dx, self.dy, self.totalOptions, self.screen) background = MENU_RESOURCES["screens"]["main"][0] self.render = RenderComponent(self) self.render.add("background", background) self.render.state = "background" self.audio = AudioComponent(self, isAutoPlay=False) self.audio.add("enter", SFX_RESOURCES["menu_enter"])
class IntroMenu(BaseMenu): """ The intro screen of the game after the splash screen. """ def __init__(self, screen): super().__init__(screen) self.effect = FadeEffect(screen) self.effect.timeEndDarken += 1.0 ICONS = ICON_RESOURCES["assets"] self.render = RenderComponent(self) self.render.add("red", ICONS["red"][0]) self.render.add("black", ICONS["black"][0]) self.render.state = "black" w, h = ICONS["red"][0].get_size() offsetX = (settings.WIDTH - w) / 2 offsetY = (settings.HEIGHT - h) / 2 self.rect.center = (offsetX, offsetY) self.audio = AudioComponent(self, isRepeat=True, isAutoPlay=True) self.audio.add("intro_1", SFX_RESOURCES["menu_intro_1"]) self.audio.add("intro_2", SFX_RESOURCES["menu_intro_2"]) self.audio.state = "intro_1" def __str__(self): return "intro_menu" def handleEvent(self, event): if event.type == pg.KEYDOWN: if event.key == pg.K_RETURN: self._sendMessage() def update(self): self.audio.update() self.render.update() if not self.effect.isComplete: self.effect.update() if self.effect.time > self.effect.timeStartDarken + 2.0: self.render.state = "red" self.audio.state = "intro_2" else: self._sendMessage() def draw(self, camera=None): self.render.draw(camera) self.effect.draw() def _sendMessage(self): """ Sends a message to start the next menu. """ self.messageMenu("transition", "main_menu") pg.mixer.stop()
class Switch(GameObject): """ A switch entity that the player can turn on and off. """ def __init__(self, x, y, switchNum, screen, zoneArtwork=1): """ :param x: Integer, the x-position of the wall. :param y: Integer, the y-position of the wall. :param switchNum: Integer, identifying the number of the button. :param screen: pygame.Surface, the screen to draw the wall onto. :param zoneArtwork: Integer, choosing the image based on zone. """ self.screen = screen self.num = switchNum self.isOn = True # REFACTOR: Clumsy implementation to change image of the renderer # - Causes instantiating argument to be needlessly long. # - The code below does not scale well. if zoneArtwork == 1: button = ZONE1_RESOURCES["buttons"] if zoneArtwork == 2: button = ZONE2_RESOURCES["buttons"] self.render = RenderComponent(self, enableRepeat=False) self.render.add("on", [button["switch"][0]]) self.render.add("off", button["switch"], 500) self.render.state = "on" self.rect = pg.Rect(x, y, 0, 0) self.audio = AudioComponent(self, isAutoPlay=False) self.audio.add("click", SFX_RESOURCES["scene_switch"]) def __str__(self): return "switch " + str(self.num) def update(self): self.render.update() self.audio.update() def draw(self, camera=None): self.render.draw(camera) def turnOff(self): """ Changes the state of the button to off and sends out an event. """ self.isOn = False self.render.state = "off" self.audio.state = "click" self.messageScene("switch", (self.num, self.isOn))
class PauseMenu(BaseMenu): """ The pause menu of the game. """ def __init__(self, screen): super().__init__(screen) self.fontSize = 50 self.fontColour = "orange" self.x = 270 self.y = 225 self.pauseText = TextLabel("Pause", self.fontSize, self.fontColour, self.x, self.y, self.screen, isItalic=True) image = MENU_RESOURCES["screens"]["fade"][0] self.render = RenderComponent(self) self.render.add("background", image) self.render.state = "background" self.audio = AudioComponent(self, isAutoPlay=False) self.audio.add("pause", SFX_RESOURCES["menu_pause"]) self.audio.state = "pause" # Need to draw once only otherwise it overrides what is already drawn # by the Scene Engine self.render.update() self.pauseText.update() self.pauseText.draw() self.render.draw() def __str__(self): return "pause_menu" def handleEvent(self, event): if event.type == pg.KEYDOWN: if event.key == pg.K_ESCAPE: self.audio.state = "pause" def update(self): self.audio.update() def draw(self, camera=None): pass
def __init__(self, screen): super().__init__(screen) self.rect = pg.Rect(0, 0, 0, 0) self.x = 50 self.y = 50 self.dx = 30 self._HP = None self._maxHP = None self._lives = [] self._audio = AudioComponent(self, isAutoPlay=False, isRepeat=True) self._audio.add("healthy", SFX_RESOURCES["menu_heartbeat_healthy"]) self._audio.add("injured", SFX_RESOURCES["menu_heartbeat_injured"]) self._audio.add("danger", SFX_RESOURCES["menu_heartbeat_danger"])
def __init__(self, screen): super().__init__(screen) self.rect = pg.Rect(0, 0, 0, 0) telephone = CUTSCENE_RESOURCES["telephone"] self.origin = pg.time.get_ticks() # milliseconds self.elapsed = 0 # milliseconds self._isComplete = False self._isReversed = False self.render = RenderComponent(self, enableRepeat=False) self.render.add("telephone_none", telephone["none"]) self.render.add("telephone_pick", telephone["pick"], 1100) self.render.add("telephone_hold", telephone["hold"]) self.render.add("telephone_put", telephone["put"], 1100) self.audio = AudioComponent(self) self.dialogue = Dialogue(self.screen) self.dialogue.add(dialogue.TELEPHONE_1, 350, 150, "left") self.dialogue.add(dialogue.TELEPHONE_2, 350, 150, "left") speed = 1 ts = [2000, 2800, 3300, 6300, 10300, 11300, 12100, 14000] self.timings = [speed * t for t in ts]
def __init__(self, screen): super().__init__(screen) self.rect = pg.Rect(0, 0, 0, 0) office = CUTSCENE_RESOURCES["office"] self.origin = pg.time.get_ticks() # milliseconds self.elapsed = 0 # milliseconds self._isSentMessage = False self.render = RenderComponent(self) self.render.add("office_dog", office["dog"], 1500) self.render.add("office_cat", office["cat"], 1500) self.audio = AudioComponent(self, isAutoPlay=False) # self.audio.add("meow", SFX_RESOURCES["meow_1"]) self.dialogue = Dialogue(self.screen) self.dialogue.add(dialogue.OFFICE_1, 240, 50, "left") self.dialogue.add(dialogue.OFFICE_2, 370, 100) self.dialogue.add(dialogue.OFFICE_3, 240, 50, "left") self.dialogue.add(dialogue.OFFICE_4, 370, 100) speed = 1 ts = [0, 4000, 8000, 12000, 16000] self.timings = [speed * t for t in ts]
class WinMenu(BaseMenu): """ The win menu of the game. """ def __init__(self, screen): super().__init__(screen) self.screen = screen self.fontSize = 18 self.fontColour = "white" self.x = 150 self.y = 320 self.enterText = TextLabel("Enter para salir", self.fontSize, self.fontColour, self.x, self.y, self.screen) self.Text2 = TextLabel("NIVEL 3 COMING SOON...", 32, self.fontColour, 100, 100, self.screen) image = MENU_RESOURCES["screens"]["fade"][0] self.render = RenderComponent(self) self.render.add("idle", image) self.render.state = "idle" self.audio = AudioComponent(self, isAutoPlay=False, isRepeat=True) self.audio.add("win", SFX_RESOURCES["scene_win"]) self.audio.state = "win" def __str__(self): return "win_menu" def handleEvent(self, event): if event.type == pg.KEYDOWN: if event.key == pg.K_RETURN: self.messageMenu("transition", "main_menu") self.messageScene("no_mode") def update(self): self.render.update() self.audio.update() self.enterText.update() self.Text2.update() def draw(self, camera=None): self.render.draw(camera) self.enterText.draw() self.Text2.draw()
def __init__(self, screen): super().__init__(screen) self.effect = FadeEffect(screen) self.effect.timeStartLighten = 0.0 self.effect.timeEndLighten = 1.0 self.effect.timeStartDarken = 1.0 self.effect.timeEndDarken = 1.5 image = pg.Surface((settings.WIDTH, settings.HEIGHT)) image.fill(settings.COLOURS["dark_red"]) image = image.convert() self.render = RenderComponent(self) self.render.add("idle", image) self.render.state = "idle" self.audio = AudioComponent(self, isAutoPlay=True, isRepeat=False) self.audio.add("death", SFX_RESOURCES["menu_death"]) self.audio.state = "death"
def __init__(self, screen): super().__init__(screen) self.fontSize = 18 self.fontColour = "white" self.x = 150 self.y = 320 self.enterText = TextLabel("Enter para salir", self.fontSize, self.fontColour, self.x, self.y, self.screen) image = MENU_RESOURCES["screens"]["lose"][0] self.render = RenderComponent(self) self.render.add("idle", image) self.render.state = "idle" self.audio = AudioComponent(self, isAutoPlay=False) self.audio.add("meow", SFX_RESOURCES["menu_lose"]) self.audio.state = "meow"
def __init__(self, screen): super().__init__(screen) self.effect = FadeEffect(screen) background = MENU_RESOURCES["screens"]["splash"][0] background = addBackground(background) self.render = RenderComponent(self) self.render.add("background", background) self.render.state = "background" self.audio = AudioComponent(self) self.audio.add("door_knock", SFX_RESOURCES["splash_door_knock"]) self.audio.add("door_open", SFX_RESOURCES["splash_door_open"]) self.audio.add("door_close", SFX_RESOURCES["splash_door_close"]) self.audio.add("meow", SFX_RESOURCES["splash_meow"]) self.audio.link("door_knock", "door_open", delay=1000) self.audio.link("door_open", "meow") self.audio.link("meow", "door_close") self.audio.state = "door_knock"
def __init__(self, screen): super().__init__(screen) self.effect = FadeEffect(screen) self.effect.timeEndDarken += 1.0 ICONS = ICON_RESOURCES["assets"] self.render = RenderComponent(self) self.render.add("red", ICONS["red"][0]) self.render.add("black", ICONS["black"][0]) self.render.state = "black" w, h = ICONS["red"][0].get_size() offsetX = (settings.WIDTH - w) / 2 offsetY = (settings.HEIGHT - h) / 2 self.rect.center = (offsetX, offsetY) self.audio = AudioComponent(self, isRepeat=True, isAutoPlay=True) self.audio.add("intro_1", SFX_RESOURCES["menu_intro_1"]) self.audio.add("intro_2", SFX_RESOURCES["menu_intro_2"]) self.audio.state = "intro_1"
def __init__(self, screen): """ :param screen: pygame.Surface, representing the screen. """ super().__init__() self.screen = screen self.rect = pg.Rect(0, 0, 0, 0) self.num = 0 self.lives = 5 self.isOnGround = False self.jumpSpeed = -12 self.moveSpeed = 9 self.physics = PhysicsComponent(self) self.render = RenderComponent(self, enableOrientation=True) self.audio = AudioComponent(self, isAutoPlay=False) self.audio.add("jump", SFX_RESOURCES["cat_jump"]) self.keybinds = None
class DeathMenu(BaseMenu): """ The death screen of the game. """ def __init__(self, screen): super().__init__(screen) self.effect = FadeEffect(screen) self.effect.timeStartLighten = 0.0 self.effect.timeEndLighten = 1.0 self.effect.timeStartDarken = 1.0 self.effect.timeEndDarken = 1.5 image = pg.Surface((settings.WIDTH, settings.HEIGHT)) image.fill(settings.COLOURS["dark_red"]) image = image.convert() self.render = RenderComponent(self) self.render.add("idle", image) self.render.state = "idle" self.audio = AudioComponent(self, isAutoPlay=True, isRepeat=False) self.audio.add("death", SFX_RESOURCES["menu_death"]) self.audio.state = "death" def __str__(self): return "death_menu" def handleEvent(self, event): print("'{}' safely ignored event {}".format(self.__str__(), event)) def update(self): self.render.update() self.audio.update() self.effect.update() if self.effect.isComplete: self.messageScene("revive") def draw(self, camera=None): self.render.draw() self.effect.draw()
class LoseMenu(BaseMenu): """ The game over menu of the game. """ def __init__(self, screen): super().__init__(screen) self.fontSize = 18 self.fontColour = "white" self.x = 150 self.y = 320 self.enterText = TextLabel("Enter para salir", self.fontSize, self.fontColour, self.x, self.y, self.screen) image = MENU_RESOURCES["screens"]["lose"][0] self.render = RenderComponent(self) self.render.add("idle", image) self.render.state = "idle" self.audio = AudioComponent(self, isAutoPlay=False) self.audio.add("meow", SFX_RESOURCES["menu_lose"]) self.audio.state = "meow" def __str__(self): return "lose_menu" def handleEvent(self, event): if event.type == pg.KEYDOWN: if event.key == pg.K_RETURN: self.messageMenu("transition", "splash_menu") self.messageScene("no_mode") def update(self): self.render.update() self.audio.update() self.enterText.update() def draw(self, camera=None): self.render.draw() self.enterText.draw()
def __init__(self, screen): super().__init__(screen) self.screen = screen self.fontSize = 18 self.fontColour = "white" self.x = 150 self.y = 320 self.enterText = TextLabel("Enter para salir", self.fontSize, self.fontColour, self.x, self.y, self.screen) self.Text2 = TextLabel("NIVEL 3 COMING SOON...", 32, self.fontColour, 100, 100, self.screen) image = MENU_RESOURCES["screens"]["fade"][0] self.render = RenderComponent(self) self.render.add("idle", image) self.render.state = "idle" self.audio = AudioComponent(self, isAutoPlay=False, isRepeat=True) self.audio.add("win", SFX_RESOURCES["scene_win"]) self.audio.state = "win"
def __init__(self, screen): super().__init__(screen) self.rect = pg.Rect(0, 0, 0, 0) pig = CUTSCENE_RESOURCES["pig"]["appear"] pig = [addBackground(p) for p in pig] self.origin = pg.time.get_ticks() # milliseconds self.elapsed = 0 # milliseconds self._isComplete = False self.render = RenderComponent(self, enableRepeat=False) self.render.add("appear", pig, 3000) self.audio = AudioComponent(self) self.audio.add("machine", SFX_RESOURCES["pig_machine"]) self.audio.state = "machine" self.dialogue = Dialogue(self.screen) speed = 1 ts = [3000] self.timings = [speed * t for t in ts]
def __init__(self, x, y, dx, dy, totalOptions, screen): """ :param x: Integer, the x-position of the arrow. :param y: Integer, the y-position of the arrow. :param dx: Integer, the change in x-position per movement. :param dy: Integer, the change in y-position per moevement. :param totalOptions: Integer, the total number of options. :param screen: pygame.Surface, representing the screen. """ self.rect = pg.Rect(x, y, 0, 0) self.dx = dx self.dy = dy self.totalOptions = totalOptions self.screen = screen self.index = 0 self.render = RenderComponent(self) self.render.add("spin", MENU_RESOURCES["assets"]["coin"], 350) self.render.state = "spin" self.audio = AudioComponent(self, isAutoPlay=False) self.audio.add("move", SFX_RESOURCES["menu_arrow"]) self.audio.state = "move"
def __init__(self, screen): super().__init__(screen) self.rect = pg.Rect(0, 0, 0, 0) jail = CUTSCENE_RESOURCES["jail"] self.origin = pg.time.get_ticks() # milliseconds self.elapsed = 0 # milliseconds self._isComplete = False self.render = RenderComponent(self, enableRepeat=False) self.render.add("fence_show", jail["fence_show"], 2000) self.render.add("fence_static", jail["fence_static"]) self.render.add("fence_hide", jail["fence_hide"], 2000) self.render.add("cat_static", jail["cat_static"]) self.render.add("cat_close", jail["cat_close"], 3500) self.audio = AudioComponent(self) self.dialogue = Dialogue(self.screen) self.dialogue.add(dialogue.JAIL_1, 19, 25, "caption") speed = 1 ts = [2000, 4000, 6000, 9500, 11500] self.timings = [speed * t for t in ts]
class PigCutscene(BaseCutscene): """ The cutscene where the pig boss appears. """ def __init__(self, screen): super().__init__(screen) self.rect = pg.Rect(0, 0, 0, 0) pig = CUTSCENE_RESOURCES["pig"]["appear"] pig = [addBackground(p) for p in pig] self.origin = pg.time.get_ticks() # milliseconds self.elapsed = 0 # milliseconds self._isComplete = False self.render = RenderComponent(self, enableRepeat=False) self.render.add("appear", pig, 3000) self.audio = AudioComponent(self) self.audio.add("machine", SFX_RESOURCES["pig_machine"]) self.audio.state = "machine" self.dialogue = Dialogue(self.screen) speed = 1 ts = [3000] self.timings = [speed * t for t in ts] def __str__(self): return "pig_cutscene" def handleEvent(self, event): if event.type == pg.KEYDOWN: if event.key == pg.K_RETURN: self._messageStart() def update(self): self.elapsed = pg.time.get_ticks() - self.origin if self.timings[0] > self.elapsed: self.render.state = "appear" self.dialogue.index = None else: if not self._isComplete: self._isComplete = True self._messageStart() self.dialogue.update() self.render.update() self.audio.update() def draw(self, camera=None): self.render.draw(camera) self.render.draw() self.dialogue.draw() def _messageStart(self): """ Sends out events to end the cutscene and start playing the game. """ self.messageCutScene("transition", "blank_cutscene") self.messageScene("unpause") pg.mixer.stop()
class PlayerBase(GameObject, pg.sprite.Sprite): """ The controllable character to be played. """ def __init__(self, screen): """ :param screen: pygame.Surface, representing the screen. """ super().__init__() self.screen = screen self.rect = pg.Rect(0, 0, 0, 0) self.num = 0 self.lives = 5 self.isOnGround = False self.jumpSpeed = -12 self.moveSpeed = 9 self.physics = PhysicsComponent(self) self.render = RenderComponent(self, enableOrientation=True) self.audio = AudioComponent(self, isAutoPlay=False) self.audio.add("jump", SFX_RESOURCES["cat_jump"]) self.keybinds = None def update(self): self.render.update() self.audio.update() self.physics.update() # Hacky solution to fix hit box sizes without tampering with the # animation images directly (too much effort at this point) w, h = self.rect.size self.rect.size = (w-10, h) pressed = pg.key.get_pressed() left = self.keybinds["move_left"] right = self.keybinds["move_right"] if pressed[left] and not pressed[right]: self.moveLeft() if pressed[right] and not pressed[left]: self.moveRight() def handleEvent(self, event): if event.type == pg.KEYDOWN: if event.key == self.keybinds["jump"]: self.jump() if event.type == pg.KEYUP: if event.key == self.keybinds["move_left"]: self.stop() if event.key == self.keybinds["move_right"]: self.stop() def draw(self, camera=None): self.render.draw(camera) def jump(self): """ Makes the character jump. """ if self.isOnGround: self.physics.velocity.y = self.jumpSpeed self.physics.addVelocityY("jump", self.jumpSpeed) self.isOnGround = False self.audio.state = "jump" def moveLeft(self): """ Moves the character left. """ self.render.state = "running" self.render.orientation = "left" self.physics.addDisplacementX("move", -self.moveSpeed) def moveRight(self): """ Moves the character right. """ self.render.state = "running" self.render.orientation = "right" self.physics.addDisplacementX("move", self.moveSpeed) def stop(self): """ Stops the character. """ self.render.state = "idle" self.physics.velocity.x = 0
class Door(GameObject): """ A door entity that the player can enter. """ def __init__(self, x, y, num, screen, zoneArtwork=1): """ :param x: Integer, the x-position of the wall. :param y: Integer, the y-position of the wall. :param num: Integer, identifying the number of the button. :param screen: pygame.Surface, the screen to draw the wall onto. :param zoneArtwork: Integer, choosing the image based on zone. """ self.screen = screen self.num = num self.switchesWaiting = [] self.isClosed = True # REFACTOR: Clumsy implementation to change image of the renderer. # - Causes instantiating argument to be needlessly long. # - The code below does not scale well. if zoneArtwork == 1: door = ZONE1_RESOURCES["doors"] if zoneArtwork == 2: door = ZONE2_RESOURCES["doors"] self.render = RenderComponent(self, enableRepeat=False) self.render.add("open", door["open"]) self.render.add("closed", door["close"]) self.render.state = "closed" self.rect = pg.Rect(x, y, 0, 0) self.audio = AudioComponent(self, isAutoPlay=False) self.audio.add("open", SFX_RESOURCES["scene_door"]) def __str__(self): return "door_{}".format(self.num) def handleEvent(self, event): if event.type == self.SCENE_EVENT: if event.category == "switch": try: switchNum, isOn = event.data self.switchesWaiting.remove(switchNum) if not self.switchesWaiting: self.openDoor() except ValueError: print("'{}' is safely ignoring switch number {}" .format(self.__str__(), switchNum)) except AttributeError: print("'{}' is not a valid switch number!".format(switchNum)) def update(self): self.render.update() self.audio.update() def draw(self, camera=None): self.render.draw(camera) def openDoor(self): """ Changes the state of the door to open and sends out an event. """ self.isClosed = False self.render.state = "open" self.audio.state = "open" self.messageScene("door", (self.num, self.isClosed))
class CollisionEngine(GameObject): """ A specialised (non-scalable) collision engine that handles collisions between all entities in a scene. """ def __init__(self, scene): """ :param scene: Scene Class, representing a level. """ self.scene = scene self.audio = AudioComponent(self, isAutoPlay=False) self.audio.add("explosion", SFX_RESOURCES["cat_coop_jump"]) def __str__(self): return "collision_engine" def eventHandler(self, event): if event.type == pg.KEYDOWN: if event.key == pg.K_RETURN: self.resolveDoorCollisions() try: p1, p2 = self.scene.players p1Jump = p1.keybinds["coop_jump"] p2Jump = p2.keybinds["coop_jump"] if event.key == p1Jump or event.key == p2Jump: self.resolvePlayerCollisions(30) self.audio.state = "explosion" except ValueError: pass def update(self): self.resolveWallCollisions() self.resolveSwitchCollisions() self.resolveSPlatformCollisions() self.resolveDPlatformCollisions() self.resolveMPlatformCollisions() self.resolveSpikeCollisions() self.resolveBossCollisions() self.resolveBoundaryCollision() self.audio.update() def resolvePlayerCollisions(self, explosionSpeed): """ Resolves any collisions between players. :param explosionSpeed: Integer, the speed at which both players fly away from each other in the x-axis and y-axis respectively. """ try: p1, p2 = self.scene.players if pg.sprite.collide_rect(p1, p2): p1.physics.addVelocityY("collision", -explosionSpeed) p2.physics.addVelocityY("collision", -explosionSpeed) if p2.rect.x > p1.rect.x: p1.physics.addVelocityX("collision", -explosionSpeed) p2.physics.addVelocityX("collision", explosionSpeed) else: p1.physics.addVelocityX("collision", explosionSpeed) p2.physics.addVelocityX("collision", -explosionSpeed) except ValueError: pass def resolveWallCollisions(self): """ Resolves any wall collisions. """ for player in self.scene.players: self._resolveBasicCollision(player, self.scene.walls) def resolveSPlatformCollisions(self): """ Resolves any static platform collisions. """ for player in self.scene.players: self._resolveBasicCollision(player, self.scene.sPlatforms) def resolveDPlatformCollisions(self): """ Resolves any directional platform collisions. """ for player in self.scene.players: hits = pg.sprite.spritecollide(player, self.scene.dPlatforms, False) for platform in hits: direction = self._checkCollisionDirection(player, platform) if direction == "bottom": tol = abs(player.rect.bottom - platform.rect.top) if tol < 30: player.rect.bottom = platform.rect.top player.isOnGround = True # Allows conversation of velocity if the player jumps through if player.physics.velocity.y > 0: player.physics.velocity.y = 0 def resolveMPlatformCollisions(self): """ Resolves any moving platform collisions. """ for player in self.scene.players: hits = pg.sprite.spritecollide(player, self.scene.mPlatforms, False) self._resolveBasicCollision(player, self.scene.mPlatforms) for platform in hits: player.physics.addDisplacementX("platform", platform.dx) player.physics.addDisplacementY("platform", platform.dy) def resolveSwitchCollisions(self): """ Resolves any switch collisions. """ switchesOn = [s for s in self.scene.switches if s.isOn] for s in switchesOn: for player in self.scene.players: if pg.sprite.collide_rect(player, s): if (player.physics.velocity.x != 0 or player.physics.velocity.y != 0): s.turnOff() def resolveDoorCollisions(self): """ Resolves any door collisions. """ for player in self.scene.players: hits = pg.sprite.spritecollide(player, self.scene.doors, False) doorsClosed = [d for d in self.scene.doors if d.isClosed] if hits and not doorsClosed: self.messageScene("complete") def resolveSpikeCollisions(self): """ Resolves any spike collisions. """ for player in self.scene.players: hits = pg.sprite.spritecollide(player, self.scene.spikes, False) if hits: self.messageScene("death", player.num) def resolveBossCollisions(self): """ Resolves any boss collisions. """ for player in self.scene.players: hits = pg.sprite.spritecollide(player, self.scene.bosses, False) if hits: self.messageScene("death", player.num) def resolveBoundaryCollision(self): """ Checks if the players have 'fallen' out of the level. """ w, h = self.scene.rect.size boundary = pg.Rect(-1000, -1000, w + 2000, h + 2000) for player in self.scene.players: if not pg.Rect.contains(boundary, player): self.messageScene("death", player.num) def _resolveBasicCollision(self, moving, group): """ Resolves any collisions between a moving object and a group of objects such that the moving object cannot pass through such objects. :param moving: GameObject instance, representing a moving scene entity. :param group: List, containing GameObject instance in a scene. :return: """ hits = pg.sprite.spritecollide(moving, group, False) for wall in hits: direction = self._checkCollisionDirection(moving, wall) if direction == "bottom": moving.rect.bottom = wall.rect.top moving.physics.velocity.y = 0 moving.isOnGround = True elif direction == "left": moving.rect.left = wall.rect.right moving.physics.velocity.x = 0 elif direction == "top": moving.rect.top = wall.rect.bottom moving.physics.velocity.y = 0 elif direction == "right": moving.rect.right = wall.rect.left moving.physics.velocity.x = 0 def _checkCollisionDirection(self, moving, static): """ Checks if the moving game object has collided with the static game object, and determines the direciton of collision. :param moving: GameObject instance, representing a moving game object. :param static: GameObject instance, representing a static game object. :return: String, whether 'bottom', 'left', 'top', or 'right'. """ if pg.sprite.collide_rect(moving, static): # Defining points on the static game object x, y = static.rect.center S00 = static.rect.topleft S10 = static.rect.topright S11 = static.rect.bottomright S01 = static.rect.bottomleft # Defining points on the moving game object u, v = moving.rect.center M00 = moving.rect.topleft M10 = moving.rect.topright M11 = moving.rect.bottomright M01 = moving.rect.bottomleft # Defining vectors on the static game object which will be used in # accurate collision handling. The vectors are from the center of # the game object to its corners. vec_M00 = Vector2(x - S00[0], y - S00[1]) vec_M10 = Vector2(x - S10[0], y - S10[1]) vec_M11 = Vector2(x - S11[0], y - S11[1]) vec_M01 = Vector2(x - S01[0], y - S01[1]) # Defining variables for our new coordinate system based on angles # (which is mathematically equivalent to bearings) FULL_ROTATION = 360 origin = vec_M00 # Calculating angles of the static game object vectors angle_00 = origin.angle_to(vec_M00) % FULL_ROTATION angle_10 = origin.angle_to(vec_M10) % FULL_ROTATION angle_11 = origin.angle_to(vec_M11) % FULL_ROTATION angle_01 = origin.angle_to(vec_M01) % FULL_ROTATION # Calculating the displacement angle between the moving and # static game objects displacement = Vector2(x - u, y - v) angle = origin.angle_to(displacement) % FULL_ROTATION # Calculating direction of the collision isCollideBottom = angle_00 < angle < angle_10 isCollideLeft = angle_10 < angle < angle_11 isCollideTop = angle_11 < angle < angle_01 isCollideRight = angle_01 < angle if isCollideBottom: return "bottom" elif isCollideLeft: return "left" elif isCollideTop: return "top" elif isCollideRight: return "right"