Merge pull request #25 from n0remac/cleanup
Animations and Map Refactor
@@ -3,12 +3,16 @@ config.py
|
||||
Holds all constants used for setting up the game.
|
||||
May later hold functions for loading/saving configuration files.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from enum import Enum
|
||||
|
||||
BASE_PATH = os.path.dirname(os.path.abspath(__file__))
|
||||
RESOURCES = os.path.join(BASE_PATH, "resources")
|
||||
IMAGES = os.path.join(RESOURCES, "images")
|
||||
|
||||
|
||||
class Config(object):
|
||||
"""
|
||||
A simple class dedicated to loading, storing and organizing constants.
|
||||
@@ -20,29 +24,50 @@ class Config(object):
|
||||
SCREEN_TITLE = "Triple Dungeon"
|
||||
TILE_WIDTH = 63
|
||||
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
|
||||
CHARACTER_SCALING = 1
|
||||
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
|
||||
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
|
||||
RIGHT_VIEWPORT_MARGIN = 250
|
||||
BOTTOM_VIEWPORT_MARGIN = 50
|
||||
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.
|
||||
"""
|
||||
|
||||
__CHARACTERS = os.path.join(IMAGES, "characters")
|
||||
__MONSTERS = os.path.join(IMAGES, "monsters")
|
||||
|
||||
# Single frame sprites
|
||||
SKELETON = os.path.join(__MONSTERS, "skeleton.png")
|
||||
GHOST = os.path.join(__MONSTERS, "ghost", "ghost1.png")
|
||||
FROG = os.path.join(__MONSTERS, "frog", "frog1.png")
|
||||
|
||||
# Animated sprites
|
||||
KNIGHT = os.path.join(__CHARACTERS, "knight")
|
||||
|
||||
@@ -3,12 +3,13 @@ main.py
|
||||
The main class used to load the game.
|
||||
Holds the main game window, as well as manages basic functions for organizing the game.
|
||||
"""
|
||||
import random
|
||||
|
||||
import arcade
|
||||
|
||||
from config import Config
|
||||
from map import Dungeon
|
||||
from mobs import Player, Enemy
|
||||
from config import Config, Sprites
|
||||
from mobs import Player
|
||||
|
||||
|
||||
class Game(arcade.Window):
|
||||
@@ -25,11 +26,10 @@ class Game(arcade.Window):
|
||||
self.wall_list = None
|
||||
self.floor_list = None
|
||||
self.enemy_list = None
|
||||
self.player_list = None
|
||||
|
||||
# Separate variable that holds the player sprite
|
||||
self.player = None
|
||||
|
||||
self.dungeon = None
|
||||
|
||||
# list to keep track of keypresses
|
||||
self.prev_keypress = []
|
||||
|
||||
@@ -40,38 +40,30 @@ class Game(arcade.Window):
|
||||
self.view_bottom = 0
|
||||
self.view_left = 0
|
||||
|
||||
arcade.set_background_color(arcade.csscolor.BLACK)
|
||||
arcade.set_background_color(arcade.color.BLACK)
|
||||
|
||||
def setup(self):
|
||||
""" Set up the game here. Call this function to restart the game. """
|
||||
# Create the Sprite lists
|
||||
|
||||
self.player_list = arcade.SpriteList()
|
||||
self.wall_list = arcade.SpriteList()
|
||||
self.floor_list = arcade.SpriteList()
|
||||
self.enemy_list = arcade.SpriteList()
|
||||
|
||||
# Set up the player, specifically placing it at these coordinates.
|
||||
self.player = Player()
|
||||
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
|
||||
dungeon = Dungeon()
|
||||
self.dungeon = Dungeon(0, 3)
|
||||
|
||||
self.floor_list = dungeon.floor_list
|
||||
self.wall_list = dungeon.wall_list
|
||||
self.player.center_x, self.player.center_y = random.choice(self.dungeon.levelList).center()
|
||||
|
||||
# Create monsters
|
||||
# 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/frog/frog1.png", 200, 1000, 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))
|
||||
|
||||
# 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):
|
||||
""" Render the screen. """
|
||||
@@ -80,43 +72,46 @@ class Game(arcade.Window):
|
||||
arcade.start_render()
|
||||
|
||||
# Draw our sprites
|
||||
self.floor_list.draw()
|
||||
self.player_list.draw()
|
||||
self.dungeon.render()
|
||||
self.player.draw()
|
||||
self.enemy_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):
|
||||
"""Called whenever a key is pressed. """
|
||||
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
elif key == 65307:
|
||||
self.close()
|
||||
self.close()
|
||||
|
||||
def on_key_release(self, key, modifiers):
|
||||
"""Called when the user releases a key. """
|
||||
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
if self.prev_keypress:
|
||||
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
|
||||
self.physics_engine.update()
|
||||
|
||||
self.player_list.update_animation()
|
||||
self.player.update_animation()
|
||||
changed = False # Track if we need to change the viewport
|
||||
|
||||
# Below manages all scrolling mechanics
|
||||
# Scroll left
|
||||
left_boundary = self.view_left + Config.LEFT_VIEWPORT_MARGIN
|
||||
if self.player_list.left < left_boundary:
|
||||
self.view_left -= left_boundary - self.player_list.left
|
||||
if self.player.left < left_boundary:
|
||||
self.view_left -= left_boundary - self.player.left
|
||||
changed = True
|
||||
# Scroll right
|
||||
right_boundary = self.view_left + Config.SCREEN_WIDTH - Config.RIGHT_VIEWPORT_MARGIN
|
||||
if self.player_list.right > right_boundary:
|
||||
self.view_left += self.player_list.right - right_boundary
|
||||
if self.player.right > right_boundary:
|
||||
self.view_left += self.player.right - right_boundary
|
||||
changed = True
|
||||
# Scroll up
|
||||
top_boundary = self.view_bottom + Config.SCREEN_HEIGHT - Config.TOP_VIEWPORT_MARGIN
|
||||
if self.player_list.top > top_boundary:
|
||||
self.view_bottom += self.player_list.top - top_boundary
|
||||
if self.player.top > top_boundary:
|
||||
self.view_bottom += self.player.top - top_boundary
|
||||
changed = True
|
||||
# Scroll down
|
||||
bottom_boundary = self.view_bottom + Config.BOTTOM_VIEWPORT_MARGIN
|
||||
if self.player_list.bottom < bottom_boundary:
|
||||
self.view_bottom -= bottom_boundary - self.player_list.bottom
|
||||
if self.player.bottom < bottom_boundary:
|
||||
self.view_bottom -= bottom_boundary - self.player.bottom
|
||||
changed = True
|
||||
|
||||
if changed:
|
||||
|
||||
@@ -5,12 +5,16 @@ Pathfinding will also depend on objects here, and is thus integral to it's funct
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
from config import Config
|
||||
|
||||
import json
|
||||
|
||||
import arcade
|
||||
import json
|
||||
import numpy as np
|
||||
|
||||
from itertools import chain
|
||||
|
||||
from config import Config
|
||||
|
||||
|
||||
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 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.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.load_file('resources/levels/map1/center.json')
|
||||
center.render()
|
||||
center_floor, center_wall = center.floor_list, center.wall_list
|
||||
self.floor_list.extend(center_floor)
|
||||
self.wall_list.extend(center_wall)
|
||||
center = "resources/levels/map1/center.json"
|
||||
self.levels = [
|
||||
[Level.load_file(x, y, center) for y in range(size)] for x in range(size)
|
||||
]
|
||||
|
||||
# get a side room
|
||||
room = Level()
|
||||
room.load_file('resources/levels/map1/room.json')
|
||||
room.rotate_level(2)
|
||||
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 getWalls(self) -> arcade.SpriteList:
|
||||
"""
|
||||
Simple one time function for getting all Wall sprites from all Levels.
|
||||
Used by the physics engine during game startup.
|
||||
|
||||
# get a side room
|
||||
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)
|
||||
:return: A SpriteList containing all Sprites.
|
||||
"""
|
||||
|
||||
def add_level(self, sprit_list):
|
||||
for x in sprit_list:
|
||||
self.levels.append(x)
|
||||
walls = arcade.SpriteList()
|
||||
walls.extend(
|
||||
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:
|
||||
"""
|
||||
@@ -71,7 +68,19 @@ class Dungeon(object):
|
||||
for column in self.levels:
|
||||
for level in column:
|
||||
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:
|
||||
@@ -91,60 +100,70 @@ class Level:
|
||||
|
||||
self.x, self.y = level_x, level_y
|
||||
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.
|
||||
# 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.
|
||||
self.floor_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.
|
||||
|
||||
: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.
|
||||
: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:
|
||||
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']
|
||||
self.level = level['structure']
|
||||
tile_scale = Config.TILE_WIDTH * Config.TILE_SCALING
|
||||
|
||||
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
|
||||
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
|
||||
return self.x * Config.LEVEL_SIZE, self.y * Config.LEVEL_SIZE
|
||||
|
||||
def rotate_level(self, times_rotated):
|
||||
"""
|
||||
Rotates the
|
||||
:param times_rotated:
|
||||
:return:
|
||||
"""
|
||||
m = np.array(self.level)
|
||||
for i in range(0, times_rotated % 4):
|
||||
m = np.rot90(m)
|
||||
|
||||
@@ -5,19 +5,15 @@ Organizes all classes related to Mobs, Entities, Enemies, Players and Items.
|
||||
|
||||
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):
|
||||
"""
|
||||
Represents a Mob. No defined behaviour, it has no intelligence.
|
||||
"""
|
||||
|
||||
def __init__(self, max_health=100, max_armor=0, *args, **kwargs) -> None:
|
||||
# Set up parent class
|
||||
super().__init__()
|
||||
@@ -42,70 +38,52 @@ class Player(Mob):
|
||||
Represents a Player.
|
||||
While this is a instance, there should only be one in the world at any given time.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
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.character_face_direction = FRONT_FACING
|
||||
self.refreshIndex = 0
|
||||
self.prev = Enums.IDLE
|
||||
self.texture = next(self.map[self.prev])
|
||||
|
||||
# Load textures for idle standing
|
||||
for i in range(4):
|
||||
texture = arcade.load_texture(f"{main_path}knight iso char_idle_{i}.png")
|
||||
self.idle_textures.append(texture)
|
||||
def update_animation(self, delta_time: float = 1 / 60) -> None:
|
||||
"""
|
||||
Updates animations for the Player.
|
||||
:param delta_time: No idea.
|
||||
"""
|
||||
|
||||
# Load textures for running horizontally
|
||||
for i in range(6):
|
||||
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)])
|
||||
# Increase the refresh index according
|
||||
self.refreshIndex = (self.refreshIndex + 1) % Config.RUN_UPDATES_PER_FRAME
|
||||
|
||||
# Load textures for running down
|
||||
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
|
||||
# Logic to determine what direction we're in.
|
||||
if self.change_x == 0 and self.change_y == 0:
|
||||
self.cur_texture += 1
|
||||
if self.cur_texture > 3 * Config.IDLE_UPDATES_PER_FRAME:
|
||||
self.cur_texture = 0
|
||||
self.texture = self.idle_textures[self.cur_texture // Config.IDLE_UPDATES_PER_FRAME]
|
||||
return
|
||||
cur = Enums.IDLE
|
||||
elif self.change_y > 0: # Up
|
||||
cur = Enums.UP
|
||||
elif self.change_y < 0: # Down
|
||||
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 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.up_textures[self.cur_texture // Config.RUN_UPDATES_PER_FRAME]
|
||||
return
|
||||
# If we're in a new direction or the refresh index has reset
|
||||
if self.prev is not cur or self.refreshIndex == 0:
|
||||
self.texture = next(self.map[cur])
|
||||
|
||||
#walk down animation
|
||||
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]
|
||||
self.prev = cur
|
||||
|
||||
def tick(self):
|
||||
"""
|
||||
@@ -120,12 +98,10 @@ class Enemy(Mob):
|
||||
Represents an Enemy Mob.
|
||||
Will take basic offensive actions against Player objects.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super(Enemy, self).__init__(*args, **kwargs)
|
||||
|
||||
def get_enemy(self):
|
||||
return self
|
||||
|
||||
def tick(self) -> None:
|
||||
"""
|
||||
A on_update function, the Enemy Mob should scan for the player, decide how to path to it, and
|
||||
|
||||
|
Before Width: | Height: | Size: 597 B After Width: | Height: | Size: 597 B |
|
Before Width: | Height: | Size: 597 B After Width: | Height: | Size: 597 B |
|
Before Width: | Height: | Size: 621 B After Width: | Height: | Size: 621 B |
|
Before Width: | Height: | Size: 621 B After Width: | Height: | Size: 621 B |
|
Before Width: | Height: | Size: 547 B After Width: | Height: | Size: 547 B |
|
Before Width: | Height: | Size: 566 B After Width: | Height: | Size: 566 B |
|
Before Width: | Height: | Size: 545 B After Width: | Height: | Size: 545 B |
|
Before Width: | Height: | Size: 570 B After Width: | Height: | Size: 570 B |
|
Before Width: | Height: | Size: 583 B After Width: | Height: | Size: 583 B |
|
Before Width: | Height: | Size: 438 B After Width: | Height: | Size: 438 B |
|
Before Width: | Height: | Size: 498 B After Width: | Height: | Size: 498 B |
|
Before Width: | Height: | Size: 508 B After Width: | Height: | Size: 508 B |
|
Before Width: | Height: | Size: 444 B After Width: | Height: | Size: 444 B |
|
Before Width: | Height: | Size: 476 B After Width: | Height: | Size: 476 B |
|
Before Width: | Height: | Size: 488 B After Width: | Height: | Size: 488 B |
|
Before Width: | Height: | Size: 441 B After Width: | Height: | Size: 441 B |
|
Before Width: | Height: | Size: 499 B After Width: | Height: | Size: 499 B |
|
Before Width: | Height: | Size: 509 B After Width: | Height: | Size: 509 B |
|
Before Width: | Height: | Size: 451 B After Width: | Height: | Size: 451 B |
|
Before Width: | Height: | Size: 480 B After Width: | Height: | Size: 480 B |
|
Before Width: | Height: | Size: 498 B After Width: | Height: | Size: 498 B |
|
Before Width: | Height: | Size: 409 B After Width: | Height: | Size: 409 B |
|
Before Width: | Height: | Size: 429 B After Width: | Height: | Size: 429 B |
|
Before Width: | Height: | Size: 435 B After Width: | Height: | Size: 435 B |
|
Before Width: | Height: | Size: 449 B After Width: | Height: | Size: 449 B |
|
Before Width: | Height: | Size: 498 B After Width: | Height: | Size: 498 B |
|
Before Width: | Height: | Size: 547 B After Width: | Height: | Size: 547 B |
|
Before Width: | Height: | Size: 552 B After Width: | Height: | Size: 552 B |
|
Before Width: | Height: | Size: 767 B After Width: | Height: | Size: 767 B |
|
Before Width: | Height: | Size: 441 B After Width: | Height: | Size: 441 B |
|
Before Width: | Height: | Size: 569 B After Width: | Height: | Size: 569 B |
|
Before Width: | Height: | Size: 481 B After Width: | Height: | Size: 481 B |
|
Before Width: | Height: | Size: 438 B After Width: | Height: | Size: 438 B |
|
Before Width: | Height: | Size: 563 B After Width: | Height: | Size: 563 B |
|
Before Width: | Height: | Size: 486 B After Width: | Height: | Size: 486 B |
|
Before Width: | Height: | Size: 383 B After Width: | Height: | Size: 383 B |
|
Before Width: | Height: | Size: 396 B After Width: | Height: | Size: 396 B |
|
Before Width: | Height: | Size: 633 B After Width: | Height: | Size: 633 B |
70
triple-dungeon/sprites.py
Normal 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'))
|
||||