Class inheritance in python with pygame - python

So I'm working on a hobby game, and i'm not able to get one class to inherit another properly. The file structure is as shown below:
main.py
Enemy
|walker.py
|genericEnemy.py
main.py calls walker.py, whose main class inherits from genericEnemy.py. Both of their contents are below:
walker.py
import pygame
from .genericEnemy import generic
class walker(generic):
pass
genericEnemy.py
'''
This class controls generic capabilities for all enemies.
Specific abilities are in the enemy type's class in the superfolder
'''
import pygame
class generic:
def __init__(self, speed, pos, size):
'''
speed - The entity speed, an int greater than 0
pos - The (x,y) position of the entity, a list of length 2
size - The entity hitbox, a list with length 2
'''
#Movement Variables
self.speed = speed
self.currDir = 1
self.isMoving = True
#Drawing Variables
self.pos = pos
self.size = size
#Gravity Variables
self.isJumping = False
self.fallCounter = 0
self.gravityTimer = 0
#==================================================
def draw(self, surface):
pygame.draw.rect(surface, (255, 0, 0), (self.pos[0], self.pos[1], self.size[0], self.size[1]))
#==================================================
def updateGravity(self):
self.fallCounter += 1
if self.fallCounter == 8:
self.fallCounter = 0
self.gravityTimer += 1
#==================================================
def walk(self):
if self.isMoving:
self.pos[0] += self.speed * self.currDir
The issue I'm having is that in main when I say:
SCREEN = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
ENEMY = walker(6, [120, 1000], [10, 30])
and then later on
ENEMY.draw(SCREEN)
I get the error: AttributeError: 'walker' object has no attribute 'draw'
Any help would be greatly appreciated, like I said this is a hobbyist project, so i'm fairly inexperienced in python/pygame

User Barmar was correct. I, as an absolute walnut, had my functions indented too far.

Use class generic(pygame.sprite.Sprite):
and
all_sprites = pygame.sprite.Group()
all_sprites.add(ENEMY)
all_sprites.draw(SCREEN)
pygame sprite group has
draw function
And maybe it need sprite.image!
It can be surface
self.image = pygame.Surface([self.size[0], self.size[1]])
self.image.fill((255, 0, 0))
or image file
self.image = pygame.transform.scale(pygame.image.load(imgname).convert_alpha(), (self.size[0], self.size[1]))

Related

The particle is not moving so what is the problem?

I follow a youtube video 'https://www.youtube.com/watch?v=cCiXqK9c18g' and in the video he make a class that represent a ball and he used pymunk to make a body and added it to the space and after that he created a method inside the ball class that will use pygame to draw the ball and I did almost like him
import pygame
import pymunk
pygame.init()
fps = 60
dt = 1/fps
dsX = 800 # screen width
dsY = 500 # screen height
display = pygame.display.set_mode((dsX, dsY))
space = pymunk.Space()
clock = pygame.time.Clock()
def convert_cor(point): # convet the coordinates from pymunk to pygame coordinates
return point[0], dsY - point[1]
class Particle: # v: velocity, pos: position[x, y], r: radius of particle(Circle)
def __init__(self, pos = [0, 0], v = [0, 0], r = 10, color = (255, 0, 0)):
self.pos = pos
self.v = v
self.r = r
self.color = color
self.body = pymunk.Body()
self.body.position = self.pos
self.body.velocity = self.v # this is the veclocity
self.shape = pymunk.Circle(self.body, self.r)
self.shape.dencity = 1
self.shape.elasticity = 1
space.add(self.body, self.shape)
def draw(self):
pygame.draw.circle(display, self.color, convert_cor(self.pos), self.r)
class Box: # thickness of the sides of the box and L1, L2, L3, L4 are the sides of the box
def __init__(self, thickness, color):
self.thickness = thickness
self.color = color
L1 = pymunk.Body(body_type = pymunk.Body.STATIC)
L2 = pymunk.Body(body_type = pymunk.Body.STATIC)
L3 = pymunk.Body(body_type = pymunk.Body.STATIC)
L4 = pymunk.Body(body_type = pymunk.Body.STATIC)
L1_shape = pymunk.Segment(L1, (0, 0), (dsX, 0), self.thickness)
L2_shape = pymunk.Segment(L2, (dsX, 0), (dsX, dsY), self.thickness)
L3_shape = pymunk.Segment(L3, (dsX, dsY), (0, dsY), self.thickness)
L4_shape = pymunk.Segment(L4, (0, dsY), (0, 0), self.thickness)
space.add(L1, L1_shape)
space.add(L2, L2_shape)
space.add(L3, L3_shape)
space.add(L4, L4_shape)
def draw(self):
pygame.draw.line(display, self.color, convert_cor((0, 0)), convert_cor((dsX, 0)), self.thickness * 2)
pygame.draw.line(display, self.color, convert_cor((dsX, 0)), convert_cor((dsX, dsY)), self.thickness * 2)
pygame.draw.line(display, self.color, convert_cor((dsX, dsY)), convert_cor((0, dsY)), self.thickness * 2)
pygame.draw.line(display, self.color, convert_cor((0, dsY)), convert_cor((0, 0)), self.thickness * 2)
def Sim(): # the infinite while loop as a function
box = Box(2, (0, 255, 255))
particle = Particle(pos =[dsX/2, dsY/2], v = [-200, 500]) # here i gave the position and the velocity
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
return
display.fill((255, 255, 255))
box.draw()
particle.draw()
clock.tick(fps)
space.step(dt)
pygame.display.update()
Sim()
pygame.quit()
The thing is, I did also a class that will add a rigid sides for the display and i drew the sides from the Box class using the method 'draw' The problem is in the time 5:58 in the video he gave the ball velocity and it start moving and in my code it does not move. any idea why it doen't move?
note: I called the ball particle in my code
You error is both a typo and using the wrong variable.
Inside your particles draw function...
# OLD
def draw(self):
pygame.draw.circle(display, self.color, convert_cor(self.pos), self.r)
# New
def draw(self):
pygame.draw.circle(display, self.color, convert_cor(self.body.position), self.r)
You have to use the body's position cause that is the position of the physics body in pymunk's space.
Secondly...
class Particle: # v: velocity, pos: position[x, y], r: radius of particle(Circle)
def __init__(self, pos, v, r=10, color=(255, 0, 0)):
...
# Old
self.shape.dencity = 1
# New
self.shape.density = 1
Since density was not set to anything Pymunk was having a divide by zero error so it wouldn't update the body's position.
self.body.position = self.pos
To be clear about the problem:
From what I could find in the documentation, pymunk.Body.position is a property; it expects you to pass either an ordinary Python tuple or a Vec2d (not a list, although anything it's relying on internally to handle a tuple probably handles a list just fine), and calls some internal code written in another programming language. The effect is that, instead of storing your list object and then making changes to the list (which would be visible in your class, since it would be the same object), it just gets the initial values out of your list, and then doesn't use the list object.
This means that when Pymunk applies physics calculations, self.body.position changes, but self.pos does not. So the current self.pos is useless; we can't use it to check the object position.
If you don't need to do that, then there is no need to create a self.pos at all - just feed self.body.position = pos directly, and make sure to use self.body.position when drawing.
If you do, I recommend using your own property instead of trying to set self.pos. Do the above, and then add to the class:
#property
def pos(self):
return self.body.position
And if you want to change the position from your code (but you probably shouldn't! Why else are you using the physics engine in the first place?), also add:
#pos.setter
def pos(self, value):
self.body.position = value

Difficiating objects in a group

In a group called powerUpGroup, there are two objects: speedUp and jumpUP. The following code checks if the player object has collided with any objects in the group:
for eachPowerUp in powerUpGroup:
if pygame.sprite.spritecollide(eachPowerUp, playerGroup, False) :
eachPowerUp.kill()
Let's say I only want the program to print "Speed increased" when the player collides with speedUp object. Based on the code above, it will print the message if it either speedUp or jumpUP since they are in the same group. Without creating a new group, is there a way for python to identify that they are different objects run certain code?
If speedUp and jumpUp are different classes, you should implement their behaviour in their very classes (something something polymorphism):
The speepUp class should contain the code for speeding up (or whatever it does) and trigger secondary effects (like displaying stuff to the player and removing itself from the game), and the jumpUp class should do the same. Of course you could go down the abstraction rabbit hole but let's keep it simple.
Here's a litte example:
import pygame
from random import choice
class Actor(pygame.sprite.Sprite):
def __init__(self, color, pos, *grps):
super().__init__(*grps)
self.image = pygame.Surface((64, 64))
self.image.fill(color)
self.rect = self.image.get_rect(topleft=pos)
def update(self, events, dt):
pass
class Player(Actor):
def __init__(self, *grps):
super().__init__('dodgerblue', (400, 300), *grps)
self.pos = pygame.Vector2(self.rect.topleft)
self.dir = pygame.Vector2((0, 0))
self.speed = 300
def update(self, events, dt):
pressed = pygame.key.get_pressed()
self.dir.x, self.dir.y = (0, 0)
if pressed[pygame.K_d]: self.dir += ( 1, 0)
if pressed[pygame.K_a]: self.dir += (-1, 0)
if pressed[pygame.K_s]: self.dir += ( 0, 1)
if pressed[pygame.K_w]: self.dir += ( 0, -1)
if self.dir.length() > 0:
self.dir.normalize()
self.pos += self.dir * self.speed * dt
self.rect.topleft = self.pos
class PowerUp(Actor):
def __init__(self, color, pos, player, *grps):
super().__init__(color, pos, *grps)
self.player = player
def apply(self):
pass
def update(self, events, dt):
if pygame.sprite.collide_rect(self, self.player):
self.apply()
self.kill()
class SpeedUp(PowerUp):
def __init__(self, player, *grps):
super().__init__('yellow', (100, 100), player, *grps)
def apply(self):
print('Speed Up')
self.player.speed += 200
class ColorChange(PowerUp):
def __init__(self, player, *grps):
super().__init__('purple', (600, 300), player, *grps)
def apply(self):
print('ColorChange!')
self.player.image.fill(choice(['green', 'blue', 'yellow', 'grey']))
def main():
pygame.init()
screen = pygame.display.set_mode((800, 600))
clock, dt = pygame.time.Clock(), 0
sprites = pygame.sprite.Group()
player = Player(sprites)
SpeedUp(player, sprites)
ColorChange(player, sprites)
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
screen.fill('black')
sprites.draw(screen)
sprites.update(events, dt)
pygame.display.flip()
dt = clock.tick(120)/1000
if __name__ == '__main__':
main()
Use an if statement to check if the power-up is speedUp
for eachPowerUp in powerUpGroup:
if pygame.sprite.spritecollide(eachPowerUp, playerGroup, False):
if eachPowerUp == speedUp:
print("Speed increased")
eachPowerUp.kill()
Edit for second part of question:
Instead of checking if the power up is a certain object, you can check if it is a certain class.
Say you have a class called SpeedUp. You can check to see if eachPowerUp is a SpeedUp object and then print the message you wanted.
for eachPowerUp in powerUpGroup:
if pygame.sprite.spritecollide(eachPowerUp, playerGroup, False):
if type(eachPowerUp) == SpeedUp:
print("Speed increased")
eachPowerUp.kill()
The previous code was an equality test between two sprites, not a type of sprite. The variable speedUp is one of your powerUpGroup sprites. The if statement will only be true if the particular speedUp that the player is colliding with is the particular speedUp in that variable.
So I added a property.
In the constructor(That was not added in the OP), I have self.type = "multiShot" or self.type = "speed", depending on which type it is. Then, an if statement would now be something like:
for eachPowerUp in powerUpGroup:
if pygame.sprite.spritecollide(eachPowerUp, playerGroup, False) :
if eachPowerUp.type == "multiShot" :
...
elif eachPowerUp.type == "speed" :
...

Python programming trouble

I was programming a game and I did not know how to program in python 3.7 so I had to get a book called Code This Game, book from OddDot. And When I got to chapter 6 I was learning how to create a group for the enemy sprites. and every time I ran the program all it drew was the background image and the grid(because the book is not only teaching me how to code in Python, while I am learning I am making a game from the book while I learn from the book.) it did not draw the group of Vampire Pizzas in the right column if fact it doesn't even draw them at all. I have tried redoing the chapter over and over again and I can't do it
here is my code
#Import Libraries
import pygame
from pygame import *
from random import randint
#Initialize pygame
pygame.init()
#Define constant variables
WINDOW_WIDTH = 1100
WINDOW_HEIGHT = 600
WINDOW_RES = (WINDOW_WIDTH, WINDOW_HEIGHT)
#Define Tile Parameters
WIDTH = 100
HEIGHT = 100
#Define Tile colors
WHITE = (255, 255, 255)
#Set up rates
SPAWN_RATE = 360
#This is the code where the game window will show up
GAME_WINDOW = display.set_mode(WINDOW_RES)
display.set_caption('Attack of the Vampire Pizzaas!')
#---------------------------------------------------------
#Set up the enemy image
pizza_img = image.load('vampire.png')
pizza_surf = Surface.convert_alpha(pizza_img)
VAMPIRE_PIZZA = transform.scale(pizza_surf, (HEIGHT, WIDTH))
#Create a subclass of Sprite called VampireSprite
class VampireSprite(sprite.Sprite):
#Set up enemy instances
def __init__(self):
super().__init__()
self.speed = 2
self.lane = randint(0, 4)
all_vampires.add(self)
self.image = VAMPIRE_PIZZA.copy()
y = 50 + self.lane * 100
self.rect = self.image.get_rect(center = (1100, y))
def update(self, game_window):
game_window.blit(self.image, (self.rect.x, self.rect.y))
#Set up the background image
background_img = image.load('restaurant.jpg')
background_surf = Surface.convert_alpha(background_img)
BACKGROUND = transform.scale(background_surf, WINDOW_RES)
#------------------------------------------------------
all_vampires = sprite.Group()
#Initialile and draw background grid
tile_color = WHITE
for row in range(6):
for column in range(11):
draw.rect(BACKGROUND, tile_color, (WIDTH * column,
HEIGHT * row, WIDTH, HEIGHT),1)
GAME_WINDOW.blit(BACKGROUND, (0, 0))
#---------------------------------------------------------
#Start The Main Game Loop
#Game Loop
game_running = True
while game_running:
#Check for Events
for event in pygame.event.get():
if event.type == QUIT:
game_running = False
#Spawn vampire pizza sprites
if randint(1, SPAWN_RATE) == 1:
VampireSprite()
#Update displays
for vampire in all_vampires:
vampire.update(GAME_WINDOW)
display.update()
#End of the Main game loop
#---------------------------------------------------------
#Clean up game
pygame.quit()
I don't know if I am doing something wrong or what
If my eyes are working fine(I guess) the code is valid
Make sure that the pygame version you are using is the one compatible with Python 3.7 and not older versions such as Python 2.
I had the same issue while working my way through this fun coding book. Comparing the code from the book's website and my own code I found the issue was incorrect spacing while defining the update() function within the VampireSprite class.
# Create a subclass of Sprite called VampireSprite
class VampireSprite(sprite.Sprite):
# Set up enemy instances
def __init__(self):
super().__init__()
self.Speed = 2
self. Lane = randint(0, 4)
all_vampires.add(self)
self.image = VAMPIRE_PIZZA.copy()
y = 50 + self.lane * 100
self.rect = self.image.get_rect(center = (1100, y))
def update(self, game_window):
game_window.blit(self.image, (self.rect.x, self.rect.y))
The correct code is:
# Create an enemy class
class VampireSprite(sprite.Sprite):
# This function creates an instance of the enemy
def __init__(self):
super().__init__()
self.speed = 2
self.lane = randint(0, 4)
all_vampires.add(self)
self.image = VAMPIRE_PIZZA.copy()
y = 50 + self.lane * 100
self.rect = self.image.get_rect(center=(1100, y))
# This function moves the enemies from right to left and destroys them after they've left the screen
def update(self, game_window):
game_window.blit(self.image, (self.rect.x, self.rect.y))

Problems with Sprites Appearing and Collision with Rotated Objects; Pygame object is not iterable

I'm currently trying to make pixel perfect collisions between my pong ball and my player's paddle using the mask and collide_rect functions. I made my own checkCollision function in the pong class which would check for pixel perfect collision. However, right now, I can't even get the Sprites to work or appear on the screen because my "Pong object is not iterable.
Here is my pong class with the important features: (I will post additional code if needed)
class Pong(pygame.sprite.Sprite):
def __init__(self, screensize):
pygame.sprite.Sprite.__init__(self)
self.screensize = screensize
self.centerx = screensize[0] // 2
self.centery = screensize[1] // 2
self.radius = 25
self.rect = pygame.Rect(self.centerx-self.radius,
self.centery-self.radius,
self.radius*2, self.radius*2)
self.pokeimage = pygame.image.load("pokeball.png")
self.pokeimage = pygame.transform.scale(self.pokeimage, (self.radius, self.radius))
#Create the mask
self.mask = pygame.mask.from_surface(self.pokeimage)
def checkCollision(self, player_paddle, ai_paddle):
col = pygame.sprite.collide_rect(self, player_paddle)
return col
def collisionFormula(self, player_paddle, ai_paddle):
if self.checkCollision(self, player_paddle):
def collision_checks(self, player_paddle, ai_paddle):
#if the ball hits the top or bottom of the screen, change the y direction
if self.rect.top <= 0 or self.rect.bottom >= self.screensize[1] - 1:
self.direction[1] *= -1
#if the pong hits the paddles, change how the pong ball moves
if self.rect.colliderect(player_paddle.rect) or self.rect.colliderect(ai_paddle.rect):
self.collisionFormula(player_paddle, ai_paddle)
def update(self, player_paddle, ai_paddle):
self.update_ball_position()
self.reset_ball()
self.collision_checks(player_paddle, ai_paddle)
In my PlayerPaddle class, I do the same mask initialization.
class PlayerPaddle(pygame.sprite.Sprite):
def __init__(self, screensize):
pygame.sprite.Sprite.__init__(self)
self.screensize = screensize
self.centerx = 50
self.centery = screensize[1]//2
self.height = 100
self.width = 20
self.imageMaster = pygame.image.load("naruto.png").convert_alpha()
self.imageMaster = pygame.transform.scale(self.imageMaster, (self.width, self.height))
self.image = self.imageMaster
#mask
self.mask = pygame.mask.from_surface(self.image)
def turnLeft(self):
self.dir += 45
if self.dir > 360:
self.dir = 45
def turnRight(self):
self.dir -= 45
if self.dir < 0:
self.dir = 315
def update(self):
#Rotate functions
oldCenter = self.rect.center
self.image = pygame.transform.rotate(self.imageMaster, self.dir)
self.rect = self.image.get_rect()
self.rect.center = oldCenter
And here is my main function:
def main():
pygame.init()
screensize = (640,700)
screen = pygame.display.set_mode(screensize)
background = pygame.Surface(screen.get_size())
background.fill((0, 255, 0))
clock = pygame.time.Clock()
pong = Pong(screensize)
player_paddle = PlayerPaddle(screensize)
ai_paddle = AIPaddle(screensize)
paddleSprite = pygame.sprite.Group(player_paddle)
pongSprite = pygame.sprite.Group(pong)
while running:
running = True
#object updating phase
ai_paddle.update(pong, player_paddle)
player_paddle.update()
pong.update(player_paddle, ai_paddle)
if pygame.sprite.spritecollide(player_paddle, pong, False, pygame.sprite.collide_mask):
print("Collided")
#rendering phase
ai_paddle.render(screen)
player_paddle.render(screen)
pong.render(screen)
paddleSprite.clear(screen, background)
paddleSprite.update()
paddleSprite.draw(screen)
pongSprite.clear(screen,background)
pongSprite.update()
pongSprite.draw(screen)
pygame.display.flip()
pygame.quit()
main()
I made two "group" objects (the pong and the player_paddle) but I'm not sure why I'm even failing to run the program. Additionally, I know the collision will not work because the pong ball will hit the rectangle of the original image, but not the rotated image, but I'm not sure why that will happen if I use the built in sprite function. Thanks.
Read documentation for spritecollide
Find sprites in a group that intersect another sprite.
spritecollide(sprite, group, dokill, collided = None) -> Sprite_list
Second argument has to be group (pygame.sprite.Group), not single Sprite.
It can be group event with one sprite. But you use pong which is single sprite, not group.
See documentation for collide_mask
Collision detection between two sprites, using masks.
collide_mask(SpriteLeft, SpriteRight) -> point
It checks collision between two sprites using mask.
EDIT: in your code you have problem with
spritecollide(player_paddle, pong,...)
because pong is single Sprite, not Group.
With pong you should use collide_mask
collide_mask(player_paddle, pong)
You can use spritecollidewith pongSprite which is Group
spritecollide(player_paddle, pongSprite,...)`
BTW: you could use better names ie. pong_group instead of pongSprite.
And eventually pong_sprite instead of pong (but pong is OK, too).

Ways to reach variables from one class to another class?

I'm trying to create a basic game, but I'm fairly new to the python programming scene. I've come across a problem where with two classes (a player and enemy class), I want to access class variables like health from the player and enemy, and vice versa. What are some ways of doing this?
Here's the code to better emphasize what I'm asking:
class Player(object):
def __init__(self, image):
self.x = 100
self.y = 240
self.health = 30
self.defense = 25
self.image = image
self.black = (0, 0, 0)
self.draw()
def draw(self):
screen.blit(self.image, (self.x, self.y))
line = pygame.draw.rect(screen, self.black, (80, 300, 100, 5))
def attack(self):
pass
class Enemy(object):
def __init__(self, image):
self.x = 480
self.y = 240
self.health = 20
self.defense = 15
self.image = image
self.black = (0, 0, 0)
self.draw()
def draw(self):
screen.blit(self.image, (self.x, self.y))
line = pygame.draw.rect(screen, self.black, (460, 300, 100, 5))
def attack(self):
pass
Basically, I don't understand how I can take something like the "self.health" from one class, and easily access it from the other class. I've tried some methods of using return methods etc., but I'm curious if there are any simple ways to do this. Help is appreciated!
This code creates a class, and in the special __init__ method, it assigns values to various member variables.
class Player(object):
def __init__(self, image):
self.x = 100
self.y = 240
self.health = 30
self.defense = 25
self.image = image
self.black = (0, 0, 0)
self.draw()
These member variables are also called properties in Python. If you have a reference to a Player instance (an instance of the Player class):
p = Player()
You can access the properties all you like:
print(p.health)
Perhaps you need some kind of "main loop" or controller class that has access to players and enemies and can set their properties accordingly:
class MainLoop(object):
def __init__(self):
self.player_1 = Player()
self.enemy_1 = Enemy()
def run(self):
if fight(): # fight() is some function of your game
self.player_1.health -= 10
self.enemy_1.health -= 20

Categories