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.
|
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")
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
|
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'))
|
||||||