Reenabled Player and all animations, upgrades, attacks and spells

This commit is contained in:
Vasilis Valatsos 2023-09-27 20:03:37 +02:00
parent 83ab08326e
commit cd0333cbff
37 changed files with 1262 additions and 469 deletions

View file

View 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"}}

View file

@ -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"}
}

View file

View 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}}

View file

@ -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)

View file

@ -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()

View file

@ -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))

View file

View file

View 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

View 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

View 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])

View 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

View 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
View 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
View 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
View 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)

View file

View 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)

View file

@ -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

View file

@ -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
View file

42
game/level/camera.py Normal file
View 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
View 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
View 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
View 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
View file

View 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__()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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)

View file

@ -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:

View file

@ -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)

View file

@ -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)