Merge pull request #40 from n0remac/enemy-movement

Enemy movement
This commit is contained in:
Lief9100
2020-04-24 11:27:38 -07:00
committed by GitHub
4 changed files with 123 additions and 84 deletions

View File

@@ -37,6 +37,8 @@ class Config(object):
# Movement speed of player, in pixels per frame # Movement speed of player, in pixels per frame
PLAYER_MOVEMENT_SPEED = 14 PLAYER_MOVEMENT_SPEED = 14
MONSTER_MOVEMENT_SPEED = 11
# How many pixels to keep as a minimum margin between the characters 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 = 700 LEFT_VIEWPORT_MARGIN = 700
RIGHT_VIEWPORT_MARGIN = 700 RIGHT_VIEWPORT_MARGIN = 700
@@ -47,7 +49,7 @@ class Config(object):
DEBUG = False DEBUG = False
# Monster Count to be spawned # Monster Count to be spawned
MONSTER_COUNT = 6 MONSTER_COUNT = 8
class Enums(Enum): class Enums(Enum):

View File

@@ -57,7 +57,6 @@ class Game(arcade.Window):
# Used to keep track of our scrolling # Used to keep track of our scrolling
self.view_bottom = self.view_left = 0 self.view_bottom = self.view_left = 0
self.Recipe = [] self.Recipe = []
self.enemies_in_range = []
arcade.set_background_color(arcade.color.BLACK) arcade.set_background_color(arcade.color.BLACK)
@@ -80,12 +79,11 @@ class Game(arcade.Window):
level = random.choice(self.dungeon.levelList) level = random.choice(self.dungeon.levelList)
self.player.center_x, self.player.center_y = level.center() self.player.center_x, self.player.center_y = level.center()
self.player.cur_recipe = self.Recipe.active self.player.cur_recipe = self.Recipe.active
self.player.monster_collisions = arcade.PhysicsEngineSimple(self.player, self.dungeon.getWalls()) self.player.collisions = arcade.PhysicsEngineSimple(self.player, self.dungeon.getWalls())
# Set up monsters # Set up monsters
self.Mobs = MobHandler() self.Mobs = MobHandler()
self.enemy_list = self.Mobs.setup(Config.MONSTER_COUNT, Config.MONSTER_COUNT, self.player, self.dungeon) self.enemy_list = self.Mobs.setup(Config.MONSTER_COUNT, Config.MONSTER_COUNT, self.player, self.dungeon)
self.active_enemies = self.Mobs.active_enemies
# Setup viewport # Setup viewport
self.view_bottom = self.player.center_x - (0.5 * Config.SCREEN_WIDTH) + 300 self.view_bottom = self.player.center_x - (0.5 * Config.SCREEN_WIDTH) + 300
@@ -103,9 +101,8 @@ class Game(arcade.Window):
# Draw our sprites # Draw our sprites
self.dungeon.render() self.dungeon.render()
self.player.draw()
self.Mobs.render() self.Mobs.render()
self.active_enemies.draw() #self.active_enemies.draw()
self.bullet_list.draw() self.bullet_list.draw()
self.Recipe.render() self.Recipe.render()
@@ -225,13 +222,41 @@ class Game(arcade.Window):
def on_update(self, delta_time): def on_update(self, delta_time):
""" Movement and game logic """ """ Movement and game logic """
# Move the player with the physics engine # Update Mobs
self.player.monster_collisions.update() self.Mobs.update()
self.player.update_animation()
# scroll screen
self.scroll_screen()
# Projectile updates
self.bullet_list.update()
for bullet in self.bullet_list:
# Collision Checks
hit_list = arcade.check_for_collision_with_list(bullet, self.dungeon.getWalls())
enemy_hit_list = arcade.check_for_collision_with_list(bullet, self.enemy_list)
# If it did, get rid of the bullet
if len(hit_list) > 0:
bullet.remove_from_sprite_lists()
if len(enemy_hit_list):
self.player.add_kill(enemy_hit_list[0].monster_type)
self.Recipe.add_kill(enemy_hit_list[0].monster_type)
enemy_hit_list[0].remove_from_sprite_lists()
bullet.remove_from_sprite_lists()
# If the bullet flies off-screen, remove it. TEMP change to range calc
if (
bullet.bottom < self.view_bottom or
bullet.top > self.view_bottom + Config.SCREEN_HEIGHT or
bullet.right > self.view_left + Config.SCREEN_WIDTH or
bullet.left < self.view_left
):
bullet.remove_from_sprite_lists()
def scroll_screen(self):
# Below manages all scrolling mechanics
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
# 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.left < left_boundary: if self.player.left < left_boundary:
@@ -264,34 +289,6 @@ class Game(arcade.Window):
Config.SCREEN_WIDTH + self.view_left, Config.SCREEN_WIDTH + self.view_left,
self.view_bottom, self.view_bottom,
Config.SCREEN_HEIGHT + self.view_bottom) Config.SCREEN_HEIGHT + self.view_bottom)
# Update Mobs
self.Mobs.update()
# Projectile updates
self.bullet_list.update()
for bullet in self.bullet_list:
# Collision Checks
hit_list = arcade.check_for_collision_with_list(bullet, self.dungeon.getWalls())
enemy_hit_list = arcade.check_for_collision_with_list(bullet, self.active_enemies)
# If it did, get rid of the bullet
if len(hit_list) > 0:
bullet.remove_from_sprite_lists()
if len(enemy_hit_list):
self.player.add_kill(enemy_hit_list[0].monster_type)
self.Recipe.add_kill(enemy_hit_list[0].monster_type)
enemy_hit_list[0].remove_from_sprite_lists()
bullet.remove_from_sprite_lists()
# If the bullet flies off-screen, remove it. TEMP change to range calc
if (
bullet.bottom < self.view_bottom or
bullet.top > self.view_bottom + Config.SCREEN_HEIGHT or
bullet.right > self.view_left + Config.SCREEN_WIDTH or
bullet.left < self.view_left
):
bullet.remove_from_sprite_lists()
def main() -> None: def main() -> None:
""" """

View File

@@ -10,7 +10,7 @@ import json
from pprint import pprint from pprint import pprint
import arcade import arcade
import numpy as np import random
from itertools import chain from itertools import chain
from config import Config from config import Config
@@ -177,13 +177,10 @@ class Level:
""" """
return int((self.x + 0.5) * Config.LEVEL_SIZE), int((self.y + 0.5) * Config.LEVEL_SIZE) return int((self.x + 0.5) * Config.LEVEL_SIZE), int((self.y + 0.5) * Config.LEVEL_SIZE)
def rotate_level(self, times_rotated): def random(self) -> tuple:
""" """
Rotates the Returns a random spot in the level.
:param times_rotated: :return: A tuple containing the X and Y coordinates
:return:
""" """
m = np.array(self.level)
for i in range(0, times_rotated % 4): return int((self.x + random.randint(1,9)/10) * Config.LEVEL_SIZE), int((self.y + random.randint(1,9)/10) * Config.LEVEL_SIZE)
m = np.rot90(m)
self.level = m.tolist()

View File

@@ -13,56 +13,85 @@ from config import Config, Enums, SpritePaths
from map import Dungeon from map import Dungeon
from sprites import PlayerAnimations from sprites import PlayerAnimations
class MobHandler(arcade.SpriteList): class MobHandler:
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.enemy_list = [] self.enemy_list = []
self.active_enemies = [] self.avoid_list = []
self.dungeon = None self.dungeon = None
self.player = None self.player = None
def setup(self, ghost, frogs, player, dungeon) -> list: def setup(self, ghost, frogs, player, dungeon) -> list:
self.enemy_list = arcade.SpriteList() self.enemy_list = arcade.SpriteList()
self.active_enemies = arcade.SpriteList()
self.dungeon = dungeon self.dungeon = dungeon
self.player = player self.player = player
self.avoid_list = arcade.SpriteList()
self.avoid_list.append(self.player)
for d in dungeon.getWalls():
self.avoid_list.append(d)
for count in range(ghost): for count in range(ghost):
mob = Enemy(filename="resources/images/monsters/ghost/ghost1.png", dungeon=self.dungeon) mob = Enemy(filename="resources/images/monsters/ghost/ghost1.png", dungeon=self.dungeon)
mob.center_x, mob.center_y = random.choice(self.dungeon.levelList).center() level = random.choice(self.dungeon.levelList)
mob.center_x, mob.center_y = level.random()
mob.target = self.player mob.target = self.player
mob.scale = 4 mob.scale = 4
mob.monster_type = 'ghost' mob.monster_type = 'ghost'
mob.monster_collisions = arcade.PhysicsEngineSimple(mob, self.active_enemies) mob.collisions = arcade.PhysicsEngineSimple(mob, self.avoid_list)
mob.level = level
self.enemy_list.append(mob) self.enemy_list.append(mob)
self.avoid_list.append(mob)
for count in range(frogs): for count in range(frogs):
mob = Enemy(filename="resources/images/monsters/frog/frog1.png", dungeon=self.dungeon) mob = Enemy(filename="resources/images/monsters/frog/frog1.png", dungeon=self.dungeon)
mob.center_x, mob.center_y = random.choice(self.dungeon.levelList).center() level = random.choice(self.dungeon.levelList)
mob.center_x, mob.center_y = level.random()
mob.target = self.player mob.target = self.player
mob.scale = 4 mob.scale = 4
mob.monster_type = 'frog' mob.monster_type = 'frog'
mob.monster_collisions = arcade.PhysicsEngineSimple(mob, self.active_enemies) mob.collisions = arcade.PhysicsEngineSimple(mob, self.avoid_list)
mob.level = level
self.enemy_list.append(mob) self.enemy_list.append(mob)
self.avoid_list.append(mob)
return self.enemy_list return self.enemy_list
def render(self) -> None: def render(self) -> None:
self.player.draw()
self.enemy_list.draw() self.enemy_list.draw()
def update(self) -> None: def update(self) -> None:
#update player
self.player.collisions.update()
self.player.update_animation()
# Enemy activation and update # Enemy activation and update
for enemy in reversed(self.enemy_list): for enemy in reversed(self.enemy_list):
# TODO replace with distance checking # TODO replace with distance checking
distance = self.get_distance(enemy) distance = self.get_distance(enemy)
enemy.collisions.update()
if (distance < 300): if (distance < 300):
self.active_enemies.append(enemy) enemy.speed = Config.MONSTER_MOVEMENT_SPEED
self.enemy_list.remove(enemy)
enemy.active = True
try: try:
for enemy in self.active_enemies: path = enemy.get_path(enemy.target.position)
enemy.monster_collisions.update() enemy.tick(path)
path = enemy.get_path() except Exception:
import traceback
traceback.print_exc()
else:
left, right, bottom, top = arcade.get_viewport()
if (
enemy.bottom > bottom and
enemy.top < bottom + Config.SCREEN_HEIGHT and
enemy.right < left + Config.SCREEN_WIDTH and
enemy.left > left
):
enemy.speed = 5
ran = random.randint(0,1000)
if ran > 950:
print(ran)
try:
path = enemy.get_path(enemy.level.random())
enemy.tick(path) enemy.tick(path)
except Exception: except Exception:
import traceback import traceback
@@ -76,6 +105,20 @@ class MobHandler(arcade.SpriteList):
distance = math.sqrt(math.pow(start_x - end_x, 2) + math.pow(start_y - end_y, 2)) distance = math.sqrt(math.pow(start_x - end_x, 2) + math.pow(start_y - end_y, 2))
return distance return distance
@staticmethod
def draw_path(path: List[Tuple[int, int]]) -> None:
"""
Draws a line between positions in a list of tuple, also known as the path.
:param path: A list of tuple positions defining a path that can be traversed.
"""
if len(path) > 2:
path = map(lambda point: ((point[0]) * Config.TILE_SIZE, (point[1]) * Config.TILE_SIZE), path)
path = list(path)
#print(path)
for pos1, pos2 in zip(path, path[1:]):
arcade.draw_line(*pos1, *pos2, color=arcade.color.RED)
class Mob(arcade.Sprite): class Mob(arcade.Sprite):
""" """
@@ -93,10 +136,10 @@ class Mob(arcade.Sprite):
self.up_textures = [] self.up_textures = []
self.down_textures = [] self.down_textures = []
self.cur_texture = 0 self.cur_texture = 0
self.monster_collisions = None self.collisions = None
self.dungeon = dungeon self.dungeon = dungeon
self.target = None self.target = None
self.collisions = None self.level = None
class Player(Mob): class Player(Mob):
@@ -123,6 +166,7 @@ class Player(Mob):
self.texture = next(self.map[self.prev]) self.texture = next(self.map[self.prev])
self.kill_list = [] self.kill_list = []
self.cur_recipe = None self.cur_recipe = None
self.speed = 14
def add_kill(self, creature): def add_kill(self, creature):
# Adds a kill to kill_list. If 3 or more check the recipe then give a power up if it matches. # Adds a kill to kill_list. If 3 or more check the recipe then give a power up if it matches.
@@ -181,7 +225,6 @@ class Enemy(Mob):
def __init__(self, *args, **kwargs) -> None: def __init__(self, *args, **kwargs) -> None:
super(Enemy, self).__init__(*args, **kwargs) super(Enemy, self).__init__(*args, **kwargs)
self.monster_type = '' self.monster_type = ''
self.active = False
def nearestPosition(self) -> Tuple[int, int]: def nearestPosition(self) -> Tuple[int, int]:
""" """
@@ -196,25 +239,26 @@ class Enemy(Mob):
""" """
A on_update function, the Mob should decide it's next actions here. A on_update function, the Mob should decide it's next actions here.
""" """
near_pos = self.nearestPosition()
curpos, nextpos = self.nearestPosition(), path[1] curpos, nextpos = near_pos, path[1]
# print(curpos, nextpos) # print(curpos, nextpos)
if nextpos[0] > curpos[0]: if nextpos[0] > curpos[0]:
self.change_x = Config.PLAYER_MOVEMENT_SPEED - 3 self.change_x = self.speed
elif nextpos[0] < curpos[0]: elif nextpos[0] < curpos[0]:
self.change_x = -Config.PLAYER_MOVEMENT_SPEED + 3 self.change_x = -self.speed
else: else:
self.change_x = 0 self.change_x = 0
if nextpos[1] > curpos[1]: if nextpos[1] > curpos[1]:
self.change_y = Config.PLAYER_MOVEMENT_SPEED - 3 self.change_y = self.speed
elif nextpos[1] < curpos[1]: elif nextpos[1] < curpos[1]:
self.change_y = -Config.PLAYER_MOVEMENT_SPEED + 3 self.change_y = -self.speed
else: else:
self.change_y = 0 self.change_y = 0
# print(self.change_x, self.change_y)
def get_path(self, end: Tuple[int, int] = None) -> List[Tuple[int, int]]: def get_path(self, end: Tuple[int, int] = None) -> List[Tuple[int, int]]:
""" """
@@ -223,8 +267,7 @@ class Enemy(Mob):
:param end: A the endpoint tuple. Must be a valid position within the matrix. :param end: A the endpoint tuple. Must be a valid position within the matrix.
:return: :return:
""" """
if end is None:
end = self.target.position
start, end = self.nearestPosition(), (round(end[0] / Config.TILE_SIZE), round(end[1] / Config.TILE_SIZE)) start, end = self.nearestPosition(), (round(end[0] / Config.TILE_SIZE), round(end[1] / Config.TILE_SIZE))
start, end = self.dungeon.grid.node(*start), self.dungeon.grid.node(*end) start, end = self.dungeon.grid.node(*start), self.dungeon.grid.node(*end)
paths, runs = self.dungeon.finder.find_path(start, end, self.dungeon.grid) paths, runs = self.dungeon.finder.find_path(start, end, self.dungeon.grid)