diff --git a/src/game/Game.py b/src/game/Game.py index b0573ab..35204ee 100644 --- a/src/game/Game.py +++ b/src/game/Game.py @@ -37,6 +37,11 @@ class Game: print("The screen cannot be in a vertical orientation. Exiting...") exit(1) + # Initialize timers + self.pgTimer = pygame.time.Clock() + self.ingameTimer = Timer() + self.ingameTimer.startClock() + self.screen = Screen(self, self.config["window"]) print("OK") @@ -50,6 +55,9 @@ class Game: def mainLoop(self): while self.running: + # Update ingame clock + self.ingameTimer.updateTime(self.pgTimer.tick()) + self.eventManager.handleEvents() self.spritesList.draw(self.screen.pygameScreen) pygame.display.flip() diff --git a/src/game/Screen.py b/src/game/Screen.py index 0534af3..112c4e6 100644 --- a/src/game/Screen.py +++ b/src/game/Screen.py @@ -2,11 +2,19 @@ import math from enum import Enum import pygame +from src.ui.Ui import Ui # minimum UI width MARGIN = 300 +# screen locations enum +class Locations(Enum): + RIGHT_UI = 1 + LEFT_UI = 2 + MAP = 3 + + class Screen: def __init__(self, gameObject, windowConfig): self.gameObject = gameObject @@ -24,6 +32,8 @@ class Screen: # draw a white rect to resemble map pygame.draw.rect(self.pygameScreen, (255, 255, 255), [self.mapCoord, 0, self.mapSize, self.mapSize]) + self.__initUi__() + def calculateMapDimensions(self): result = 0 expectedSize = self.winY @@ -49,9 +59,25 @@ class Screen: def removeSprite(self, sprite): self.gameObject.spritesList.remove(sprite) + def getUiWidth(self, location: Locations): + if location is Locations.RIGHT_UI: + return self.winX - (self.mapCoord + self.mapSize) + elif location is Locations.LEFT_UI: + return self.mapCoord + elif location is Locations.MAP: + return self.mapSize -# screen locations enum -class Locations(Enum): - RIGHT_UI = 1 - LEFT_UI = 2 - MAP = 3 + def __initUi__(self): + self.ui = Ui(self.getUiWidth(Locations.RIGHT_UI), self.getUiWidth(Locations.LEFT_UI), self.winY, + self.gameObject.ingameTimer) + self.draw(self.ui.timerTextView, Locations.LEFT_UI, 0, 0) + self.draw(self.ui.isDayTextView, Locations.LEFT_UI, 0, 0) + self.draw(self.ui.console, Locations.LEFT_UI, 0, 0) + self.draw(self.ui.healthTextView, Locations.RIGHT_UI, 0, 0) + self.draw(self.ui.healthBar, Locations.RIGHT_UI, 0, 0) + self.draw(self.ui.hungerTextView, Locations.RIGHT_UI, 0, 0) + self.draw(self.ui.hungerBar, Locations.RIGHT_UI, 0, 0) + self.draw(self.ui.staminaTextView, Locations.RIGHT_UI, 0, 0) + self.draw(self.ui.staminaBar, Locations.RIGHT_UI, 0, 0) + self.draw(self.ui.thirstTextView, Locations.RIGHT_UI, 0, 0) + self.draw(self.ui.thirstBar, Locations.RIGHT_UI, 0, 0) diff --git a/src/ui/Ui.py b/src/ui/Ui.py index 289b0d4..ec88a58 100644 --- a/src/ui/Ui.py +++ b/src/ui/Ui.py @@ -1,3 +1,60 @@ +from enum import Enum + +import pygame + +from src.ui.UiBar import UiBar +from src.ui.UiConsole import UiConsole +from src.ui.UiText import UiText + + class Ui(): - def __init__(self): - self.elements = [] + def __init__(self, rightUiWidth, leftUiWidth, screenHeight, timer, font=None): + self.elements = pygame.sprite.Group() + + self.leftUiWidth = leftUiWidth + self.rightUiWidth = rightUiWidth + self.screenHeight = screenHeight + + self.barHeight = 25 + + if font is None: + font = pygame.font.Font(None, self.barHeight) + self.font = font + + self.timer = timer + self.timerTextView = UiText(pygame.Rect(0, 0, leftUiWidth, self.barHeight), font=self.font, + text=timer.getPrettyTime(), textColor=Colors.WHITE.value, + backgroundColor=Colors.GRAY.value) + self.isDayTextView = UiText(pygame.Rect(0, self.timerTextView.rect.y + self.barHeight, leftUiWidth, self.barHeight), text="Day", + font=self.font, backgroundColor=Colors.GRAY.value, textColor=Colors.WHITE.value) + + self.healthTextView = UiText(pygame.Rect(0, 0, rightUiWidth, self.barHeight), text="Health points", + font=self.font, textColor=Colors.WHITE.value) + self.healthBar = UiBar(pygame.Rect(0, self.healthTextView.rect.y + self.barHeight, rightUiWidth, self.barHeight)) + + self.hungerTextView = UiText(pygame.Rect(0, self.healthBar.rect.y + self.barHeight, rightUiWidth, self.barHeight), + text="Hunger", font=self.font, textColor=Colors.WHITE.value) + self.hungerBar = UiBar(pygame.Rect(0, self.hungerTextView.rect.y + self.barHeight, rightUiWidth, self.barHeight), initialFilledPercent=0, + filledBarColor=Colors.YELLOW.value) + + self.staminaTextView = UiText(pygame.Rect(0, self.hungerBar.rect.y + self.barHeight, rightUiWidth, self.barHeight), text="Stamina", + font=self.font, textColor=Colors.WHITE.value) + self.staminaBar = UiBar(pygame.Rect(0, self.staminaTextView.rect.y + self.barHeight, rightUiWidth, self.barHeight), filledBarColor=Colors.GREEN.value) + + self.thirstTextView = UiText(pygame.Rect(0, self.staminaBar.rect.y + self.barHeight, rightUiWidth, self.barHeight), text="Thirst", + font=self.font, textColor=Colors.WHITE.value) + self.thirstBar = UiBar(pygame.Rect(0, self.thirstTextView.rect.y + self.barHeight, rightUiWidth, self.barHeight), initialFilledPercent=0, + filledBarColor=Colors.BLUE.value) + + self.console = UiConsole(pygame.Rect(0, self.timerTextView.rect.h + self.isDayTextView.rect.h, leftUiWidth, + screenHeight - self.timerTextView.rect.h - self.isDayTextView.rect.h), + font=self.font) + + +class Colors(Enum): + RED = (255, 0, 0) + GREEN = (0, 255, 0) + BLUE = (0, 0, 255) + YELLOW = (255, 255, 0) + WHITE = (255, 255, 255) + GRAY = (125, 125, 125) diff --git a/src/ui/UiBar.py b/src/ui/UiBar.py index a8dac52..2112573 100644 --- a/src/ui/UiBar.py +++ b/src/ui/UiBar.py @@ -1,5 +1,30 @@ -import src.ui.UiElement as UiElement +import pygame + +from src.ui.UiElement import UiElement class UiBar(UiElement): - pass + def __init__(self, rect: pygame.Rect, initialFilledPercent=100, filledBarColor=(255, 0, 0), emptyBarColor=(0, 0, 0), + outlineColor=(75, 75, 75), outlineThickness=10): + super().__init__(rect) + self.filledPercent = initialFilledPercent / 100 + self.emptyBarColor = emptyBarColor + self.barColor = filledBarColor + self.outlineColor = outlineColor + self.outlineThickness = outlineThickness + self.filledBarColor = filledBarColor + + self.__genBar__() + + def __genBar__(self): + self.image = pygame.Surface((self.rect.width, self.rect.height)) + filledPartRect = pygame.rect.Rect(self.outlineThickness / 2, self.outlineThickness / 2, + (self.rect.width - self.outlineThickness) * self.filledPercent, + self.rect.height - self.outlineThickness) + self.image.fill(self.filledBarColor, filledPartRect) + pygame.draw.rect(self.image, self.outlineColor, pygame.rect.Rect(0, 0, self.rect.width, self.rect.height), + self.outlineThickness) + + def updateFill(self, filledPercent): + self.filledPercent = filledPercent / 100 + self.__genBar__() diff --git a/src/ui/UiButton.py b/src/ui/UiButton.py index 7c64ced..0587557 100644 --- a/src/ui/UiButton.py +++ b/src/ui/UiButton.py @@ -1,5 +1,67 @@ -import src.ui.UiElement as UiElement +from enum import Enum +import pygame + +from src.ui.UiElement import UiElement class UiButton(UiElement): - pass + + def __init__(self, rect: pygame.Rect, notClickedBtnColor=(125, 125, 125), clickedBtnColor=(255, 255, 255), + text="Click", textColor=(0, 0, 0), font=None, functionsToInvokeWhenClicked=[]): + """ + :type functionsToInvokeWhenClicked : list of tuple(function, args*), args are function arguments + """ + super().__init__(rect) + if font is None: + font = pygame.font.Font(None, 25) + self.font = font + self.textColor = textColor + self.clickedBtnColor = clickedBtnColor + self.notClickedBtnColor = notClickedBtnColor + self.text = text + self.__initBtnImages__() + + self.beingClicked = False + self.image = self._images[0] + + self.functionsToInvokeWhenClicked = [] + + self.functionsToInvokeWhenClicked.extend(functionsToInvokeWhenClicked) + + def eventHandler(self, event): + # change selected color if rectangle clicked + if event.type == pygame.MOUSEBUTTONDOWN: + if event.button == 1: + if self.rect.collidepoint(event.pos): # is mouse over button + self.image = self._images[ButtonImages.CLICKING_IMAGE.value] + self.beingClicked = True + for func, *args in self.functionsToInvokeWhenClicked: + func(*args) + elif event.type == pygame.MOUSEBUTTONUP and self.beingClicked: + if event.button == 1: + self.beingClicked = False + self.image = self._images[ButtonImages.DEFAULT_IMAGE.value] + + def __initBtnImages__(self): + self._images = [ + pygame.Surface((self.rect.width, self.rect.height)), + pygame.Surface((self.rect.width, self.rect.height)), + ] + self._images[ButtonImages.DEFAULT_IMAGE.value].fill(self.notClickedBtnColor) + self._images[ButtonImages.CLICKING_IMAGE.value].fill(self.clickedBtnColor) + self.textSurface = self.font.render(self.text, False, (0, 0, 0)) + self.textSurfaceDest = (self.rect.centerx - (self.textSurface.get_width() / 2), + self.rect.centery - (self.textSurface.get_height() / 2)) + self._images[0].blit(self.textSurface, self.textSurfaceDest) + self._images[1].blit(self.textSurface, self.textSurfaceDest) + + def addFuncToInvoke(self, tupleOfFuncAndArgs): + """ + :type tupleOfFuncAndArgs: tuple(function, *args) + """ + self.functionsToInvokeWhenClicked.append(tupleOfFuncAndArgs) + + +class ButtonImages(Enum): + DEFAULT_IMAGE = 0 + CLICKING_IMAGE = 1 diff --git a/src/ui/UiConsole.py b/src/ui/UiConsole.py new file mode 100644 index 0000000..1c57793 --- /dev/null +++ b/src/ui/UiConsole.py @@ -0,0 +1,79 @@ +import pygame + +from src.ui.UiElement import UiElement + + +class UiConsole(UiElement): + def __init__(self, rect: pygame.Rect, bgColor=(125, 125, 125), textColor=(255, 255, 255), font=None): + super().__init__(rect) + self.textColor = textColor + + if font is None: + font = pygame.font.Font(None, 25) + self.font = font + self.bgColor = bgColor + + self.image = pygame.Surface((rect.width, rect.height)) + self.image.fill(bgColor) + + self.consoleWidth = self.image.get_width() + self.linesImagesCount = 0 + + self.consoleLines = [] + self.linesCount = 0 + self.topWrittenLineInd = 0 + + self.linesImages = [] + self.lineHeight = font.render("sampleText", False, textColor).get_height() + + self.maxLines = int(self.image.get_height() / self.lineHeight) + + self.addLinesToConsole(["Hello from console!"]) + self.writeConsoleLines() + + def writeConsoleLines(self, startingLineInd=0): + self.image.fill(self.bgColor) + if startingLineInd < 0: + startingLineInd = 0 + elif startingLineInd > self.linesImagesCount: + startingLineInd = self.linesImagesCount + self.topWrittenLineInd = startingLineInd + writtenLines = 0 + for i in range(startingLineInd, min(self.maxLines + startingLineInd, self.linesImagesCount)): + self.image.blit(self.linesImages[i], (0, writtenLines * self.lineHeight)) + writtenLines += 1 + + def addLinesToConsole(self, linesToAdd): + for line in linesToAdd: + self.consoleLines.append(line) + self.linesCount += 1 + + row = pygame.Surface((self.consoleWidth, self.lineHeight)) + row.fill(self.bgColor) + + howMuchRowIsFilled = 0 + words = line.split(' ') + for word in words: + wordImage = self.font.render(' ' + word, False, self.textColor) + if howMuchRowIsFilled + wordImage.get_width() <= self.consoleWidth: + row.blit(wordImage, (howMuchRowIsFilled, 0)) + howMuchRowIsFilled += wordImage.get_width() + else: + self.linesImages.append(row) + self.linesImagesCount += 1 + row = pygame.Surface((self.consoleWidth, self.lineHeight)) + row.fill(self.bgColor) + howMuchRowIsFilled = 0 + + row.blit(wordImage, (howMuchRowIsFilled, 0)) + howMuchRowIsFilled += wordImage.get_width() + + self.linesImages.append(row) + self.linesImagesCount += 1 + + def addLinesToConsoleAndScrollToDisplayThem(self, linesToAdd): + self.addLinesToConsole(linesToAdd) + ind = 0 + if self.linesImagesCount > self.maxLines: + ind = self.linesImagesCount - self.maxLines + self.writeConsoleLines(ind) diff --git a/src/ui/UiElement.py b/src/ui/UiElement.py index a398959..6ec18b0 100644 --- a/src/ui/UiElement.py +++ b/src/ui/UiElement.py @@ -1,2 +1,7 @@ -class UiElement: - pass +import pygame + + +class UiElement(pygame.sprite.Sprite): + def __init__(self, rect): + super().__init__() + self.rect = rect diff --git a/src/ui/UiImage.py b/src/ui/UiImage.py index d8cd485..6dbb5fc 100644 --- a/src/ui/UiImage.py +++ b/src/ui/UiImage.py @@ -1,5 +1,10 @@ -import src.ui.UiElement as UiElement +from src.ui.UiElement import UiElement +import pygame class UiImage(UiElement): - pass + def __init__(self, rect: pygame.Rect, image: pygame.Surface): + super().__init__(rect) + self.image = pygame.transform.scale(image, (rect.width, rect.height)) + + diff --git a/src/ui/UiText.py b/src/ui/UiText.py index 2200d35..a6beb6a 100644 --- a/src/ui/UiText.py +++ b/src/ui/UiText.py @@ -1,5 +1,26 @@ -import src.ui.UiElement as UiElement +from typing import Tuple + +import pygame + +from src.ui.UiElement import UiElement class UiText(UiElement): - pass + def __init__(self, rect: pygame.Rect, text: str, font: pygame.font.Font = None, + textColor=(0, 0, 0), antialias: bool = False, + backgroundColor=None): + super().__init__(rect) + + self.backgroundColor = backgroundColor + self.antialias = antialias + self.textColor = textColor + self.text = text + if font is None: + font = pygame.font.Font(None, 12) + self.font = font + + self.image = pygame.Surface((rect.w, rect.h)) + if backgroundColor is not None: + self.image.fill(backgroundColor) + wordImage = self.font.render(text, antialias, textColor) + self.image.blit(wordImage, (0, 0))