import pygame import math import random import time pygame.init() clock = pygame.time.Clock() width = 800 height = 600 screen = pygame.display.set_mode((width, height)) pygame.display.set_caption("Shadow Warrior") #its like that because this reduses lag a lot and help for better gameplay def loadify(imgname): return pygame.image.load(imgname).convert_alpha(screen) #Models: logo = loadify("textures/logo.png") pygame.display.set_icon(logo) wallModel = loadify("textures/wall.png") groundModel = loadify("textures/ground.png") weaponModel = loadify("textures/weapon.png") enemyBullet = loadify("textures/bullet.png") playerBullet = loadify("textures/enemyBullet.png") crosshair = loadify("textures/crosshair.png") playerWalkAnimR = [loadify("textureswalk0.png"), loadify("textureswalk1.png"), loadify("textureswalk2.png")] playerWalkAnimL = [loadify("textureswalk3.png"), loadify("textureswalk4.png"), loadify("textureswalk5.png")] pygame.mouse.set_visible(False) tileSize = 64 level = 0 hitParticles = [] weapons = [] removed_bullets = [] enemies = [] backgrounds = [] walls = [] all_bullets = [] # Classes class Particle: def __init__(self, x, y, velocityX, velocityY, radius, color): self.x = x self.y = y self.velocityX = velocityX self.velocityY = velocityY self.radius = radius self.color = color self.lifetime = random.randrange(50, 100) def draw(self, screen, camx, camy): self.lifetime -= 1 self.x += self.velocityX self.y += self.velocityY pygame.draw.circle(screen, self.color, (self.x - camx, self.y - camy), self.radius) class Background: def __init__(self, x, y): self.x = x self.y = y def draw(self, camx, camy): screen.blit(groundModel, (self.x - camx - tileSize / 2, self.y - camy - tileSize / 2)) class Wall: def __init__(self, x, y): self.x = x self.y = y #Check if wall collides with smth def collidesWith(self, other): return pygame.Rect(self.x, self.y, tileSize, tileSize).colliderect(other.x, other.y, tileSize-16, tileSize) def draw(self, camx, camy): screen.blit(wallModel, (self.x - camx - tileSize / 2, self.y - camy - tileSize / 2)) class Bullet: def __init__(self, x, y, velx, vely, isPlayer): self.x = x self.y = y self.velx = velx self.vely = vely self.isPlayer = isPlayer def collidesWith(self, other): return pygame.Rect(self.x, self.y, tileSize - 16, tileSize - 16).colliderect(other.x, other.y, tileSize-36, tileSize-36) def update(self): self.x += self.velx self.y += self.vely for bullet in all_bullets: for wall in walls: if bullet.collidesWith(wall): removed_bullets.append(bullet) for i in removed_bullets: if i in all_bullets: all_bullets.remove(i) def draw(self, camx, camy): if (self.isPlayer): screen.blit(pygame.transform.smoothscale(playerBullet, (16, 16)), (self.x - camx + 5, self.y - camy + 7)) else: screen.blit(pygame.transform.smoothscale(enemyBullet, (16, 16)), (self.x - camx + 5, self.y - camy + 7)) class slimeEnemy: def __init__(self, x, y): self.x = x self.y = y self.bulletSpeed = 1 self.aliveAnimations = [loadify("texturesslime_animation_0.png"), loadify("texturesslime_animation_1.png"), loadify("texturesslime_animation_2.png"), loadify("texturesslime_animation_3.png"),] self.animationsCount = 0 self.attackRate = 60 self.resetOffset = 0 self.SlimeHp = 7 + level * 2 self.slimeCollisionDmg = 1 + level/2 self.dmg = 1 + level / 2 self.isAlive = True # self.isBoss = isBoss #adding offset so the enemy doesnt move directly towards the player self.offsetX = random.randrange(-150, 150) self.offsetY = random.randrange(-150, 150) def collidesWithAnything(self): for wall in walls: if wall.collidesWith(self): return True return False def healthBar(self): pygame.draw.rect(screen, (0, 255, 0), (self.x - camx - 20, self.y + 40 - camy, self.SlimeHp*10 + level*2, 7)) def collidesWith(self, other): return pygame.Rect(self.x, self.y, tileSize-36, tileSize-36).colliderect(other.x, other.y, tileSize-36, tileSize-36) def update(self): # move enemy accordingly if self.resetOffset == 0: self.offsetX = random.randrange(-400, 400) self.offsetY = random.randrange(-400, 400) self.resetOffset = random.randrange(120, 150) else: self.resetOffset -= 1 if player.x + self.offsetX > self.x: self.x += 1 if self.collidesWithAnything(): self.x -= 1 elif player.x + self.offsetX < self.x: self.x -= 1 if self.collidesWithAnything(): self.x += 1 if player.y + self.offsetY > self.y: self.y += 1 if self.collidesWithAnything(): self.y -= 1 elif player.y + self.offsetY < self.y: self.y -= 1 if self.collidesWithAnything(): self.y += 1 self.healthBar() def attack(self): for i in range(3): angle = random.randrange(0, 360) bulletSpeed_x = self.bulletSpeed * math.cos(angle) + random.uniform(-5, 5) bulletSpeed_y = self.bulletSpeed * math.sin(angle) + random.uniform(-5, 5) all_bullets.append(Bullet(self.x, self.y, bulletSpeed_x, bulletSpeed_y, False)) def drawAlive(self, camx, camy): if self.animationsCount + 1 == 32: self.animationsCount = 0 self.animationsCount += 1 if self.attackRate == 0: self.attackRate = 60 self.attack() self.attackRate -= 1 screen.blit(pygame.transform.scale(self.aliveAnimations[self.animationsCount// 8], (32, 30)), (self.x - camx, self.y - camy)) class Weapon: def __init__(self, bulletSpeed, fireRate, bulletDmg): self.shooting = False self.bulletSpeed = bulletSpeed self.fireRate = fireRate self.bulletDmg = bulletDmg self.energy = 100 self.i = 0 def handle_weapons(self, screen): mouse_x, mouse_y = pygame.mouse.get_pos() rel_x, rel_y = mouse_x - width/2, mouse_y - height/2 angle = (180/math.pi) * - math.atan2(rel_y, rel_x) #draw crosshair screen.blit(crosshair, (mouse_x, mouse_y+5)) #rotate and draw weapon accordingly if angle > 90 or angle < -90: player_weapon_copy = pygame.transform.rotate(pygame.transform.flip(weaponModel, True, False), angle - 180) else: player_weapon_copy = pygame.transform.rotate(weaponModel, angle) screen.blit(player_weapon_copy, (width/2 +5 - int(player_weapon_copy.get_width()/2), height/2 + 15 - int(player_weapon_copy.get_height()/2))) def Shooting(self): if self.shooting: #calculates the angles the bullet should travel :/ if self.i % self.fireRate == 0: if (self.energy == 0): pass else: mouse_x, mouse_y = pygame.mouse.get_pos() distance_x = mouse_x - width/2 distance_y = mouse_y - height/2 angle = math.atan2(distance_y, distance_x) bulletSpeed_x = self.bulletSpeed * math.cos(angle) bulletSpeed_y = self.bulletSpeed * math.sin(angle) all_bullets.append(Bullet(player.x, player.y, bulletSpeed_x, bulletSpeed_y, True)) self.energy -= 1 self.i += 1 class Player: def __init__(self, x, y): self.x = int(x) self.y = int(y) self.leftPr = False self.rightPr = False self.downPr = False self.upPr = False self.speed = 4 self.health = 5 self.maxHp = 5 self.animationsCount = 0 def healthBar(self): pygame.draw.rect(screen, (255, 0, 0), (20, 25, 200, 10)) pygame.draw.rect(screen, (0, 255, 0), (20, 25, self.health*40, 10)) def energyBar(self): pygame.draw.rect(screen, (255, 0, 0), (20, 55, 200, 10)) pygame.draw.rect(screen, (48, 117, 255), (20, 55, weapon.energy*2, 10)) def collidesWithAnything(self): for wall in walls: if wall.collidesWith(self): return True return False def update(self): if self.leftPr and not self.rightPr: self.x -= self.speed if self.collidesWithAnything(): self.x += self.speed if self.rightPr and not self.leftPr: self.x += self.speed if self.collidesWithAnything(): self.x -= self.speed if self.upPr and not self.downPr: self.y -= self.speed if self.collidesWithAnything(): self.y += self.speed # revert if collision if self.downPr and not self.upPr: self.y += self.speed if self.collidesWithAnything(): self.y -= self.speed # check for enemy colisions def animations(self): if self.animationsCount + 1 >= 36: self.animationsCount = 0 if self.rightPr: screen.blit(pygame.transform.scale(playerWalkAnimR[self.animationsCount//12],(64, 64)), (width/2 - 40, height/2 - 30)) elif self.leftPr: screen.blit(pygame.transform.scale(playerWalkAnimL[self.animationsCount//12],(64, 64)), (width/2 - 40, height/2 - 30)) else: screen.blit(pygame.transform.scale(playerWalkAnimR[0],(64, 64)), (width/2 - 40, height/2 - 30)) self.animationsCount += 1 weapon = Weapon(10, 20, 2) player = Player(1, 0) #Loads map from file def loadMapFromFile(path): walls.clear() backgrounds.clear() enemies.clear() with open(path, "r") as f: y = 0 enemyLocations = [] # bossLocations = [] for line in f.readlines(): x = 0 for char in line: if char != ' ' and char != "n": backgrounds.append(Background(x * tileSize, y * tileSize)) if char == '#': walls.append(Wall(x * tileSize, y * tileSize)) elif char == 'p' or char == 'P': player.x = x * tileSize player.y = y * tileSize elif char == 'e' or char == 'E': if random.randint(1, 100) <= 40: enemyLocations.append((x * tileSize, y * tileSize)) # elif char == 'b' or char == 'B': # if random.randint(1, 100) <= 10: # bossLocations.append((x * tileSize, y * tileSize, "True")) x += 1 y += 1 for enemyL in enemyLocations: enemies.append(slimeEnemy(*enemyL)) # for enemyL in bossLocations: # enemies.append(slimeEnemy(*enemyL, "True")) def gameOverScreen(): pygame.font.init() font = pygame.font.Font('arial.ttf', 32) text = font.render('GAME OVER', True, (255, 0, 0), (0, 0, 0)) text.set_colorkey((0, 0, 0)) screen.blit(text, (width/2-80,height/2-40)) pygame.display.flip() for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() loadMapFromFile('maps/Map0.txt') hitCooldown = 75 cooldown = 600 #running loop gameOver = False running = True while running: if gameOver: gameOverScreen() pass else: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False if event.type == pygame.KEYDOWN: if event.key == pygame.K_w: player.upPr = True if event.key == pygame.K_s: player.downPr = True if event.key == pygame.K_d: player.rightPr = True if event.key == pygame.K_a: player.leftPr = True if event.type == pygame.KEYUP: if event.key == pygame.K_w: player.upPr = False if event.key == pygame.K_s: player.downPr = False if event.key == pygame.K_d: player.rightPr = False if event.key == pygame.K_a: player.leftPr = False if event.type == pygame.MOUSEBUTTONDOWN: weapon.shooting = True if event.type == pygame.MOUSEBUTTONUP: weapon.shooting = False # updating camx = player.x - width / 2 camy = player.y - height / 2 screen.fill((0, 0, 0)) player.update() #check if slime got hit for bullet in all_bullets: bullet.update() for enemy in enemies: if enemy.collidesWith(player): if hitCooldown < 0: hitCooldown = 75 player.health -= enemy.slimeCollisionDmg if player.health <= 0: gameOver = True elif enemy.collidesWith(bullet) and bullet.isPlayer: removed_bullets.append(bullet) for i in range(10): hitParticles.append(Particle(enemy.x, enemy.y, random.randrange(-5, 5)/10, random.randrange(-5, 5)/10, 4, (108, 216, 32))) enemy.SlimeHp -= weapon.bulletDmg if enemy.SlimeHp <= 0: enemy.isAlive = False if player.health >= player.maxHp: player.health = player.maxHp else: player.health += 1 if weapon.energy >= 100: weapon.energy = 100 else: weapon.energy += 2 if bullet.collidesWith(player) and not bullet.isPlayer: if hitCooldown < 0: hitCooldown = 75 player.health -= enemy.dmg if player.health <= 0: gameOver = True removed_bullets.append(bullet) for bg in backgrounds: bg.draw(camx, camy) for wall in walls: wall.draw(camx, camy) # update all other entities #draw bullet for bullet in all_bullets: bullet.draw(camx, camy) # slime animations and remove dead slimes for enemy in enemies: enemy.update() if enemy.isAlive: enemy.drawAlive(camx, camy) if not enemy.isAlive: enemies.remove(enemy) # draw particles for particle in hitParticles: if particle.lifetime > 0: particle.draw(screen, camx, camy) else: hitParticles.pop(hitParticles.index(particle)) player.animations() weapon.handle_weapons(screen) weapon.Shooting() player.healthBar() player.energyBar() if len(enemies) == 0: if cooldown <= 0: cooldown = 600 loadMapFromFile("mapsMap{0}.txt".format(level+1)) level+=1 hitCooldown -= 1 cooldown -= 1 # finally update screen pygame.display.update() clock.tick(60)
I know it is written really badly but I’m learning. The game is really laggy and I was wondering how I can make it run faster and smoother. Also is the lag created from the many calculations or am I just dumb? The lag comes when there are a lot of enemies on the map and they shoot at the same time. Can I fix the lag by making all the enemies shoot in different times? The game is for a school project.
Advertisement
Answer
I think your biggest issue is this:
class Bullet: [ ... ] def update(self): self.x += self.velx self.y += self.vely for bullet in all_bullets: # <-- HERE for wall in walls: if bullet.collidesWith(wall): removed_bullets.append(bullet) for i in removed_bullets: if i in all_bullets: all_bullets.remove(i)
Then in the main loop:
#check if slime got hit for bullet in all_bullets: # <-- AGAIN bullet.update()
The code is iterating through the bullets many more times than is necessary. By the time all your bullet objects are updated, they have been processed /N/ * /N/ times (i.e.: N-squared). So as you get more any more bullets, this square grows catastrophically.
Try changing your Bullet.update()
to only check itself. This will probably fix your lag issues – or at least help a lot. It will also “correct” your object encapsulation. A single Bullet
has no business knowing about all the others, let alone checking collisions on their behalf.
class Bullet: [ ... ] def update(self): self.x += self.velx self.y += self.vely for wall in walls: if self.collidesWith(wall): all_bullets.remove( self ) break
If you have any more N-squared loops like this, it will give the best speedup reworking them first.