pocket-friends/pocket_friends/elements/sprites.py

278 lines
11 KiB
Python

import pygame
import json
class SpriteSheet:
"""
Class to be used by sprites in order to give them a texture and an animation.
Attributes:
images (list) List of all the sprites in the animation separated from the sprite sheet.
"""
def __init__(self, sprite_sheet, texture_json):
"""
Creates a sprite sheet given a sprite image and its corresponding JSON file.
Args:
sprite_sheet (str): The path of the sprite sheet image component.
texture_json (str): The path of the sprite sheet JSON component, contains the number of frames in the
sprite sheet, and the width and height of an individual sprite from the sprite sheet.
"""
# Load in whole sprite sheet as one image.
sprite_sheet = pygame.image.load(sprite_sheet).convert_alpha()
self.images = []
# Get the sprite sheet json file.
with open(texture_json, 'r') as json_file:
img_attrib = json.load(json_file)
json_file.close()
# Count for how many images have been added in the image list
image_count = 0
# Get the sprite size as a tuple
sprite_size = img_attrib['width'], img_attrib['height']
# Iterate through every image location on the sprite sheet given the sprite size
for i in range(sprite_sheet.get_size()[1] // sprite_size[1]):
i *= sprite_size[1]
for j in range(sprite_sheet.get_size()[0] // sprite_size[0]):
j *= sprite_size[0]
# Create a new transparent surface
sprite = pygame.Surface(sprite_size, pygame.SRCALPHA)
# Blit the sprite onto the image
sprite.blit(sprite_sheet, (0, 0), (j, i, sprite_size[0], sprite_size[1]))
# Add the image to the list of images
self.images.append(sprite)
image_count += 1
# Break the loop if the specified number of frames has been reached.
if image_count >= img_attrib['frames']:
break
if image_count >= img_attrib['frames']:
break
class SelectionEgg(pygame.sprite.Sprite):
"""
Sprite to render the egg on the egg selection screen.
Attributes:
egg_color (str): The color of the egg (also its name).
description (str): The description of the egg to be displayed when selected.
contentedness (int): How likely the egg is to stay happy, ranges from 0-5.
metabolism (int): How quickly the egg will get hungry, ranges from 0-5.
rect (pygame.Rect): Pygame rectangle used to position the egg on screen.
"""
def __init__(self, egg_color, resources_dir):
"""
Creates a SelectionEgg object given an egg color and a resource location.
Args:
egg_color (str): The color egg that should be rendered.
resources_dir (str): The path of the resources directory.
"""
pygame.sprite.Sprite.__init__(self)
self.egg_color = egg_color
# Loads the JSON file of the egg to read in data.
with open(resources_dir + '/data/bloop_info/{0}.json'.format(egg_color), 'r') as save_file:
json_file = json.load(save_file)
save_file.close()
# Gets the description off the egg from the JSON file.
self.description = json_file.get('description')
self.contentedness = json_file.get('contentedness')
self.metabolism = json_file.get('metabolism')
# Load the egg from the given color and get the bounding rectangle for the image.
sprite_sheet = SpriteSheet(resources_dir + '/images/bloops/{0}/egg.png'.format(self.egg_color),
resources_dir + '/images/bloops/{0}/egg.json'.format(self.egg_color))
self._images = sprite_sheet.images
# Get the rectangle from the first image in the list
self.rect = self._images[0].get_rect()
self._index = 0
self.image = self._images[self._index]
def update(self):
"""
Update the sprite to the next animation frame.
"""
# Animate the sprite
self._index = (self._index + 1) % len(self._images)
self.image = self._images[self._index]
class InfoText:
"""
Class for drawing large amounts of text on the screen at a time
"""
def __init__(self, resources_dir, game_res, text='Test text.'):
"""
Creates an InfoText object to be used on a surface.
Args:
resources_dir (str): The full path of the game's resources directory
game_res (int): The internal resolution of the game. Used for correct scaling.
text (:obj:`str`, optional): The given text to render. Defaults to "Test text."'
"""
self.font = pygame.font.Font(resources_dir + '/fonts/5Pts5.ttf', 10)
self.text = [] # Text broken up into a list according to how it will fit on screen.
self.max_lines = 6 # Max number of lines to be shown on screen at a time.
self.offset = 0
self.game_res = game_res
# Arrow icons to indicate scrolling
self.up_arrow = pygame.image.load(resources_dir + '/images/gui/up_arrow.png').convert_alpha()
self.down_arrow = pygame.image.load(resources_dir + '/images/gui/down_arrow.png').convert_alpha()
raw_text = text # Copy the text to a different variable to be cut up.
margins = 4.5
max_line_width = self.game_res - (margins * 2) # The maximum pixel width that drawn text can be.
cut_chars = '.,! ' # Characters that will be considered "cuts" aka when a line break can occur.
# Prevents freezing if the end of the string does not end in a cut character
# Will fix eventually more elegantly
if raw_text[-1:] not in cut_chars:
raw_text += ' '
# Calculating line breaks.
while len(raw_text) > 0:
index = 0
test_text = '' # Chunk of text to pseudo-render and test the width of.
# Loops until the testing text has reached the size limit.
while True:
# Break if the current index is larger than the remaining text.
if index + 1 > len(raw_text):
index -= 1
break
# Add one character to the testing text from the raw text.
test_text += raw_text[index]
# Get the width of the pseudo-rendered text.
text_width = self.font.size(test_text)[0]
# Break if the text is larger than the defined max width.
if text_width > max_line_width:
break
index += 1
pass
# Gets the chunk of text to be added to the list.
text_chunk = raw_text[0:index + 1]
# Determines if the chunk of text has any break characters.
has_breaks = any(cut_chars in text_chunk for cut_chars in cut_chars)
# If the text has break characters, start with the last character and go backwards until
# one has been found, decreasing the index each time.
if has_breaks:
while raw_text[index] not in cut_chars:
index -= 1
text_chunk = raw_text[0:index + 1]
# If there are no break characters in the chunk, simply decrease the index by one and insert
# a dash at the end of the line to indicate the word continues.
else:
index -= 1
text_chunk = raw_text[0:index + 1]
text_chunk += '-'
# Append the text chunk to the list of text to draw.
self.text.append(text_chunk)
# Cut the text to repeat the process with the new cut string.
raw_text = raw_text[index + 1:]
def draw(self, surface):
"""
Draw the text on a given surface.
Args:
surface (:obj:`pygame.Surface`): The surface to draw the text on
"""
# Constants to help draw the text
line_separation = 7
left_margin = 3
top_margin = 25
bottom_margin = 10
# Draw the lines on the screen
for i in range(min(len(self.text), self.max_lines)):
text = self.font.render(self.text[i + self.offset], False, (64, 64, 64))
surface.blit(text, (left_margin, top_margin + (i * line_separation)))
# Draw the arrows if there is more text than is on screen.
if self.offset != 0:
surface.blit(self.up_arrow, ((self.game_res / 2) - (self.up_arrow.get_rect().width / 2), top_margin - 3))
if len(self.text) - (self.offset + 1) >= self.max_lines:
surface.blit(self.down_arrow,
((self.game_res / 2) - (self.down_arrow.get_rect().width / 2), self.game_res - bottom_margin))
def scroll_down(self):
"""
Scrolls the text on the screen down.
"""
# Ensures that the offset cannot be too big as to try to render non-existent lines.
if len(self.text) - (self.offset + 1) >= self.max_lines:
self.offset += 1
def scroll_up(self):
"""
Scrolls the text on the screen up.
"""
if self.offset > 0: # Ensures a non-zero offset is not possible.
self.offset -= 1
class EggInfo:
"""
Class to draw the contentedness and metabolism value off the egg on the info screen.
"""
def __init__(self, resources_dir, contentedness, metabolism, location):
self.contentedness = contentedness
self.metabolism = metabolism
self.x = location[0]
self.y = location[1]
# Create a new surface to blit onto the other surface
self.surface = pygame.Surface((44, 15), pygame.SRCALPHA)
# Blit the two indicator icons on screen
smiley = pygame.image.load(resources_dir + '/images/gui/smiley.png').convert_alpha()
self.surface.blit(smiley, (0, 0))
apple = pygame.image.load(resources_dir + '/images/gui/apple.png').convert_alpha()
self.surface.blit(apple, (1, 9))
# Draw 5 stars. If the value of the contentedness is less than the current star, make it a blank star.
for i in range(5):
if i < self.contentedness:
star = pygame.image.load(resources_dir + '/images/gui/star.png').convert_alpha()
else:
star = pygame.image.load(resources_dir + '/images/gui/blank_star.png').convert_alpha()
self.surface.blit(star, (11 + (i * 6), 1))
# Draw 5 stars. If the value of the metabolism is less than the current star, make it a blank star.
for i in range(5):
if i < self.metabolism:
star = pygame.image.load(resources_dir + '/images/gui/star.png').convert_alpha()
else:
star = pygame.image.load(resources_dir + '/images/gui/blank_star.png').convert_alpha()
self.surface.blit(star, (11 + (i * 6), 10))
def draw(self, surface):
"""
Draw the info icons on a given surface.
Args:
surface (:obj:`pygame.Surface`): The surface to draw the text on
"""
# Blit the info onto the given surface.
surface.blit(self.surface, (self.x, self.y))