Merge pull request #25 from n0remac/cleanup

Animations and Map Refactor
This commit is contained in:
2020-04-20 05:49:10 -05:00
committed by GitHub
43 changed files with 264 additions and 179 deletions

View File

@@ -3,12 +3,16 @@ config.py
Holds all constants used for setting up the game. Holds all constants used for setting up the game.
May later hold functions for loading/saving configuration files. May later hold functions for loading/saving configuration files.
""" """
import os import os
from enum import Enum
BASE_PATH = os.path.dirname(os.path.abspath(__file__)) BASE_PATH = os.path.dirname(os.path.abspath(__file__))
RESOURCES = os.path.join(BASE_PATH, "resources") RESOURCES = os.path.join(BASE_PATH, "resources")
IMAGES = os.path.join(RESOURCES, "images") IMAGES = os.path.join(RESOURCES, "images")
class Config(object): class Config(object):
""" """
A simple class dedicated to loading, storing and organizing constants. A simple class dedicated to loading, storing and organizing constants.
@@ -20,29 +24,50 @@ class Config(object):
SCREEN_TITLE = "Triple Dungeon" SCREEN_TITLE = "Triple Dungeon"
TILE_WIDTH = 63 TILE_WIDTH = 63
IDLE_UPDATES_PER_FRAME = 20 IDLE_UPDATES_PER_FRAME = 20
RUN_UPDATES_PER_FRAME = 10 RUN_UPDATES_PER_FRAME = 8
# Constants used to scale our sprites from their original size # Constants used to scale our sprites from their original size
CHARACTER_SCALING = 1 CHARACTER_SCALING = 1
TILE_SCALING = 2 TILE_SCALING = 2
# The number of pixels across the level
LEVEL_SIZE = 10 * TILE_SCALING * TILE_WIDTH
# Movement speed of player, in pixels per frame # Movement speed of player, in pixels per frame
PLAYER_MOVEMENT_SPEED = 7 PLAYER_MOVEMENT_SPEED = 7
# How many pixels to keep as a minimum margin between the character and the edge of the screen. # How many pixels to keep as a minimum margin between the characters and the edge of the screen.
LEFT_VIEWPORT_MARGIN = 250 LEFT_VIEWPORT_MARGIN = 250
RIGHT_VIEWPORT_MARGIN = 250 RIGHT_VIEWPORT_MARGIN = 250
BOTTOM_VIEWPORT_MARGIN = 50 BOTTOM_VIEWPORT_MARGIN = 50
TOP_VIEWPORT_MARGIN = 100 TOP_VIEWPORT_MARGIN = 100
class Sprites(object): class Enums(Enum):
"""
A simple class used for tracking different simple
"""
# Play Direction Enums
RIGHT = 0
LEFT = 1
UP = 2
DOWN = 3
IDLE = 4
class SpritePaths(object):
""" """
Simple class for holding sprite paths. Simple class for holding sprite paths.
""" """
__CHARACTERS = os.path.join(IMAGES, "characters")
__MONSTERS = os.path.join(IMAGES, "monsters") __MONSTERS = os.path.join(IMAGES, "monsters")
# Single frame sprites
SKELETON = os.path.join(__MONSTERS, "skeleton.png") SKELETON = os.path.join(__MONSTERS, "skeleton.png")
GHOST = os.path.join(__MONSTERS, "ghost", "ghost1.png") GHOST = os.path.join(__MONSTERS, "ghost", "ghost1.png")
FROG = os.path.join(__MONSTERS, "frog", "frog1.png") FROG = os.path.join(__MONSTERS, "frog", "frog1.png")
# Animated sprites
KNIGHT = os.path.join(__CHARACTERS, "knight")

View File

@@ -3,12 +3,13 @@ main.py
The main class used to load the game. The main class used to load the game.
Holds the main game window, as well as manages basic functions for organizing the game. Holds the main game window, as well as manages basic functions for organizing the game.
""" """
import random
import arcade import arcade
from config import Config
from map import Dungeon from map import Dungeon
from mobs import Player, Enemy from mobs import Player
from config import Config, Sprites
class Game(arcade.Window): class Game(arcade.Window):
@@ -25,11 +26,10 @@ class Game(arcade.Window):
self.wall_list = None self.wall_list = None
self.floor_list = None self.floor_list = None
self.enemy_list = None self.enemy_list = None
self.player_list = None
# Separate variable that holds the player sprite
self.player = None self.player = None
self.dungeon = None
# list to keep track of keypresses # list to keep track of keypresses
self.prev_keypress = [] self.prev_keypress = []
@@ -40,38 +40,30 @@ class Game(arcade.Window):
self.view_bottom = 0 self.view_bottom = 0
self.view_left = 0 self.view_left = 0
arcade.set_background_color(arcade.csscolor.BLACK) arcade.set_background_color(arcade.color.BLACK)
def setup(self): def setup(self):
""" Set up the game here. Call this function to restart the game. """ """ Set up the game here. Call this function to restart the game. """
# Create the Sprite lists # Create the Sprite lists
self.player_list = arcade.SpriteList()
self.wall_list = arcade.SpriteList()
self.floor_list = arcade.SpriteList()
self.enemy_list = arcade.SpriteList() self.enemy_list = arcade.SpriteList()
# Set up the player, specifically placing it at these coordinates. # Set up the player, specifically placing it at these coordinates.
self.player = Player() self.player = Player()
self.player.scale = 1 self.player.scale = 1
self.player.center_x = Config.SCREEN_WIDTH / 2
self.player.center_y = Config.SCREEN_HEIGHT / 2
self.player_list = self.player
# Create the dungeon # Create the dungeon
dungeon = Dungeon() self.dungeon = Dungeon(0, 3)
self.floor_list = dungeon.floor_list self.player.center_x, self.player.center_y = random.choice(self.dungeon.levelList).center()
self.wall_list = dungeon.wall_list
# Create monsters # Create monsters
# This needs to be updated to comply with the new mobs.py code # This needs to be updated to comply with the new mobs.py code
#self.enemy_list.append(Enemy("resources/images/monsters/ghost/ghost1.png", 200, 200, 4).get_enemy()) # self.enemy_list.append(Enemy("resources/images/monsters/ghost/ghost1.png", 200, 200, 4))
#self.enemy_list.append(Enemy("resources/images/monsters/frog/frog1.png", 200, 1000, 4).get_enemy()) # self.enemy_list.append(Enemy("resources/images/monsters/frog/frog1.png", 200, 1000, 4))
# Create the 'physics engine' # Create the 'physics engine'
self.physics_engine = arcade.PhysicsEngineSimple(self.player, self.wall_list) self.physics_engine = arcade.PhysicsEngineSimple(self.player, self.dungeon.getWalls())
def on_draw(self): def on_draw(self):
""" Render the screen. """ """ Render the screen. """
@@ -80,25 +72,28 @@ class Game(arcade.Window):
arcade.start_render() arcade.start_render()
# Draw our sprites # Draw our sprites
self.floor_list.draw() self.dungeon.render()
self.player_list.draw() self.player.draw()
self.enemy_list.draw() self.enemy_list.draw()
self.wall_list.draw() self.wall_list.draw()
x, y = self.player.center_x, self.player.center_y + 100
arcade.draw_text(f"({x}, {y})", x, y, arcade.color.WHITE, 15)
def on_key_press(self, key, modifiers): def on_key_press(self, key, modifiers):
"""Called whenever a key is pressed. """ """Called whenever a key is pressed. """
if key == arcade.key.UP or key == arcade.key.W: if key == arcade.key.UP or key == arcade.key.W:
self.player_list.change_y = Config.PLAYER_MOVEMENT_SPEED self.player.change_y = Config.PLAYER_MOVEMENT_SPEED
self.prev_keypress.append(key) self.prev_keypress.append(key)
elif key == arcade.key.DOWN or key == arcade.key.S: elif key == arcade.key.DOWN or key == arcade.key.S:
self.player_list.change_y = -Config.PLAYER_MOVEMENT_SPEED self.player.change_y = -Config.PLAYER_MOVEMENT_SPEED
self.prev_keypress.append(key) self.prev_keypress.append(key)
elif key == arcade.key.LEFT or key == arcade.key.A: elif key == arcade.key.LEFT or key == arcade.key.A:
self.player_list.change_x = -Config.PLAYER_MOVEMENT_SPEED self.player.change_x = -Config.PLAYER_MOVEMENT_SPEED
self.prev_keypress.append(key) self.prev_keypress.append(key)
elif key == arcade.key.RIGHT or key == arcade.key.D: elif key == arcade.key.RIGHT or key == arcade.key.D:
self.player_list.change_x = Config.PLAYER_MOVEMENT_SPEED self.player.change_x = Config.PLAYER_MOVEMENT_SPEED
self.prev_keypress.append(key) self.prev_keypress.append(key)
elif key == 65307: elif key == 65307:
self.close() self.close()
@@ -107,16 +102,16 @@ class Game(arcade.Window):
"""Called when the user releases a key. """ """Called when the user releases a key. """
if key == arcade.key.UP or key == arcade.key.W: if key == arcade.key.UP or key == arcade.key.W:
self.player_list.change_y = 0 self.player.change_y = 0
self.prev_keypress.remove(key) self.prev_keypress.remove(key)
elif key == arcade.key.DOWN or key == arcade.key.S: elif key == arcade.key.DOWN or key == arcade.key.S:
self.player_list.change_y = 0 self.player.change_y = 0
self.prev_keypress.remove(key) self.prev_keypress.remove(key)
elif key == arcade.key.LEFT or key == arcade.key.A: elif key == arcade.key.LEFT or key == arcade.key.A:
self.player_list.change_x = 0 self.player.change_x = 0
self.prev_keypress.remove(key) self.prev_keypress.remove(key)
elif key == arcade.key.RIGHT or key == arcade.key.D: elif key == arcade.key.RIGHT or key == arcade.key.D:
self.player_list.change_x = 0 self.player.change_x = 0
self.prev_keypress.remove(key) self.prev_keypress.remove(key)
if self.prev_keypress: if self.prev_keypress:
self.on_key_press(self.prev_keypress.pop(0), 0) self.on_key_press(self.prev_keypress.pop(0), 0)
@@ -127,29 +122,29 @@ class Game(arcade.Window):
# Move the player with the physics engine # Move the player with the physics engine
self.physics_engine.update() self.physics_engine.update()
self.player_list.update_animation() self.player.update_animation()
changed = False # Track if we need to change the viewport changed = False # Track if we need to change the viewport
# Below manages all scrolling mechanics # Below manages all scrolling mechanics
# Scroll left # Scroll left
left_boundary = self.view_left + Config.LEFT_VIEWPORT_MARGIN left_boundary = self.view_left + Config.LEFT_VIEWPORT_MARGIN
if self.player_list.left < left_boundary: if self.player.left < left_boundary:
self.view_left -= left_boundary - self.player_list.left self.view_left -= left_boundary - self.player.left
changed = True changed = True
# Scroll right # Scroll right
right_boundary = self.view_left + Config.SCREEN_WIDTH - Config.RIGHT_VIEWPORT_MARGIN right_boundary = self.view_left + Config.SCREEN_WIDTH - Config.RIGHT_VIEWPORT_MARGIN
if self.player_list.right > right_boundary: if self.player.right > right_boundary:
self.view_left += self.player_list.right - right_boundary self.view_left += self.player.right - right_boundary
changed = True changed = True
# Scroll up # Scroll up
top_boundary = self.view_bottom + Config.SCREEN_HEIGHT - Config.TOP_VIEWPORT_MARGIN top_boundary = self.view_bottom + Config.SCREEN_HEIGHT - Config.TOP_VIEWPORT_MARGIN
if self.player_list.top > top_boundary: if self.player.top > top_boundary:
self.view_bottom += self.player_list.top - top_boundary self.view_bottom += self.player.top - top_boundary
changed = True changed = True
# Scroll down # Scroll down
bottom_boundary = self.view_bottom + Config.BOTTOM_VIEWPORT_MARGIN bottom_boundary = self.view_bottom + Config.BOTTOM_VIEWPORT_MARGIN
if self.player_list.bottom < bottom_boundary: if self.player.bottom < bottom_boundary:
self.view_bottom -= bottom_boundary - self.player_list.bottom self.view_bottom -= bottom_boundary - self.player.bottom
changed = True changed = True
if changed: if changed:

View File

@@ -5,12 +5,16 @@ Pathfinding will also depend on objects here, and is thus integral to it's funct
""" """
from __future__ import annotations from __future__ import annotations
from config import Config
import json
import arcade import arcade
import json
import numpy as np import numpy as np
from itertools import chain
from config import Config
class Dungeon(object): class Dungeon(object):
""" """
@@ -24,44 +28,37 @@ class Dungeon(object):
:param level_count: The number of Active Levels that should be stored within the Dungeon. :param level_count: The number of Active Levels that should be stored within the Dungeon.
:param size: The diameter of the dungeon. Allows for a total of size^2 slots for levels. :param size: The diameter of the dungeon. Allows for a total of size^2 slots for levels.
""" """
# setup
self.level_count = level_count
self.size = size
self.floor_list = arcade.SpriteList() self.floor_list = arcade.SpriteList()
self.wall_list = arcade.SpriteList() self.wall_list = arcade.SpriteList()
level_size = 10 * Config.TILE_SCALING * Config.TILE_WIDTH
# get center level # center = Level.load_file(1, 1, 'resources/levels/map1/center.json')
# side = Level.load_file(2, 1, 'resources/levels/map1/room.json')
center = Level() center = "resources/levels/map1/center.json"
center.load_file('resources/levels/map1/center.json') self.levels = [
center.render() [Level.load_file(x, y, center) for y in range(size)] for x in range(size)
center_floor, center_wall = center.floor_list, center.wall_list ]
self.floor_list.extend(center_floor)
self.wall_list.extend(center_wall)
# get a side room def getWalls(self) -> arcade.SpriteList:
room = Level() """
room.load_file('resources/levels/map1/room.json') Simple one time function for getting all Wall sprites from all Levels.
room.rotate_level(2) Used by the physics engine during game startup.
room.render()
room_floor, room_wall = room.get_lists()
room_floor.move(level_size, 0)
room_wall.move(level_size, 0)
self.floor_list.extend(room_floor)
self.wall_list.extend(room_wall)
# get a side room :return: A SpriteList containing all Sprites.
room = Level() """
room.load_file('resources/levels/map1/room.json')
room.render()
room_floor, room_wall = room.get_lists()
room_floor.move(-level_size, 0)
room_wall.move(-level_size, 0)
self.floor_list.extend(room_floor)
self.wall_list.extend(room_wall)
def add_level(self, sprit_list): walls = arcade.SpriteList()
for x in sprit_list: walls.extend(
self.levels.append(x) list(chain.from_iterable(
chain.from_iterable([level.wallSprites for level in column if level is not None]) for column in
self.levels
))
)
return walls
def render(self) -> None: def render(self) -> None:
""" """
@@ -71,7 +68,19 @@ class Dungeon(object):
for column in self.levels: for column in self.levels:
for level in column: for level in column:
if level is not None: if level is not None:
level.render() level.floorSprites.draw()
level.wallSprites.draw()
@property
def levelList(self) -> list:
"""
Retrieves all Level objects from Dungeon instance.
:return: A list containing all Level objects.
"""
return list(filter(
lambda level: level is not None, chain.from_iterable(self.levels)
))
class Level: class Level:
@@ -91,60 +100,70 @@ class Level:
self.x, self.y = level_x, level_y self.x, self.y = level_x, level_y
self.sprites = [] self.sprites = []
self.level = [] self.structure = []
self.floorSprites = arcade.SpriteList()
self.wallSprites = arcade.SpriteList()
# Tuples containing the Node positions of where walls, floor and entrances are. # Tuples containing the Node positions of where walls, floor and entrances are.
# All positions are generated based on the level's X and Y position, so that all points within # All positions are generated based on the level's X and Y position, so that all points within
# the dungeon can be mapped by a proper pathfinding system. # the dungeon can be mapped by a proper pathfinding system.
self.floor_list = [] self.floor_list = []
self.wall_list = [] self.wall_list = []
# self.entrances = []
def load_file(self, path: str): @staticmethod
def load_file(level_x: int, level_y: int, path: str) -> Level:
""" """
Builds a Level from a given file path. Builds a Level from a given file path.
:param level_x: The level's X position within the Dungeon level matrix.
:param level_y: The level's Y position within the Dungeon level matrix.
:param path: Path to the Level file. :param path: Path to the Level file.
:return: The new generated Level file. :return: The new generated Level file.
""" """
self.floor_list = arcade.SpriteList()
self.wall_list = arcade.SpriteList()
level = Level(level_x, level_y)
with open(path) as file: with open(path) as file:
level = json.load(file) data = json.load(file)
# Loads elements and structure data from level file
level.sprites = data['elements']
level.structure = data['structure']
self.sprites = level['elements'] tile_scale = Config.TILE_WIDTH * Config.TILE_SCALING
self.level = level['structure']
def render(self) -> None: # Places all of the tiles & sprites
for x in range(0, 10):
for y in range(0, 10):
tilePath = level.sprites[level.structure[x][y]]
sprite = arcade.Sprite(tilePath, Config.TILE_SCALING)
sprite.center_x, sprite.center_y = x * tile_scale, y * tile_scale
if 'floor' in tilePath:
level.floorSprites.append(sprite)
elif 'wall' in tilePath:
level.wallSprites.append(sprite)
else:
print(f'Could not handle Tile: {tilePath}')
# Move everything into correct positions
level.floorSprites.move(*level.center())
level.wallSprites.move(*level.center())
return level
def center(self) -> tuple:
""" """
Calls render on all sprites. Returns the pixel center of the level.
:return: A tuple containing the X and Y coordinates of the level's center
""" """
x = 0 return self.x * Config.LEVEL_SIZE, self.y * Config.LEVEL_SIZE
y = 0
level_size = 10 * Config.TILE_SCALING * Config.TILE_WIDTH
# Create the level
# This shows using a loop to place multiple sprites horizontally and vertically
for y_pos in range(0, level_size , 63 * Config.TILE_SCALING):
for x_pos in range(0, level_size, 63 * Config.TILE_SCALING):
cur_tile = self.level[y][x]
sprite = self.sprites[cur_tile]
floor = arcade.Sprite(sprite, Config.TILE_SCALING)
floor.center_x = x_pos
floor.center_y = y_pos
if cur_tile == ' ':
self.floor_list.append(floor)
elif cur_tile == 'w':
self.wall_list.append(floor)
x += 1
x = 0
y += 1
def get_lists(self):
return self.floor_list, self.wall_list
def rotate_level(self, times_rotated): def rotate_level(self, times_rotated):
"""
Rotates the
:param times_rotated:
:return:
"""
m = np.array(self.level) m = np.array(self.level)
for i in range(0, times_rotated % 4): for i in range(0, times_rotated % 4):
m = np.rot90(m) m = np.rot90(m)

View File

@@ -5,19 +5,15 @@ Organizes all classes related to Mobs, Entities, Enemies, Players and Items.
import arcade import arcade
from config import Config, Sprites from config import Config, Enums, SpritePaths
from sprites import PlayerAnimations
# Constants used to track if the player is facing left or right
RIGHT_FACING = 0
LEFT_FACING = 1
FRONT_FACING = 2
UP_FACING = 3
DOWN_FACING = 4
class Mob(arcade.Sprite): class Mob(arcade.Sprite):
""" """
Represents a Mob. No defined behaviour, it has no intelligence. Represents a Mob. No defined behaviour, it has no intelligence.
""" """
def __init__(self, max_health=100, max_armor=0, *args, **kwargs) -> None: def __init__(self, max_health=100, max_armor=0, *args, **kwargs) -> None:
# Set up parent class # Set up parent class
super().__init__() super().__init__()
@@ -42,70 +38,52 @@ class Player(Mob):
Represents a Player. Represents a Player.
While this is a instance, there should only be one in the world at any given time. While this is a instance, there should only be one in the world at any given time.
""" """
def __init__(self, *args, **kwargs) -> None: def __init__(self, *args, **kwargs) -> None:
super(Player, self).__init__(*args, **kwargs) super(Player, self).__init__(*args, **kwargs)
main_path = "resources/images/character/knight/" self.animations = PlayerAnimations(SpritePaths.KNIGHT)
# Used for mapping directions to animations
self.map = {
Enums.IDLE: self.animations.idles,
Enums.UP: self.animations.up,
Enums.DOWN: self.animations.down,
Enums.RIGHT: self.animations.right,
Enums.LEFT: self.animations.left
}
# Default to face-front self.refreshIndex = 0
self.character_face_direction = FRONT_FACING self.prev = Enums.IDLE
self.texture = next(self.map[self.prev])
# Load textures for idle standing def update_animation(self, delta_time: float = 1 / 60) -> None:
for i in range(4): """
texture = arcade.load_texture(f"{main_path}knight iso char_idle_{i}.png") Updates animations for the Player.
self.idle_textures.append(texture) :param delta_time: No idea.
"""
# Load textures for running horizontally # Increase the refresh index according
for i in range(6): self.refreshIndex = (self.refreshIndex + 1) % Config.RUN_UPDATES_PER_FRAME
self.walking_textures.append([arcade.load_texture(f"{main_path}knight iso char_run left_{i}.png"),arcade.load_texture(f"{main_path}knight iso char_run left_{i}.png", mirrored=True)])
# Load textures for running down # Logic to determine what direction we're in.
for i in range(5):
self.down_textures.append(arcade.load_texture(f"{main_path}knight iso char_run down_{i}.png"))
# Load textures for running up
for i in range(5):
self.up_textures.append(arcade.load_texture(f"{main_path}knight iso char_run up_{i}.png"))
def update_animation(self, delta_time: float = 1/60):
# Figure out if we need to flip face left, right, up, or down
if self.change_x > 0:
self.character_face_direction = LEFT_FACING
elif self.change_x < 0:
self.character_face_direction = RIGHT_FACING
elif self.change_x == 0 and self.change_y == 0:
self.character_face_direction = FRONT_FACING
# idle animation
if self.change_x == 0 and self.change_y == 0: if self.change_x == 0 and self.change_y == 0:
self.cur_texture += 1 cur = Enums.IDLE
if self.cur_texture > 3 * Config.IDLE_UPDATES_PER_FRAME: elif self.change_y > 0: # Up
self.cur_texture = 0 cur = Enums.UP
self.texture = self.idle_textures[self.cur_texture // Config.IDLE_UPDATES_PER_FRAME] elif self.change_y < 0: # Down
return cur = Enums.DOWN
elif self.change_x > 0: # Left
cur = Enums.RIGHT
elif self.change_x < 0: # Right
cur = Enums.LEFT
else: # Idle
cur = Enums.IDLE
#walk up animation # If we're in a new direction or the refresh index has reset
if self.change_y > 0: if self.prev is not cur or self.refreshIndex == 0:
self.cur_texture += 1 self.texture = next(self.map[cur])
if self.cur_texture > 4 * Config.RUN_UPDATES_PER_FRAME:
self.cur_texture = 0
self.texture = self.up_textures[self.cur_texture // Config.RUN_UPDATES_PER_FRAME]
return
#walk down animation self.prev = cur
if self.change_y < 0:
self.cur_texture += 1
if self.cur_texture > 4 * Config.RUN_UPDATES_PER_FRAME:
self.cur_texture = 0
self.texture = self.down_textures[self.cur_texture // Config.RUN_UPDATES_PER_FRAME]
return
# Walking left/right animation
self.cur_texture += 1
if self.cur_texture > 5 * Config.RUN_UPDATES_PER_FRAME:
self.cur_texture = 0
self.texture = self.walking_textures[self.cur_texture // Config.RUN_UPDATES_PER_FRAME][self.character_face_direction]
def tick(self): def tick(self):
""" """
@@ -120,12 +98,10 @@ class Enemy(Mob):
Represents an Enemy Mob. Represents an Enemy Mob.
Will take basic offensive actions against Player objects. Will take basic offensive actions against Player objects.
""" """
def __init__(self, *args, **kwargs) -> None: def __init__(self, *args, **kwargs) -> None:
super(Enemy, self).__init__(*args, **kwargs) super(Enemy, self).__init__(*args, **kwargs)
def get_enemy(self):
return self
def tick(self) -> None: def tick(self) -> None:
""" """
A on_update function, the Enemy Mob should scan for the player, decide how to path to it, and A on_update function, the Enemy Mob should scan for the player, decide how to path to it, and

View File

Before

Width:  |  Height:  |  Size: 597 B

After

Width:  |  Height:  |  Size: 597 B

View File

Before

Width:  |  Height:  |  Size: 597 B

After

Width:  |  Height:  |  Size: 597 B

View File

Before

Width:  |  Height:  |  Size: 621 B

After

Width:  |  Height:  |  Size: 621 B

View File

Before

Width:  |  Height:  |  Size: 621 B

After

Width:  |  Height:  |  Size: 621 B

View File

Before

Width:  |  Height:  |  Size: 547 B

After

Width:  |  Height:  |  Size: 547 B

View File

Before

Width:  |  Height:  |  Size: 566 B

After

Width:  |  Height:  |  Size: 566 B

View File

Before

Width:  |  Height:  |  Size: 545 B

After

Width:  |  Height:  |  Size: 545 B

View File

Before

Width:  |  Height:  |  Size: 570 B

After

Width:  |  Height:  |  Size: 570 B

View File

Before

Width:  |  Height:  |  Size: 583 B

After

Width:  |  Height:  |  Size: 583 B

View File

Before

Width:  |  Height:  |  Size: 438 B

After

Width:  |  Height:  |  Size: 438 B

View File

Before

Width:  |  Height:  |  Size: 498 B

After

Width:  |  Height:  |  Size: 498 B

View File

Before

Width:  |  Height:  |  Size: 508 B

After

Width:  |  Height:  |  Size: 508 B

View File

Before

Width:  |  Height:  |  Size: 444 B

After

Width:  |  Height:  |  Size: 444 B

View File

Before

Width:  |  Height:  |  Size: 476 B

After

Width:  |  Height:  |  Size: 476 B

View File

Before

Width:  |  Height:  |  Size: 488 B

After

Width:  |  Height:  |  Size: 488 B

View File

Before

Width:  |  Height:  |  Size: 441 B

After

Width:  |  Height:  |  Size: 441 B

View File

Before

Width:  |  Height:  |  Size: 499 B

After

Width:  |  Height:  |  Size: 499 B

View File

Before

Width:  |  Height:  |  Size: 509 B

After

Width:  |  Height:  |  Size: 509 B

View File

Before

Width:  |  Height:  |  Size: 451 B

After

Width:  |  Height:  |  Size: 451 B

View File

Before

Width:  |  Height:  |  Size: 480 B

After

Width:  |  Height:  |  Size: 480 B

View File

Before

Width:  |  Height:  |  Size: 498 B

After

Width:  |  Height:  |  Size: 498 B

View File

Before

Width:  |  Height:  |  Size: 409 B

After

Width:  |  Height:  |  Size: 409 B

View File

Before

Width:  |  Height:  |  Size: 429 B

After

Width:  |  Height:  |  Size: 429 B

View File

Before

Width:  |  Height:  |  Size: 435 B

After

Width:  |  Height:  |  Size: 435 B

View File

Before

Width:  |  Height:  |  Size: 449 B

After

Width:  |  Height:  |  Size: 449 B

View File

Before

Width:  |  Height:  |  Size: 498 B

After

Width:  |  Height:  |  Size: 498 B

View File

Before

Width:  |  Height:  |  Size: 547 B

After

Width:  |  Height:  |  Size: 547 B

View File

Before

Width:  |  Height:  |  Size: 552 B

After

Width:  |  Height:  |  Size: 552 B

View File

Before

Width:  |  Height:  |  Size: 767 B

After

Width:  |  Height:  |  Size: 767 B

View File

Before

Width:  |  Height:  |  Size: 441 B

After

Width:  |  Height:  |  Size: 441 B

View File

Before

Width:  |  Height:  |  Size: 569 B

After

Width:  |  Height:  |  Size: 569 B

View File

Before

Width:  |  Height:  |  Size: 481 B

After

Width:  |  Height:  |  Size: 481 B

View File

Before

Width:  |  Height:  |  Size: 438 B

After

Width:  |  Height:  |  Size: 438 B

View File

Before

Width:  |  Height:  |  Size: 563 B

After

Width:  |  Height:  |  Size: 563 B

View File

Before

Width:  |  Height:  |  Size: 486 B

After

Width:  |  Height:  |  Size: 486 B

View File

Before

Width:  |  Height:  |  Size: 383 B

After

Width:  |  Height:  |  Size: 383 B

View File

Before

Width:  |  Height:  |  Size: 396 B

After

Width:  |  Height:  |  Size: 396 B

View File

Before

Width:  |  Height:  |  Size: 633 B

After

Width:  |  Height:  |  Size: 633 B

70
triple-dungeon/sprites.py Normal file
View File

@@ -0,0 +1,70 @@
"""
sprites.py
A file dedicated to managing sprites and animations for characters.
"""
from itertools import cycle
import arcade
import os
import re
from typing import Pattern
class AnimationSet(object):
"""
A class that helps assist with grabbing new animations from a set.
"""
def __init__(self, directory: str):
"""
Initializes the AnimationSet class by loading files and
:param directory: A directory containing valid animation files in the correct format.
"""
self.directory = directory
self.animations = os.listdir(directory)
def getAnimations(self, pattern: Pattern) -> iter:
"""
Loads all animations from the AnimationSet's directory that match the pattern.
The pattern must have 1 group that specifies the animation's index.
:param pattern: A RegEx Pattern object.
:return: A infinite iterable looping through arcade.Texture objects.
"""
# Finds all matching files
matches = map(lambda file: re.match(pattern, file), self.animations)
matches = list(filter(lambda match: match is not None, matches))
# Sort in ascending order based on the connected animation index. Zero-indexing or not does not affect order.
matches.sort(key=lambda match: int(match.group(1)))
# Grab the filename and load it to the file directory
matches = list(map(lambda match: arcade.load_texture(os.path.join(self.directory, match.group(0))), matches))
return cycle(matches)
class PlayerAnimations(AnimationSet):
"""
A class dedicated to serving player animations.
Player animations must be served to the class in the correct format.
The correct format is: {action}[_{direction}]_{index}.png
action: [idle, run, slice] - The action being taken.
direction: [down, right, left, up] - The direction of the action, if applicable. Omit the underscore if not.
index: [0,) - The index of the animation. Index should be enumerated in ascending order.
"""
def __init__(self, directory: str):
"""
Initializes the PlayerAnimations class.
"""
super(PlayerAnimations, self).__init__(directory)
# Grabs all animations needed. These are infinite iters, use next(iter) to grab the next animation.
self.idles = self.getAnimations(re.compile(r'idle_(\d+).png'))
self.down = self.getAnimations(re.compile(r'run_down_(\d+).png'))
self.right = self.getAnimations(re.compile(r'run_right_(\d+).png'))
self.up = self.getAnimations(re.compile(r'run_up_(\d+).png'))
self.left = self.getAnimations(re.compile(r'run_left_(\d+).png'))