collision detection – player disappears when collides with enemy

I was making a game and then when I tested it out, if I keep on colliding with the enemy, the player’s image just disappears but the rect is still there.

main.py:

import pygame as pg
import sys
from random import choice, random
from os import path
from settings import *
from sprites import *
from tilemap import *

# HUD functions
def draw_player_health(surf, x, y, pct):
    if pct < 0:
        pct = 0
    BAR_LENGTH = 100
    BAR_HEIGHT = 20
    fill = pct * BAR_LENGTH
    outline_rect = pg.Rect(x, y, BAR_LENGTH, BAR_HEIGHT)
    fill_rect = pg.Rect(x, y, fill, BAR_HEIGHT)
    if pct > 0.6:
        col = GREEN
    elif pct > 0.3:
        col = YELLOW
    else:
        col = RED
    pg.draw.rect(surf, col, fill_rect)
    pg.draw.rect(surf, WHITE, outline_rect, 2)

class Game:
    def __init__(self):
        pg.mixer.pre_init(44100, -16, 4, 2048)
        pg.init()
        self.screen = pg.display.set_mode((WIDTH, HEIGHT))
        pg.display.set_caption(TITLE)
        self.clock = pg.time.Clock()
        self.load_data()

    def draw_text(self, text, font_name, size, color, x, y, align="topleft"):
        font = pg.font.Font(font_name, size)
        text_surface = font.render(text, True, color)
        text_rect = text_surface.get_rect(**{align: (x, y)})
        self.screen.blit(text_surface, text_rect)

    def load_data(self):
        game_folder = path.dirname(__file__)
        self.img_folder = path.join(game_folder, 'img')
        self.font_folder = path.join(game_folder, 'fonts')
        self.weapon_folder = path.join(game_folder, 'weapons')
        self.snd_folder = path.join(game_folder, 'snd')
        self.music_folder = path.join(game_folder, 'music')
        self.item_folder = path.join(game_folder, 'items')
        self.potion_folder = path.join(self.item_folder, 'potions')
        self.map_folder = path.join(game_folder, 'maps')
        self.monster_folder = path.join(game_folder, 'enemies')
        self.title_font = path.join(self.font_folder, 'AlegreyaSC-Italic.ttf')
        self.hud_font = path.join(self.font_folder, 'AlegreyaSC-Italic.ttf')
        self.dim_screen = pg.Surface(self.screen.get_size()).convert_alpha()
        self.dim_screen.fill((0, 0, 0, 180))
        self.player_img = pg.image.load(path.join(self.img_folder, PLAYER_IMG)).convert_alpha()
        self.bullet_images = {}
        self.bullet_images('lg') = pg.image.load(path.join(self.weapon_folder, BULLET_IMG)).convert_alpha()
        self.bullet_images('sm') = pg.transform.scale(self.bullet_images('lg'), (10, 10))
        self.mob_img = pg.image.load(path.join(self.monster_folder, MOB_IMG)).convert_alpha()
        self.splat = pg.image.load(path.join(self.img_folder, SPLAT)).convert_alpha()
        self.splat = pg.transform.scale(self.splat, (64, 64))
        self.item_images = {}
        for item in ITEM_IMAGES:
            # Todo: give each item a type, and make it like path.join(f'{item(type)}_folder, ITEM_IMAGES(item)')
            self.item_images(item) = pg.image.load(path.join(self.potion_folder, ITEM_IMAGES(item))).convert_alpha()        # Sound loading
        pg.mixer.music.load(path.join(self.music_folder, BG_MUSIC))
        self.effects_sounds = {}
        for type in EFFECTS_SOUNDS:
            self.effects_sounds(type) = pg.mixer.Sound(path.join(self.snd_folder, EFFECTS_SOUNDS(type)))
        self.weapon_sounds = {}
        for weapon in WEAPON_SOUNDS:
            self.weapon_sounds(weapon) = ()
            for snd in WEAPON_SOUNDS(weapon):
                s = pg.mixer.Sound(path.join(self.snd_folder, snd))
                s.set_volume(0.3)
                self.weapon_sounds(weapon).append(s)
        self.zombie_moan_sounds = ()
        for snd in ZOMBIE_MOAN_SOUNDS:
            s = pg.mixer.Sound(path.join(self.snd_folder, snd))
            s.set_volume(0.2)
            self.zombie_moan_sounds.append(s)
        self.player_hit_sounds = ()
        for snd in PLAYER_HIT_SOUNDS:
            self.player_hit_sounds.append(pg.mixer.Sound(path.join(self.snd_folder, snd)))
        self.zombie_hit_sounds = ()
        for snd in ZOMBIE_HIT_SOUNDS:
            self.zombie_hit_sounds.append(pg.mixer.Sound(path.join(self.snd_folder, snd)))

    def new(self, map_name):
        # initialize all variables and do all the setup for a new game
        self.all_sprites = pg.sprite.LayeredUpdates()
        self.walls = pg.sprite.Group()
        self.mobs = pg.sprite.Group()
        self.bullets = pg.sprite.Group()
        self.items = pg.sprite.Group()
        self.portals = pg.sprite.Group()
        self.map = TiledMap(path.join(self.map_folder, map_name))
        self.map_img = self.map.make_map()
        self.map.rect = self.map_img.get_rect()
        for tile_object in self.map.tmxdata.objects:
            obj_center = vec(tile_object.x + tile_object.width / 2,
                             tile_object.y + tile_object.height / 2)
            if tile_object.name == 'player':
                self.player = Player(self, obj_center.x, obj_center.y)
            if tile_object.name == 'zombie':
                Mob(self, obj_center.x, obj_center.y)
            if tile_object.name == "https://gamedev.stackexchange.com/#":
                Obstacle(self, tile_object.x, tile_object.y,
                         tile_object.width, tile_object.height)
            if tile_object.name in ('health', 'shotgun'):
                Item(self, obj_center, tile_object.name)
            # delete this if cant work!!!
            if tile_object.name == 'portal':
                Portal(self, tile_object.x, tile_object.y,
                       tile_object.width, tile_object.height, tile_object.type)


        self.camera = Camera(self.map.width, self.map.height)
        self.draw_debug = False
        self.paused = False
        self.night = False
        self.effects_sounds('level_start').play()

    def run(self):
        # game loop - set self.playing = False to end the game
        self.playing = True
        pg.mixer.music.play(loops=-1)
        while self.playing:
            self.dt = self.clock.tick(FPS) / 1000.0  # fix for Python 2.x
            self.events()
            if not self.paused:
                self.update()
            self.draw()

    def quit(self):
        pg.quit()
        sys.exit()

    def update(self):
        # update portion of the game loop
        self.all_sprites.update()
        self.camera.update(self.player)
        # player hits items
        hits = pg.sprite.spritecollide(self.player, self.items, False)
        for hit in hits:
            if hit.type == 'health' and self.player.health < PLAYER_HEALTH:
                hit.kill()
                self.effects_sounds('health_up').play()
                self.player.add_health(HEALTH_PACK_AMOUNT)
            if hit.type == 'shotgun':
                hit.kill()
                self.effects_sounds('gun_pickup').play()
                self.player.weapon = 'shotgun'
        # mobs hit player
        hits = pg.sprite.spritecollide(self.player, self.mobs, False, collide_hit_rect)
        for hit in hits:
            if random() < 0.7:
                choice(self.player_hit_sounds).play()
            self.player.health -= MOB_DAMAGE
            hit.vel = vec(0, 0)
            if self.player.health <= 0:
                self.playing = False
        if hits:
            self.player.hit()
            self.player.pos += vec(MOB_KNOCKBACK, 0).rotate(-hits(0).rot)
        # bullets hit mobs
        hits = pg.sprite.groupcollide(self.mobs, self.bullets, False, True)
        for mob in hits:
            # hit.health -= WEAPONS(self.player.weapon)('damage') * len(hits(hit))
            for bullet in hits(mob):
                mob.health -= bullet.damage
            mob.vel = vec(0, 0)

    def draw_grid(self):
        for x in range(0, WIDTH, TILESIZE):
            pg.draw.line(self.screen, LIGHTGREY, (x, 0), (x, HEIGHT))
        for y in range(0, HEIGHT, TILESIZE):
            pg.draw.line(self.screen, LIGHTGREY, (0, y), (WIDTH, y))


    def draw(self):
        pg.display.set_caption("{:.2f}".format(self.clock.get_fps()))
        # self.screen.fill(BGCOLOR)
        self.screen.blit(self.map_img, self.camera.apply(self.map))
        # self.draw_grid()
        for sprite in self.all_sprites:
            if isinstance(sprite, Mob):
                sprite.draw_health()
            self.screen.blit(sprite.image, self.camera.apply(sprite))
            if self.draw_debug:
                pg.draw.rect(self.screen, CYAN, self.camera.apply_rect(sprite.hit_rect), 1)
        if self.draw_debug:
            for wall in self.walls:
                pg.draw.rect(self.screen, CYAN, self.camera.apply_rect(wall.rect), 1)

        # pg.draw.rect(self.screen, WHITE, self.player.hit_rect, 2)
        # HUD functions
        draw_player_health(self.screen, 10, 10, self.player.health / PLAYER_HEALTH)
        self.draw_text('Zombies: {}'.format(len(self.mobs)), self.hud_font, 30, WHITE,
                       WIDTH - 10, 10, align="topright")
        if self.paused:
            self.screen.blit(self.dim_screen, (0, 0))
            self.draw_text("Paused", self.title_font, 105, RED, WIDTH / 2, HEIGHT / 2, align="center")
        pg.display.flip()

    def events(self):
        # catch all events here
        for event in pg.event.get():
            if event.type == pg.QUIT:
                self.quit()
            if event.type == pg.KEYDOWN:
                if event.key == pg.K_ESCAPE:
                    self.quit()
                if event.key == pg.K_h:
                    self.draw_debug = not self.draw_debug
                if event.key == pg.K_p:
                    self.paused = not self.paused
                if event.key == pg.K_n:
                    self.night = not self.night

    def show_start_screen(self):
        pass

    def show_go_screen(self):
        self.screen.fill(BLACK)
        self.draw_text("GAME OVER", self.title_font, 100, RED,
                       WIDTH / 2, HEIGHT / 2, align="center")
        self.draw_text("Press a key to start", self.title_font, 75, WHITE,
                       WIDTH / 2, HEIGHT * 3 / 4, align="center")
        pg.display.flip()
        self.wait_for_key()

    def wait_for_key(self):
        pg.event.wait()
        waiting = True
        while waiting:
            self.clock.tick(FPS)
            for event in pg.event.get():
                if event.type == pg.QUIT:
                    waiting = False
                    self.quit()
                if event.type == pg.KEYUP:
                    waiting = False

thegame.py

from main import *

#the game!
g = Game()
while True:

sprites.py

import pygame as pg
from random import uniform, choice, randint, random
from settings import *
from tilemap import collide_hit_rect
import pytweening as tween
from itertools import chain
vec = pg.math.Vector2

def collide_with_walls(sprite, group, dir):
    if dir == 'x':
        hits = pg.sprite.spritecollide(sprite, group, False, collide_hit_rect)
        if hits:
            if hits(0).rect.centerx > sprite.hit_rect.centerx:
                sprite.pos.x = hits(0).rect.left - sprite.hit_rect.width / 2
            if hits(0).rect.centerx < sprite.hit_rect.centerx:
                sprite.pos.x = hits(0).rect.right + sprite.hit_rect.width / 2
            sprite.vel.x = 0
            sprite.hit_rect.centerx = sprite.pos.x
    if dir == 'y':
        hits = pg.sprite.spritecollide(sprite, group, False, collide_hit_rect)
        if hits:
            if hits(0).rect.centery > sprite.hit_rect.centery:
                sprite.pos.y = hits(0).rect.top - sprite.hit_rect.height / 2
            if hits(0).rect.centery < sprite.hit_rect.centery:
                sprite.pos.y = hits(0).rect.bottom + sprite.hit_rect.height / 2
            sprite.vel.y = 0
            sprite.hit_rect.centery = sprite.pos.y

def collide_with_portal(game, destination, sprite, group, direction):
    # todo: copy paste above and make it into the portal thing and look at tutorials
    if direction == 'y':
        hits = pg.sprite.spritecollide(sprite, group, False, collide_hit_rect)
        if hits:
            if hits(0).rect.centery > sprite.hit_rect.centery:
                sprite.pos.y = hits(0).rect.top - sprite.hit_rect.height / 2
            if hits(0).rect.centery < sprite.hit_rect.centery:
                sprite.pos.y = hits(0).rect.bottom + sprite.hit_rect.height / 2
            sprite.vel.y = 0
            sprite.hit_rect.centery = sprite.pos.y
            go_to_place(game, destination)
    # todo: same for x!
    if direction == 'x':
        hits = pg.sprite.spritecollide(sprite, group, False, collide_hit_rect)
        if hits:
            if hits(0).rect.centerx > sprite.hit_rect.centerx:
                sprite.pos.x = hits(0).rect.left - sprite.hit_rect.width / 2
            if hits(0).rect.centerx < sprite.hit_rect.centerx:
                sprite.pos.x = hits(0).rect.right + sprite.hit_rect.width / 2
            sprite.vel.x = 0
            sprite.hit_rect.centerx = sprite.pos.x
            go_to_place(game, destination)

def go_to_place(game, destination):
    # portal thingy
    game.new(destination)

class Player(pg.sprite.Sprite):
    def __init__(self, game, x, y):
        self._layer = PLAYER_LAYER
        self.groups = game.all_sprites
        pg.sprite.Sprite.__init__(self, self.groups)
        self.game = game
        self.image = game.player_img
        self.rect = self.image.get_rect()
        self.rect.center = (x, y)
        self.hit_rect = PLAYER_HIT_RECT
        self.hit_rect.center = self.rect.center
        self.vel = vec(0, 0)
        self.pos = vec(x, y)
        self.rot = 0
        self.last_shot = 0
        self.health = PLAYER_HEALTH
        self.weapon = 'pistol'
        self.damaged = False

    def get_keys(self):
        # self.rot_speed = 0
        # self.vel = vec(0, 0)
        # keys = pg.key.get_pressed()
        # if keys(pg.K_LEFT) or keys(pg.K_a):
        #     self.rot_speed = PLAYER_ROT_SPEED
        # if keys(pg.K_RIGHT) or keys(pg.K_d):
        #     self.rot_speed = -PLAYER_ROT_SPEED
        # if keys(pg.K_UP) or keys(pg.K_w):
        #     self.vel = vec(PLAYER_SPEED, 0).rotate(-self.rot)
        # if keys(pg.K_DOWN) or keys(pg.K_s):
        #     self.vel = vec(-PLAYER_SPEED / 2, 0).rotate(-self.rot)
        # if keys(pg.K_SPACE):
        #     self.shoot()
        self.vel = vec(0, 0)
        keys = pg.key.get_pressed()
        if keys(pg.K_LEFT) or keys(pg.K_a):
            self.vel = vec(-PLAYER_SPEED, 0)
        if keys(pg.K_RIGHT) or keys(pg.K_d):
            self.vel = vec(PLAYER_SPEED, 0)
        if keys(pg.K_UP) or keys(pg.K_w):
            self.vel = vec(0, -PLAYER_SPEED)
        if keys(pg.K_DOWN) or keys(pg.K_s):
            self.vel = vec(0, PLAYER_SPEED)
        if keys(pg.K_SPACE):
            self.shoot()

    def shoot(self):
        now = pg.time.get_ticks()
        if now - self.last_shot > WEAPONS(self.weapon)('rate'):
            self.last_shot = now
            dir = vec(1, 0).rotate(-self.rot)
            pos = self.pos + BARREL_OFFSET.rotate(-self.rot)
            self.vel = vec(-WEAPONS(self.weapon)('kickback'), 0).rotate(-self.rot)
            for i in range(WEAPONS(self.weapon)('bullet_count')):
                spread = uniform(-WEAPONS(self.weapon)('spread'), WEAPONS(self.weapon)('spread'))
                Bullet(self.game, pos, dir.rotate(spread), WEAPONS(self.weapon)('damage'))
                snd = choice(self.game.weapon_sounds(self.weapon))
                if snd.get_num_channels() > 2:
                    snd.stop()
                snd.play()

    def hit(self):
        self.damaged = True
        self.damage_alpha = chain(DAMAGE_ALPHA * 4)

    def update(self):
        self.get_keys()
        self.image = self.game.player_img
        if self.damaged:
            try:
                self.image.fill((255, 255, 255, next(self.damage_alpha)), special_flags=pg.BLEND_RGBA_MULT)
            except:
                self.damaged = False
        self.rect = self.image.get_rect()
        self.rect.center = self.pos
        self.pos += self.vel * self.game.dt
        self.hit_rect.centerx = self.pos.x
        collide_with_walls(self, self.game.walls, 'x')
        collide_with_portal(self.game, 'eg.dungeon.tmx', self, self.game.portals, 'x')
        self.hit_rect.centery = self.pos.y
        collide_with_walls(self, self.game.walls, 'y')
        collide_with_portal(self.game, 'eg.dungeon.tmx', self, self.game.portals, 'y')
        self.rect.center = self.hit_rect.center

    def add_health(self, amount):
        self.health += amount
        if self.health > PLAYER_HEALTH:
            self.health = PLAYER_HEALTH

class Mob(pg.sprite.Sprite):
    def __init__(self, game, x, y):
        self._layer = MOB_LAYER
        self.groups = game.all_sprites, game.mobs
        pg.sprite.Sprite.__init__(self, self.groups)
        self.game = game
        self.image = game.mob_img.copy()
        self.rect = self.image.get_rect()
        self.rect.center = (x, y)
        self.hit_rect = MOB_HIT_RECT.copy()
        self.hit_rect.center = self.rect.center
        self.pos = vec(x, y)
        self.vel = vec(0, 0)
        self.acc = vec(0, 0)
        self.rect.center = self.pos
        self.rot = 0
        self.health = MOB_HEALTH
        self.speed = choice(MOB_SPEEDS)
        self.target = game.player

    def avoid_mobs(self):
        for mob in self.game.mobs:
            if mob != self:
                dist = self.pos - mob.pos
                if 0 < dist.length() < AVOID_RADIUS:
                    self.acc += dist.normalize()

    def update(self):
        target_dist = self.target.pos - self.pos
        if target_dist.length_squared() < DETECT_RADIUS**2:
            if random() < 0.002:
                choice(self.game.zombie_moan_sounds).play()
            self.rot = target_dist.angle_to(vec(1, 0))
            self.image = pg.transform.rotate(self.game.mob_img, self.rot)
            self.rect.center = self.pos
            self.acc = vec(1, 0).rotate(-self.rot)
            self.avoid_mobs()
            self.acc.scale_to_length(self.speed)
            self.acc += self.vel * -1
            self.vel += self.acc * self.game.dt
            self.pos += self.vel * self.game.dt + 0.5 * self.acc * self.game.dt ** 2
            self.hit_rect.centerx = self.pos.x
            collide_with_walls(self, self.game.walls, 'x')
            self.hit_rect.centery = self.pos.y
            collide_with_walls(self, self.game.walls, 'y')
            self.rect.center = self.hit_rect.center
        if self.health <= 0:
            choice(self.game.zombie_hit_sounds).play()
            self.kill()
            self.game.map_img.blit(self.game.splat, self.pos - vec(32, 32))

    def draw_health(self):
        if self.health > 60:
            col = GREEN
        elif self.health > 30:
            col = YELLOW
        else:
            col = RED
        width = int(self.rect.width * self.health / MOB_HEALTH)
        self.health_bar = pg.Rect(0, 0, width, 7)
        if self.health < MOB_HEALTH:
            pg.draw.rect(self.image, col, self.health_bar)

class Bullet(pg.sprite.Sprite):
    def __init__(self, game, pos, dir, damage):
        self._layer = BULLET_LAYER
        self.groups = game.all_sprites, game.bullets
        pg.sprite.Sprite.__init__(self, self.groups)
        self.game = game
        self.image = game.bullet_images(WEAPONS(game.player.weapon)('bullet_size'))
        self.rect = self.image.get_rect()
        self.hit_rect = self.rect
        self.pos = vec(pos)
        self.rect.center = pos
        #spread = uniform(-GUN_SPREAD, GUN_SPREAD)
        self.vel = dir * WEAPONS(game.player.weapon)('bullet_speed') * uniform(0.9, 1.1)
        self.spawn_time = pg.time.get_ticks()
        self.damage = damage

    def update(self):
        self.pos += self.vel * self.game.dt
        self.rect.center = self.pos
        if pg.sprite.spritecollideany(self, self.game.walls):
            self.kill()
        if pg.time.get_ticks() - self.spawn_time > WEAPONS(self.game.player.weapon)('bullet_lifetime'):
            self.kill()

class Obstacle(pg.sprite.Sprite):
    def __init__(self, game, x, y, w, h):
        self.groups = game.walls
        pg.sprite.Sprite.__init__(self, self.groups)
        self.game = game
        self.rect = pg.Rect(x, y, w, h)
        self.hit_rect = self.rect
        self.x = x
        self.y = y
        self.rect.x = x
        self.rect.y = y


class Item(pg.sprite.Sprite):
    def __init__(self, game, pos, type):
        self._layer = ITEMS_LAYER
        self.groups = game.all_sprites, game.items
        pg.sprite.Sprite.__init__(self, self.groups)
        self.game = game
        self.image = game.item_images(type)
        self.rect = self.image.get_rect()
        self.type = type
        self.pos = pos
        self.rect.center = pos
        self.tween = tween.easeInOutSine
        self.step = 0
        self.dir = 1

    def update(self):
        # bobbing motion
        offset = BOB_RANGE * (self.tween(self.step / BOB_RANGE) - 0.5)
        self.rect.centery = self.pos.y + offset * self.dir
        self.step += BOB_SPEED
        if self.step > BOB_RANGE:
            self.step = 0
            self.dir *= -1

class Portal(pg.sprite.Sprite):
    def __init__(self, game, x, y, w, h, where):
        self.destination = where
        self._layer = ITEMS_LAYER
        self.groups = game.portals
        pg.sprite.Sprite.__init__(self, self.groups)
        self.game = game
        self.rect = pg.Rect(x, y, w, h)
        self.hit_rect = self.rect
        self.x = x
        self.y = y
        self.rect.x = x
        self.rect.y = y
        g.new('overworld.tmx')
        g.run()

tilemap.py

import pygame as pg
import pytmx
from settings import *

def collide_hit_rect(one, two):
    return one.hit_rect.colliderect(two.rect)

class Map:
    def __init__(self, filename):
        self.data = ()
        with open(filename, 'rt') as f:
            for line in f:
                self.data.append(line.strip())

        self.tilewidth = len(self.data(0))
        self.tileheight = len(self.data)
        self.width = self.tilewidth * TILESIZE
        self.height = self.tileheight * TILESIZE

class TiledMap:
    def __init__(self, filename):
        tm = pytmx.load_pygame(filename, pixelalpha=True)
        self.width = tm.width * tm.tilewidth
        self.height = tm.height * tm.tileheight
        self.tmxdata = tm

    def render(self, surface):
        ti = self.tmxdata.get_tile_image_by_gid
        for layer in self.tmxdata.visible_layers:
            if isinstance(layer, pytmx.TiledTileLayer):
                for x, y, gid, in layer:
                    tile = ti(gid)
                    if tile:
                        surface.blit(tile, (x * self.tmxdata.tilewidth,
                                            y * self.tmxdata.tileheight))

    def make_map(self):
        temp_surface = pg.Surface((self.width, self.height))
        self.render(temp_surface)
        return temp_surface

class Camera:
    def __init__(self, width, height):
        self.camera = pg.Rect(0, 0, width, height)
        self.width = width
        self.height = height

    def apply(self, entity):
        return entity.rect.move(self.camera.topleft)

    def apply_rect(self, rect):
        return rect.move(self.camera.topleft)

    def update(self, target):
        x = -target.rect.centerx + int(WIDTH / 2)
        y = -target.rect.centery + int(HEIGHT / 2)

        # limit scrolling to map size
        x = min(0, x)  # left
        y = min(0, y)  # top
        x = max(-(self.width - WIDTH), x)  # right
        y = max(-(self.height - HEIGHT), y)  # bottom
        self.camera = pg.Rect(x, y, self.width, self.height)

settings.py

import pygame as pg
vec = pg.math.Vector2

# define some colors (R, G, B)
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
DARKGREY = (40, 40, 40)
LIGHTGREY = (100, 100, 100)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
YELLOW = (255, 255, 0)
BROWN = (106, 55, 5)
CYAN = (0, 255, 255)

# game settings
WIDTH = 1024   # 16 * 64 or 32 * 32 or 64 * 16
HEIGHT = 768  # 16 * 48 or 32 * 24 or 64 * 12
FPS = 60
TITLE = "some rpg"
BGCOLOR = BROWN

TILESIZE = 64
GRIDWIDTH = WIDTH / TILESIZE
GRIDHEIGHT = HEIGHT / TILESIZE

# Player settings
PLAYER_HEALTH = 100
PLAYER_SPEED = 280
PLAYER_ROT_SPEED = 200
PLAYER_IMG = 'dir_down_fmale.png'
PLAYER_HIT_RECT = pg.Rect(0, 0, 35, 35)
BARREL_OFFSET = vec(30, 10)

# Weapon settings
BULLET_IMG = 'stone spear2.png'
WEAPONS = {}
WEAPONS('pistol') = {'bullet_speed': 500,
                     'bullet_lifetime': 1000,
                     'rate': 250,
                     'kickback': 200,
                     'spread': 5,
                     'damage': 10,
                     'bullet_size': 'lg',
                     'bullet_count': 1}
WEAPONS('shotgun') = {'bullet_speed': 400,
                      'bullet_lifetime': 500,
                      'rate': 900,
                      'kickback': 300,
                      'spread': 20,
                      'damage': 5,
                      'bullet_size': 'sm',
                      'bullet_count': 12}

# Mob settings
MOB_IMG = 'Enemy 06-1.png'
MOB_SPEEDS = (150, 100, 75, 125)
MOB_HIT_RECT = pg.Rect(0, 0, 30, 30)
MOB_HEALTH = 100
MOB_DAMAGE = 10
MOB_KNOCKBACK = 20
AVOID_RADIUS = 50
DETECT_RADIUS = 400

# Effects
MUZZLE_FLASHES = ('whitePuff15.png', 'whitePuff16.png', 'whitePuff17.png',
                  'whitePuff18.png')
SPLAT = 'splat.png'
FLASH_DURATION = 50
DAMAGE_ALPHA = (i for i in range(0, 255, 55))
LIGHT_RADIUS = (500, 500)
LIGHT_MASK = "light_350_soft.png"

# Layers
WALL_LAYER = 1
PLAYER_LAYER = 2
BULLET_LAYER = 3
MOB_LAYER = 2
EFFECTS_LAYER = 4
ITEMS_LAYER = 1
PORTAL_LAYER = 1

# Items
ITEM_IMAGES = {'health': 'Icon3.png',
               'shotgun': 'Icon5.png'}
HEALTH_PACK_AMOUNT = 20
BOB_RANGE = 10
BOB_SPEED = 0.3

# Sounds
BG_MUSIC = 'espionage.ogg'
PLAYER_HIT_SOUNDS = ('pain/8.wav', 'pain/9.wav', 'pain/10.wav', 'pain/11.wav')
ZOMBIE_MOAN_SOUNDS = ('brains2.wav', 'brains3.wav', 'zombie-roar-1.wav', 'zombie-roar-2.wav',
                      'zombie-roar-3.wav', 'zombie-roar-5.wav', 'zombie-roar-6.wav', 'zombie-roar-7.wav')
ZOMBIE_HIT_SOUNDS = ('splat-15.wav')
WEAPON_SOUNDS = {'pistol': ('pistol.wav'),
                 'shotgun': ('shotgun.wav')}
EFFECTS_SOUNDS = {'level_start': 'level_start.wav',
                  'health_up': 'health_pack.wav',
                  'gun_pickup': 'gun_pickup.wav'}

unity – How can I improve the current enemy spawning system for my strategy game

I’m currently making an RTS game for mobile where mini battles would take place on tiny battlegrounds.

The problem is that currently the player is spawning its units based on a currency system, whereas the enemy is spawning based on a timed system. The timed system would make sense in a tower defense game, but not here.

Here is a short clip of it in action: https://twitter.com/LewanayG/status/1378009426808950794

This is my current spawn script:

void Start()
    {
        //start spawning waves
        StartCoroutine(SpawnWaves());
    }

    IEnumerator SpawnWaves()
    {
        //before spawning the first enemies, wait a moment
        yield return new WaitForSeconds(startWait);
        while (true)
        {
            //if not all characters of this wave are spawned, spawn new enemy and that wait some time before spawning next enemy in this wave
            for (int i = 0; i < startEnemyCount; i++)
            {
                int random = Random.Range(0, enemies.Length);
                GameObject newEnemy = Instantiate(enemies(random), transform.position, transform.rotation) as GameObject;
                newEnemy.transform.parent = enemyParent.transform;
                
                yield return new WaitForSeconds(spawnWait);                         // to add some randomness between spawns
            }
            
            //make sure the next wave contains more enemies than this one
            startEnemyCount += extraEnemiesPerWave;
            //wait before starting the next wave
            yield return new WaitForSeconds(waveWait);
        }
    }

Under the current system, as long as the player has more units on the battleground than the current spawn rate of the enemy it will win, and its really difficult to balance this game based on the two spawn systems.

How do you suggest I handle this problem and the spawn system of enemy units?

unity – Make enemy move diagonally in 2D

I’m making the enemy follow the player only straight or diagonally e.g.(1,1)(1,0)(0,1)(-1,1).
I expected the problem I have but I don’t really know how to fix it. When my enemy is placed diagonally relative to the player but not exactly, altern rapidly between going diagonally and straight, causing it to appear like its vibrating

enter image description here

there is the simple script returning the vector2 to follow:

        Vector2 direction = (player.position - goblinTr.position).normalized;
        direction.x = Mathf.Round(direction.x);
        direction.y = Mathf.Round(direction.y);

        return direction;

dnd 5e – Can a creature under Dominate Monster choose to willingly fail the saving throw against Calm Emotions if the caster is viewed as an enemy?

It depends on what the caster is doing.

I think the answer to this is in what dominate monster actually does. Nothing in dominate says the target views its former allies as enemies; only that it obeys instructions.

Let’s look at the specific effects of dominate monster:

The target is charmed by the caster. A charmed creature can’t attack the charmer, and the charmer gets advantage on social rolls, but that’s all. It doesn’t have any impact on the target’s opinion of his allies.

The caster can issue commands that the target will attempt to obey, such as “attack that creature”. But obeying the instruction doesn’t mean they consider their allies to be enemies. The dominate effect is forcing them to do the task as instructed, but they’re not necessarily going to do anything hostile other than what they were generally commanded to do.

And finally, the caster can use an action to take total control of the target. Let’s look closer at that one.

You can use your action to take total and precise control of the target. Until the end of your next turn, the creature takes only the actions you choose, and doesn’t do anything that you don’t allow it to do.

So here is the first place where I see anything that would stop the target from willingly failing a save; the target “doesn’t do anything that you don’t allow it to do”, so that would potentially include choosing to fail a save, where that’s an option, or being a willing target of a spell.

So it looks to me like the victim of a dominate spell could intentionally fail the save on calm emotions, but only as long as the caster isn’t assuming direct control.

dnd 5e – Can a creature under Dominate Person choose to willingly fail the saving throw against Calm Emotions if the caster is viewed as an enemy?

It depends on what the caster is doing.

I think the answer to this is in what dominate monster actually does. Nothing in dominate says the target views its former allies as enemies; only that it obeys instructions.

Let’s look at the specific effects of dominate monster:

The target is charmed by the caster. A charmed creature can’t attack the charmer, and the charmer gets advantage on social rolls, but that’s all. It doesn’t have any impact on the target’s opinion of his allies.

The caster can issue commands that the target will attempt to obey, such as “attack that creature”. But obeying the instruction doesn’t mean they consider their allies to be enemies. The dominate effect is forcing them to do the task as instructed, but they’re not necessarily going to do anything hostile other than what they were generally commanded to do.

And finally, the caster can use an action to take total control of the target. Let’s look closer at that one.

You can use your action to take total and precise control of the target. Until the end of your next turn, the creature takes only the actions you choose, and doesn’t do anything that you don’t allow it to do.

So here is the first place where I see anything that would stop the target from willingly failing a save; the target “doesn’t do anything that you don’t allow it to do”, so that would potentially include choosing to fail a save, where that’s an option, or being a willing target of a spell.

So to me, the victim of a dominate spell could intentionally fail the save on calm emotions, but only as long as the caster isn’t assuming direct control.

dnd 5e – Can a creature under Dominate Person choose to willingly fail the saving throw from Calm emotion if the caster is viewed as an enemy?

It depends on what the caster is doing.

I think the answer to this is in what dominate monster actually does. Nothing in dominate says the target views its former allies as enemies; only that it obeys instructions.

Let’s look at the specific effects of dominate monster:

The target is charmed by the caster. A charmed creature can’t attack the charmer, and the charmer gets advantage on social rolls, but that’s all. It doesn’t have any impact on the target’s opinion of his allies.

The caster can issue commands that the target will attempt to obey, such as “attack that creature”. But obeying the instruction doesn’t mean they consider their allies to be enemies. The dominate effect is forcing them to do the task as instructed, but they’re not necessarily going to do anything hostile other than what they were specifically commanded to do.

And finally, the caster can use an action to take total control of the target. Let’s look closer at that one.

You can use your action to take total and precise control of the target. Until the end of your next turn, the creature takes only the actions you choose, and doesn’t do anything that you don’t allow it to do.

So here is the first place where I see anything that would stop the target from willingly failing a save; the target “doesn’t do anything that you don’t allow it to do”, so that would potentially include choosing to fail a save, where that’s an option, or being a willing target of a spell.

So to me, the victim of a dominate spell could intentionally fail the save on calm emotions, but only as long as the caster isn’t assuming direct control.

dnd 5e – Do Abjure Enemy effects last only one turn?

I play the Oath of Vengeance paladin and I’ve came across a problem.

There is the Channel Divinity: Abjure Enemy description, which states:

As an action, you present your holy symbol and speak a prayer of denunciation, using your Channel Divinity. Choose one creature within 60 feet of you that you can see. That creature must make a Wisdom saving throw, unless it is immune to being frightened. Fiends and undead have disadvantage on this saving throw.

On a failed save, the creature is frightened for 1 minute or until it takes any damage. While frightened, the creature’s speed is 0, and it can’t benefit from any bonus to its speed.

On a successful save, the creature’s speed is halved for 1 minute or until the creature takes any damage.

It sounds pretty good, but does anything forbid an abjured enemy from simply harming itself on its turn?

dnd 5e – Do Abjure Enemy effects last only one turn? (dnd5)

I play the OoV paladin by dnd5 rules and have came across a problem.

There is Abjure Eneme description.

As an action, you present your holy symbol and speak a prayer o f
denunciation, using your Channel Divinity. Choose one creature within
60 feet of you that you can see. That creature must make a Wisdom
saving throw, unless it is immune to being frightened. Fiends and
undead have disadvantage on this saving throw.

On a failed save, the creature is frightened for 1 minute or until it
takes any damage. While frightened, the creature’s speed is 0, and it
can’t benefit from any bonus to its speed.

On a successful save, the creature’s speed is halved for 1 minute or
until the creature takes any damage.

Sounds pretty good but what forbids an abjured enemy to harm himself on his turn?

unity – delaying enemy attacks

I have an enemy attack script that when a raycast hits the player it deals damage to the player however the enemy attacks at multiple times per second and I wish for the enemy to have to wait 1 second after it attacks the player before it can attack again. the script is here, the main focal point is the void update(). Any help is appreciated, let me know if i’ve missed out any needed details. 🙂

public class DamagePlayer : MonoBehaviour
{
int attackDamage = 10;

(SerializeField)
float agroRange;

(SerializeField)
Transform castPoint;



Rigidbody2D rb2d;

bool isFacingLeft;


// Start is called before the first frame update
void Start()
{
    rb2d = GetComponent<Rigidbody2D>();

}

bool CanSeePlayer(float distance)
{
    bool val = false;
    float castDist = distance;

    if (isFacingLeft == true)
    {
        castDist = -distance;
    }

    Vector2 endPos = castPoint.position + Vector3.right * castDist;
    RaycastHit2D hit = Physics2D.Linecast(castPoint.position, endPos, 1 << LayerMask.NameToLayer("Action"));

    if (hit.collider != null)
    {
        if (hit.collider.gameObject.CompareTag("Player"))
        {
            val = true;

        }
        else
        {
            val = false;
        }

        Debug.DrawLine(castPoint.position, endPos, Color.red);
    }
    else
    {
        Debug.DrawLine(castPoint.position, endPos, Color.green);
    }

    return val;
}

// Update is called once per frame
void Update()
{
    if (CanSeePlayer(agroRange))
    {
        PlayerHealth.currentHealth -= attackDamage;
        //Debug.Log("can see player");
    }
    else
    {
        
    }
}

}

dnd 5e – Can I use an Immovable Rod to Pin an enemy?

In order to attempt to pin an opponent, they must first be on the ground, or perhaps pressed flat up against a solid surface like a wall. In order to put them there, you’ll need to Shove them, requiring an Athletics Check on your part, opposed by an Athletics or Acrobatics check on their part (allows for Forced Movement to press them into the wall, or to knock them prone).

At that point, you need to place the immovable rod in an appropriate location to actually pin them to the ground. As I’ll get to below, this is more complicated than it sounds. What this takes is going to be up to your DM, to place the rod in the ‘right place’ while your opponent is probably trying to prevent you from doing so. Personally, I’d call it a grapple check, either at Disadvantage for how precise you have to be in placing the rod, or requiring you to ‘Pin’ them (with the Grappler Feat). That’s another Action.

Now you have to activate the Immovable Rod…that’s a third Action.

And you have to do all of this without your opponent being able to break free.

(Realistically, it would probably require Grapple > Shove > Grapple again (to get the rod settled right) > Activate Rod…as this would allow you to sequentially keep them contained in a way so they couldn’t just stand up after you knocked them down) So, a total of up to 3 to 4 Actions, depending on if your opponent is going to get a turn between you knocking them down and you working to pin them with the rod.

You can trim these down with class features…

A Fighter is probably your best choice here. Their multi-attack allows you to Shove and Grapple sequentially (PHB 195), then either take another grapple at Disadvantage (if you have Multiattack 2) and pop your Action Surge to activate the rod…or use your Action Surge to Pin, and wait til next round to activate the rod.

Just for a reference on this: I have several years training with the short staff (3-foot). So this difficulty is rooted in my knowledge of how to use a staff to pin someone down.

In order to pin someone with an Immovable Rod, you have to place it in such a way that they cannot simply wiggle out from under it. So if your rod is poorly placed, if it is placed at an angle to their body (and not secured by another part of their body), or if you don’t press it down hard enough against them–they’ll just wiggle out from underneath it and be free. You describe it as being a ‘very heavy weight.’ But the rod on them will only press them down as hard as you, personally, can press it into them and still have a hand ready to press the button. And trying to do this while they are trying to resist you.

Most staff locks depend on the human holding the staff being able to tighten the lock to inflict increasing pain if the pinned opponent tries to get free. And they depend on being able to adjust the position of the staff to secure the lock if the opponent begins to attempt to wiggle free. Alternately, you can use a hand to grip the ‘loose’ part of the lock (often the wrist) to assist the staff-pin. This becomes necessary because a thin straight rod pressing down on the wrist is easier to get loose from than a hand that is wrapped entirely around that wrist.

While an Immovable Rod takes a DC 30 Strength check to move, it’s not actually pressing down on you. It’s simply stuck in place. So if you can shift any part of yourself so that the rod is against a thinner profile of your body, it’s no longer pressing as hard. If it it placed against your chest, and you manage to wiggle to where it would now be placed over your stomach…it’s possibly not even touching you any more. Once you set it, you can’t adjust it to maintain the lock.

Even a ‘pin’ such as pressing the staff into an opponent’s throat is more about the threat of ‘if you move, I’ll press harder’ than it is about actually restraining them.

Furthermore, In accordance with this question, we can estimate that an immovable rod is only 2 to 3 feet long and about an inch thick. So given its length, you have to position your opponent in such a way that they can’t reach the button. This either means standing the rod on end (which is very easy to wiggle out of), or making sure their arms are positioned in a way where they can’t reach the ends of the rod effectively, either because their hands are restrained as well, or they simply can’t reach it).

They may not know how it works…but the button on an immovable rod isn’t exactly hidden. Someone grabbing at the rod might simply find it by accident.

What wouldn’t work

If you try to simply lay it across their chest and activate it, their arms are free (your chest is thicker than your arms, and the rod is a straight object). And the chest compresses fairly well…you can wiggle out from something pressed against your chest unless it is set extremely tight. And if the rod is tilted at all…it’ll be even easier.

If you place the tip into their throat and activate it, they may still be able to reach the button and either you have it pressed hard enough into their throat that they can’t breathe, or they could just slip out from under it. And, well…placing it precisely there while they are trying to get away from you is not easy.

Similarly, pinning individual hands, feet, etc. simply won’t work for these same reasons

What you need to do

For an ideal pin with an immovable rod, you want your opponent to be face-down, and you want to get them into a joint lock that can be maintained by the rod itself, and prevents the arm nearest the button from being able to move.

The simplest lock to use for this would be a hammerlock, modified for a staff.

So, start with something like this:

Hammerlock

With the arm twisted up behind the back, then shove the rod through the bend in their elbow, pull their wrist as far up their spine as possible, and press the top of the rod into their wrist as hard as you can….then press the button. They might still be able to wiggle out of that…getting their hand out from under the rod, but it would be hard.