Add combat system
After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 3.7 KiB |
|
@ -6,6 +6,7 @@ keybinds = {
|
|||
"left": ["a", "LEFT"],
|
||||
"right": ["d", "RIGHT"],
|
||||
"shift": ["RSHIFT", "LSHIFT"],
|
||||
"attack": ["SPACE"],
|
||||
}
|
||||
|
||||
def is_down(control):
|
||||
|
|
|
@ -31,7 +31,7 @@ map = Map(METER)
|
|||
map.generate(0)
|
||||
|
||||
# Player
|
||||
player = Player()
|
||||
player = Player(map)
|
||||
player.texture = spritesheet.SpriteSheet(assets.get("character.png"), 32, 48)
|
||||
player.rect = pygame.Rect(8, 32, 16, 16)
|
||||
# TODO: Use asset loader for spritesheets
|
||||
|
@ -115,7 +115,7 @@ while 1:
|
|||
pos = get_screenpos(sprite.pos.x, sprite.pos.y)
|
||||
if pos[0] + scaledsize[0] >= 0 and pos[0] <= winsize[0] and \
|
||||
pos[1] + scaledsize[1] >= 0 and pos[1] <= winsize[1]:
|
||||
screen.blit(pygame.transform.scale(sprite.texture.frame, scaledsize), pos)
|
||||
screen.blit(pygame.transform.rotate(pygame.transform.scale(sprite.texture.frame, scaledsize), sprite.rot), pos)
|
||||
|
||||
player.hud.render(screen, SCALE)
|
||||
|
||||
|
|
14
src/map.py
|
@ -1,6 +1,6 @@
|
|||
import pygame
|
||||
import json, math
|
||||
from . import assets, dungeon, level, sprite, tiles
|
||||
from . import assets, dungeon, level, rand, sprite, tiles
|
||||
from .sprite import Sprite
|
||||
from .tiles import Tile
|
||||
from .vector import *
|
||||
|
@ -69,10 +69,18 @@ class Map:
|
|||
self.sprites.append([])
|
||||
|
||||
sprite = Sprite(name, Vector(x, y), z)
|
||||
sprite.idx = len(self.sprites[z])
|
||||
sprite.id = rand.rand(0, 65535)
|
||||
self.sprites[z].append(sprite)
|
||||
return sprite
|
||||
|
||||
def remove_sprite(self, id):
|
||||
for layer in self.sprites:
|
||||
for i in range(len(layer)):
|
||||
if layer[i].id == id:
|
||||
layer.pop(i)
|
||||
return
|
||||
|
||||
# Bresenham's line algorithm
|
||||
# Based on https://www.codeproject.com/Articles/15604/Ray-casting-in-a-2D-tile-based-environment
|
||||
def raycast(self, pos1, pos2, z):
|
||||
p1 = Vector(pos1.x, pos1.y)
|
||||
|
@ -109,7 +117,7 @@ class Map:
|
|||
if steep:
|
||||
check = (y, x)
|
||||
|
||||
tile = self.get_tile(round(check[0]), round(check[1]), z)
|
||||
tile = self.get_tile(math.ceil(check[0]), math.ceil(check[1]), z)
|
||||
if tile and tile.is_solid():
|
||||
result.append(check)
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import pygame
|
||||
import math
|
||||
from . import controller
|
||||
from .vector import *
|
||||
from . import controller, rand, vector
|
||||
from .vector import Vector
|
||||
from .hud import Hud
|
||||
|
||||
class Player:
|
||||
|
@ -12,7 +12,8 @@ class Player:
|
|||
rect = pygame.Rect(0, 0, 0, 0)
|
||||
texture = "none.png"
|
||||
|
||||
hp = 100
|
||||
hp = 30
|
||||
xp = 0
|
||||
coins = 0
|
||||
|
||||
key = False
|
||||
|
@ -20,26 +21,57 @@ class Player:
|
|||
dir = 0
|
||||
vel = Vector(0, 0)
|
||||
|
||||
# TODO: Fix diagonal speed
|
||||
speed = 3 # meters per second
|
||||
|
||||
def __init__(self):
|
||||
attacking = False
|
||||
|
||||
def __init__(self, map):
|
||||
self.map = map # Bind to map for use later
|
||||
|
||||
# Add HUD info
|
||||
self.hud = Hud()
|
||||
|
||||
self.hud.add("coin", [0, 0.9], {
|
||||
"type": "image",
|
||||
"texture": "coin.png",
|
||||
"texture": "hud_coin.png",
|
||||
"scale": 2
|
||||
})
|
||||
|
||||
self.hud.add("coincount", [0.08, 0.92], {
|
||||
"type": "text",
|
||||
"text": 0,
|
||||
"size": 15,
|
||||
})
|
||||
|
||||
self.hud.add("heart", [0, 0.8], {
|
||||
"type": "image",
|
||||
"texture": "hud_heart.png",
|
||||
"scale": 2
|
||||
})
|
||||
|
||||
self.hud.add("hp", [0.08, 0.82], {
|
||||
"type": "text",
|
||||
"text": self.hp,
|
||||
"size": 15
|
||||
})
|
||||
|
||||
self.hud.add("shiny", [0, 0.7], {
|
||||
"type": "image",
|
||||
"texture": "hud_xp.png",
|
||||
"scale": 2
|
||||
})
|
||||
|
||||
self.hud.add("xp", [0.08, 0.72], {
|
||||
"type": "text",
|
||||
"text": self.xp,
|
||||
"size": 15
|
||||
})
|
||||
|
||||
def set_pos(self, vec_or_x, y = None):
|
||||
self.pos = vec_or_num(vec_or_x, y)
|
||||
self.pos = vector.vec_or_num(vec_or_x, y)
|
||||
|
||||
def update(self, dtime, map):
|
||||
# Input handling
|
||||
self.vel = Vector(0, 0)
|
||||
|
||||
if controller.is_down("left"):
|
||||
|
@ -73,8 +105,60 @@ class Player:
|
|||
if map.collides(self.pos, self.z, self.rect):
|
||||
self.set_pos(self.pos.x, oldy)
|
||||
|
||||
# Animations
|
||||
if controller.is_down("up") or controller.is_down("down") or \
|
||||
controller.is_down("left") or controller.is_down("right"):
|
||||
self.texture.set_animation(self.dir * 4, (self.dir * 4) + 3, self.speed * 2)
|
||||
else:
|
||||
self.texture.set_animation(self.dir * 4, self.dir * 4, 0)
|
||||
|
||||
# Do attacking
|
||||
if controller.is_down("attack"):
|
||||
if not self.attacking:
|
||||
# Check for enemies
|
||||
if self.z < len(map.sprites):
|
||||
for sprite in map.sprites[self.z]:
|
||||
if sprite.name[:6] == "enemy:":
|
||||
if vector.distance(self.pos, sprite.pos) <= 1.5:
|
||||
# TODO: Fix player dir
|
||||
rot = ((self.dir + 2) % 4) * 90
|
||||
if abs(rot - vector.angle(self.pos, sprite.pos)) <= 45:
|
||||
sprite.hp -= 3 + (self.xp // 15)
|
||||
sprite.vel = (sprite.vel * -2) + self.vel
|
||||
if sprite.hp <= 0:
|
||||
setat = round(sprite.pos)
|
||||
map.set_tile(int(setat.x), int(setat.y), int(sprite.z), sprite.name + "_dead")
|
||||
map.remove_sprite(sprite.id)
|
||||
drop = rand.rand(0, 7)
|
||||
if drop == 0:
|
||||
item = map.add_sprite(sprite.pos.x, sprite.pos.y, sprite.z, "item:health")
|
||||
item.texture.set_animation(0, 4, 3)
|
||||
elif drop == 1:
|
||||
item = map.add_sprite(sprite.pos.x, sprite.pos.y, sprite.z, "item:xp")
|
||||
item.amount = rand.rand(3, 6)
|
||||
item.texture.set_animation(0, 4, 4)
|
||||
|
||||
# Slash visual
|
||||
dirs = [
|
||||
(0, 1.5),
|
||||
(1, 0.5),
|
||||
(0, -0.5),
|
||||
(-1, 0.5)
|
||||
]
|
||||
at = self.pos + Vector(*dirs[self.dir])
|
||||
sprite = self.map.add_sprite(at.x, at.y, self.z, "player:slash")
|
||||
sprite.rot = ((self.dir + 2) % 4) * 90
|
||||
sprite.texture.set_animation(0, 3, 5)
|
||||
|
||||
self.attacking = True
|
||||
else:
|
||||
self.attacking = False
|
||||
|
||||
def set_hp(self, hp):
|
||||
self.hp = max(0, hp)
|
||||
self.hud.change("hp", {
|
||||
"text": self.hp
|
||||
})
|
||||
|
||||
def get_hp(self):
|
||||
return self.hp
|
||||
|
|
|
@ -2,7 +2,7 @@ import math, random, time
|
|||
from . import assets, controller, sprite, spritesheet, tiles
|
||||
from .vector import Vector
|
||||
|
||||
def is_collided(tile, map, player):
|
||||
def is_collided(pos, map, player):
|
||||
METER = map.METER
|
||||
rect = player.rect
|
||||
cx = player.pos.x + (rect[0] / METER)
|
||||
|
@ -11,7 +11,7 @@ def is_collided(tile, map, player):
|
|||
cw = cx + (rect[2] / METER)
|
||||
ch = cy + (rect[3] / METER)
|
||||
|
||||
if cw >= tile.pos[0] and cx <= (tile.pos[0] + 1) and ch >= tile.pos[1] and cy <= (tile.pos[1] + 1):
|
||||
if cw >= pos[0] and cx <= (pos[0] + 1) and ch >= pos[1] and cy <= (pos[1] + 1):
|
||||
return True
|
||||
|
||||
# Structure tiles
|
||||
|
@ -100,9 +100,9 @@ tiles.register_tile("map:stair_down", {
|
|||
|
||||
# Items
|
||||
def pick_up_key(self, dtime, map, player):
|
||||
if is_collided(self, map, player):
|
||||
if is_collided(self.pos, map, player):
|
||||
player.key = True
|
||||
player.hud.add("key", [0, 0.8], {
|
||||
player.hud.add("key", [0, 0.6], {
|
||||
"type": "image",
|
||||
"texture": "key.png",
|
||||
"scale": 1.5
|
||||
|
@ -117,7 +117,7 @@ tiles.register_tile("item:key", {
|
|||
|
||||
# Loot
|
||||
def pick_up_coin(self, dtime, map, player):
|
||||
if is_collided(self, map, player):
|
||||
if is_collided(self.pos, map, player):
|
||||
player.coins += random.randint(self.min_value, self.max_value)
|
||||
player.hud.change("coincount", {
|
||||
"text": player.coins
|
||||
|
@ -141,10 +141,37 @@ tiles.register_tile("loot:pile", {
|
|||
"on_step": pick_up_coin
|
||||
})
|
||||
|
||||
def pick_up_health(self, dtime, map, player):
|
||||
if is_collided((self.pos.x, self.pos.y), map, player):
|
||||
player.hp += 3
|
||||
player.hud.change("hp", {
|
||||
"text": player.hp
|
||||
})
|
||||
map.remove_sprite(self.id)
|
||||
|
||||
sprite.register_sprite("item:health", {
|
||||
"texture": spritesheet.SpriteSheet(assets.get("health.png"), 32, 32),
|
||||
"on_step": pick_up_health
|
||||
})
|
||||
|
||||
def pick_up_xp(self, dtime, map, player):
|
||||
if is_collided((self.pos.x, self.pos.y), map, player):
|
||||
player.xp += self.amount
|
||||
player.hud.change("xp", {
|
||||
"text": player.xp
|
||||
})
|
||||
map.remove_sprite(self.id)
|
||||
|
||||
sprite.register_sprite("item:xp", {
|
||||
"texture": spritesheet.SpriteSheet(assets.get("xp.png"), 32, 32),
|
||||
"on_step": pick_up_xp
|
||||
})
|
||||
|
||||
# Enemies
|
||||
def spawn_slime(self, dtime, map, player):
|
||||
map.set_tile(*self.pos, None)
|
||||
sprite = map.add_sprite(*self.pos, "enemy:slime")
|
||||
sprite.hp = 4 + (2 * sprite.z)
|
||||
sprite.set_rect(0, 0, 32, 32)
|
||||
sprite.texture.set_animation(0, 3, 4)
|
||||
sprite.target_pos = Vector(self.pos[0], self.pos[1])
|
||||
|
@ -154,6 +181,11 @@ tiles.register_tile("enemy:slime", {
|
|||
"on_step": spawn_slime
|
||||
})
|
||||
|
||||
tiles.register_tile("enemy:slime_dead", {
|
||||
"textures": ["slime_dead.png"],
|
||||
"solid": False,
|
||||
})
|
||||
|
||||
from .slime import slime_logic
|
||||
|
||||
sprite.register_sprite("enemy:slime", {
|
||||
|
@ -162,3 +194,15 @@ sprite.register_sprite("enemy:slime", {
|
|||
"target_pos": Vector(0),
|
||||
"on_step": slime_logic
|
||||
})
|
||||
|
||||
# Effects
|
||||
def slash(self, dtime, map, player):
|
||||
self.timer += dtime
|
||||
if self.timer >= 2 / 4:
|
||||
map.remove_sprite(self.id)
|
||||
|
||||
sprite.register_sprite("player:slash", {
|
||||
"texture": spritesheet.SpriteSheet(assets.get("magic_slash.png"), 32, 32),
|
||||
"timer": 0,
|
||||
"on_step": slash
|
||||
})
|
||||
|
|
|
@ -5,7 +5,7 @@ from .vector import Vector
|
|||
|
||||
def slime_logic(self, dtime, map, player):
|
||||
self.timer += dtime
|
||||
if self.timer >= 0.1: # Let's not do raycasting every frame
|
||||
if self.timer >= 0.3: # Let's not do raycasting every frame
|
||||
if vector.distance(self.pos, player.pos) <= 6 \
|
||||
and len(map.raycast(self.pos, player.pos, self.z)) == 0:
|
||||
self.target_pos = player.pos
|
||||
|
@ -15,6 +15,10 @@ def slime_logic(self, dtime, map, player):
|
|||
self.vel = Vector(0)
|
||||
else:
|
||||
self.vel = vector.direction(self.pos, self.target_pos) * 1.5
|
||||
|
||||
if vector.distance(self.pos, player.pos) <= 1:
|
||||
if rand(0, 20) == 0:
|
||||
player.set_hp(player.hp - 2)
|
||||
|
||||
if self.target_pos == self.pos:
|
||||
if rand(0, 2) == 0:
|
||||
|
|
|
@ -15,6 +15,7 @@ class Sprite:
|
|||
def __init__(self, name, pos, z):
|
||||
self.name = name
|
||||
self.pos = pos
|
||||
self.rot = 0
|
||||
self.z = z
|
||||
self.vel = Vector(0, 0)
|
||||
for key in registered_sprites[name]:
|
||||
|
|