Compare commits

...

16 Commits

Author SHA1 Message Date
Michał Czekański
b4bebfc375 Add test code for printToConsole in Game.py 2020-05-15 11:39:58 +02:00
Michał Czekański
21ea6cf1de Use new printToConsole method in Ui class 2020-05-15 11:17:44 +02:00
Michał Czekański
085beb263a Add comments in Ui.py 2020-05-15 02:09:43 +02:00
Michał Czekański
9fa6da8e8e Add comments in UiText.py 2020-05-15 01:49:31 +02:00
Michał Czekański
8df653bacf Add scroll up and down method in UiConsole class 2020-05-15 01:44:32 +02:00
Michał Czekański
741222e0aa Add update method, static buffer, static printing to console method
Added static buffer in UiConsole class which stores lines to print. Even if there is no UiConsole object instantiated (cos it's static).
Everything from buffer is being printed on update() calls, update is called every frame.
To add lines to buffer use static printToConsole() method.
2020-05-15 01:37:07 +02:00
Michał Czekański
7bf924e2c9 Merge branch 'CodeUpdate_WriteToConsole' into UI_doc 2020-05-15 01:17:43 +02:00
Michał Czekański
8c479be091 Add comments in UiButton.py 2020-05-15 01:17:22 +02:00
Michał Czekański
b606124d38 Add comments in UiImage.py 2020-05-14 21:18:57 +02:00
Michał Czekański
786db30b58 Remove unnecessary field in UiBar class 2020-05-14 21:15:57 +02:00
Michał Czekański
25903c2126 Add comments in UiBar.py 2020-05-14 21:10:51 +02:00
Michał Czekański
f0d6f5f8e4 Add comments in UiElement.py 2020-05-14 21:10:09 +02:00
Michał Czekański
d2f7cbc6ed Add type hints for fields and method parameters in UiConsole 2020-05-10 18:27:45 +02:00
Michał Czekański
82eb2b9857 Add comments to all methods in UiConsole class 2020-05-10 18:03:29 +02:00
Michał Czekański
bdcc564ebb Fix writeToConsole method
This method was adding given string to the console's list of lines, but wasn't displaying this given string.
User had to perform scroll to display this new string.
2020-05-10 17:57:27 +02:00
Michał Czekański
3a028da28f Add method writing to console 2020-05-10 17:47:54 +02:00
8 changed files with 324 additions and 48 deletions

View File

@ -12,6 +12,9 @@ from src.game.Timer import Timer
# Main Game class
from src.ui.UiConsole import UiConsole
class Game:
def __init__(self, filesPath):
"""
@ -77,6 +80,11 @@ class Game:
self.map.addEntity(self.player, DONTADD=True)
self.eventManager = EventManager(self, self.player)
# UiConsole.printToConsole() Test
testMessages = ["Test message " + str(i) for i in range(1, 51)]
for testMsg in testMessages:
UiConsole.printToConsole(testMsg)
# Start game loop
self.mainLoop()

View File

@ -1,39 +1,52 @@
from enum import Enum
from pathlib import Path
from typing import Union
import pygame
from src.entities.Enums import StatisticNames
from src.entities.Interactable import Interactable
from src.entities.Pickupable import Pickupable
from src.entities.Player import Player
from src.entities.Statistics import Statistics
from src.game.Timer import Timer
from src.ui.UiBar import UiBar
from src.ui.UiConsole import UiConsole
from src.ui.UiText import UiText
class Ui():
def __init__(self, rightUiWidth, leftUiWidth, screenHeight, timer, font=None, antialias=True):
class Ui:
elements: pygame.sprite.Group
timer: Timer
antialias: bool
barHeight: float
def __init__(self, rightUiWidth: int, leftUiWidth: int, screenHeight: int, timer: Timer,
font: Union[pygame.font.Font, None] = None, antialias: bool = True):
"""
Creates Ui object. Instantiates all UI elements.
:param rightUiWidth:
:param leftUiWidth:
:param screenHeight:
:param timer:
:param font:
:param antialias:
"""
self.elements = pygame.sprite.Group()
self.leftUiWidth = leftUiWidth
self.rightUiWidth = rightUiWidth
self.screenHeight = screenHeight
# Default bar's height, like hp bar.
self.barHeight = 25
self.antialias = antialias
fontName = "FiraCode-Light.ttf"
fontFolder = ""
fontFile = ""
try:
fontFolder = Path("./data/fonts")
fontFile = fontFolder / fontName
except IOError:
print("Cannot load texture from " + fontFolder + ". Exiting...")
exit(1)
fontPath = str(fontFile.resolve())
# If no font was given then load it from predefined file.
if font is None:
font = pygame.font.Font(fontPath, int(self.barHeight / 1.5))
font = self.__loadFont__()
self.font = font
self.timer = timer
@ -76,34 +89,90 @@ class Ui():
screenHeight - self.timerTextView.rect.h - self.isDayTextView.rect.h),
font=self.font, antialias=self.antialias)
def __loadFont__(self) -> pygame.font.Font:
"""
Loads project's default font.
:return: Font loaded from file.
"""
fontName = "FiraCode-Light.ttf"
fontFolder = ""
fontFile = ""
try:
fontFolder = Path("./data/fonts")
fontFile = fontFolder / fontName
except IOError:
print("Cannot load font from " + fontFolder + ". Exiting...")
exit(1)
fontPath = str(fontFile.resolve())
font = pygame.font.Font(fontPath, int(self.barHeight / 1.5))
return font
def updateConsoleBasedOnPlayerStats(self, statistics: Statistics):
"""
Prints statistics on console.
:param statistics: Statistics to print
"""
consoleLines = ["Health: " + str(statistics.hp), "Hunger: " + str(statistics.hunger),
"Stamina: " + str(statistics.stamina), "Thirst: " + str(statistics.thirst)]
self.console.addLinesToConsoleAndScrollToDisplayThem(consoleLines)
for line in consoleLines:
self.console.printToConsole(line)
def updateBarsBasedOnPlayerStats(self, statistics: Statistics):
"""
Updates bars like hp bar to match player's statistics.
:param statistics:
"""
self.healthBar.updateFill(statistics.hp)
self.hungerBar.updateFill(statistics.hunger)
self.staminaBar.updateFill(statistics.stamina)
self.thirstBar.updateFill(statistics.thirst)
def updateBasedOnPygameEvent(self, event: pygame.event):
"""
Examines event and does actions:
- console scrolling
:param event: pygame event to examine.
"""
if event.type == pygame.MOUSEBUTTONDOWN:
console = self.console
if event.button == 4:
console.writeConsoleLines(console.topWrittenLineInd + 1)
console.scrollDown()
elif event.button == 5:
console.writeConsoleLines(console.topWrittenLineInd - 1)
console.scrollUp()
def updateOnPlayerPickup(self, playerStats, pickedObject):
self.console.addLinesToConsoleAndScrollToDisplayThem([self.timer.getPrettyTime() + " - Picked object " + str(pickedObject.id) + ":"])
def updateOnPlayerPickup(self, playerStats, pickedObject: Pickupable):
"""
This method should be called to update UI state after player pickup.
Given player statistics and picked object updates bars and prints message to console.
:param playerStats:
:param pickedObject:
"""
self.console.printToConsole(self.timer.getPrettyTime() + " - Picked object " + str(pickedObject.id) + ":")
self.updateConsoleBasedOnPlayerStats(playerStats)
def updateOnPlayerInteraction(self, playerStats, interactedObject):
self.console.addLinesToConsoleAndScrollToDisplayThem([self.timer.getPrettyTime() + " - Player interacted with " + str(interactedObject.id) + ":"])
def updateOnPlayerInteraction(self, playerStats, interactedObject: Interactable):
"""
This method should be called to update UI state after player interaction.
Updates bars and prints message to console.
:param playerStats:
:param interactedObject:
"""
self.console.printToConsole(self.timer.getPrettyTime() + " - Player interacted with " + str(interactedObject.id) + ":")
self.updateConsoleBasedOnPlayerStats(playerStats)
def updateOnDeath(self, player):
def updateOnDeath(self, player: Player):
"""
Updates UI after player death. Prints death reason to console.
:param player: Dead player.
"""
consoleLines = []
deathReason: StatisticNames = player.deathReason
@ -123,9 +192,14 @@ class Ui():
consoleLines.append("Time alive: " + str(player.timeAlive / 1000) + "s")
self.console.addLinesToConsoleAndScrollToDisplayThem(consoleLines)
for line in consoleLines:
self.console.printToConsole(line)
def updateTime(self):
"""
Updates timer and changes text eventually changes day or night text.
"""
self.timerTextView.changeText(self.timer.getPrettyTime())
if self.timer.isItDay():
self.isDayTextView.changeText("Day")

View File

@ -1,32 +1,62 @@
from typing import Tuple
import pygame
from src.ui.UiElement import UiElement
class UiBar(UiElement):
def __init__(self, rect: pygame.Rect, initialFilledPercent=100, filledBarColor=(255, 0, 0), emptyBarColor=(0, 0, 0),
outlineColor=(75, 75, 75), outlineThickness=10):
filledBarColor: Tuple[int, int, int]
outlineThickness: int
outlineColor: Tuple[int, int, int]
emptyBarColor: Tuple[int, int, int]
filledPercent: int
def __init__(self, rect: pygame.Rect, initialFilledPercent: int = 100,
filledBarColor: Tuple[int, int, int] = (255, 0, 0), emptyBarColor: Tuple[int, int, int] = (0, 0, 0),
outlineColor: Tuple[int, int, int] = (75, 75, 75), outlineThickness: int = 10):
"""
Creates UiBar object
:param rect:
:param initialFilledPercent: How much bar is filled at the beginning. Number between 0 and 100
:param filledBarColor: Color of the filled part of the bar.
:param emptyBarColor: Color of the empty part of the bar.
:param outlineColor: Color of the bar outline.
:param outlineThickness:
"""
super().__init__(rect)
self.filledPercent = initialFilledPercent / 100
# Make sure that filled percent is between 0 and 100
if initialFilledPercent < 0:
initialFilledPercent = 0
elif initialFilledPercent > 100:
initialFilledPercent = 100
self.filledPercent = initialFilledPercent
self.emptyBarColor = emptyBarColor
self.barColor = filledBarColor
self.outlineColor = outlineColor
self.outlineThickness = outlineThickness
self.filledBarColor = filledBarColor
self.value = initialFilledPercent
self.__genBar__()
def __genBar__(self):
"""
Generates bar image based on filled percent field.
"""
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.width - self.outlineThickness) * (self.filledPercent / 100),
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.value = filledPercent
def updateFill(self, filledPercent: int):
"""
Updates how much bar is filled
:param filledPercent: Value between 0 and 100
"""
self.filledPercent = filledPercent
self.__genBar__()

View File

@ -1,15 +1,36 @@
from enum import Enum
from typing import Tuple, List, Any, Callable
import pygame
from pygame.font import FontType
from src.ui.UiElement import UiElement
class UiButton(UiElement):
def __init__(self, rect: pygame.Rect, notClickedBtnColor=(125, 125, 125), clickedBtnColor=(255, 255, 255),
text="Click", textColor=(0, 0, 0), font=None, functionsToInvokeWhenClicked=[]):
functionsToInvokeWhenClicked: List[Tuple[Callable, Any]]
image: pygame.Surface
beingClicked: bool
text: str
clickedBtnColor: Tuple[int, int, int]
textColor: Tuple[int, int, int]
font: pygame.font.Font
def __init__(self, rect: pygame.Rect, notClickedBtnColor: Tuple[int, int, int] = (125, 125, 125),
clickedBtnColor: Tuple[int, int, int] = (255, 255, 255),
text: str = "Click", textColor: Tuple[int, int, int] = (0, 0, 0), font: pygame.font.Font = None,
functionsToInvokeWhenClicked: List[Tuple[Callable, Any]] = []):
"""
:type functionsToInvokeWhenClicked : list of tuple(function, args*), args are function arguments
Creates UiButton object.
:param rect: Rectangle on which button will be displayed.
:param notClickedBtnColor: Button color when it is not clicked.
:param clickedBtnColor: Button color when it is clicked.
:param text: Text to be displayed on button.
:param textColor:
:param font: Font for button text. If None is given then default font will be used.
:type functionsToInvokeWhenClicked : list of tuple(function, args*), args are function arguments.
"""
super().__init__(rect)
if font is None:
@ -28,8 +49,14 @@ class UiButton(UiElement):
self.functionsToInvokeWhenClicked.extend(functionsToInvokeWhenClicked)
def eventHandler(self, event):
# change selected color if rectangle clicked
def eventHandler(self, event: pygame.event):
"""
Checks if this button was clicked based on given pygame event.
If yes, then it calls all functions that are on the list of functions to invoke.
:param event: pygame event, which will be examined to check if this button was clicked.
"""
# change selected color if this button's rectangle was clicked
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
if self.rect.collidepoint(event.pos): # is mouse over button
@ -42,7 +69,11 @@ class UiButton(UiElement):
self.beingClicked = False
self.image = self._images[ButtonImages.DEFAULT_IMAGE.value]
def __initBtnImages__(self):
def __initBtnImages__(self) -> None:
"""
Creates button images which will be drawn, so that button is displayed.
"""
self._images = [
pygame.Surface((self.rect.width, self.rect.height)),
pygame.Surface((self.rect.width, self.rect.height)),
@ -55,13 +86,21 @@ class UiButton(UiElement):
self._images[0].blit(self.textSurface, self.textSurfaceDest)
self._images[1].blit(self.textSurface, self.textSurfaceDest)
def addFuncToInvoke(self, tupleOfFuncAndArgs):
def addFuncToInvoke(self, tupleOfFuncAndArgs: Tuple[Callable, Any]) -> None:
"""
Adds given function to list of functions, which will be invoked when this button is clicked.
:type tupleOfFuncAndArgs: tuple(function, *args)
"""
self.functionsToInvokeWhenClicked.append(tupleOfFuncAndArgs)
class ButtonImages(Enum):
"""
This enum is used to display proper button images.
When button is not clicked, then default image is being drawn.
When button is clicked, clicking image is being drawn.
"""
DEFAULT_IMAGE = 0
CLICKING_IMAGE = 1

View File

@ -1,10 +1,49 @@
import pygame
from typing import Tuple, List
from pygame.font import FontType
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, antialias=True):
linesCount: int
antialias: bool
bgColor: Tuple[int, int, int]
font: pygame.font.Font
maxLines: int
lineHeight: int
linesImages: List[pygame.Surface]
topWrittenLineInd: int
consoleLines: List[str]
linesImagesCount: int
consoleWidth: float
image: pygame.Surface
# Static field, with every update() call strings from buffer are written to the console. See printToConsole()
buffer: List[str] = []
@staticmethod
def printToConsole(inp: str):
"""
Adds given string to buffer. Everything from buffer will be written to console when update() will be called.
update() is called every frame.
:param inp: String to be written to console.
"""
UiConsole.buffer.append(inp)
def __init__(self, rect: pygame.Rect, bgColor: Tuple[int, int, int] = (125, 125, 125),
textColor: Tuple[int, int, int] = (255, 255, 255), font: pygame.font.Font = None,
antialias: bool = True):
"""
Creates UiConsole object.
:param rect: Rectangle on which console will be drawn.
:param bgColor:
:param textColor:
:param font: Defaults to None, then default pygame font will be used.
:param antialias:
"""
super().__init__(rect)
self.textColor = textColor
@ -30,9 +69,46 @@ class UiConsole(UiElement):
self.maxLines = int(self.image.get_height() / self.lineHeight)
self.addLinesToConsole(["Hello from console!"])
self.writeConsoleLines()
self.__writeConsoleLines__()
def writeConsoleLines(self, startingLineInd=0):
def update(self, *args):
"""
This method is called every frame. If there is something in buffer, then it is written to console.
:param args:
"""
while len(UiConsole.buffer) > 0:
self.__writeToConsole__(UiConsole.buffer.pop(0))
def __writeToConsole__(self, inp: str):
"""
Writes given string to console and scrolls (console) down to display it.
Warning: It is advised to use printToConsole() method.
:param inp: String to be written to console.
"""
self.addLinesToConsoleAndScrollToDisplayThem([inp])
def scrollUp(self):
"""
Scrolls one line up.
"""
self.__writeConsoleLines__(self.topWrittenLineInd - 1)
def scrollDown(self):
"""
Scrolls one line down.
"""
self.__writeConsoleLines__(self.topWrittenLineInd + 1)
def __writeConsoleLines__(self, startingLineInd: int = 0):
"""
Displays lines stored in console's list of lines, starting from line with given index.
:param startingLineInd: Line index, which will be written on top of the console.
"""
self.image.fill(self.bgColor)
if startingLineInd < 0:
startingLineInd = 0
@ -44,7 +120,15 @@ class UiConsole(UiElement):
self.image.blit(self.linesImages[i], (0, writtenLines * self.lineHeight))
writtenLines += 1
def addLinesToConsole(self, linesToAdd):
def addLinesToConsole(self, linesToAdd: List[str]):
"""
Adds lines to console's list of lines. If one line is too long to display, then it is being cut to pieces,
so that it is appropriate size.
Warning: this method doesn't display given lines, just adds to list of lines.
:param linesToAdd:
"""
for line in linesToAdd:
self.consoleLines.append(line)
self.linesCount += 1
@ -72,9 +156,14 @@ class UiConsole(UiElement):
self.linesImages.append(row)
self.linesImagesCount += 1
def addLinesToConsoleAndScrollToDisplayThem(self, linesToAdd):
def addLinesToConsoleAndScrollToDisplayThem(self, linesToAdd: List[str]):
"""
Adds given lines to console's list of lines, writes them and scrolls console down to display them.
:param linesToAdd: Lines to add to console's list of lines and to display.
"""
self.addLinesToConsole(linesToAdd)
ind = 0
if self.linesImagesCount > self.maxLines:
ind = self.linesImagesCount - self.maxLines
self.writeConsoleLines(ind)
self.__writeConsoleLines__(ind)

View File

@ -2,6 +2,12 @@ import pygame
class UiElement(pygame.sprite.Sprite):
def __init__(self, rect):
rect: pygame.Rect
def __init__(self, rect: pygame.Rect):
"""
Creates UiElement object.
:param rect: UiElement will be drawn on this rectangle.
"""
super().__init__()
self.rect = rect

View File

@ -3,7 +3,14 @@ import pygame
class UiImage(UiElement):
image: pygame.Surface
def __init__(self, rect: pygame.Rect, image: pygame.Surface):
"""
Creates UiImage object.
:param rect: Rectangle on which image will be displayed
:param image: Image to display
"""
super().__init__(rect)
self.image = pygame.transform.scale(image, (rect.width, rect.height))

View File

@ -1,14 +1,32 @@
from typing import Tuple
from typing import Tuple, Union
import pygame
from pygame.font import FontType
from src.ui.UiElement import UiElement
class UiText(UiElement):
image: pygame.Surface
font: pygame.font.Font
text: str
antialias: bool
textColor: Tuple[int, int, int]
backgroundColor: Tuple[int, int, int]
def __init__(self, rect: pygame.Rect, text: str, font: pygame.font.Font = None,
textColor=(0, 0, 0), antialias: bool = False,
backgroundColor=None):
textColor: Tuple[int, int, int] = (0, 0, 0), antialias: bool = False,
backgroundColor: Union[Tuple[int, int, int], None] = None):
"""
Creates UiText object.
:param rect: Rectangle on which text view will be drawn.
:param text:
:param font: If no font is given then default pygame font will be used.
:param textColor:
:param antialias:
:param backgroundColor: Can be None.
"""
super().__init__(rect)
self.backgroundColor = backgroundColor
@ -25,7 +43,12 @@ class UiText(UiElement):
wordImage = self.font.render(text, antialias, textColor)
self.image.blit(wordImage, (0, 0))
def changeText(self, newText):
def changeText(self, newText: str):
"""
Changes text view's text.
:param newText:
"""
self.text = newText
if self.backgroundColor is not None: