mirror of
https://github.com/n0remac/game-jam-2020.git
synced 2025-12-15 10:09:59 -06:00
Merge pull request #38 from n0remac/monster-kill-combos
Monster kill combos
This commit is contained in:
@@ -29,21 +29,25 @@ class Config(object):
|
||||
# Constants used to scale our sprites from their original size
|
||||
CHARACTER_SCALING = 1
|
||||
TILE_SCALING = 2
|
||||
TILE_SIZE = TILE_WIDTH * TILE_SCALING
|
||||
|
||||
# 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
|
||||
PLAYER_MOVEMENT_SPEED = 14
|
||||
|
||||
# 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
|
||||
LEFT_VIEWPORT_MARGIN = 700
|
||||
RIGHT_VIEWPORT_MARGIN = 700
|
||||
BOTTOM_VIEWPORT_MARGIN = 300
|
||||
TOP_VIEWPORT_MARGIN = 350
|
||||
|
||||
# All debug statements and renderings should use this
|
||||
DEBUG = True
|
||||
DEBUG = False
|
||||
|
||||
# Monster Count to be spawned
|
||||
MONSTER_COUNT = 10
|
||||
|
||||
|
||||
class Enums(Enum):
|
||||
|
||||
@@ -8,13 +8,14 @@ import collections
|
||||
import math
|
||||
import random
|
||||
import time
|
||||
from typing import Tuple, List
|
||||
|
||||
import arcade
|
||||
|
||||
from config import Config
|
||||
from map import Dungeon
|
||||
from mobs import Player
|
||||
from mobs import Player, Enemy
|
||||
from projectiles import Temp
|
||||
from recipe import ActiveRecipe
|
||||
|
||||
|
||||
class FPSCounter:
|
||||
@@ -47,6 +48,7 @@ class Game(arcade.Window):
|
||||
|
||||
# Sprite Lists
|
||||
self.enemy_list = None
|
||||
self.active_enemies = []
|
||||
self.bullet_list = None
|
||||
self.player = None
|
||||
# Game Objects
|
||||
@@ -55,6 +57,7 @@ class Game(arcade.Window):
|
||||
self.physics_engine = None # Our physics engine
|
||||
# Used to keep track of our scrolling
|
||||
self.view_bottom = self.view_left = 0
|
||||
self.active_recipe = []
|
||||
|
||||
arcade.set_background_color(arcade.color.BLACK)
|
||||
|
||||
@@ -63,19 +66,41 @@ class Game(arcade.Window):
|
||||
# Create the Sprite lists
|
||||
|
||||
self.enemy_list = arcade.SpriteList()
|
||||
self.active_enemies = arcade.SpriteList()
|
||||
self.fps = FPSCounter()
|
||||
self.bullet_list = arcade.SpriteList()
|
||||
|
||||
# Create the dungeon
|
||||
self.dungeon = Dungeon(0, 3)
|
||||
|
||||
# Set up recipes
|
||||
self.active_recipe = ActiveRecipe()
|
||||
self.active_recipe.set_ghosts()
|
||||
|
||||
# Set up the player, specifically placing it at these coordinates.
|
||||
self.player = Player()
|
||||
self.player = Player(self.dungeon)
|
||||
self.player.scale = 1
|
||||
level = random.choice(self.dungeon.levelList)
|
||||
self.player.center_x, self.player.center_y = level.center()
|
||||
self.player.cur_recipe = self.active_recipe.active
|
||||
# x, y = level.center()
|
||||
|
||||
# Set up monsters
|
||||
for count in range(Config.MONSTER_COUNT//2):
|
||||
mob = Enemy(filename="resources/images/monsters/ghost/ghost1.png", dungeon=self.dungeon)
|
||||
mob.center_x, mob.center_y = random.choice(self.dungeon.levelList).center()
|
||||
mob.target = self.player
|
||||
mob.scale = 4
|
||||
mob.monster_type = 'ghost'
|
||||
self.enemy_list.append(mob)
|
||||
for count in range(Config.MONSTER_COUNT//2):
|
||||
mob = Enemy(filename="resources/images/monsters/frog/frog1.png", dungeon=self.dungeon)
|
||||
mob.center_x, mob.center_y = random.choice(self.dungeon.levelList).center()
|
||||
mob.target = self.player
|
||||
mob.scale = 4
|
||||
mob.monster_type = 'frog'
|
||||
self.enemy_list.append(mob)
|
||||
|
||||
# Setup viewport
|
||||
self.view_bottom = self.player.center_x - (0.5 * Config.SCREEN_WIDTH) + 300
|
||||
self.view_left = self.player.center_x - (0.5 * Config.SCREEN_WIDTH)
|
||||
@@ -84,10 +109,6 @@ class Game(arcade.Window):
|
||||
self.view_bottom,
|
||||
Config.SCREEN_HEIGHT + self.view_bottom)
|
||||
|
||||
# Create monsters
|
||||
# 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.dungeon.getWalls())
|
||||
|
||||
@@ -101,22 +122,48 @@ class Game(arcade.Window):
|
||||
self.dungeon.render()
|
||||
self.player.draw()
|
||||
self.enemy_list.draw()
|
||||
self.active_enemies.draw()
|
||||
self.bullet_list.draw()
|
||||
self.active_recipe.render()
|
||||
|
||||
if Config.DEBUG:
|
||||
x, y = self.player.position
|
||||
tile = Config.TILE_WIDTH * Config.TILE_SCALING
|
||||
arcade.draw_rectangle_outline(round(x / tile) * tile, round(y / tile) * tile, tile, tile,
|
||||
arcade.color.RED)
|
||||
arcade.draw_rectangle_outline(round(x / Config.TILE_SIZE) * Config.TILE_SIZE,
|
||||
round(y / Config.TILE_SIZE) * Config.TILE_SIZE,
|
||||
Config.TILE_SIZE, Config.TILE_SIZE, arcade.color.RED)
|
||||
self.player.draw_hit_box()
|
||||
arcade.draw_text(str((x, y)), x - 40, y + 50, arcade.color.WHITE, 15, font_name='Arial')
|
||||
arcade.draw_text(f"FPS: {self.fps.get_fps():3.0f}", self.view_left + 50, self.view_bottom + 30,
|
||||
arcade.color.WHITE, 16, font_name='Arial')
|
||||
arcade.color.WHITE, 16, font_name='Arial')
|
||||
|
||||
# Draw paths for all mobs
|
||||
for mob in self.active_enemies:
|
||||
if mob.target is not None:
|
||||
t1 = time.time()
|
||||
path = mob.get_path()
|
||||
t2 = time.time()
|
||||
print(f'Path acquired in {round(t2 - t1, 4)}s')
|
||||
self.draw_path(path)
|
||||
mob.tick(path)
|
||||
|
||||
self.fps.tick()
|
||||
except Exception:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
@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)
|
||||
for pos1, pos2 in zip(path, path[1:]):
|
||||
arcade.draw_line(*pos1, *pos2, color=arcade.color.RED)
|
||||
|
||||
def on_key_press(self, key, modifiers):
|
||||
"""Called whenever a key is pressed. """
|
||||
|
||||
@@ -134,6 +181,9 @@ class Game(arcade.Window):
|
||||
self.prev_keypress.append(key)
|
||||
elif key == 65307:
|
||||
self.close()
|
||||
elif key == 65505:
|
||||
self.active_recipe.next_recipe()
|
||||
self.player.cur_recipe = self.active_recipe.active
|
||||
|
||||
def on_key_release(self, key, modifiers):
|
||||
"""Called when the user releases a key. """
|
||||
@@ -150,6 +200,7 @@ class Game(arcade.Window):
|
||||
elif key == arcade.key.RIGHT or key == arcade.key.D:
|
||||
self.player.change_x = 0
|
||||
self.prev_keypress.remove(key)
|
||||
|
||||
if self.prev_keypress:
|
||||
self.on_key_press(self.prev_keypress.pop(0), 0)
|
||||
|
||||
@@ -157,6 +208,7 @@ class Game(arcade.Window):
|
||||
"""
|
||||
Called whenever the mouse is clicked.
|
||||
"""
|
||||
|
||||
# Create a bullet TEMP SPRITE, currently wielding frog slingshot
|
||||
bullet = Temp()
|
||||
# Position the bullet at the player's current location
|
||||
@@ -229,16 +281,39 @@ class Game(arcade.Window):
|
||||
Config.SCREEN_WIDTH + self.view_left,
|
||||
self.view_bottom,
|
||||
Config.SCREEN_HEIGHT + self.view_bottom)
|
||||
|
||||
# Enemy activation and update
|
||||
for enemy in reversed(self.enemy_list):
|
||||
if (
|
||||
enemy.bottom > self.view_bottom and
|
||||
enemy.top < self.view_bottom + Config.SCREEN_HEIGHT and
|
||||
enemy.right < self.view_left + Config.SCREEN_WIDTH and
|
||||
enemy.left > self.view_left
|
||||
):
|
||||
if Config.DEBUG:
|
||||
print("Activate Enemy")
|
||||
self.active_enemies.append(enemy)
|
||||
self.enemy_list.remove(enemy)
|
||||
try:
|
||||
for enemy in self.active_enemies:
|
||||
enemy.update()
|
||||
path = enemy.get_path()
|
||||
enemy.tick(path)
|
||||
except Exception:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
# 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)
|
||||
enemy_hit_list[0].remove_from_sprite_lists()
|
||||
|
||||
# If the bullet flies off-screen, remove it. TEMP change to range calc
|
||||
if (
|
||||
|
||||
@@ -7,11 +7,16 @@ Pathfinding will also depend on objects here, and is thus integral to it's funct
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pprint import pprint
|
||||
|
||||
import arcade
|
||||
import numpy as np
|
||||
|
||||
from itertools import chain
|
||||
from config import Config
|
||||
from pathfinding.core.diagonal_movement import DiagonalMovement
|
||||
from pathfinding.core.grid import Grid
|
||||
from pathfinding.finder.a_star import AStarFinder
|
||||
|
||||
|
||||
class Dungeon(object):
|
||||
@@ -40,6 +45,16 @@ class Dungeon(object):
|
||||
self.levels = [
|
||||
[Level.load_file(x, y, center) for y in range(size)] for x in range(size)
|
||||
]
|
||||
self.matrix = [[1 for yy in range(size * 10)] for xx in range(10 * size)]
|
||||
for column in self.levels:
|
||||
for level in column:
|
||||
for xx in range(10):
|
||||
for yy in range(10):
|
||||
if level.structure[xx][yy] == 'w':
|
||||
self.matrix[(level.x * 10) + xx][(level.y * 10) + yy] = 0
|
||||
self.grid = Grid(matrix=self.matrix)
|
||||
self.finder = AStarFinder(diagonal_movement=DiagonalMovement.always)
|
||||
pprint(self.matrix, width=1000)
|
||||
|
||||
def getWalls(self) -> arcade.SpriteList:
|
||||
"""
|
||||
|
||||
@@ -3,9 +3,11 @@ mobs.py
|
||||
Organizes all classes related to Mobs, Entities, Enemies, Players and Items.
|
||||
"""
|
||||
|
||||
import arcade
|
||||
from typing import List, Tuple
|
||||
|
||||
import arcade
|
||||
from config import Config, Enums, SpritePaths
|
||||
from map import Dungeon
|
||||
from sprites import PlayerAnimations
|
||||
|
||||
|
||||
@@ -14,9 +16,9 @@ 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:
|
||||
def __init__(self, dungeon: Dungeon, max_health=100, max_armor=0, *args, **kwargs) -> None:
|
||||
# Set up parent class
|
||||
super().__init__()
|
||||
super(Mob, self).__init__(*args, **kwargs)
|
||||
|
||||
self.max_health, self.max_armor = max_health, max_armor
|
||||
self.health, self.armor = max_health, max_armor
|
||||
@@ -26,13 +28,10 @@ class Mob(arcade.Sprite):
|
||||
self.down_textures = []
|
||||
self.cur_texture = 0
|
||||
|
||||
def tick(self) -> None:
|
||||
"""
|
||||
A on_update function, the Mob should decide it's next actions here.
|
||||
"""
|
||||
pass
|
||||
|
||||
self.dungeon = dungeon
|
||||
self.target = None
|
||||
|
||||
|
||||
class Player(Mob):
|
||||
"""
|
||||
Represents a Player.
|
||||
@@ -55,6 +54,19 @@ class Player(Mob):
|
||||
self.refreshIndex = 0
|
||||
self.prev = Enums.IDLE
|
||||
self.texture = next(self.map[self.prev])
|
||||
self.kill_list = []
|
||||
self.cur_recipe = None
|
||||
|
||||
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.
|
||||
self.kill_list.append(creature)
|
||||
print(self.kill_list)
|
||||
print(self.cur_recipe)
|
||||
if self.cur_recipe == self.kill_list:
|
||||
print("+++++++++++++++++++++++++++++++++++++++++++++++++++++++")
|
||||
self.kill_list = []
|
||||
elif len(self.kill_list) >= 3:
|
||||
self.kill_list = []
|
||||
|
||||
def update_animation(self, delta_time: float = 1 / 60) -> None:
|
||||
"""
|
||||
@@ -101,17 +113,51 @@ class Enemy(Mob):
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super(Enemy, self).__init__(*args, **kwargs)
|
||||
self.monster_type = ''
|
||||
|
||||
def tick(self) -> None:
|
||||
def nearestPosition(self) -> Tuple[int, int]:
|
||||
"""
|
||||
A on_update function, the Enemy Mob should scan for the player, decide how to path to it, and
|
||||
decide how to take offensive action.
|
||||
"""
|
||||
pass
|
||||
Returns the nearest absolute dungeon tile the Mob is placed on.
|
||||
|
||||
def path(self) -> None:
|
||||
:return: A tuple containing the Mob's dungeon tile position.
|
||||
"""
|
||||
Not yet decided how this function should work.
|
||||
Basically, most pathfinding decisions should be kept within this function.
|
||||
return (round(self.center_x / Config.TILE_SIZE),
|
||||
round(self.center_y / Config.TILE_SIZE))
|
||||
|
||||
def tick(self, path: Tuple[int, int] = None) -> None:
|
||||
"""
|
||||
pass
|
||||
A on_update function, the Mob should decide it's next actions here.
|
||||
"""
|
||||
curpos, nextpos = self.nearestPosition(), path[1]
|
||||
# print(curpos, nextpos)
|
||||
|
||||
if nextpos[0] > curpos[0]:
|
||||
self.change_x = Config.PLAYER_MOVEMENT_SPEED - 3
|
||||
elif nextpos[0] < curpos[0]:
|
||||
self.change_x = -Config.PLAYER_MOVEMENT_SPEED + 3
|
||||
else:
|
||||
self.change_x = 0
|
||||
|
||||
if nextpos[1] > curpos[1]:
|
||||
self.change_y = Config.PLAYER_MOVEMENT_SPEED - 3
|
||||
elif nextpos[1] < curpos[1]:
|
||||
self.change_y = -Config.PLAYER_MOVEMENT_SPEED + 3
|
||||
else:
|
||||
self.change_y = 0
|
||||
|
||||
# print(self.change_x, self.change_y)
|
||||
|
||||
def get_path(self, end: Tuple[int, int] = None) -> List[Tuple[int, int]]:
|
||||
"""
|
||||
Returns the path to get to the Mob's target in absolute integer positions.
|
||||
|
||||
:param end: A the endpoint tuple. Must be a valid position within the matrix.
|
||||
: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.dungeon.grid.node(*start), self.dungeon.grid.node(*end)
|
||||
paths, runs = self.dungeon.finder.find_path(start, end, self.dungeon.grid)
|
||||
self.dungeon.grid.cleanup()
|
||||
return paths
|
||||
|
||||
59
triple-dungeon/recipe.py
Normal file
59
triple-dungeon/recipe.py
Normal file
@@ -0,0 +1,59 @@
|
||||
'''
|
||||
Recipes are combinations of three monsters. When a player fills a recipe they get an updgrade
|
||||
'''
|
||||
|
||||
import arcade
|
||||
|
||||
|
||||
class Recipe:
|
||||
'''
|
||||
A class of different recipes
|
||||
'''
|
||||
|
||||
GHOSTS = ['ghost', 'ghost', 'ghost']
|
||||
FROGS = ['frog', 'frog', 'frog']
|
||||
GHOST_FROG = ['ghost', 'ghost', 'frog']
|
||||
FROG_GHOST = ['ghost', 'frog', 'frog']
|
||||
|
||||
|
||||
class ActiveRecipe(arcade.SpriteList):
|
||||
'''
|
||||
Keeps track of the active recipe and draws it.
|
||||
'''
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.active = Recipe.GHOSTS
|
||||
self.cycle_recipes = [self.set_frogs, self.set_ghosts]
|
||||
self.pos = 0
|
||||
|
||||
def render(self) -> None:
|
||||
x = 0
|
||||
for sprite in self.sprite_list:
|
||||
screen_right = arcade.get_viewport()[1] - 100
|
||||
screen_top = arcade.get_viewport()[3] - 80
|
||||
sprite.scale = 4
|
||||
sprite.center_x = screen_right - x
|
||||
sprite.center_y = screen_top
|
||||
x += 70
|
||||
sprite.draw()
|
||||
|
||||
def next_recipe(self):
|
||||
self.cycle_recipes[self.pos]()
|
||||
self.pos += 1
|
||||
if self.pos == len(self.cycle_recipes):
|
||||
self.pos = 0
|
||||
|
||||
def set_ghosts(self) -> None:
|
||||
self.active = Recipe.GHOSTS
|
||||
self.sprite_list = []
|
||||
self.sprite_list.append(arcade.Sprite(filename="resources/images/monsters/ghost/ghost1.png"))
|
||||
self.sprite_list.append(arcade.Sprite(filename="resources/images/monsters/ghost/ghost1.png"))
|
||||
self.sprite_list.append(arcade.Sprite(filename="resources/images/monsters/ghost/ghost1.png"))
|
||||
|
||||
def set_frogs(self) -> None:
|
||||
self.active = Recipe.FROGS
|
||||
self.sprite_list = []
|
||||
self.sprite_list.append(arcade.Sprite(filename="resources/images/monsters/frog/frog1.png"))
|
||||
self.sprite_list.append(arcade.Sprite(filename="resources/images/monsters/frog/frog1.png"))
|
||||
self.sprite_list.append(arcade.Sprite(filename="resources/images/monsters/frog/frog1.png"))
|
||||
Reference in New Issue
Block a user