2

I tried to make the enemies(yellow box) follow the player(red box) with a constant velocity from all directions.

The problem of my code is the speed of the enemy from x+(Right) and y+(Bottom) axis to the player is slower than x-(Left) and y-(Above).

Image: https://i.stack.imgur.com/qJNp7.png

I think the problem is in here. I can't find it. It may be somewhere else.

for enemy_types in Global_Obj[2]:
            zetas = math.sqrt((enemy_types.rect.center[1] - FirstPlayer.rect.center[1])**2 + (FirstPlayer.rect.center[0] - enemy_types.rect.center[0])**2)

            print(enemy_types.rect.center)

            x,y = enemy_types.rect.center
            x += dt * enemy_types.speed * (FirstPlayer.rect.center[0] - enemy_types.rect.center[0]) / zetas
            y += dt * enemy_types.speed * (FirstPlayer.rect.center[1] - enemy_types.rect.center[1]) / zetas


            enemy_types.rect.center = (x,y) 

Here is all of my code.

# -*- coding: utf-8 -*-
"""
Created on Wed Sep  8 21:56:06 2021

@author: Toon
"""
import pygame
import cv2
import numpy as np
import math
import random
import time

dt = 0.01
window = (1280, 720)
win = pygame.display.set_mode(window)
background = pygame.Surface(window)
pygame.display.set_caption("First Game")
run = True
class playerIO():
    def __init__(self):
        self.name = 'Player'
        self.width = 30
        self.height = 30
        self.speed = 1000
        self.color = (255,0,0)
        self.HPwidth = 50
        self.HPheight = 8
        self.HPoffset = 10
        self.MAXHP = 1000
        self.HP = self.MAXHP
        self.HPregeneration_per_sec = 5
        
        self.MANAwidth = 50
        self.MANAheight = 8
        self.MANAoffset = 10
        self.MAXMANA = 3000
        self.MANA = self.MAXMANA
        self.MANAregeneration_per_sec = 10
        
        self.image = pygame.Surface([self.width, self.height])
        self.image.fill(self.color)
        self.rect = self.image.get_rect(topleft=(80, 80))
        
        self.bullet_delay = [0, 0, 0]
    def draw(self):
        self.HPbar_high = self.rect.y-self.HPheight-self.HPoffset
        self.MANAbar_high = self.HPbar_high-self.MANAheight-self.MANAoffset 
        pygame.draw.rect(win, self.color, self.rect)
        
        pygame.draw.rect(win, (255,255,255), (self.rect.x + self.width/2 - self.HPwidth/2, self.HPbar_high, self.HPwidth, self.HPheight))
        pygame.draw.rect(win, (0,255,0), (self.rect.x + self.width/2 - self.HPwidth/2, self.HPbar_high, self.HPwidth*self.HP/self.MAXHP, self.HPheight))
        
        pygame.draw.rect(win, (255,255,255), (self.rect.x + self.width/2 - self.MANAwidth/2, self.MANAbar_high, self.MANAwidth, self.MANAheight))
        pygame.draw.rect(win, (0,0,255), (self.rect.x + self.width/2 - self.MANAwidth/2, self.MANAbar_high, self.MANAwidth*self.MANA/self.MAXMANA, self.MANAheight))



class BulletIO():
    def __init__(self):
        self.MANA_usage =  5
        self.zeta = 0
        self.width = 15
        self.height = 15
        self.speed = 1600
        self.damage = 8
        self.color = (0,255,0)
        self.image = pygame.Surface([self.width, self.height])
        self.image.fill(self.color)
        self.rect = self.image.get_rect()
        self.reload_delay = 0.2 #shot delay  
    def variant(self,X,Y, Zeta):
        self.rect.x = X
        self.rect.y = Y
        self.zeta = Zeta
        
    def draw(self):
        pygame.draw.rect(win, self.color, self.rect)
        

class LaserIO():
    def __init__(self):
        self.MANA_usage =  10
        self.zeta = 0
        self.width = 8
        self.height = 8
        self.speed = 500
        self.damage = 20
        self.color = (0,0,160)
        self.image = pygame.Surface([self.width, self.height])
        self.image.fill(self.color)
        self.rect = self.image.get_rect()
        self.reload_delay = 0 #shot delay  
    def variant(self,X,Y, Zeta):
        self.rect.x = X
        self.rect.y = Y
        self.zeta = Zeta
        
    def draw(self):
        pygame.draw.rect(win, self.color, self.rect)    

class LaserBeamIO():
    def __init__(self):
        self.MANA_usage_per_sec =  1000
        self.zeta = 0
        self.width = 8
        self.height = 8
        self.speed = 500
        self.damage = 20
        self.color = (0,160,160)
        self.image = pygame.Surface([self.width, self.height])
        self.image.fill(self.color)
        self.rect = self.image.get_rect()
        self.reload_delay = 5 #shot delay
        self.charge_time = 0
        self.beam_distance = 60
        self.max_charge_time = 10
        self.charge_size_per_sec = 20
        self.charge_damage_per_sec = 100
        
        
    def charge_beam(self, player, Zeta):
        self.charge_time+=dt
        self.width += self.charge_size_per_sec * dt
        self.height += self.charge_size_per_sec * dt
        self.damage += self.charge_damage_per_sec * dt
        self.image = pygame.Surface([self.width, self.height])
        self.image.fill(self.color)
        self.rect = self.image.get_rect()
        self.rect.center = (player[0] + self.beam_distance * math.cos(Zeta), player[1] + self.beam_distance * math.sin(Zeta))
        self.zeta = Zeta
        
    def draw(self):
        pygame.draw.rect(win, self.color, self.rect)    

class EnemyIO():
    def __init__(self):
        self.name = 'enemy'
        self.width = 15
        self.height = 15
        self.zeta = 0
        self.speed = 200
        self.color = (255,255,0)
        self.MAXHP = 16
        self.regeneration = 0.2
        
        self.HPwidth = 20
        self.HPheight = 5
        self.HPoffset = 8
        self.HP = self.MAXHP
        
        
        self.damage = 10
        self.image = pygame.Surface([self.width, self.height])
        self.image.fill(self.color)
        self.rect = self.image.get_rect(topleft=(20, 20))
        
    def randompos(self, spawn_offset = 200):
        while 1 :
            try:
                rand_pole = random.randint(1,4)
                if rand_pole == 1 or rand_pole == 2:
                    self.rect.x = random.randint(FirstPlayer.rect.x+1+spawn_offset, window[0]-self.width)
                else:
                    self.rect.x = random.randint(1,FirstPlayer.rect.x-spawn_offset)
                
                if rand_pole == 1 or rand_pole == 4:
                    self.rect.y = random.randint(FirstPlayer.rect.y+1+spawn_offset, window[1]-self.height)
                else:
                    self.rect.x = random.randint(1,FirstPlayer.rect.y-spawn_offset)
                return 0
            except:
                pass
    def draw(self):
        self.HPbar_high = self.rect.y-self.HPheight-self.HPoffset
    
        pygame.draw.rect(win, self.color, self.rect)
        pygame.draw.rect(win, (255,255,255), (self.rect.x + self.width/2 - self.HPwidth/2, self.HPbar_high, self.HPwidth, self.HPheight))
        pygame.draw.rect(win, (0,255,0), (self.rect.x + self.width/2 - self.HPwidth/2, self.HPbar_high, self.HPwidth*self.HP/self.MAXHP, self.HPheight))
        
def direction(mouse, obj):
    #y = 1 clockwise
    zeta = math.atan2((mouse[1]-obj[1]),(mouse[0]-obj[0]))
    return zeta

def enemy_spawn(Enemy_max = 30, slow_rate = 2):
    
    num = len(Global_Obj[2])
    if  num < Enemy_max:
        # random spawn enemy 1/100
        rand = random.randint(1, int((num+1)*slow_rate))
        if rand == 1:
            ene = EnemyIO()
            ene.randompos()
            Global_Obj[2].append(ene)
       

#Global_Obj[0] are player obj type, Global_Obj[1] are bullet obj type, Global_Obj[2] are enemy obj type, Global_Obj[3] are beam obj type
Global_Obj = [[],[],[],[]]
FirstPlayer = playerIO()
Global_Obj[0].append(FirstPlayer)
win.blit(background,(0, 0))
mouse_pos = (-1,-1)
beam_load = False

pygame.init()
 
# define the RGB value for white,
#  green, blue colour .
text_color = (0, 0, 0)
white = (255, 255, 255)
# set the pygame window name
pygame.display.set_caption('Game')
 
# create a font object.
# 1st parameter is the font file
# which is present in pygame.
# 2nd parameter is size of the font
font = pygame.font.Font('freesansbold.ttf', 32)
 
# create a text surface object,
# on which text is drawn on it.
text = font.render('Game start', True, text_color)
 
# create a rectangular object for the
# text surface object
textRect = text.get_rect()
 
# set the center of the rectangular object.
textRect.center = (window[0] // 2, window[1] // 2)
inteface_run = True
# infinite loop
while inteface_run:
 
    # completely fill the surface object
    # with white color
    win.fill(white)
 
    # copying the text surface object
    # to the display surface object
    # at the center coordinate.
    win.blit(text, textRect)
 
    # iterate over the list of Event objects
    # that was returned by pygame.event.get() method.
    for event in pygame.event.get():
        if pygame.mouse.get_pressed()[0] or event.type == pygame.QUIT:
            inteface_run = False
        # Draws the surface object to the screen.
        pygame.display.update()
pygame.time.delay(1000)
font = pygame.font.Font('freesansbold.ttf', 16)
end_time = 300 # sec
t0= time.time()

while run:
        pygame.display.flip() 
        pygame.time.delay(int(dt*1000))
        
        
        # player move event
        keys = pygame.key.get_pressed()
        if keys[pygame.K_ESCAPE]:
            run = False
        if keys[pygame.K_SPACE] :
            FirstPlayer.rect.x, FirstPlayer.rect.y = (80,80)
        if (keys[pygame.K_a] or keys[pygame.K_LEFT]):
            FirstPlayer.rect.x, FirstPlayer.rect.y = (FirstPlayer.rect.x - FirstPlayer.speed * dt, FirstPlayer.rect.y)
        if keys[pygame.K_d] or keys[pygame.K_RIGHT]:
            FirstPlayer.rect.x, FirstPlayer.rect.y = (FirstPlayer.rect.x + FirstPlayer.speed * dt, FirstPlayer.rect.y)
        if keys[pygame.K_w] or keys[pygame.K_UP]:
            FirstPlayer.rect.x, FirstPlayer.rect.y = (FirstPlayer.rect.x, FirstPlayer.rect.y - FirstPlayer.speed * dt)
        if keys[pygame.K_s] or keys[pygame.K_DOWN]:
            FirstPlayer.rect.x, FirstPlayer.rect.y = (FirstPlayer.rect.x, FirstPlayer.rect.y + FirstPlayer.speed * dt)
        
        
        if FirstPlayer.rect.x < 0:
            FirstPlayer.rect.x = 0
        if FirstPlayer.rect.x+FirstPlayer.width > window[0]:
            FirstPlayer.rect.x = window[0] - FirstPlayer.width
        if FirstPlayer.rect.y < 0:
            FirstPlayer.rect.y = 0
        if FirstPlayer.rect.y+FirstPlayer.height > window[1]:
            FirstPlayer.rect.y = window[1] - FirstPlayer.height
        
        
        #player HP regen
        if FirstPlayer.HP < FirstPlayer.MAXHP:
            FirstPlayer.HP +=FirstPlayer.HPregeneration_per_sec*dt
            if FirstPlayer.HP > FirstPlayer.MAXHP:
                FirstPlayer.HP = FirstPlayer.MAXHP
        
        #player MANA regen
        if FirstPlayer.MANA < FirstPlayer.MAXMANA:
            FirstPlayer.MANA +=FirstPlayer.MANAregeneration_per_sec*dt
            if FirstPlayer.MANA > FirstPlayer.MAXMANA:
                FirstPlayer.MANA = FirstPlayer.MAXMANA




        #bullet reload time
        for i in range(len(FirstPlayer.bullet_delay)):
            if FirstPlayer.bullet_delay[i]>0:
                FirstPlayer.bullet_delay[i]-= dt 
                if FirstPlayer.bullet_delay[i]<0:
                    FirstPlayer.bullet_delay[i] =0
        
        # bullet click event
        mouse_pos = pygame.mouse.get_pos()

        if pygame.mouse.get_pressed()[0] and FirstPlayer.MANA >= BulletIO().MANA_usage and FirstPlayer.bullet_delay[0]==0:
            bullet = BulletIO()
            FirstPlayer.MANA -= bullet.MANA_usage
            zeta = direction(mouse_pos, FirstPlayer.rect.center)
            bullet.variant(*FirstPlayer.rect.center, zeta)
            FirstPlayer.bullet_delay[0] = bullet.reload_delay
            Global_Obj[1].append(bullet)
        
        if pygame.mouse.get_pressed()[2] and FirstPlayer.MANA >= LaserIO().MANA_usage and FirstPlayer.bullet_delay[1]==0:
            laser = LaserIO()
            FirstPlayer.MANA -= laser.MANA_usage
            zeta = direction(mouse_pos, FirstPlayer.rect.center)
            laser.variant( *FirstPlayer.rect.center, zeta)
            FirstPlayer.bullet_delay[1] = laser.reload_delay
            Global_Obj[1].append(laser)




        # Global_Obj[1] bullet obj type movement
        for bullet_type in Global_Obj[1]:
            bullet_type.rect.x += dt * bullet_type.speed * math.cos(bullet_type.zeta)
            bullet_type.rect.y += dt * bullet_type.speed * math.sin(bullet_type.zeta)
            if bullet_type.rect.x > window[0] or bullet_type.rect.x < 0 or bullet_type.rect.y > window[1] or bullet_type.rect.y < 0:
                Global_Obj[1].remove(bullet_type)
        # Global_Obj[3] beam obj type movement
        for bullet_type in Global_Obj[3]:
            bullet_type.rect.x += dt * bullet_type.speed * math.cos(bullet_type.zeta)
            bullet_type.rect.y += dt * bullet_type.speed * math.sin(bullet_type.zeta)
            if bullet_type.rect.x > window[0] or bullet_type.rect.x < 0 or bullet_type.rect.y > window[1] or bullet_type.rect.y < 0:
                Global_Obj[3].remove(bullet_type)
        # enemy
        enemy_spawn()
        
        
        # Global_Obj[2] enemy obj type movement
        for enemy_types in Global_Obj[2]:
            zetas = math.sqrt((enemy_types.rect.center[1] - FirstPlayer.rect.center[1])**2 + (FirstPlayer.rect.center[0] - enemy_types.rect.center[0])**2)
            
            print(enemy_types.rect.center)
            
            x,y = enemy_types.rect.center
            x += dt * enemy_types.speed * (FirstPlayer.rect.center[0] - enemy_types.rect.center[0]) / zetas
            y += dt * enemy_types.speed * (FirstPlayer.rect.center[1] - enemy_types.rect.center[1]) / zetas
            
            
            enemy_types.rect.center = (x,y)
        
       
        
        # enemy-player damage check
        for enemy_type in Global_Obj[2]:
            if FirstPlayer.rect.colliderect(enemy_type.rect):
                FirstPlayer.HP -= enemy_type.damage
                Global_Obj[2].remove(enemy_type)
                
        # bullet - enemy damage check
        for bullet_type in Global_Obj[1]:
            for enemy_type in Global_Obj[2]:
                if bullet_type.rect.colliderect(enemy_type.rect):
                    enemy_type.HP -= bullet_type.damage
                    if enemy_type.HP<=0:
                        Global_Obj[2].remove(enemy_type)
                    Global_Obj[1].remove(bullet_type)
                    break

        # beam - enemy damage check
        for bullet_type in Global_Obj[3]:
            for enemy_type in Global_Obj[2]:
                if bullet_type.rect.colliderect(enemy_type.rect):
                    enemy_type.HP -= bullet_type.damage
                    if enemy_type.HP<=0:
                        Global_Obj[2].remove(enemy_type)
        
        #end game
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                run = False
            elif event.type == pygame.MOUSEBUTTONDOWN:
                if event.button == 2 and FirstPlayer.bullet_delay[2]==0: # left mouse button
                    beam = LaserBeamIO()
                    Global_Obj[3].append(beam)
                    beam_load = True

            elif event.type == pygame.MOUSEBUTTONUP:
                if event.button == 2 and beam_load:
                    zeta = direction(mouse_pos, FirstPlayer.rect.center)
                    beam.charge_beam( FirstPlayer.rect.center, zeta)
                    FirstPlayer.bullet_delay[2] = beam.reload_delay
                    beam_load = False
                
        if beam_load:
            if FirstPlayer.MANA <= beam.MANA_usage_per_sec * dt:
                zeta = direction(mouse_pos, FirstPlayer.rect.center)
                beam.charge_beam( FirstPlayer.rect.center, zeta)
                FirstPlayer.bullet_delay[2] = beam.reload_delay
                beam_load = False
            else:
                FirstPlayer.MANA -= beam.MANA_usage_per_sec * dt
                zeta = direction(mouse_pos, FirstPlayer.rect.center)
                beam.charge_beam( FirstPlayer.rect.center, zeta)
                
            
        if FirstPlayer.HP <= 0:
            run = False
        te = int(time.time() - t0)
        if te >= end_time:
            run = False



        # draw obj
        win.blit(background,(0, 0))
    
        for obj_types in Global_Obj:
            for obj in obj_types:
                obj.draw()
                
        text = font.render(f'timer:{te}/{end_time} sec', True, white)
 
        # create a rectangular object for the
        # text surface object
        textRect = text.get_rect()
         
        # set the center of the rectangular object.
        textRect.center = (window[0] // 2, window[1]-40)
        win.blit(text, textRect)
        
pygame.quit()

Feel free to give me any suggestions to improve my code. I'm very new to Pygame.

Pachara.17
  • 23
  • 3

2 Answers2

2

Your enemies do move at the same speed in all directions, which can be measured by adding a few lines:

        # Global_Obj[2] enemy obj type movement

        for enemy_types in Global_Obj[2]:
            zetas = math.dist(enemy_types.rect.center, FirstPlayer.rect.center)
            x, y = enemy_types.rect.center
            dx = dt * enemy_types.speed * (FirstPlayer.rect.center[0] - enemy_types.rect.center[0]) / zetas
            dy = dt * enemy_types.speed * (FirstPlayer.rect.center[1] - enemy_types.rect.center[1]) / zetas
            x += dx
            y += dy
            print((dx ** 2 + dy ** 2) ** 0.5)
            enemy_types.rect.center = (x, y)

The code consistently prints out 2.

I suspect that it has something to do with the amount of time it takes to evaluate negative/positive calculations, which can be measured using the time.perf_counter() method.

Tip: Your

zetas = math.sqrt((enemy_types.rect.center[1] - FirstPlayer.rect.center[1])**2 + (FirstPlayer.rect.center[0] - enemy_types.rect.center[0])**2)

can be replaced with

zetas = math.dist(enemy_types.rect.center, FirstPlayer.rect.center)

a much more efficient method.

Red
  • 26,798
  • 7
  • 36
  • 58
1

Since pygame.Rect is supposed to represent an area on the screen, a pygame.Rect object can only store integral data:

The coordinates for Rect objects are all integers. [...]

When you do

enemy_types.rect.center = (x,y) 

it is the same as you would do:

enemy_types.rect.center = (int(x), int(y))

The fraction component of the coordinate get lost. This causes that the movement to the left and to the top is faster than to the right and to the bottom.

If you want to store object positions with floating point accuracy, you have to store the location of the object in separate variables respectively attributes and to synchronize the pygame.Rect object. round the coordinates and assign it to the location of the rectangle:

class EnemyIO():
    def __init__(self):
        # [...]

        self.x, self.y = self.rect.center
for enemy_types in Global_Obj[2]:
    zetas = math.sqrt((enemy_types.rect.center[1] - FirstPlayer.rect.center[1])**2 + (FirstPlayer.rect.center[0] - enemy_types.rect.center[0])**2)

    enemy_types.x += dt * enemy_types.speed * (FirstPlayer.rect.center[0] - enemy_types.rect.center[0]) / zetas
    enemy_types.y += dt * enemy_types.speed * (FirstPlayer.rect.center[1] - enemy_types.rect.center[1]) / zetas

    enemy_types.rect.center = round(enemy_types.x), round(enemy_types.y) 
Rabbid76
  • 202,892
  • 27
  • 131
  • 174