Add combat system

master
GreenXenith 2020-05-02 02:28:38 -07:00
parent c9bd6cda07
commit ce23bf8b6c
14 changed files with 160 additions and 18 deletions

BIN
assets/health.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
assets/hud_heart.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
assets/hud_xp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
assets/magic_slash.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
assets/slime_dead.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
assets/xp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -6,6 +6,7 @@ keybinds = {
"left": ["a", "LEFT"],
"right": ["d", "RIGHT"],
"shift": ["RSHIFT", "LSHIFT"],
"attack": ["SPACE"],
}
def is_down(control):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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