Reenabled Player and all animations, upgrades, attacks and spells
This commit is contained in:
parent
83ab08326e
commit
cd0333cbff
37 changed files with 1262 additions and 469 deletions
0
game/configs/game/__init__.py
Normal file
0
game/configs/game/__init__.py
Normal file
|
@ -1,3 +1,9 @@
|
|||
import os
|
||||
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
asset_path = os.path.join(
|
||||
script_dir, '../../..', 'assets')
|
||||
|
||||
magic_data = {
|
||||
'flame': {'strength': 5,'cost': 20,'graphic':'../Graphics/particles/flame/fire.png'},
|
||||
'heal' : {'strength': 20,'cost': 10,'graphic':'../Graphics/particles/heal/heal.png'}}
|
||||
'flame': {'strength': 5, 'cost': 20, 'graphic': f"{asset_path}/graphics/particles/flame/fire.png"},
|
||||
'heal': {'strength': 20, 'cost': 10, 'graphic': f"{asset_path}/graphics/particles/heal/heal.png"}}
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
import os
|
||||
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
asset_path = os.path.join(
|
||||
script_dir, '../../..', 'assets')
|
||||
|
||||
weapon_data = {
|
||||
'sword': {'cooldown': 100, 'damage': 15,'graphic':'../Graphics/weapons/sword/full.png'},
|
||||
'lance': {'cooldown': 400, 'damage': 30,'graphic':'../Graphics/weapons/lance/full.png'},
|
||||
'axe': {'cooldown': 300, 'damage': 20, 'graphic':'../Graphics/weapons/axe/full.png'},
|
||||
'rapier':{'cooldown': 50, 'damage': 8, 'graphic':'../Graphics/weapons/rapier/full.png'},
|
||||
'sai':{'cooldown': 80, 'damage': 10, 'graphic':'../Graphics/weapons/sai/full.png'}}
|
||||
'sword': {'cooldown': 100, 'damage': 15, 'graphic': f"{asset_path}/graphics/weapons/sword/full.png"},
|
||||
'lance': {'cooldown': 400, 'damage': 30, 'graphic': f"{asset_path}/graphics/weapons/lance/full.png"},
|
||||
'axe': {'cooldown': 300, 'damage': 20, 'graphic': f"{asset_path}/graphics/weapons/axe/full.png"},
|
||||
'rapier': {'cooldown': 50, 'damage': 8, 'graphic': f"{asset_path}/graphics/weapons/rapier/full.png"},
|
||||
'sai': {'cooldown': 80, 'damage': 10, 'graphic': f"{asset_path}/graphics/weapons/sai/full.png"}
|
||||
}
|
||||
|
|
0
game/configs/system/__init__.py
Normal file
0
game/configs/system/__init__.py
Normal file
|
@ -1,23 +1,15 @@
|
|||
# game setup
|
||||
WIDTH = 1280
|
||||
HEIGHT = 720
|
||||
FPS = 60
|
||||
WIDTH = 1280
|
||||
HEIGHT = 720
|
||||
FPS = 60
|
||||
TILESIZE = 64
|
||||
HITBOX_OFFSET = {
|
||||
'player': (-6, -26),
|
||||
'camera': (-50, -50),
|
||||
'object': (0, -40),
|
||||
'grass': (0, -10),
|
||||
'invisible': (0, 0)
|
||||
}
|
||||
|
||||
# ui
|
||||
BAR_HEIGHT = 20
|
||||
HEALTH_BAR_WIDTH = 200
|
||||
ENERGY_BAR_WIDTH = 140
|
||||
ITEM_BOX_SIZE = 80
|
||||
UI_FONT = '../Graphics/graphics/font/joystix.ttf'
|
||||
UI_FONT_SIZE = 18
|
||||
'player': (-6, -26),
|
||||
'camera': (-50, -50),
|
||||
'object': (0, -40),
|
||||
'grass': (0, -10),
|
||||
'invisible': (0, 0)
|
||||
}
|
||||
|
||||
# general colors
|
||||
WATER_COLOR = '#71ddee'
|
||||
|
@ -36,22 +28,22 @@ BAR_COLOR = '#EEEEEE'
|
|||
BAR_COLOR_SELECTED = '#111111'
|
||||
UPGRADE_BG_COLOR_SELECTED = '#EEEEEE'
|
||||
|
||||
# weapons
|
||||
weapon_data = {
|
||||
'sword': {'cooldown': 100, 'damage': 15,'graphic':'../Graphics/graphics/weapons/sword/full.png'},
|
||||
'lance': {'cooldown': 400, 'damage': 30,'graphic':'../Graphics/graphics/weapons/lance/full.png'},
|
||||
'axe': {'cooldown': 300, 'damage': 20, 'graphic':'../Graphics/graphics/weapons/axe/full.png'},
|
||||
'rapier':{'cooldown': 50, 'damage': 8, 'graphic':'../Graphics/graphics/weapons/rapier/full.png'},
|
||||
'sai':{'cooldown': 80, 'damage': 10, 'graphic':'../Graphics/graphics/weapons/sai/full.png'}}
|
||||
|
||||
# magic
|
||||
magic_data = {
|
||||
'flame': {'strength': 5,'cost': 20,'graphic':'../Graphics/graphics/particles/flame/fire.png'},
|
||||
'heal' : {'strength': 20,'cost': 10,'graphic':'../Graphics/graphics/particles/heal/heal.png'}}
|
||||
|
||||
# enemy
|
||||
monster_data = {
|
||||
'squid': {'health': 100,'exp':100,'damage':20,'attack_type': 'slash', 'attack_sound':'../Graphics/audio/attack/slash.wav', 'speed': 3, 'resistance': 3, 'attack_radius': 80, 'notice_radius': 360},
|
||||
'raccoon': {'health': 300,'exp':250,'damage':40,'attack_type': 'claw', 'attack_sound':'../Graphics/audio/attack/claw.wav','speed': 2, 'resistance': 3, 'attack_radius': 120, 'notice_radius': 400},
|
||||
'spirit': {'health': 100,'exp':110,'damage':8,'attack_type': 'thunder', 'attack_sound':'../Graphics/audio/attack/fireball.wav', 'speed': 4, 'resistance': 3, 'attack_radius': 60, 'notice_radius': 350},
|
||||
'bamboo': {'health': 70,'exp':120,'damage':6,'attack_type': 'leaf_attack', 'attack_sound':'../Graphics/audio/attack/slash.wav', 'speed': 3, 'resistance': 3, 'attack_radius': 50, 'notice_radius': 300}}
|
||||
# # weapons
|
||||
# weapon_data = {
|
||||
# 'sword': {'cooldown': 100, 'damage': 15,'graphic':'../Graphics/graphics/weapons/sword/full.png'},
|
||||
# 'lance': {'cooldown': 400, 'damage': 30,'graphic':'../Graphics/graphics/weapons/lance/full.png'},
|
||||
# 'axe': {'cooldown': 300, 'damage': 20, 'graphic':'../Graphics/graphics/weapons/axe/full.png'},
|
||||
# 'rapier':{'cooldown': 50, 'damage': 8, 'graphic':'../Graphics/graphics/weapons/rapier/full.png'},
|
||||
# 'sai':{'cooldown': 80, 'damage': 10, 'graphic':'../Graphics/graphics/weapons/sai/full.png'}}
|
||||
#
|
||||
# # magic
|
||||
# magic_data = {
|
||||
# 'flame': {'strength': 5,'cost': 20,'graphic':'../Graphics/graphics/particles/flame/fire.png'},
|
||||
# 'heal' : {'strength': 20,'cost': 10,'graphic':'../Graphics/graphics/particles/heal/heal.png'}}
|
||||
#
|
||||
# # enemy
|
||||
# monster_data = {
|
||||
# 'squid': {'health': 100,'exp':100,'damage':20,'attack_type': 'slash', 'attack_sound':'../Graphics/audio/attack/slash.wav', 'speed': 3, 'resistance': 3, 'attack_radius': 80, 'notice_radius': 360},
|
||||
# 'raccoon': {'health': 300,'exp':250,'damage':40,'attack_type': 'claw', 'attack_sound':'../Graphics/audio/attack/claw.wav','speed': 2, 'resistance': 3, 'attack_radius': 120, 'notice_radius': 400},
|
||||
# 'spirit': {'health': 100,'exp':110,'damage':8,'attack_type': 'thunder', 'attack_sound':'../Graphics/audio/attack/fireball.wav', 'speed': 4, 'resistance': 3, 'attack_radius': 60, 'notice_radius': 350},
|
||||
# 'bamboo': {'health': 70,'exp':120,'damage':6,'attack_type': 'leaf_attack', 'attack_sound':'../Graphics/audio/attack/slash.wav', 'speed': 3, 'resistance': 3, 'attack_radius': 50, 'notice_radius': 300}}
|
||||
|
|
|
@ -1,49 +1,61 @@
|
|||
import pygame
|
||||
from utils.settings import *
|
||||
from random import randint
|
||||
|
||||
from configs.system.window_config import TILESIZE
|
||||
|
||||
from configs.game.spell_config import *
|
||||
|
||||
|
||||
class MagicPlayer:
|
||||
def __init__(self, animation_player):
|
||||
self.animation_player = animation_player
|
||||
|
||||
|
||||
# Sound Setup
|
||||
self.sounds = {
|
||||
'heal': pygame.mixer.Sound('../Graphics/audio/heal.wav'),
|
||||
'flame': pygame.mixer.Sound('../Graphics/audio/flame.wav')
|
||||
}
|
||||
|
||||
# self.sounds = {
|
||||
# 'heal': pygame.mixer.Sound('../Graphics/audio/heal.wav'),
|
||||
# 'flame': pygame.mixer.Sound('../Graphics/audio/flame.wav')
|
||||
# }
|
||||
|
||||
def heal(self, player, strength, cost, groups):
|
||||
if player.energy >= cost:
|
||||
self.sounds['heal'].play()
|
||||
player.health += strength
|
||||
player.energy -= cost
|
||||
if player.health >= player.stats['health']:
|
||||
player.health = player.stats['health']
|
||||
self.animation_player.generate_particles('aura', player.rect.center, groups)
|
||||
self.animation_player.generate_particles('heal', player.rect.center + pygame.math.Vector2(0, -50), groups)
|
||||
|
||||
if player.stats.energy >= cost:
|
||||
# self.sounds['heal'].play()
|
||||
player.stats.health += strength
|
||||
player.stats.energy -= cost
|
||||
if player.stats.health >= player.stats.stats['health']:
|
||||
player.stats.health = player.stats.stats['health']
|
||||
self.animation_player.generate_particles(
|
||||
'aura', player.rect.center, groups)
|
||||
self.animation_player.generate_particles(
|
||||
'heal', player.rect.center + pygame.math.Vector2(0, -50), groups)
|
||||
|
||||
def flame(self, player, cost, groups):
|
||||
if player.energy >= cost:
|
||||
player.energy -= cost
|
||||
self.sounds['flame'].play()
|
||||
|
||||
if player.stats.energy >= cost:
|
||||
player.stats.energy -= cost
|
||||
# self.sounds['flame'].play()
|
||||
|
||||
if player.status.split('_')[0] == 'right':
|
||||
direction = pygame.math.Vector2(1,0)
|
||||
direction = pygame.math.Vector2(1, 0)
|
||||
elif player.status.split('_')[0] == 'left':
|
||||
direction = pygame.math.Vector2(-1,0)
|
||||
direction = pygame.math.Vector2(-1, 0)
|
||||
elif player.status.split('_')[0] == 'up':
|
||||
direction = pygame.math.Vector2(0,-1)
|
||||
direction = pygame.math.Vector2(0, -1)
|
||||
else:
|
||||
direction = pygame.math.Vector2(0,1)
|
||||
|
||||
direction = pygame.math.Vector2(0, 1)
|
||||
|
||||
for i in range(1, 6):
|
||||
if direction.x:
|
||||
offset_x = direction.x * i * TILESIZE
|
||||
x = player.rect.centerx + offset_x + randint(-TILESIZE // 3, TILESIZE //3)
|
||||
y = player.rect.centery + randint(-TILESIZE // 3, TILESIZE //3)
|
||||
self.animation_player.generate_particles('flame', (x,y), groups)
|
||||
x = player.rect.centerx + offset_x + \
|
||||
randint(-TILESIZE // 3, TILESIZE // 3)
|
||||
y = player.rect.centery + \
|
||||
randint(-TILESIZE // 3, TILESIZE // 3)
|
||||
self.animation_player.generate_particles(
|
||||
'flame', (x, y), groups)
|
||||
else:
|
||||
offset_y = direction.y * i * TILESIZE
|
||||
x = player.rect.centerx + randint(-TILESIZE // 3, TILESIZE //3)
|
||||
y = player.rect.centery + offset_y + randint(-TILESIZE // 3, TILESIZE //3)
|
||||
self.animation_player.generate_particles('flame', (x,y), groups)
|
||||
x = player.rect.centerx + \
|
||||
randint(-TILESIZE // 3, TILESIZE // 3)
|
||||
y = player.rect.centery + offset_y + \
|
||||
randint(-TILESIZE // 3, TILESIZE // 3)
|
||||
self.animation_player.generate_particles(
|
||||
'flame', (x, y), groups)
|
||||
|
|
|
@ -1,44 +1,58 @@
|
|||
import os
|
||||
import pygame
|
||||
from utils.support import import_folder
|
||||
|
||||
from utils.resource_loader import import_folder
|
||||
from random import choice
|
||||
|
||||
|
||||
class AnimationPlayer:
|
||||
def __init__(self):
|
||||
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
asset_path = os.path.join(
|
||||
script_dir, '../..', 'assets')
|
||||
|
||||
self.frames = {
|
||||
# magic
|
||||
'flame': import_folder('../Graphics/graphics/particles/flame/frames'),
|
||||
'aura': import_folder('../Graphics/graphics/particles/aura'),
|
||||
'heal': import_folder('../Graphics/graphics/particles/heal/frames'),
|
||||
'flame': import_folder(f'{asset_path}/graphics/particles/flame/frames'),
|
||||
'aura': import_folder(f'{asset_path}/graphics/particles/aura'),
|
||||
'heal': import_folder(f'{asset_path}/graphics/particles/heal/frames'),
|
||||
|
||||
# attacks
|
||||
'claw': import_folder('../Graphics/graphics/particles/claw'),
|
||||
'slash': import_folder('../Graphics/graphics/particles/slash'),
|
||||
'sparkle': import_folder('../Graphics/graphics/particles/sparkle'),
|
||||
'leaf_attack': import_folder('../Graphics/graphics/particles/leaf_attack'),
|
||||
'thunder': import_folder('../Graphics/graphics/particles/thunder'),
|
||||
# attacks
|
||||
'claw': import_folder(f'{asset_path}/graphics/particles/claw'),
|
||||
'slash': import_folder(f'{asset_path}/graphics/particles/slash'),
|
||||
'sparkle': import_folder(f'{asset_path}/graphics/particles/sparkle'),
|
||||
'leaf_attack': import_folder(f'{asset_path}/graphics/particles/leaf_attack'),
|
||||
'thunder': import_folder(f'{asset_path}/graphics/particles/thunder'),
|
||||
|
||||
# monster deaths
|
||||
'squid': import_folder('../Graphics/graphics/particles/smoke_orange'),
|
||||
'raccoon': import_folder('../Graphics/graphics/particles/raccoon'),
|
||||
'spirit': import_folder('../Graphics/graphics/particles/nova'),
|
||||
'bamboo': import_folder('../Graphics/graphics/particles/bamboo'),
|
||||
'squid': import_folder(f'{asset_path}/graphics/particles/smoke_orange'),
|
||||
'raccoon': import_folder(f'{asset_path}/graphics/particles/raccoon'),
|
||||
'spirit': import_folder(f'{asset_path}/graphics/particles/nova'),
|
||||
'bamboo': import_folder(f'{asset_path}/graphics/particles/bamboo'),
|
||||
|
||||
# leafs
|
||||
# leafs
|
||||
'leaf': (
|
||||
import_folder('../Graphics/graphics/particles/leaf1'),
|
||||
import_folder('../Graphics/graphics/particles/leaf2'),
|
||||
import_folder('../Graphics/graphics/particles/leaf3'),
|
||||
import_folder('../Graphics/graphics/particles/leaf4'),
|
||||
import_folder('../Graphics/graphics/particles/leaf5'),
|
||||
import_folder('../Graphics/graphics/particles/leaf6'),
|
||||
self.reflect_images(import_folder('../Graphics/graphics/particles/leaf1')),
|
||||
self.reflect_images(import_folder('../Graphics/graphics/particles/leaf2')),
|
||||
self.reflect_images(import_folder('../Graphics/graphics/particles/leaf3')),
|
||||
self.reflect_images(import_folder('../Graphics/graphics/particles/leaf4')),
|
||||
self.reflect_images(import_folder('../Graphics/graphics/particles/leaf5')),
|
||||
self.reflect_images(import_folder('../Graphics/graphics/particles/leaf6'))
|
||||
import_folder(f'{asset_path}/graphics/particles/leaf1'),
|
||||
import_folder(f'{asset_path}/graphics/particles/leaf2'),
|
||||
import_folder(f'{asset_path}/graphics/particles/leaf3'),
|
||||
import_folder(f'{asset_path}/graphics/particles/leaf4'),
|
||||
import_folder(f'{asset_path}/graphics/particles/leaf5'),
|
||||
import_folder(f'{asset_path}/graphics/particles/leaf6'),
|
||||
self.reflect_images(import_folder(
|
||||
f'{asset_path}/graphics/particles/leaf1')),
|
||||
self.reflect_images(import_folder(
|
||||
f'{asset_path}/graphics/particles/leaf2')),
|
||||
self.reflect_images(import_folder(
|
||||
f'{asset_path}/graphics/particles/leaf3')),
|
||||
self.reflect_images(import_folder(
|
||||
f'{asset_path}/graphics/particles/leaf4')),
|
||||
self.reflect_images(import_folder(
|
||||
f'{asset_path}/graphics/particles/leaf5')),
|
||||
self.reflect_images(import_folder(
|
||||
f'{asset_path}/graphics/particles/leaf6'))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def reflect_images(self, frames):
|
||||
new_frames = []
|
||||
|
@ -49,12 +63,13 @@ class AnimationPlayer:
|
|||
|
||||
def create_grass_particles(self, position, groups):
|
||||
animation_frames = choice(self.frames['leaf'])
|
||||
ParticleEffect(position, animation_frames,groups)
|
||||
ParticleEffect(position, animation_frames, groups)
|
||||
|
||||
def generate_particles(self, animation_type, position, groups):
|
||||
animation_frames = self.frames[animation_type]
|
||||
ParticleEffect(position, animation_frames, groups)
|
||||
|
||||
|
||||
class ParticleEffect(pygame.sprite.Sprite):
|
||||
def __init__(self, position, animation_frames, groups):
|
||||
super().__init__(groups)
|
||||
|
@ -62,15 +77,15 @@ class ParticleEffect(pygame.sprite.Sprite):
|
|||
self.animation_speed = 0.15
|
||||
self.frames = animation_frames
|
||||
self.image = self.frames[self.frame_index]
|
||||
self.rect = self.image.get_rect(center = position)
|
||||
self.rect = self.image.get_rect(center=position)
|
||||
self.sprite_type = 'magic'
|
||||
|
||||
|
||||
def animate(self):
|
||||
self.frame_index += self.animation_speed
|
||||
if self.frame_index >= len(self.frames):
|
||||
self.kill()
|
||||
else:
|
||||
self.image = self.frames[int(self.frame_index)]
|
||||
|
||||
|
||||
def update(self):
|
||||
self.animate()
|
||||
|
|
|
@ -1,23 +1,33 @@
|
|||
import os
|
||||
import pygame
|
||||
|
||||
|
||||
class Weapon(pygame.sprite.Sprite):
|
||||
|
||||
|
||||
def __init__(self, player, groups):
|
||||
super().__init__(groups)
|
||||
|
||||
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
asset_path = os.path.join(
|
||||
script_dir, '../..', 'assets')
|
||||
|
||||
self.sprite_type = 'weapon'
|
||||
direction = player.status.split('_')[0]
|
||||
|
||||
|
||||
# Graphic
|
||||
full_path = f"../Graphics/graphics/weapons/{player.weapon}/{direction}.png"
|
||||
full_path = f"{asset_path}/graphics/weapons/{player._input.combat.weapon}/{direction}.png"
|
||||
self.image = pygame.image.load(full_path).convert_alpha()
|
||||
|
||||
|
||||
# Sprite Placement
|
||||
if direction == 'right':
|
||||
self.rect = self.image.get_rect(midleft = player.rect.midright + pygame.math.Vector2(0, 16))
|
||||
self.rect = self.image.get_rect(
|
||||
midleft=player.rect.midright + pygame.math.Vector2(0, 16))
|
||||
elif direction == 'left':
|
||||
self.rect = self.image.get_rect(midright = player.rect.midleft + pygame.math.Vector2(0, 16))
|
||||
self.rect = self.image.get_rect(
|
||||
midright=player.rect.midleft + pygame.math.Vector2(0, 16))
|
||||
elif direction == 'down':
|
||||
self.rect = self.image.get_rect(midtop = player.rect.midbottom + pygame.math.Vector2(-10, 0))
|
||||
self.rect = self.image.get_rect(
|
||||
midtop=player.rect.midbottom + pygame.math.Vector2(-10, 0))
|
||||
else:
|
||||
self.rect = self.image.get_rect(midbottom = player.rect.midtop + pygame.math.Vector2(-10, 0))
|
||||
self.rect = self.image.get_rect(
|
||||
midbottom=player.rect.midtop + pygame.math.Vector2(-10, 0))
|
||||
|
|
0
game/entities/__init__.py
Normal file
0
game/entities/__init__.py
Normal file
0
game/entities/components/__init__.py
Normal file
0
game/entities/components/__init__.py
Normal file
138
game/entities/components/_input.py
Normal file
138
game/entities/components/_input.py
Normal file
|
@ -0,0 +1,138 @@
|
|||
import pygame
|
||||
from random import randint
|
||||
|
||||
from configs.game.spell_config import magic_data
|
||||
from configs.game.weapon_config import weapon_data
|
||||
|
||||
from .movement import MovementHandler
|
||||
from .combat import CombatHandler
|
||||
|
||||
|
||||
class InputHandler:
|
||||
|
||||
def __init__(self, sprite_type, status):
|
||||
self.status = status
|
||||
|
||||
# Setup Movement
|
||||
self.movement = MovementHandler(sprite_type)
|
||||
self.move_cooldown = 15
|
||||
self.can_move = True
|
||||
self.move_time = None
|
||||
|
||||
# Setup Combat
|
||||
self.combat = CombatHandler()
|
||||
self.attacking = False
|
||||
self.attack_cooldown = 400
|
||||
self.attack_time = None
|
||||
self.can_move = True
|
||||
|
||||
# Setup Special Actions
|
||||
self.can_rotate_weapon = True
|
||||
self.weapon_rotation_time = None
|
||||
self.rotate_attack_cooldown = 600
|
||||
|
||||
self.can_swap_magic = True
|
||||
self.magic_swap_time = None
|
||||
|
||||
def check_input(self, speed, hitbox, obstacle_sprites, rect, player):
|
||||
|
||||
if not self.attacking and self.can_move:
|
||||
keys = pygame.key.get_pressed()
|
||||
button = randint(0, 7)
|
||||
|
||||
self.move_time = pygame.time.get_ticks()
|
||||
|
||||
# Movement Input
|
||||
if button == 0: # keys[pygame.K_w]:
|
||||
self.movement.direction.y = -1
|
||||
self.status = 'up'
|
||||
self.can_move = False
|
||||
elif button == 1: # keys[pygame.K_s]:
|
||||
self.movement.direction.y = 1
|
||||
self.status = 'down'
|
||||
self.can_move = False
|
||||
else:
|
||||
self.movement.direction.y = 0
|
||||
|
||||
if button == 2: # keys[pygame.K_a]:
|
||||
self.movement.direction.x = -1
|
||||
self.status = 'left'
|
||||
self.can_move = False
|
||||
elif button == 3: # keys[pygame.K_d]:
|
||||
self.movement.direction.x = 1
|
||||
self.status = 'right'
|
||||
self.can_move = False
|
||||
else:
|
||||
self.movement.direction.x = 0
|
||||
|
||||
self.movement.move(speed, hitbox, obstacle_sprites, rect)
|
||||
|
||||
# Combat Input
|
||||
if button == 4 and not self.attacking: # keys[pygame.K_e]
|
||||
self.attacking = True
|
||||
self.attack_time = pygame.time.get_ticks()
|
||||
self.combat.create_attack_sprite(player)
|
||||
# self.weapon_attack_sound.play()
|
||||
|
||||
# Magic Input
|
||||
if button == 5: # keys[pygame.K_q]:
|
||||
self.attacking = True
|
||||
self.attack_time = pygame.time.get_ticks()
|
||||
|
||||
self.combat.magic = list(magic_data.keys())[
|
||||
self.combat.magic_index]
|
||||
|
||||
strength = list(magic_data.values())[
|
||||
self.combat.magic_index]['strength'] + player.stats.magic
|
||||
|
||||
cost = list(magic_data.values())[
|
||||
self.combat.magic_index]['cost']
|
||||
print(self.combat.magic)
|
||||
self.combat.create_magic_sprite(
|
||||
player, self.combat.magic, strength, cost)
|
||||
|
||||
# Rotating Weapons
|
||||
if button == 6 and self.can_rotate_weapon: # keys[pygame.K_LSHIFT]
|
||||
self.can_rotate_weapon = False
|
||||
self.weapon_rotation_time = pygame.time.get_ticks()
|
||||
if self.combat.weapon_index < len(list(weapon_data.keys())) - 1:
|
||||
self.combat.weapon_index += 1
|
||||
else:
|
||||
self.combat.weapon_index = 0
|
||||
|
||||
self.combat.weapon = list(weapon_data.keys())[
|
||||
self.combat.weapon_index]
|
||||
|
||||
# Swap Spells
|
||||
if button == 7 and self.can_swap_magic: # keys[pygame.K_LCTRL] :
|
||||
self.can_swap_magic = False
|
||||
self.magic_swap_time = pygame.time.get_ticks()
|
||||
if self.combat.magic_index < len(list(magic_data.keys())) - 1:
|
||||
self.combat.magic_index += 1
|
||||
else:
|
||||
self.combat.magic_index = 0
|
||||
|
||||
def cooldowns(self, vulnerable):
|
||||
current_time = pygame.time.get_ticks()
|
||||
|
||||
if self.attacking:
|
||||
if current_time - self.attack_time > self.attack_cooldown + weapon_data[self.combat.weapon]['cooldown']:
|
||||
self.attacking = False
|
||||
if self.combat.current_attack:
|
||||
self.combat.delete_attack_sprite()
|
||||
|
||||
if not self.can_rotate_weapon:
|
||||
if current_time - self.weapon_rotation_time > self.rotate_attack_cooldown:
|
||||
self.can_rotate_weapon = True
|
||||
|
||||
if not self.can_swap_magic:
|
||||
if current_time - self.magic_swap_time > self.rotate_attack_cooldown:
|
||||
self.can_swap_magic = True
|
||||
|
||||
if not vulnerable:
|
||||
if current_time - self.hurt_time >= self.invulnerability_duration:
|
||||
vulnerable = True
|
||||
|
||||
if not self.can_move:
|
||||
if current_time - self.move_time >= self.move_cooldown:
|
||||
self.can_move = True
|
69
game/entities/components/animaton.py
Normal file
69
game/entities/components/animaton.py
Normal file
|
@ -0,0 +1,69 @@
|
|||
import os
|
||||
import pygame
|
||||
from math import sin
|
||||
|
||||
from utils.resource_loader import import_folder
|
||||
from configs.system.window_config import HITBOX_OFFSET
|
||||
|
||||
|
||||
class AnimationHandler:
|
||||
|
||||
def __init__(self):
|
||||
self.frame_index = 0
|
||||
self.animation_speed = 0.15
|
||||
|
||||
def import_assets(self, sprite_type, position, name=None):
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
asset_path = os.path.join(
|
||||
script_dir, '../../..', 'assets', 'graphics')
|
||||
if sprite_type == 'player':
|
||||
|
||||
character_path = f"{asset_path}/player"
|
||||
|
||||
# Import Graphic Assets
|
||||
self.image = pygame.image.load(
|
||||
f"{character_path}/down/down_0.png").convert_alpha()
|
||||
self.rect = self.image.get_rect(topleft=position)
|
||||
self.hitbox = self.rect.inflate(HITBOX_OFFSET[sprite_type])
|
||||
self.animations = {
|
||||
'up': [], 'down': [], 'left': [], 'right': [],
|
||||
'up_idle': [], 'down_idle': [], 'left_idle': [], 'right_idle': [],
|
||||
'up_attack': [], 'down_attack': [], 'left_attack': [], 'right_attack': []
|
||||
}
|
||||
for animation in self.animations.keys():
|
||||
full_path = f"{character_path}/{animation}"
|
||||
self.animations[animation] = import_folder(full_path)
|
||||
|
||||
elif sprite_type == 'enemy':
|
||||
|
||||
character_path = f"{asset_path}/monsters/{name}"
|
||||
self.animations = {'idle': [], 'move': [], 'attack': []}
|
||||
main_path = f"{asset_path}/monsters/{name}"
|
||||
|
||||
for animation in self.animations.keys():
|
||||
self.animations[animation] = import_folder(
|
||||
f"{main_path}/{animation}")
|
||||
|
||||
def animate(self, status, vulnerable):
|
||||
animation = self.animations[status]
|
||||
|
||||
self.frame_index += self.animation_speed
|
||||
if self.frame_index >= len(animation):
|
||||
self.frame_index = 0
|
||||
|
||||
# Set the image
|
||||
self.image = animation[int(self.frame_index)]
|
||||
self.rect = self.image.get_rect(center=self.hitbox.center)
|
||||
|
||||
if not vulnerable:
|
||||
alpha = self.wave_value()
|
||||
self.image.set_alpha(alpha)
|
||||
else:
|
||||
self.image.set_alpha(255)
|
||||
|
||||
def wave_value(self):
|
||||
value = sin(pygame.time.get_ticks())
|
||||
if value >= 0:
|
||||
return 255
|
||||
else:
|
||||
return 0
|
59
game/entities/components/combat.py
Normal file
59
game/entities/components/combat.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
import os
|
||||
import pygame
|
||||
|
||||
from effects.weapon_effects import Weapon
|
||||
from effects.magic_effects import MagicPlayer
|
||||
from effects.particle_effects import AnimationPlayer
|
||||
|
||||
from configs.game.weapon_config import weapon_data
|
||||
from configs.game.spell_config import magic_data
|
||||
|
||||
|
||||
class CombatHandler:
|
||||
|
||||
def __init__(self):
|
||||
|
||||
# Setup Combat
|
||||
self.animation_player = AnimationPlayer()
|
||||
self.magic_player = MagicPlayer(self.animation_player)
|
||||
self.current_attack = None
|
||||
|
||||
# Spell and Weapon Rotation
|
||||
self.weapon_index = 0
|
||||
self.weapon = list(weapon_data.keys())[self.weapon_index]
|
||||
|
||||
self.magic_index = 0
|
||||
self.magic = list(magic_data.keys())[self.magic_index]
|
||||
|
||||
# Damage Timer
|
||||
self.vulnerable = True
|
||||
self.hurt_time = None
|
||||
self.invulnerability_duration = 300
|
||||
|
||||
# Import Sounds
|
||||
# script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
# asset_path = os.path.join(
|
||||
# script_dir, '../../..', 'assets', 'audio')
|
||||
#
|
||||
# self.weapon_attack_sound = pygame.mixer.Sound(
|
||||
# f"{asset_path}/sword.wav")
|
||||
# self.weapon_attack_sound.set_volume(0.2)
|
||||
|
||||
def create_attack_sprite(self, player):
|
||||
self.current_attack = Weapon(
|
||||
player, [player.visible_sprites, player.attack_sprites])
|
||||
|
||||
def delete_attack_sprite(self):
|
||||
if self.current_attack:
|
||||
self.current_attack.kill()
|
||||
self.current_attack = None
|
||||
|
||||
def create_magic_sprite(self, player, style, strength, cost):
|
||||
print(style)
|
||||
if style == 'heal':
|
||||
self.magic_player.heal(player, strength, cost, [
|
||||
player.visible_sprites])
|
||||
|
||||
if style == 'flame':
|
||||
self.magic_player.flame(
|
||||
player, cost, [player.visible_sprites, player.attack_sprites])
|
41
game/entities/components/movement.py
Normal file
41
game/entities/components/movement.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
import pygame
|
||||
|
||||
|
||||
class MovementHandler:
|
||||
|
||||
def __init__(self, sprite_type):
|
||||
|
||||
self.direction = pygame.math.Vector2()
|
||||
|
||||
def move(self, speed, hitbox, obstacle_sprites, rect):
|
||||
if self.direction.magnitude() != 0:
|
||||
self.direction = self.direction.normalize()
|
||||
|
||||
hitbox.x += self.direction.x * speed
|
||||
self.collision('horizontal', hitbox, obstacle_sprites)
|
||||
hitbox.y += self.direction.y * speed
|
||||
self.collision('vertical', hitbox, obstacle_sprites)
|
||||
rect.center = hitbox.center
|
||||
|
||||
def collision(self, direction, hitbox, obstacle_sprites):
|
||||
if direction == 'horizontal':
|
||||
for sprite in obstacle_sprites:
|
||||
# The following works for static obstacles only
|
||||
if sprite.hitbox.colliderect(hitbox):
|
||||
# Moving Right
|
||||
if self.direction.x > 0:
|
||||
hitbox.right = sprite.hitbox.left
|
||||
# Moving Left
|
||||
if self.direction.x < 0:
|
||||
hitbox.left = sprite.hitbox.right
|
||||
|
||||
if direction == 'vertical':
|
||||
for sprite in obstacle_sprites:
|
||||
# The following works for static obstacles only
|
||||
if sprite.hitbox.colliderect(hitbox):
|
||||
# Moving Down
|
||||
if self.direction.y > 0:
|
||||
hitbox.bottom = sprite.hitbox.top
|
||||
# Moving Up
|
||||
if self.direction.y < 0:
|
||||
hitbox.top = sprite.hitbox.bottom
|
45
game/entities/components/stats.py
Normal file
45
game/entities/components/stats.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
class StatsHandler:
|
||||
|
||||
def __init__(self):
|
||||
self.stats = {
|
||||
'health': 100,
|
||||
'energy': 60,
|
||||
'attack': 10,
|
||||
'magic': 4,
|
||||
'speed': 5
|
||||
}
|
||||
|
||||
self.max_stats = {
|
||||
'health': 300,
|
||||
'energy': 150,
|
||||
'attack': 20,
|
||||
'magic': 10,
|
||||
'speed': 10
|
||||
}
|
||||
|
||||
self.upgrade_costs = {
|
||||
'health': 100,
|
||||
'energy': 100,
|
||||
'attack': 100,
|
||||
'magic': 100,
|
||||
'speed': 100
|
||||
}
|
||||
|
||||
self.health = self.stats['health']
|
||||
self.energy = self.stats['energy']
|
||||
self.attack = self.stats['attack']
|
||||
self.magic = self.stats['magic']
|
||||
self.speed = self.stats['speed']
|
||||
self.exp = 10000
|
||||
|
||||
def energy_recovery(self):
|
||||
if self.energy < self.stats['energy']:
|
||||
self.energy += 0.01 * self.stats['magic']
|
||||
else:
|
||||
self.energy = self.stats['energy']
|
||||
|
||||
def get_value_by_index(self, index):
|
||||
return list(self.stats.values())[index]
|
||||
|
||||
def get_cost_by_index(self, index):
|
||||
return list(self.upgrade_costs.values())[index]
|
9
game/entities/entity.py
Normal file
9
game/entities/entity.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
import pygame
|
||||
from math import sin
|
||||
import random
|
||||
|
||||
|
||||
class Entity():
|
||||
|
||||
def __init__(self, groups, position, sprite_type):
|
||||
super().__init__(groups)
|
65
game/entities/observer.py
Normal file
65
game/entities/observer.py
Normal file
|
@ -0,0 +1,65 @@
|
|||
import os
|
||||
import pygame
|
||||
|
||||
from configs.system.window_config import HITBOX_OFFSET
|
||||
|
||||
|
||||
class Observer(pygame.sprite.Sprite):
|
||||
|
||||
def __init__(self, position, groups):
|
||||
super().__init__(groups)
|
||||
|
||||
self.sprite_type = 'camera'
|
||||
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
asset_path = os.path.join(
|
||||
script_dir, '../..', 'assets')
|
||||
|
||||
self.image = pygame.image.load(
|
||||
f"{asset_path}/graphics/observer.png").convert_alpha()
|
||||
self.rect = self.image.get_rect(topleft=position)
|
||||
self.hitbox = self.rect.inflate(HITBOX_OFFSET[self.sprite_type])
|
||||
|
||||
# Stats
|
||||
self.exp = -1 # This prints OBSERVER in the UI
|
||||
self.speed = 10 # Speed for moving around
|
||||
|
||||
# Movement
|
||||
self.direction = pygame.math.Vector2()
|
||||
|
||||
def input(self):
|
||||
keys = pygame.key.get_pressed()
|
||||
|
||||
# Movement Input
|
||||
if keys[pygame.K_w]:
|
||||
self.direction.y = -1
|
||||
self.status = 'up'
|
||||
self.can_move = False
|
||||
elif keys[pygame.K_s]:
|
||||
self.direction.y = 1
|
||||
self.status = 'down'
|
||||
self.can_move = False
|
||||
else:
|
||||
self.direction.y = 0
|
||||
|
||||
if keys[pygame.K_a]:
|
||||
self.direction.x = -1
|
||||
self.status = 'left'
|
||||
self.can_move = False
|
||||
elif keys[pygame.K_d]:
|
||||
self.direction.x = 1
|
||||
self.status = 'right'
|
||||
self.can_move = False
|
||||
else:
|
||||
self.direction.x = 0
|
||||
|
||||
def move(self, speed):
|
||||
if self.direction.magnitude() != 0:
|
||||
self.direction = self.direction.normalize()
|
||||
self.hitbox.x += self.direction.x * speed
|
||||
self.hitbox.y += self.direction.y * speed
|
||||
self.rect.center = self.hitbox.center
|
||||
|
||||
def update(self):
|
||||
self.input()
|
||||
self.move(self.speed)
|
67
game/entities/player.py
Normal file
67
game/entities/player.py
Normal file
|
@ -0,0 +1,67 @@
|
|||
import pygame
|
||||
|
||||
from .components.combat import CombatHandler
|
||||
from .components.stats import StatsHandler
|
||||
from .components._input import InputHandler
|
||||
from .components.animaton import AnimationHandler
|
||||
|
||||
|
||||
class Player(pygame.sprite.Sprite):
|
||||
|
||||
def __init__(self, position, groups, obstacle_sprites, visible_sprites, attack_sprites):
|
||||
super().__init__(groups)
|
||||
|
||||
# Setup Sprites
|
||||
self.sprite_type = 'player'
|
||||
self.visible_sprites = visible_sprites
|
||||
self.attack_sprites = attack_sprites
|
||||
self.obstacle_sprites = obstacle_sprites
|
||||
self.status = 'down'
|
||||
|
||||
# Setup Inputs
|
||||
self._input = InputHandler(
|
||||
self.sprite_type, self.status)
|
||||
|
||||
# Setup Graphics
|
||||
self.animation = AnimationHandler()
|
||||
self.animation.import_assets(self.sprite_type, position)
|
||||
self.animate = self.animation.animate
|
||||
self.image = self.animation.image
|
||||
self.animate(self.status, self._input.combat.vulnerable)
|
||||
self.rect = self.animation.rect
|
||||
|
||||
# Setup Stats
|
||||
self.stats = StatsHandler()
|
||||
|
||||
def get_status(self):
|
||||
if self._input.movement.direction.x == 0 and self._input.movement.direction.y == 0:
|
||||
if not 'idle' in self.status and not 'attack' in self.status:
|
||||
self.status += '_idle'
|
||||
|
||||
if self._input.attacking:
|
||||
self._input.movement.direction.x = 0
|
||||
self._input.movement.direction.y = 0
|
||||
if not 'attack' in self.status:
|
||||
if 'idle' in self.status:
|
||||
self.status = self.status.replace('idle', 'attack')
|
||||
else:
|
||||
self.status += '_attack'
|
||||
else:
|
||||
if 'attack' in self.status:
|
||||
self.status = self.status.replace('_attack', '')
|
||||
|
||||
def update(self):
|
||||
# Refresh objects based on input
|
||||
self._input.check_input(
|
||||
self.stats.stats['speed'], self.animation.hitbox, self.obstacle_sprites, self.animation.rect, self)
|
||||
self.status = self._input.status
|
||||
|
||||
# Animate
|
||||
self.get_status()
|
||||
self.animation.animate(self.status, self._input.combat.vulnerable)
|
||||
self.image = self.animation.image
|
||||
self.rect = self.animation.rect
|
||||
|
||||
# Cooldowns and Regen
|
||||
self.stats.energy_recovery()
|
||||
self._input.cooldowns(self._input.combat.vulnerable)
|
0
game/interface/__init__.py
Normal file
0
game/interface/__init__.py
Normal file
|
@ -1,98 +1,116 @@
|
|||
import pygame
|
||||
from utils.settings import *
|
||||
|
||||
from configs.game.weapon_config import *
|
||||
from configs.game.spell_config import *
|
||||
|
||||
from .ui_settings import *
|
||||
|
||||
|
||||
class UI:
|
||||
def __init__(self):
|
||||
|
||||
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
asset_path = os.path.join(
|
||||
script_dir, '../..', 'assets')
|
||||
|
||||
# General info
|
||||
self.display_surface = pygame.display.get_surface()
|
||||
self.font = pygame.font.Font(UI_FONT, UI_FONT_SIZE)
|
||||
|
||||
|
||||
# Bar setup
|
||||
self.health_bar_rect = pygame.Rect(10, 10, HEALTH_BAR_WIDTH, BAR_HEIGHT)
|
||||
self.energy_bar_rect = pygame.Rect(10, 34, ENERGY_BAR_WIDTH, BAR_HEIGHT)
|
||||
|
||||
self.health_bar_rect = pygame.Rect(
|
||||
10, 10, HEALTH_BAR_WIDTH, BAR_HEIGHT)
|
||||
self.energy_bar_rect = pygame.Rect(
|
||||
10, 34, ENERGY_BAR_WIDTH, BAR_HEIGHT)
|
||||
|
||||
# Convert weapon dictionary
|
||||
self.weapon_graphics = []
|
||||
for weapon in weapon_data.values():
|
||||
path = weapon['graphic']
|
||||
weapon = pygame.image.load(path).convert_alpha()
|
||||
self.weapon_graphics.append(weapon)
|
||||
|
||||
|
||||
# Convert weapon dictionary
|
||||
self.magic_graphics = []
|
||||
for spell in magic_data.values():
|
||||
path = spell['graphic']
|
||||
spell = pygame.image.load(path).convert_alpha()
|
||||
self.magic_graphics.append(spell)
|
||||
|
||||
|
||||
|
||||
def show_bar(self, current_amount, max_amount, bg_rect, color):
|
||||
|
||||
|
||||
# Draw background
|
||||
pygame.draw.rect(self.display_surface, UI_BG_COLOR, bg_rect)
|
||||
|
||||
|
||||
# Convert stat amount to pixels
|
||||
ratio = current_amount / max_amount
|
||||
current_width = bg_rect.width * ratio
|
||||
current_rect = bg_rect.copy()
|
||||
current_rect.width = current_width
|
||||
|
||||
|
||||
# Draw stat bar
|
||||
pygame.draw.rect(self.display_surface, color, current_rect)
|
||||
pygame.draw.rect(self.display_surface, UI_BORDER_COLOR, bg_rect, 4)
|
||||
|
||||
|
||||
def show_exp(self, exp):
|
||||
if exp >= 0:
|
||||
text_surf = self.font.render(f"EXP: {str(int(exp))}", False, TEXT_COLOR)
|
||||
text_surf = self.font.render(
|
||||
f"EXP: {str(int(exp))}", False, TEXT_COLOR)
|
||||
x = self.display_surface.get_size()[0] - 20
|
||||
y = self.display_surface.get_size()[1] - 20
|
||||
text_rect = text_surf.get_rect(bottomright = (x,y))
|
||||
|
||||
pygame.draw.rect(self.display_surface, UI_BG_COLOR, text_rect.inflate(10, 10))
|
||||
text_rect = text_surf.get_rect(bottomright=(x, y))
|
||||
|
||||
pygame.draw.rect(self.display_surface, UI_BG_COLOR,
|
||||
text_rect.inflate(10, 10))
|
||||
self.display_surface.blit(text_surf, text_rect)
|
||||
pygame.draw.rect(self.display_surface, UI_BORDER_COLOR, text_rect.inflate(10, 10), 4)
|
||||
pygame.draw.rect(self.display_surface,
|
||||
UI_BORDER_COLOR, text_rect.inflate(10, 10), 4)
|
||||
else:
|
||||
text_surf = self.font.render(f"OBSERVER", False, TEXT_COLOR)
|
||||
x = self.display_surface.get_size()[0] - 20
|
||||
y = self.display_surface.get_size()[1] - 20
|
||||
text_rect = text_surf.get_rect(bottomright = (x,y))
|
||||
|
||||
pygame.draw.rect(self.display_surface, UI_BG_COLOR, text_rect.inflate(10, 10))
|
||||
text_rect = text_surf.get_rect(bottomright=(x, y))
|
||||
|
||||
pygame.draw.rect(self.display_surface, UI_BG_COLOR,
|
||||
text_rect.inflate(10, 10))
|
||||
self.display_surface.blit(text_surf, text_rect)
|
||||
pygame.draw.rect(self.display_surface, UI_BORDER_COLOR, text_rect.inflate(10, 10), 4)
|
||||
|
||||
|
||||
pygame.draw.rect(self.display_surface,
|
||||
UI_BORDER_COLOR, text_rect.inflate(10, 10), 4)
|
||||
|
||||
def selection_box(self, left, top, has_rotated):
|
||||
bg_rect = pygame.Rect(left, top, ITEM_BOX_SIZE, ITEM_BOX_SIZE)
|
||||
pygame.draw.rect(self.display_surface, UI_BG_COLOR, bg_rect)
|
||||
if not has_rotated:
|
||||
pygame.draw.rect(self.display_surface, UI_BORDER_COLOR_ACTIVE, bg_rect, 4)
|
||||
pygame.draw.rect(self.display_surface,
|
||||
UI_BORDER_COLOR_ACTIVE, bg_rect, 4)
|
||||
else:
|
||||
pygame.draw.rect(self.display_surface, UI_BORDER_COLOR, bg_rect, 4)
|
||||
return bg_rect
|
||||
|
||||
|
||||
def weapon_overlay(self, weapon_index, has_rotated):
|
||||
bg_rect = self.selection_box(10, 630, has_rotated)
|
||||
weapon_surf = self.weapon_graphics[weapon_index]
|
||||
weapon_rect = weapon_surf.get_rect(center = bg_rect.center)
|
||||
|
||||
weapon_rect = weapon_surf.get_rect(center=bg_rect.center)
|
||||
|
||||
self.display_surface.blit(weapon_surf, weapon_rect)
|
||||
|
||||
|
||||
def magic_overlay(self, magic_index, has_swaped):
|
||||
bg_rect = self.selection_box(100, 630, has_swaped)
|
||||
bg_rect = self.selection_box(100, 630, has_swaped)
|
||||
magic_surf = self.magic_graphics[magic_index]
|
||||
magic_rect = magic_surf.get_rect(center = bg_rect.center)
|
||||
|
||||
magic_rect = magic_surf.get_rect(center=bg_rect.center)
|
||||
|
||||
self.display_surface.blit(magic_surf, magic_rect)
|
||||
|
||||
|
||||
def display(self, player):
|
||||
if player.sprite_type == 'player':
|
||||
self.show_bar(player.health, player.stats['health'], self.health_bar_rect, HEALTH_COLOR)
|
||||
self.show_bar(player.energy, player.stats['energy'], self.energy_bar_rect, ENERGY_COLOR)
|
||||
self.show_exp(player.exp)
|
||||
self.weapon_overlay(player.weapon_index, player.can_rotate_weapon)
|
||||
self.magic_overlay(player.magic_index, player.can_swap_magic)
|
||||
self.show_bar(
|
||||
player.stats.health, player.stats.stats['health'], self.health_bar_rect, HEALTH_COLOR)
|
||||
self.show_bar(
|
||||
player.stats.energy, player.stats.stats['energy'], self.energy_bar_rect, ENERGY_COLOR)
|
||||
self.show_exp(player.stats.exp)
|
||||
self.weapon_overlay(player._input.combat.weapon_index,
|
||||
player._input.can_rotate_weapon)
|
||||
self.magic_overlay(player._input.combat.magic_index,
|
||||
player._input.can_swap_magic)
|
||||
if player.sprite_type == 'camera':
|
||||
self.show_exp(player.exp)
|
||||
|
|
@ -1,9 +1,15 @@
|
|||
import os
|
||||
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
asset_path = os.path.join(
|
||||
script_dir, '../..', 'assets')
|
||||
|
||||
# ui
|
||||
BAR_HEIGHT = 20
|
||||
HEALTH_BAR_WIDTH = 200
|
||||
ENERGY_BAR_WIDTH = 140
|
||||
ITEM_BOX_SIZE = 80
|
||||
UI_FONT = '../Graphics/graphics/font/joystix.ttf'
|
||||
UI_FONT = f"{asset_path}/font/joystix.ttf"
|
||||
UI_FONT_SIZE = 18
|
||||
|
||||
# general colors
|
|
@ -1,31 +1,34 @@
|
|||
import pygame
|
||||
from utils.settings import *
|
||||
|
||||
from .ui_settings import *
|
||||
|
||||
|
||||
class Upgrade:
|
||||
|
||||
|
||||
def __init__(self, player):
|
||||
|
||||
|
||||
# General setup
|
||||
self.display_surface = pygame.display.get_surface()
|
||||
self.player = player
|
||||
self.attribute_num = len(player.stats)
|
||||
self.attribute_names = list(player.stats.keys())
|
||||
self.max_values = list(player.max_stats.values())
|
||||
self.attribute_num = len(player.stats.stats)
|
||||
self.attribute_names = list(player.stats.stats.keys())
|
||||
self.max_values = list(player.stats.max_stats.values())
|
||||
self.font = pygame.font.Font(UI_FONT, UI_FONT_SIZE)
|
||||
|
||||
|
||||
# Defining upgrade boxes
|
||||
self.height = self.display_surface.get_size()[1] * 0.8
|
||||
self.width = self.display_surface.get_size()[0] // (self.attribute_num + 1)
|
||||
self.width = self.display_surface.get_size()[
|
||||
0] // (self.attribute_num + 1)
|
||||
self.create_boxes()
|
||||
|
||||
|
||||
# Selection System
|
||||
self.selection_index = 0
|
||||
self.selection_time = None
|
||||
self.can_move = True
|
||||
|
||||
|
||||
def input(self):
|
||||
keys = pygame.key.get_pressed()
|
||||
|
||||
|
||||
if self.can_move:
|
||||
if keys[pygame.K_d]:
|
||||
self.selection_index += 1
|
||||
|
@ -39,98 +42,99 @@ class Upgrade:
|
|||
self.selection_index = self.attribute_num - 1
|
||||
self.can_move = False
|
||||
self.selection_time = pygame.time.get_ticks()
|
||||
|
||||
|
||||
if keys[pygame.K_SPACE]:
|
||||
self.can_move = False
|
||||
self.selection_time = pygame.time.get_ticks()
|
||||
self.box_list[self.selection_index].trigger(self.player)
|
||||
|
||||
|
||||
def selection_cooldown(self):
|
||||
if not self.can_move:
|
||||
current_time = pygame.time.get_ticks()
|
||||
if current_time - self.selection_time >= 150:
|
||||
self.can_move = True
|
||||
|
||||
|
||||
def create_boxes(self):
|
||||
self.box_list = []
|
||||
|
||||
|
||||
for box, index in enumerate(range(self.attribute_num)):
|
||||
|
||||
|
||||
# Horizontal position
|
||||
full_width = self.display_surface.get_size()[0]
|
||||
increment = full_width // self.attribute_num
|
||||
left = (box * increment) + (increment - self.width) // 2
|
||||
|
||||
|
||||
# Vertical position
|
||||
top = self.display_surface.get_size()[1] * 0.1
|
||||
|
||||
|
||||
box = Box(left, top, self.width, self.height, index, self.font)
|
||||
self.box_list.append(box)
|
||||
|
||||
|
||||
|
||||
def display(self):
|
||||
|
||||
|
||||
self.input()
|
||||
self.selection_cooldown()
|
||||
|
||||
|
||||
for index, box in enumerate(self.box_list):
|
||||
# Get attributes
|
||||
name = self.attribute_names[index]
|
||||
value = self.player.get_value_by_index(index)
|
||||
value = self.player.stats.get_value_by_index(index)
|
||||
max_value = self.max_values[index]
|
||||
cost = self.player.get_cost_by_index(index)
|
||||
box.display(self.display_surface, self.selection_index, name, value, max_value, cost)
|
||||
|
||||
|
||||
cost = self.player.stats.get_cost_by_index(index)
|
||||
box.display(self.display_surface, self.selection_index,
|
||||
name, value, max_value, cost)
|
||||
|
||||
|
||||
class Box:
|
||||
def __init__(self, l, t, w, h, index, font):
|
||||
self.rect = pygame.Rect(l, t, w, h)
|
||||
def __init__(self, left, top, width, height, index, font):
|
||||
self.rect = pygame.Rect(left, top, width, height)
|
||||
self.index = index
|
||||
self.font = font
|
||||
|
||||
|
||||
def display_names(self, surface, name, cost, selected):
|
||||
color = TEXT_COLOR_SELECTED if selected else TEXT_COLOR
|
||||
|
||||
|
||||
# Title
|
||||
title_surf = self.font.render(name, False, color)
|
||||
title_rect = title_surf.get_rect(midtop = self.rect.midtop + pygame.math.Vector2(0, 20))
|
||||
title_rect = title_surf.get_rect(
|
||||
midtop=self.rect.midtop + pygame.math.Vector2(0, 20))
|
||||
# Cost
|
||||
cost_surf = self.font.render(f'Cost: {int(cost)}', False, color)
|
||||
cost_rect = cost_surf.get_rect(midbottom = self.rect.midbottom +- pygame.math.Vector2(0, 20))
|
||||
|
||||
cost_rect = cost_surf.get_rect(
|
||||
midbottom=self.rect.midbottom + - pygame.math.Vector2(0, 20))
|
||||
|
||||
# Draw
|
||||
surface.blit(title_surf, title_rect)
|
||||
surface.blit(cost_surf, cost_rect)
|
||||
|
||||
|
||||
def display_bar(self, surface, value, max_value, selected):
|
||||
|
||||
|
||||
# Line setup
|
||||
top = self.rect.midtop + pygame.math.Vector2(0, 60)
|
||||
bottom = self.rect.midbottom - pygame.math.Vector2(0,60)
|
||||
bottom = self.rect.midbottom - pygame.math.Vector2(0, 60)
|
||||
color = BAR_COLOR_SELECTED if selected else BAR_COLOR
|
||||
|
||||
|
||||
# Bar setup
|
||||
full_height = bottom[1] - top[1]
|
||||
relative_number = (value / max_value) * full_height
|
||||
value_rect = pygame.Rect(top[0] - 15, bottom[1] - relative_number, 30, 10)
|
||||
|
||||
value_rect = pygame.Rect(
|
||||
top[0] - 15, bottom[1] - relative_number, 30, 10)
|
||||
|
||||
# Draw elements
|
||||
pygame.draw.line(surface, color, top, bottom, 5)
|
||||
pygame.draw.rect(surface, color, value_rect)
|
||||
|
||||
|
||||
def trigger(self, player):
|
||||
upgrade_attribute = list(player.stats.keys())[self.index]
|
||||
|
||||
if player.exp >= player.upgrade_costs[upgrade_attribute] and player.stats[upgrade_attribute] < player.max_stats[upgrade_attribute]:
|
||||
player.exp -= player.upgrade_costs[upgrade_attribute]
|
||||
player.stats[upgrade_attribute] *= 1.2
|
||||
player.upgrade_costs[upgrade_attribute] *= 1.4
|
||||
|
||||
if player.stats[upgrade_attribute] > player.max_stats[upgrade_attribute]:
|
||||
player.stats[upgrade_attribute] = player.max_stats[upgrade_attribute]
|
||||
|
||||
|
||||
|
||||
upgrade_attribute = list(player.stats.stats.keys())[self.index]
|
||||
|
||||
if player.stats.exp >= player.stats.upgrade_costs[upgrade_attribute] and player.stats.stats[upgrade_attribute] < player.stats.max_stats[upgrade_attribute]:
|
||||
player.stats.exp -= player.stats.upgrade_costs[upgrade_attribute]
|
||||
player.stats.stats[upgrade_attribute] *= 1.2
|
||||
player.stats.upgrade_costs[upgrade_attribute] *= 1.4
|
||||
|
||||
if player.stats.stats[upgrade_attribute] > player.stats.max_stats[upgrade_attribute]:
|
||||
player.stats.stats[upgrade_attribute] = player.stats.max_stats[upgrade_attribute]
|
||||
|
||||
def display(self, surface, selection_num, name, value, max_value, cost):
|
||||
if self.index == selection_num:
|
||||
pygame.draw.rect(surface, UPGRADE_BG_COLOR_SELECTED, self.rect)
|
||||
|
@ -138,6 +142,7 @@ class Box:
|
|||
else:
|
||||
pygame.draw.rect(surface, UI_BG_COLOR, self.rect)
|
||||
pygame.draw.rect(surface, UI_BORDER_COLOR, self.rect, 4)
|
||||
|
||||
|
||||
self.display_names(surface, name, cost, self.index == selection_num)
|
||||
self.display_bar(surface, value, max_value, self.index == selection_num)
|
||||
self.display_bar(surface, value, max_value,
|
||||
self.index == selection_num)
|
0
game/level/__init__.py
Normal file
0
game/level/__init__.py
Normal file
42
game/level/camera.py
Normal file
42
game/level/camera.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
import os
|
||||
import pygame
|
||||
|
||||
|
||||
class Camera(pygame.sprite.Group):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
# General Setup
|
||||
self.display_surface = pygame.display.get_surface()
|
||||
self.half_width = self.display_surface.get_size()[0] // 2
|
||||
self.half_height = self.display_surface.get_size()[1] // 2
|
||||
self.offset = pygame.math.Vector2(100, 200)
|
||||
|
||||
# Creating the floor
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
image_path = os.path.join(
|
||||
script_dir, '../..', 'assets', 'graphics', 'tilemap', 'ground.png')
|
||||
|
||||
self.floor_surf = pygame.image.load(image_path).convert()
|
||||
self.floor_rect = self.floor_surf.get_rect(topleft=(0, 0))
|
||||
|
||||
def custom_draw(self, player):
|
||||
self.sprite_type = player.sprite_type
|
||||
# Getting the offset
|
||||
self.offset.x = player.rect.centerx - self.half_width
|
||||
self.offset.y = player.rect.centery - self.half_height
|
||||
|
||||
# Drawing the floor
|
||||
floor_offset_pos = self.floor_rect.topleft - self.offset
|
||||
self.display_surface.blit(self.floor_surf, floor_offset_pos)
|
||||
|
||||
for sprite in sorted(self.sprites(), key=lambda sprite: sprite.rect.centery):
|
||||
offset_pos = sprite.rect.topleft - self.offset
|
||||
self.display_surface.blit(sprite.image, offset_pos)
|
||||
|
||||
def enemy_update(self, player):
|
||||
enemy_sprites = [sprite for sprite in self.sprites() if hasattr(
|
||||
sprite, 'sprite_type') and sprite.sprite_type == 'enemy']
|
||||
for enemy in enemy_sprites:
|
||||
enemy.enemy_update(player)
|
120
game/level/level.py
Normal file
120
game/level/level.py
Normal file
|
@ -0,0 +1,120 @@
|
|||
import os
|
||||
import pygame
|
||||
|
||||
from random import choice, randint
|
||||
|
||||
from configs.game.spell_config import magic_data
|
||||
from configs.game.weapon_config import weapon_data
|
||||
from configs.game.monster_config import monster_data
|
||||
from configs.system.window_config import TILESIZE
|
||||
|
||||
from utils.debug import debug
|
||||
from utils.resource_loader import import_csv_layout, import_folder
|
||||
|
||||
from interface.ui import UI
|
||||
from interface.upgrade import Upgrade
|
||||
|
||||
from effects.magic_effects import MagicPlayer
|
||||
from effects.particle_effects import AnimationPlayer
|
||||
from effects.weapon_effects import Weapon
|
||||
|
||||
from entities.observer import Observer
|
||||
from entities.player import Player
|
||||
|
||||
from .terrain import Tile
|
||||
from .camera import Camera
|
||||
|
||||
|
||||
class Level:
|
||||
|
||||
def __init__(self):
|
||||
|
||||
# General Settings
|
||||
self.game_paused = False
|
||||
|
||||
# Get display surface
|
||||
self.display_surface = pygame.display.get_surface()
|
||||
|
||||
# Sprite Group setup
|
||||
self.visible_sprites = Camera()
|
||||
self.obstacle_sprites = pygame.sprite.Group()
|
||||
self.attack_sprites = pygame.sprite.Group()
|
||||
self.attackable_sprites = pygame.sprite.Group()
|
||||
|
||||
# Sprite setup and entity generation
|
||||
self.create_map()
|
||||
|
||||
# UI setup
|
||||
self.ui = UI()
|
||||
self.upgrade = Upgrade(self.player)
|
||||
|
||||
def create_map(self):
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
asset_path = os.path.join(
|
||||
script_dir, '../..', 'assets')
|
||||
layouts = {
|
||||
'boundary': import_csv_layout(f"{asset_path}/map/FloorBlocks.csv"),
|
||||
'grass': import_csv_layout(f"{asset_path}/map/Grass.csv"),
|
||||
'objects': import_csv_layout(f"{asset_path}/map/Objects.csv"),
|
||||
'entities': import_csv_layout(f"{asset_path}/map/Entities.csv")
|
||||
}
|
||||
|
||||
graphics = {
|
||||
'grass': import_folder(f"{asset_path}/graphics/grass"),
|
||||
'objects': import_folder(f"{asset_path}/graphics/objects")
|
||||
}
|
||||
|
||||
for style, layout in layouts.items():
|
||||
for row_index, row in enumerate(layout):
|
||||
for col_index, col in enumerate(row):
|
||||
if col != '-1':
|
||||
x = col_index * TILESIZE
|
||||
y = row_index * TILESIZE
|
||||
if style == 'boundary':
|
||||
Tile((x, y), [self.obstacle_sprites], 'invisible')
|
||||
|
||||
if style == 'grass':
|
||||
random_grass_image = choice(graphics['grass'])
|
||||
Tile((x, y), [self.visible_sprites, self.obstacle_sprites,
|
||||
self.attackable_sprites], 'grass', random_grass_image)
|
||||
|
||||
if style == 'objects':
|
||||
surf = graphics['objects'][int(col)]
|
||||
Tile((x, y), [self.visible_sprites,
|
||||
self.obstacle_sprites], 'object', surf)
|
||||
|
||||
if style == 'entities':
|
||||
# The numbers represent their IDs in .csv files generated from TILED.
|
||||
if col == '395':
|
||||
self.observer = Observer(
|
||||
(x, y), [self.visible_sprites])
|
||||
|
||||
elif col == '394':
|
||||
# Player Generation
|
||||
self.player = Player(
|
||||
(x, y), [self.visible_sprites], self.obstacle_sprites, self.visible_sprites, self.attack_sprites)
|
||||
|
||||
else:
|
||||
pass
|
||||
# monster generation
|
||||
|
||||
def toggle_menu(self):
|
||||
self.game_paused = not self.game_paused
|
||||
|
||||
def run(self):
|
||||
# Draw the game
|
||||
self.visible_sprites.custom_draw(self.player)
|
||||
self.ui.display(self.player)
|
||||
debug(self.player.status)
|
||||
if not self.game_paused:
|
||||
# Update the game
|
||||
# self.player.distance_direction_to_player = self.get_state()
|
||||
self.visible_sprites.update()
|
||||
# self.visible_sprites.enemy_update(self.player)
|
||||
# self.player_attack_logic()
|
||||
else:
|
||||
if self.visible_sprites.sprite_type == 'player':
|
||||
self.upgrade.display()
|
||||
|
||||
if self.player.stats.health <= 0:
|
||||
self.__init__()
|
19
game/level/terrain.py
Normal file
19
game/level/terrain.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
import pygame
|
||||
|
||||
from configs.system.window_config import *
|
||||
|
||||
|
||||
class Tile(pygame.sprite.Sprite):
|
||||
|
||||
def __init__(self, position, groups, sprite_type, surface=pygame.Surface((TILESIZE, TILESIZE))):
|
||||
super().__init__(groups)
|
||||
|
||||
self.sprite_type = sprite_type
|
||||
self.image = surface
|
||||
if sprite_type == 'object':
|
||||
# Offset
|
||||
self.rect = self.image.get_rect(
|
||||
topleft=(position[0], position[1] - TILESIZE))
|
||||
else:
|
||||
self.rect = self.image.get_rect(topleft=position)
|
||||
self.hitbox = self.rect.inflate(HITBOX_OFFSET[sprite_type])
|
47
game/main.py
Normal file
47
game/main.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
import pygame
|
||||
import sys
|
||||
|
||||
from configs.system.window_config import WIDTH, HEIGHT, WATER_COLOR, FPS
|
||||
from utils.debug import debug
|
||||
|
||||
from level.level import Level
|
||||
|
||||
|
||||
class Game:
|
||||
|
||||
def __init__(self):
|
||||
|
||||
pygame.init()
|
||||
|
||||
self.screen = pygame.display.set_mode((WIDTH, HEIGHT))
|
||||
pygame.display.set_caption('Pneuma')
|
||||
self.clock = pygame.time.Clock()
|
||||
|
||||
self.level = Level()
|
||||
|
||||
# # Sound
|
||||
# main_sound = pygame.mixer.Sound('../assets/audio/main.ogg')
|
||||
# main_sound.set_volume(0.4)
|
||||
# main_sound.play(loops=-1)
|
||||
|
||||
def run(self):
|
||||
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
pygame.quit()
|
||||
sys.exit()
|
||||
if event.type == pygame.KEYDOWN:
|
||||
if event.key == pygame.K_m:
|
||||
self.level.toggle_menu()
|
||||
|
||||
self.screen.fill(WATER_COLOR)
|
||||
self.level.run()
|
||||
pygame.display.update()
|
||||
self.clock.tick(FPS)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
game = Game()
|
||||
while True:
|
||||
game.run()
|
0
game/utils/__init__.py
Normal file
0
game/utils/__init__.py
Normal file
|
@ -16,16 +16,17 @@ from terrain.tiles import Tile
|
|||
from view.observer import Observer
|
||||
from view.camera import Camera
|
||||
|
||||
|
||||
class Level:
|
||||
|
||||
|
||||
def __init__(self):
|
||||
|
||||
|
||||
# General Settings
|
||||
self.game_paused = False
|
||||
|
||||
|
||||
# Get display surface
|
||||
self.display_surface = pygame.display.get_surface()
|
||||
|
||||
|
||||
# Sprite Group setup
|
||||
self.visible_sprites = Camera()
|
||||
self.obstacle_sprites = pygame.sprite.Group()
|
||||
|
@ -58,31 +59,35 @@ class Level:
|
|||
x = col_index * TILESIZE
|
||||
y = row_index * TILESIZE
|
||||
if style == 'boundary':
|
||||
Tile((x,y), [self.obstacle_sprites], 'invisible')
|
||||
Tile((x, y), [self.obstacle_sprites], 'invisible')
|
||||
|
||||
if style == 'grass':
|
||||
random_grass_image = choice(graphics['grass'])
|
||||
Tile((x,y), [self.visible_sprites, self.obstacle_sprites, self.attackable_sprites], 'grass', random_grass_image)
|
||||
Tile((x, y), [self.visible_sprites, self.obstacle_sprites,
|
||||
self.attackable_sprites], 'grass', random_grass_image)
|
||||
|
||||
if style == 'objects':
|
||||
surf = graphics['objects'][int(col)]
|
||||
Tile((x,y), [self.visible_sprites, self.obstacle_sprites], 'object', surf)
|
||||
Tile((x, y), [self.visible_sprites,
|
||||
self.obstacle_sprites], 'object', surf)
|
||||
|
||||
if style == 'entities':
|
||||
# The numbers represent their IDs in the map .csv files generated from TILED.
|
||||
if col == '395':
|
||||
self.observer = Observer((x,y), [self.visible_sprites])
|
||||
self.observer = Observer(
|
||||
(x, y), [self.visible_sprites])
|
||||
|
||||
elif col == '394':
|
||||
pass
|
||||
#player generation
|
||||
# player generation
|
||||
|
||||
else:
|
||||
pass
|
||||
#monster generation
|
||||
# monster generation
|
||||
|
||||
def create_attack_sprite(self):
|
||||
self.current_attack = Weapon(self.player, [self.visible_sprites, self.attack_sprites])
|
||||
self.current_attack = Weapon(
|
||||
self.player, [self.visible_sprites, self.attack_sprites])
|
||||
|
||||
def delete_attack_sprite(self):
|
||||
if self.current_attack:
|
||||
|
@ -91,13 +96,27 @@ class Level:
|
|||
|
||||
def create_magic_sprite(self, style, strength, cost):
|
||||
if style == 'heal':
|
||||
self.magic_player.heal(self.player, strength, cost, [self.visible_sprites])
|
||||
self.magic_player.heal(self.player, strength, cost, [
|
||||
self.visible_sprites])
|
||||
|
||||
if style == 'flame':
|
||||
self.magic_player.flame(self.player, cost, [self.visible_sprites, self.attack_sprites])
|
||||
self.magic_player.flame(
|
||||
self.player, cost, [self.visible_sprites, self.attack_sprites])
|
||||
|
||||
def run(self):
|
||||
# Draw the game
|
||||
self.visible_sprites.custom_draw(self.observer)
|
||||
self.ui.display(self.observer)
|
||||
if self.game_paused:
|
||||
if self.visible_sprites.sprite_type == 'player':
|
||||
self.upgrade.display()
|
||||
pass
|
||||
else:
|
||||
# Update the game
|
||||
self.player.distance_direction_to_player = self.get_state()
|
||||
self.visible_sprites.update()
|
||||
self.visible_sprites.enemy_update(self.player)
|
||||
self.player_attack_logic()
|
||||
|
||||
if self.player.health <= 0:
|
||||
self.__init__()
|
||||
|
|
|
@ -6,16 +6,17 @@ from utils.debug import debug
|
|||
|
||||
from level import Level
|
||||
|
||||
|
||||
class Game:
|
||||
|
||||
def __init__(self):
|
||||
|
||||
|
||||
pygame.init()
|
||||
|
||||
self.screen = pygame.display.set_mode((WIDTH,HEIGHT))
|
||||
|
||||
self.screen = pygame.display.set_mode((WIDTH, HEIGHT))
|
||||
pygame.display.set_caption('Pneuma')
|
||||
self.clock = pygame.time.Clock()
|
||||
|
||||
|
||||
self.level = Level()
|
||||
|
||||
# # Sound
|
||||
|
@ -24,26 +25,22 @@ class Game:
|
|||
# main_sound.play(loops = -1)
|
||||
def run(self):
|
||||
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
pygame.quit()
|
||||
sys.exit()
|
||||
if event.type == pygame.KEYDOWN:
|
||||
if event.key == pygame.K_m:
|
||||
self.level.toggle_menu()
|
||||
|
||||
self.screen.fill(WATER_COLOR)
|
||||
self.level.run()
|
||||
pygame.display.update()
|
||||
self.clock.tick(FPS)
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
pygame.quit()
|
||||
sys.exit()
|
||||
if event.type == pygame.KEYDOWN:
|
||||
if event.key == pygame.K_m:
|
||||
self.level.toggle_menu()
|
||||
|
||||
self.screen.fill(WATER_COLOR)
|
||||
self.level.run()
|
||||
pygame.display.update()
|
||||
self.clock.tick(FPS)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
|
||||
game = Game()
|
||||
while True:
|
||||
game.run()
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -6,22 +6,24 @@ from utils.debug import debug
|
|||
|
||||
from objects.level import Level
|
||||
|
||||
|
||||
class Game:
|
||||
|
||||
def __init__(self):
|
||||
|
||||
|
||||
pygame.init()
|
||||
|
||||
self.screen = pygame.display.set_mode((WIDTH,HEIGHT))
|
||||
|
||||
self.screen = pygame.display.set_mode((WIDTH, HEIGHT))
|
||||
pygame.display.set_caption('Pneuma')
|
||||
self.clock = pygame.time.Clock()
|
||||
|
||||
|
||||
self.level = Level()
|
||||
|
||||
# Sound
|
||||
main_sound = pygame.mixer.Sound('../Graphics/audio/main.ogg')
|
||||
main_sound.set_volume(0.4)
|
||||
main_sound.play(loops = -1)
|
||||
main_sound.play(loops=-1)
|
||||
|
||||
def run(self):
|
||||
|
||||
for event in pygame.event.get():
|
||||
|
@ -31,19 +33,16 @@ class Game:
|
|||
if event.type == pygame.KEYDOWN:
|
||||
if event.key == pygame.K_m:
|
||||
self.level.toggle_menu()
|
||||
|
||||
|
||||
self.screen.fill(WATER_COLOR)
|
||||
self.level.run()
|
||||
pygame.display.update()
|
||||
self.clock.tick(FPS)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
game = Game()
|
||||
figure_file = 'rl/plots/pneuma.png'
|
||||
while True:
|
||||
game.run()
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -4,22 +4,24 @@ import random
|
|||
from utils.settings import *
|
||||
from utils.support import import_folder
|
||||
|
||||
|
||||
class Camera(pygame.sprite.Sprite):
|
||||
|
||||
def __init__(self, position, groups):
|
||||
super().__init__(groups)
|
||||
|
||||
|
||||
self.sprite_type = 'camera'
|
||||
|
||||
self.image = pygame.image.load('../Graphics/graphics/camera.png').convert_alpha()
|
||||
self.rect = self.image.get_rect(topleft = position)
|
||||
|
||||
self.image = pygame.image.load(
|
||||
'../Graphics/graphics/camera.png').convert_alpha()
|
||||
self.rect = self.image.get_rect(topleft=position)
|
||||
self.hitbox = self.rect.inflate(HITBOX_OFFSET[self.sprite_type])
|
||||
|
||||
# Stats
|
||||
self.exp = -1 # This prints OBSERVER in the UI
|
||||
self.speed = 10 # Speed for moving around
|
||||
|
||||
#Movement
|
||||
self.exp = -1 # This prints OBSERVER in the UI
|
||||
self.speed = 10 # Speed for moving around
|
||||
|
||||
# Movement
|
||||
self.direction = pygame.math.Vector2()
|
||||
|
||||
def input(self):
|
||||
|
@ -47,14 +49,13 @@ class Camera(pygame.sprite.Sprite):
|
|||
self.can_move = False
|
||||
else:
|
||||
self.direction.x = 0
|
||||
|
||||
|
||||
def move(self, speed):
|
||||
if self.direction.magnitude() != 0:
|
||||
self.direction = self.direction.normalize()
|
||||
self.hitbox.x += self.direction.x * speed
|
||||
self.hitbox.y += self.direction.y * speed
|
||||
self.rect.center = self.hitbox.center
|
||||
|
||||
|
||||
def update(self):
|
||||
self.input()
|
||||
|
|
|
@ -5,23 +5,24 @@ from utils.support import import_folder
|
|||
|
||||
from objects.entity import Entity
|
||||
|
||||
|
||||
class Enemy(Entity):
|
||||
|
||||
|
||||
def __init__(self, monster_name, position, groups, obstacle_sprites, damage_player, trigger_death_particles, add_exp, is_AI, state):
|
||||
super().__init__(groups, is_AI, state)
|
||||
|
||||
|
||||
# General setup
|
||||
self.sprite_type = 'enemy'
|
||||
|
||||
|
||||
# Graphics setup
|
||||
self.import_graphics(monster_name)
|
||||
self.status = 'idle'
|
||||
self.image = self.animations[self.status][self.frame_index]
|
||||
|
||||
|
||||
# Movement
|
||||
self.rect = self.image.get_rect(topleft = position)
|
||||
self.rect = self.image.get_rect(topleft=position)
|
||||
self.hitbox = self.rect.inflate(0, -10)
|
||||
|
||||
|
||||
# Stats
|
||||
self.monster_name = monster_name
|
||||
monster_info = monster_data[self.monster_name]
|
||||
|
@ -33,7 +34,7 @@ class Enemy(Entity):
|
|||
self.attack_radius = monster_info['attack_radius']
|
||||
self.notice_radius = monster_info['notice_radius']
|
||||
self.attack_type = monster_info['attack_type']
|
||||
|
||||
|
||||
# Sounds
|
||||
self.attack_sound = pygame.mixer.Sound(monster_info['attack_sound'])
|
||||
self.death_sound = pygame.mixer.Sound('../Graphics/audio/death.wav')
|
||||
|
@ -41,7 +42,7 @@ class Enemy(Entity):
|
|||
self.death_sound.set_volume(0.2)
|
||||
self.hit_sound.set_volume(0.2)
|
||||
self.attack_sound.set_volume(0.2)
|
||||
|
||||
|
||||
# Player Interaction
|
||||
self.can_attack = True
|
||||
self.attack_time = None
|
||||
|
@ -49,35 +50,36 @@ class Enemy(Entity):
|
|||
self.damage_player = damage_player
|
||||
self.trigger_death_particles = trigger_death_particles
|
||||
self.add_exp = add_exp
|
||||
|
||||
|
||||
# Invincibility times
|
||||
self.vulnerable = True
|
||||
self.hit_time = None
|
||||
self.invincibility_duration = 300
|
||||
|
||||
self.obstacle_sprites = obstacle_sprites
|
||||
|
||||
|
||||
def import_graphics(self, name):
|
||||
self.animations = {'idle':[], 'move':[], 'attack':[]}
|
||||
self.animations = {'idle': [], 'move': [], 'attack': []}
|
||||
main_path = f"../Graphics/graphics/monsters/{name}"
|
||||
for animation in self.animations.keys():
|
||||
self.animations[animation] = import_folder(f"{main_path}/{animation}")
|
||||
self.animations[animation] = import_folder(
|
||||
f"{main_path}/{animation}")
|
||||
|
||||
def get_player_distance_direction(self,player):
|
||||
def get_player_distance_direction(self, player):
|
||||
enemy_vector = pygame.math.Vector2(self.rect.center)
|
||||
player_vector = pygame.math.Vector2(player.rect.center)
|
||||
|
||||
|
||||
distance = (player_vector - enemy_vector).magnitude()
|
||||
if distance > 0:
|
||||
direction = (player_vector - enemy_vector).normalize()
|
||||
else:
|
||||
direction = pygame.math.Vector2()
|
||||
|
||||
|
||||
return (distance, direction)
|
||||
|
||||
def get_status(self, player):
|
||||
distance = self.get_player_distance_direction(player)[0]
|
||||
|
||||
|
||||
if distance <= self.attack_radius and self.can_attack:
|
||||
if self.status != 'attack':
|
||||
self.frame_index = 0
|
||||
|
@ -85,8 +87,8 @@ class Enemy(Entity):
|
|||
elif distance <= self.notice_radius:
|
||||
self.status = 'move'
|
||||
else:
|
||||
self.status = 'idle'
|
||||
|
||||
self.status = 'idle'
|
||||
|
||||
def actions(self, player):
|
||||
if self.status == 'attack':
|
||||
self.attack_sound.play()
|
||||
|
@ -99,22 +101,22 @@ class Enemy(Entity):
|
|||
|
||||
def animate(self):
|
||||
animation = self.animations[self.status]
|
||||
|
||||
|
||||
self.frame_index += self.animation_speed
|
||||
if self.frame_index >= len(animation):
|
||||
if self.status =='attack':
|
||||
if self.status == 'attack':
|
||||
self.can_attack = False
|
||||
self.frame_index = 0
|
||||
|
||||
|
||||
self.image = animation[int(self.frame_index)]
|
||||
self.rect = self.image.get_rect(center = self.hitbox.center)
|
||||
|
||||
self.rect = self.image.get_rect(center=self.hitbox.center)
|
||||
|
||||
if not self.vulnerable:
|
||||
alpha = self.wave_value()
|
||||
self.image.set_alpha(alpha)
|
||||
else:
|
||||
self.image.set_alpha(255)
|
||||
|
||||
|
||||
def cooldowns(self):
|
||||
current_time = pygame.time.get_ticks()
|
||||
|
||||
|
@ -125,7 +127,7 @@ class Enemy(Entity):
|
|||
if not self.vulnerable:
|
||||
if current_time - self.hit_time >= self.invincibility_duration:
|
||||
self.vulnerable = True
|
||||
|
||||
|
||||
def get_damage(self, player, attack_type):
|
||||
if self.vulnerable:
|
||||
self.hit_sound.play()
|
||||
|
@ -136,25 +138,25 @@ class Enemy(Entity):
|
|||
self.health -= player.get_full_magic_damage()
|
||||
self.hit_time = pygame.time.get_ticks()
|
||||
self.vulnerable = False
|
||||
|
||||
|
||||
def check_death(self):
|
||||
if self.health <= 0:
|
||||
self.trigger_death_particles(self.rect.center, self.monster_name)
|
||||
self.add_exp(self.exp)
|
||||
self.death_sound.play()
|
||||
self.kill()
|
||||
|
||||
|
||||
def hit_reaction(self):
|
||||
if not self.vulnerable:
|
||||
self.direction *= -self.resistance
|
||||
|
||||
|
||||
def update(self):
|
||||
self.hit_reaction()
|
||||
self.move(self.speed)
|
||||
self.animate()
|
||||
self.cooldowns()
|
||||
self.check_death()
|
||||
|
||||
|
||||
def enemy_update(self, player):
|
||||
self.get_status(player)
|
||||
self.actions(player)
|
||||
|
|
|
@ -4,20 +4,21 @@ import random
|
|||
|
||||
from utils.settings import *
|
||||
|
||||
|
||||
class Entity(pygame.sprite.Sprite):
|
||||
def __init__(self, groups, is_AI, state = None):
|
||||
def __init__(self, groups, is_AI, state=None):
|
||||
super().__init__(groups)
|
||||
|
||||
|
||||
# Animation
|
||||
self.frame_index = 0
|
||||
self.animation_speed = 0.15
|
||||
|
||||
|
||||
# Movement
|
||||
self.direction = pygame.math.Vector2()
|
||||
self.move_cooldown = 150
|
||||
self.can_move = True
|
||||
self.move_time = None
|
||||
|
||||
|
||||
# AI Setup
|
||||
if is_AI:
|
||||
self.possible_actions = {
|
||||
|
@ -31,14 +32,15 @@ class Entity(pygame.sprite.Sprite):
|
|||
7: ('swap_magic', None, None)
|
||||
}
|
||||
self.state = state
|
||||
self.distance_direction_to_player = [float('inf'), 0, 0, None, None, None, None, None, None, None, None, None]*5
|
||||
self.distance_direction_to_player = [
|
||||
float('inf'), 0, 0, None, None, None, None, None, None, None, None, None]*5
|
||||
|
||||
# self.agent = Agent(self.possible_actions, self.distance_direction_to_player, self.stats, self.exp, None, None)
|
||||
|
||||
#self.agent = Agent(self.possible_actions, self.distance_direction_to_player, self.stats, self.exp, None, None)
|
||||
|
||||
def move(self, speed):
|
||||
if self.direction.magnitude() != 0:
|
||||
self.direction = self.direction.normalize()
|
||||
|
||||
|
||||
self.hitbox.x += self.direction.x * speed
|
||||
self.collision('horizontal')
|
||||
self.hitbox.y += self.direction.y * speed
|
||||
|
@ -56,7 +58,7 @@ class Entity(pygame.sprite.Sprite):
|
|||
# Moving Left
|
||||
if self.direction.x < 0:
|
||||
self.hitbox.left = sprite.hitbox.right
|
||||
|
||||
|
||||
if direction == 'vertical':
|
||||
for sprite in self.obstacle_sprites:
|
||||
# The following works for static obstacles only
|
||||
|
@ -66,51 +68,52 @@ class Entity(pygame.sprite.Sprite):
|
|||
self.hitbox.bottom = sprite.hitbox.top
|
||||
# Moving Up
|
||||
if self.direction.y < 0:
|
||||
self.hitbox.top = sprite.hitbox.bottom
|
||||
self.hitbox.top = sprite.hitbox.bottom
|
||||
|
||||
def input(self):
|
||||
if not self.attacking and self.can_move:
|
||||
keys = pygame.key.get_pressed()
|
||||
button = random.randint(0, 5)
|
||||
|
||||
|
||||
self.move_time = pygame.time.get_ticks()
|
||||
|
||||
|
||||
# Movement Input
|
||||
if button == 0: #keys[pygame.K_w]:
|
||||
if button == 0: # keys[pygame.K_w]:
|
||||
self.direction.y = -1
|
||||
self.status = 'up'
|
||||
self.can_move = False
|
||||
elif button == 1: #keys[pygame.K_s]:
|
||||
elif button == 1: # keys[pygame.K_s]:
|
||||
self.direction.y = 1
|
||||
self.status = 'down'
|
||||
self.can_move = False
|
||||
else:
|
||||
self.direction.y = 0
|
||||
|
||||
if button == 2: #keys[pygame.K_a]:
|
||||
if button == 2: # keys[pygame.K_a]:
|
||||
self.direction.x = -1
|
||||
self.status = 'left'
|
||||
self.can_move = False
|
||||
elif button == 3: #keys[pygame.K_d]:
|
||||
elif button == 3: # keys[pygame.K_d]:
|
||||
self.direction.x = 1
|
||||
self.status = 'right'
|
||||
self.can_move = False
|
||||
else:
|
||||
self.direction.x = 0
|
||||
|
||||
|
||||
# Combat Input
|
||||
if button == 4: #keys[pygame.K_e]:
|
||||
if button == 4: # keys[pygame.K_e]:
|
||||
self.attacking = True
|
||||
self.attack_time = pygame.time.get_ticks()
|
||||
self.create_attack_sprite()
|
||||
self.weapon_attack_sound.play()
|
||||
|
||||
|
||||
# Magic Input
|
||||
if button == 5: #keys[pygame.K_q]:
|
||||
if button == 5: # keys[pygame.K_q]:
|
||||
self.attacking = True
|
||||
self.attack_time = pygame.time.get_ticks()
|
||||
style = list(magic_data.keys())[self.magic_index]
|
||||
strength = list(magic_data.values())[self.magic_index]['strength'] + self.stats['magic']
|
||||
strength = list(magic_data.values())[
|
||||
self.magic_index]['strength'] + self.stats['magic']
|
||||
cost = list(magic_data.values())[self.magic_index]['cost']
|
||||
self.create_magic_sprite(style, strength, cost)
|
||||
|
||||
|
@ -122,9 +125,9 @@ class Entity(pygame.sprite.Sprite):
|
|||
self.weapon_index += 1
|
||||
else:
|
||||
self.weapon_index = 0
|
||||
|
||||
|
||||
self.weapon = list(weapon_data.keys())[self.weapon_index]
|
||||
|
||||
|
||||
# Swap Spells
|
||||
if keys[pygame.K_LCTRL] and self.can_swap_magic:
|
||||
self.can_swap_magic = False
|
||||
|
@ -133,9 +136,9 @@ class Entity(pygame.sprite.Sprite):
|
|||
self.magic_index += 1
|
||||
else:
|
||||
self.magic_index = 0
|
||||
|
||||
|
||||
self.magic = list(magic_data.keys())[self.magic_index]
|
||||
|
||||
|
||||
def wave_value(self):
|
||||
value = sin(pygame.time.get_ticks())
|
||||
if value >= 0:
|
||||
|
|
|
@ -19,50 +19,48 @@ from objects.camera import Camera
|
|||
|
||||
|
||||
class Level:
|
||||
|
||||
|
||||
def __init__(self):
|
||||
|
||||
|
||||
# General Settings
|
||||
self.game_paused = False
|
||||
|
||||
|
||||
# Get the display surface
|
||||
self.display_surface = pygame.display.get_surface()
|
||||
|
||||
|
||||
# Sprite Group setup
|
||||
self.visible_sprites = YSortCameraGroup()
|
||||
self.obstacle_sprites = pygame.sprite.Group()
|
||||
self.attack_sprites = pygame.sprite.Group()
|
||||
self.attackable_sprites = pygame.sprite.Group()
|
||||
|
||||
|
||||
# Combat Sprite setup
|
||||
self.current_attack = None
|
||||
|
||||
|
||||
# Sprite setup
|
||||
self.create_map()
|
||||
|
||||
# UI setup
|
||||
self.ui = UI()
|
||||
self.upgrade = Upgrade(self.player)
|
||||
|
||||
|
||||
# Particle setup
|
||||
self.animation_player = AnimationPlayer()
|
||||
self.magic_player = MagicPlayer(self.animation_player)
|
||||
|
||||
|
||||
def create_map(self):
|
||||
layouts = {
|
||||
'boundary': import_csv_layout('../Graphics/map/map_FloorBlocks.csv'),
|
||||
'grass': import_csv_layout('../Graphics/map/map_Grass.csv'),
|
||||
'objects': import_csv_layout('../Graphics/map/map_Objects.csv'),
|
||||
'entities': import_csv_layout('../Graphics/map/map_Entities.csv')
|
||||
'boundary': import_csv_layout('../Graphics/map/map_FloorBlocks.csv'),
|
||||
'grass': import_csv_layout('../Graphics/map/map_Grass.csv'),
|
||||
'objects': import_csv_layout('../Graphics/map/map_Objects.csv'),
|
||||
'entities': import_csv_layout('../Graphics/map/map_Entities.csv')
|
||||
}
|
||||
|
||||
|
||||
graphics = {
|
||||
'grass': import_folder('../Graphics/graphics/grass'),
|
||||
'objects': import_folder('../Graphics/graphics/objects')
|
||||
'grass': import_folder('../Graphics/graphics/grass'),
|
||||
'objects': import_folder('../Graphics/graphics/objects')
|
||||
}
|
||||
|
||||
|
||||
|
||||
for style, layout in layouts.items():
|
||||
for row_index, row in enumerate(layout):
|
||||
for col_index, col in enumerate(row):
|
||||
|
@ -70,24 +68,28 @@ class Level:
|
|||
x = col_index * TILESIZE
|
||||
y = row_index * TILESIZE
|
||||
if style == 'boundary':
|
||||
Tile((x,y), [self.obstacle_sprites], 'invisible')
|
||||
|
||||
Tile((x, y), [self.obstacle_sprites], 'invisible')
|
||||
|
||||
if style == 'grass':
|
||||
random_grass_image = choice(graphics['grass'])
|
||||
Tile((x,y), [self.visible_sprites, self.obstacle_sprites, self.attackable_sprites], 'grass', random_grass_image)
|
||||
|
||||
Tile((x, y), [self.visible_sprites, self.obstacle_sprites,
|
||||
self.attackable_sprites], 'grass', random_grass_image)
|
||||
|
||||
if style == 'objects':
|
||||
surf = graphics['objects'][int(col)]
|
||||
Tile((x,y), [self.visible_sprites, self.obstacle_sprites], 'object', surf)
|
||||
|
||||
Tile((x, y), [self.visible_sprites,
|
||||
self.obstacle_sprites], 'object', surf)
|
||||
|
||||
# The numbers represent their IDs in the map .csv files generated from TILED.
|
||||
if style == 'entities':
|
||||
if col == '394':
|
||||
self.player = Player((x,y), [self.visible_sprites], self.obstacle_sprites, self.create_attack_sprite, self.delete_attack_sprite, self.create_magic_sprite, is_AI = True, state = self.get_state)
|
||||
|
||||
elif col =='395':
|
||||
self.camera = Camera((x,y), [self.visible_sprites])
|
||||
|
||||
self.player = Player((x, y), [self.visible_sprites], self.obstacle_sprites, self.create_attack_sprite,
|
||||
self.delete_attack_sprite, self.create_magic_sprite, is_AI=True, state=self.get_state)
|
||||
|
||||
elif col == '395':
|
||||
self.camera = Camera(
|
||||
(x, y), [self.visible_sprites])
|
||||
|
||||
else:
|
||||
if col == '390':
|
||||
monster_name = 'bamboo'
|
||||
|
@ -97,48 +99,57 @@ class Level:
|
|||
monster_name = 'raccoon'
|
||||
else:
|
||||
monster_name = 'squid'
|
||||
|
||||
Enemy(monster_name, (x,y), [self.visible_sprites, self.attackable_sprites], self.obstacle_sprites, self.damage_player, self.trigger_death_particles, self.add_exp, is_AI = False, state = None)
|
||||
|
||||
|
||||
Enemy(monster_name, (x, y), [self.visible_sprites, self.attackable_sprites], self.obstacle_sprites,
|
||||
self.damage_player, self.trigger_death_particles, self.add_exp, is_AI=False, state=None)
|
||||
|
||||
def create_attack_sprite(self):
|
||||
self.current_attack = Weapon(self.player, [self.visible_sprites, self.attack_sprites])
|
||||
|
||||
self.current_attack = Weapon(
|
||||
self.player, [self.visible_sprites, self.attack_sprites])
|
||||
|
||||
def delete_attack_sprite(self):
|
||||
if self.current_attack:
|
||||
self.current_attack.kill()
|
||||
self.current_attack = None
|
||||
|
||||
|
||||
def create_magic_sprite(self, style, strength, cost):
|
||||
if style == 'heal':
|
||||
self.magic_player.heal(self.player, strength, cost, [self.visible_sprites])
|
||||
|
||||
self.magic_player.heal(self.player, strength, cost, [
|
||||
self.visible_sprites])
|
||||
|
||||
if style == 'flame':
|
||||
self.magic_player.flame(self.player, cost, [self.visible_sprites, self.attack_sprites])
|
||||
|
||||
self.magic_player.flame(
|
||||
self.player, cost, [self.visible_sprites, self.attack_sprites])
|
||||
|
||||
def player_attack_logic(self):
|
||||
if self.attack_sprites:
|
||||
for attack_sprite in self.attack_sprites:
|
||||
collision_sprites = pygame.sprite.spritecollide(attack_sprite,self.attackable_sprites,False)
|
||||
collision_sprites = pygame.sprite.spritecollide(
|
||||
attack_sprite, self.attackable_sprites, False)
|
||||
if collision_sprites:
|
||||
for target_sprite in collision_sprites:
|
||||
if target_sprite.sprite_type == 'grass':
|
||||
pos = target_sprite.rect.center
|
||||
offset = pygame.math.Vector2(0,75)
|
||||
for leaf in range(randint(3,6)):
|
||||
self.animation_player.create_grass_particles(position = pos - offset, groups = [self.visible_sprites])
|
||||
offset = pygame.math.Vector2(0, 75)
|
||||
for leaf in range(randint(3, 6)):
|
||||
self.animation_player.create_grass_particles(
|
||||
position=pos - offset, groups=[self.visible_sprites])
|
||||
target_sprite.kill()
|
||||
else:
|
||||
target_sprite.get_damage(self.player,attack_sprite.sprite_type)
|
||||
|
||||
|
||||
target_sprite.get_damage(
|
||||
self.player, attack_sprite.sprite_type)
|
||||
|
||||
def get_state(self):
|
||||
state = []
|
||||
|
||||
enemy_sprites = [sprite for sprite in self.visible_sprites if hasattr(sprite, 'sprite_type') and sprite.sprite_type == 'enemy']
|
||||
|
||||
enemy_sprites = [sprite for sprite in self.visible_sprites if hasattr(
|
||||
sprite, 'sprite_type') and sprite.sprite_type == 'enemy']
|
||||
for enemy in enemy_sprites:
|
||||
distance, direction = enemy.get_player_distance_direction(self.player)
|
||||
state.append([(distance, direction.x, direction.y, enemy.monster_name, enemy.health, enemy.exp, enemy.speed, enemy.attack_damage, enemy.resistance, enemy.attack_radius, enemy.notice_radius, enemy.attack_type)])
|
||||
|
||||
distance, direction = enemy.get_player_distance_direction(
|
||||
self.player)
|
||||
state.append([(distance, direction.x, direction.y, enemy.monster_name, enemy.health, enemy.exp, enemy.speed,
|
||||
enemy.attack_damage, enemy.resistance, enemy.attack_radius, enemy.notice_radius, enemy.attack_type)])
|
||||
|
||||
# Sort by distance
|
||||
state = sorted(state, key=lambda x: x[0])
|
||||
|
||||
|
@ -147,13 +158,14 @@ class Level:
|
|||
|
||||
# If there are fewer than 5 enemies, pad the state with placeholder values
|
||||
while len(state) < 5:
|
||||
state.append((float('inf'), 0, 0, None, None, None, None, None, None, None, None, None))
|
||||
state.append((float('inf'), 0, 0, None, None, None,
|
||||
None, None, None, None, None, None))
|
||||
|
||||
# Flatten the state to be a single list of numbers and strings
|
||||
state = [item for sublist in state for item in sublist]
|
||||
|
||||
return state
|
||||
|
||||
|
||||
def damage_player(self, amount, attack_type):
|
||||
if self.player.vulnerable:
|
||||
self.player.health -= amount
|
||||
|
@ -161,17 +173,19 @@ class Level:
|
|||
self.player.health = 0
|
||||
self.player.vulnerable = False
|
||||
self.player.hurt_time = pygame.time.get_ticks()
|
||||
self.animation_player.generate_particles(attack_type, self.player.rect.center, [self.visible_sprites])
|
||||
|
||||
self.animation_player.generate_particles(
|
||||
attack_type, self.player.rect.center, [self.visible_sprites])
|
||||
|
||||
def trigger_death_particles(self, position, particle_type):
|
||||
self.animation_player.generate_particles(particle_type, position, [self.visible_sprites])
|
||||
|
||||
self.animation_player.generate_particles(
|
||||
particle_type, position, [self.visible_sprites])
|
||||
|
||||
def add_exp(self, amount):
|
||||
self.player.exp += amount
|
||||
|
||||
|
||||
def toggle_menu(self):
|
||||
self.game_paused = not self.game_paused
|
||||
|
||||
|
||||
def run(self):
|
||||
# Draw the game
|
||||
self.visible_sprites.custom_draw(self.camera)
|
||||
|
@ -186,42 +200,6 @@ class Level:
|
|||
self.visible_sprites.update()
|
||||
self.visible_sprites.enemy_update(self.player)
|
||||
self.player_attack_logic()
|
||||
|
||||
|
||||
if self.player.health <= 0:
|
||||
self.__init__()
|
||||
|
||||
|
||||
|
||||
class YSortCameraGroup(pygame.sprite.Group):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
# General Setup
|
||||
self.display_surface = pygame.display.get_surface()
|
||||
self.half_width = self.display_surface.get_size()[0] // 2
|
||||
self.half_height = self.display_surface.get_size()[1] // 2
|
||||
self.offset = pygame.math.Vector2(100, 200)
|
||||
|
||||
# Creating the floor
|
||||
self.floor_surf = pygame.image.load('../Graphics/graphics/tilemap/ground.png').convert()
|
||||
self.floor_rect = self.floor_surf.get_rect(topleft = (0,0))
|
||||
|
||||
def custom_draw(self, player):
|
||||
self.sprite_type = player.sprite_type
|
||||
#Getting the offset
|
||||
self.offset.x = player.rect.centerx - self.half_width
|
||||
self.offset.y = player.rect.centery - self.half_height
|
||||
|
||||
# Drawing the floor
|
||||
floor_offset_pos = self.floor_rect.topleft - self.offset
|
||||
self.display_surface.blit(self.floor_surf, floor_offset_pos)
|
||||
|
||||
for sprite in sorted(self.sprites(), key = lambda sprite: sprite.rect.centery):
|
||||
offset_pos = sprite.rect.topleft - self.offset
|
||||
self.display_surface.blit(sprite.image, offset_pos)
|
||||
|
||||
def enemy_update(self, player):
|
||||
enemy_sprites = [sprite for sprite in self.sprites() if hasattr(sprite, 'sprite_type') and sprite.sprite_type == 'enemy']
|
||||
for enemy in enemy_sprites:
|
||||
enemy.enemy_update(player)
|
||||
|
|
|
@ -10,16 +10,18 @@ from objects.entity import Entity
|
|||
from rl.agent import Agent
|
||||
from rl.rl_settings import *
|
||||
|
||||
|
||||
class Player(Entity):
|
||||
|
||||
def __init__(self, position, groups, obstacle_sprites, create_attack_sprite, delete_attack_sprite, create_magic_sprite, is_AI, state):
|
||||
super().__init__(groups, is_AI, state)
|
||||
|
||||
self.image = pygame.image.load('../Graphics/graphics/player/down/down_0.png').convert_alpha()
|
||||
self.rect = self.image.get_rect(topleft = position)
|
||||
|
||||
self.image = pygame.image.load(
|
||||
'../Graphics/graphics/player/down/down_0.png').convert_alpha()
|
||||
self.rect = self.image.get_rect(topleft=position)
|
||||
self.hitbox = self.rect.inflate(HITBOX_OFFSET['player'])
|
||||
self.sprite_type = 'player'
|
||||
|
||||
|
||||
# Graphics Setup
|
||||
self.import_player_assets()
|
||||
self.status = 'down'
|
||||
|
@ -32,86 +34,88 @@ class Player(Entity):
|
|||
# Weapons
|
||||
self.create_attack_sprite = create_attack_sprite
|
||||
self.delete_attack_sprite = delete_attack_sprite
|
||||
|
||||
|
||||
# Magic
|
||||
self.create_magic_sprite = create_magic_sprite
|
||||
|
||||
|
||||
# Weapon rotation
|
||||
self.weapon_index = 0
|
||||
self.weapon = list(weapon_data.keys())[self.weapon_index]
|
||||
self.can_rotate_weapon = True
|
||||
self.weapon_rotation_time = None
|
||||
self.rotate_attack_cooldown = 600
|
||||
|
||||
|
||||
# Magic rotation
|
||||
self.magic_index = 0
|
||||
self.magic = list(magic_data.keys())[self.magic_index]
|
||||
self.can_swap_magic = True
|
||||
self.magic_swap_time = None
|
||||
|
||||
|
||||
# Stats
|
||||
self.stats = {
|
||||
'health': 100,
|
||||
'energy': 60,
|
||||
'attack': 10,
|
||||
'magic': 4,
|
||||
'speed': 5
|
||||
}
|
||||
'health': 100,
|
||||
'energy': 60,
|
||||
'attack': 10,
|
||||
'magic': 4,
|
||||
'speed': 5
|
||||
}
|
||||
self.max_stats = {
|
||||
'health': 300,
|
||||
'energy': 150,
|
||||
'attack': 20,
|
||||
'magic': 10,
|
||||
'speed': 10
|
||||
'health': 300,
|
||||
'energy': 150,
|
||||
'attack': 20,
|
||||
'magic': 10,
|
||||
'speed': 10
|
||||
}
|
||||
self.upgrade_costs = {
|
||||
'health': 100,
|
||||
'energy': 100,
|
||||
'attack': 100,
|
||||
'magic': 100,
|
||||
'speed': 100
|
||||
'health': 100,
|
||||
'energy': 100,
|
||||
'attack': 100,
|
||||
'magic': 100,
|
||||
'speed': 100
|
||||
}
|
||||
|
||||
|
||||
# AI setup
|
||||
self.is_AI = is_AI
|
||||
if self.is_AI:
|
||||
self.agent = Agent(self.possible_actions, input_dims = (list(self.stats.values())+ self.distance_direction_to_player), batch_size = batch_size, alpha = alpha, n_epochs = n_epochs)
|
||||
|
||||
self.agent = Agent(self.possible_actions, input_dims=(list(self.stats.values(
|
||||
)) + self.distance_direction_to_player), batch_size=batch_size, alpha=alpha, n_epochs=n_epochs)
|
||||
|
||||
self.health = self.stats['health']
|
||||
self.energy = self.stats['energy']
|
||||
self.exp = 0
|
||||
self.speed = self.stats['speed']
|
||||
|
||||
|
||||
# Damage timer
|
||||
self.vulnerable = True
|
||||
self.hurt_time = None
|
||||
self.invulnerability_duration = 300
|
||||
|
||||
self.obstacle_sprites = obstacle_sprites
|
||||
|
||||
|
||||
# Import Sounds
|
||||
self.weapon_attack_sound = pygame.mixer.Sound('../Graphics/audio/sword.wav')
|
||||
self.weapon_attack_sound = pygame.mixer.Sound(
|
||||
'../Graphics/audio/sword.wav')
|
||||
self.weapon_attack_sound.set_volume(0.2)
|
||||
|
||||
def import_player_assets(self):
|
||||
character_path = '../Graphics/graphics/player'
|
||||
|
||||
|
||||
self.animations = {
|
||||
'up': [], 'down': [], 'left': [], 'right': [],
|
||||
'up_idle': [], 'down_idle': [], 'left_idle': [], 'right_idle': [],
|
||||
'up_attack': [], 'down_attack': [], 'left_attack': [], 'right_attack': []
|
||||
'up': [], 'down': [], 'left': [], 'right': [],
|
||||
'up_idle': [], 'down_idle': [], 'left_idle': [], 'right_idle': [],
|
||||
'up_attack': [], 'down_attack': [], 'left_attack': [], 'right_attack': []
|
||||
}
|
||||
for animation in self.animations.keys():
|
||||
full_path = f"{character_path}/{animation}"
|
||||
self.animations[animation] = import_folder(full_path)
|
||||
|
||||
|
||||
def get_status(self):
|
||||
|
||||
|
||||
# Idle Status
|
||||
if self.direction.x == 0 and self.direction.y == 0:
|
||||
if not 'idle' in self.status and not 'attack' in self.status:
|
||||
self.status += '_idle'
|
||||
|
||||
|
||||
if self.attacking:
|
||||
self.direction.x = 0
|
||||
self.direction.y = 0
|
||||
|
@ -123,44 +127,44 @@ class Player(Entity):
|
|||
else:
|
||||
if 'attack' in self.status:
|
||||
self.status = self.status.replace('_attack', '')
|
||||
|
||||
|
||||
def get_full_weapon_damage(self):
|
||||
base_damage = self.stats['attack']
|
||||
weapon_damage = weapon_data[self.weapon]['damage']
|
||||
|
||||
|
||||
return (base_damage + weapon_damage)
|
||||
|
||||
|
||||
def get_full_magic_damage(self):
|
||||
base_damage = self.stats['magic']
|
||||
spell_damage = magic_data[self.magic]['strength']
|
||||
return (base_damage + spell_damage)
|
||||
|
||||
|
||||
def get_value_by_index(self, index):
|
||||
return list(self.stats.values())[index]
|
||||
|
||||
|
||||
def get_cost_by_index(self, index):
|
||||
return list(self.upgrade_costs.values())[index]
|
||||
|
||||
|
||||
def cooldowns(self):
|
||||
current_time = pygame.time.get_ticks()
|
||||
|
||||
|
||||
if self.attacking:
|
||||
if current_time - self.attack_time > self.attack_cooldown + weapon_data[self.weapon]['cooldown']:
|
||||
self.attacking = False
|
||||
self.delete_attack_sprite()
|
||||
|
||||
|
||||
if not self.can_rotate_weapon:
|
||||
if current_time - self.weapon_rotation_time > self.rotate_attack_cooldown:
|
||||
self.can_rotate_weapon = True
|
||||
|
||||
|
||||
if not self.can_swap_magic:
|
||||
if current_time - self.magic_swap_time > self.rotate_attack_cooldown:
|
||||
self.can_swap_magic = True
|
||||
|
||||
|
||||
if not self.vulnerable:
|
||||
if current_time - self.hurt_time >= self.invulnerability_duration:
|
||||
self.vulnerable = True
|
||||
|
||||
|
||||
if not self.can_move:
|
||||
if current_time - self.move_time >= self.move_cooldown:
|
||||
self.can_move = True
|
||||
|
@ -170,24 +174,22 @@ class Player(Entity):
|
|||
self.energy += 0.01 * self.stats['magic']
|
||||
else:
|
||||
self.energy = self.stats['energy']
|
||||
|
||||
|
||||
|
||||
def animate(self):
|
||||
animation = self.animations[self.status]
|
||||
self.frame_index += self.animation_speed
|
||||
if self.frame_index >= len(animation):
|
||||
self.frame_index = 0
|
||||
|
||||
|
||||
# Set the image
|
||||
self.image = animation[int(self.frame_index)]
|
||||
self.rect = self.image.get_rect(center = self.hitbox.center)
|
||||
|
||||
self.rect = self.image.get_rect(center=self.hitbox.center)
|
||||
|
||||
if not self.vulnerable:
|
||||
alpha = self.wave_value()
|
||||
self.image.set_alpha(alpha)
|
||||
else:
|
||||
self.image.set_alpha(255)
|
||||
|
||||
|
||||
def update(self):
|
||||
self.input()
|
||||
|
@ -197,5 +199,5 @@ class Player(Entity):
|
|||
self.move(self.stats['speed'])
|
||||
self.energy_recovery()
|
||||
self.distance_direction_to_player = self.state()
|
||||
#if self.is_AI:
|
||||
# self.agent.act(self.distance_direction_to_player)
|
||||
# if self.is_AI:
|
||||
# self.agent.act(self.distance_direction_to_player)
|
||||
|
|
Loading…
Reference in a new issue