always point to other attribute's function - python

I´m working on improving my pythonic coding and want to know if there is a way to always point to an attribute's function in the same class (including "updating" its values)
the simplified code looks like this:
class xyz:
def __init__(self, widht, height):
self.image = pygame.Surface((width, height))
self.rect = self.image.get_rect()
obj = xyz(42, 42)
obj.image = pygame.Surface((some_other_width, some_other_height))
# obj.rect would still be of size 42x42 and not the new values
since the image is varying in size, changing very often and my class has to have a attribute named rect with the size of the curent image is there maybe a magic method that could the work of the update_rect() function (tried playing around with self__getattribute()__ a bit but that didn´t work)
def update_rect(self):
self.rect.width, self.rect.height = self.image.get_width(), self.image.get_height()

As jonrsharpe suggested in a comment, you can use a property. But make image a property, not rect, like this:
import pygame
class xyz(pygame.sprite.Sprite):
def __init__(self, width, height):
super().__init__()
self._image = pygame.Surface((width, height))
self.rect = self.image.get_rect()
#property
def image(self):
return self._image
#image.setter
def image(self, value):
self._image = value
self.rect = self._image.get_rect(topleft=self.rect.topleft)
obj = xyz(42, 42)
obj.rect.topleft = (300, 300)
print(obj.rect.width) # prints 42
print(obj.rect.x) # prints 300
obj.image = pygame.Surface((100, 100))
print(obj.rect.width) # prints 100
print(obj.rect.x) # prints 300
This way, you can also keep the position that is usually stored in the rect attribute.

Related

Add_internal Attribut Error with sprites and Masks

I am trying to make a Towerdefense Game
to check if the Enemy is in the hitcircle of the tower I want to use the function pygame.sprite.collide_mask to find out if they are touching.
This is the output:
AttributeError: 'pygame.mask.Mask' object has no attribute 'add_internal'
How can I make it work? Am I even remotely in the right direction of detecting the collision for the game?
I hope this code can work as much as it needs
import pygame
pygame.init()
class Tower(pygame.sprite.Sprite):
def __init__(self, window):
self.collision_circle =
pygame.image.load('assets/Towers&Projectiles/Collision_Circle/collision_circle.png')
self.tower_mask = pygame.mask.from_surface(self.collision_circle)
class Enemy(pygame.sprite.Sprite):
def __init__(self, window):
self.enemy_mask_image = pygame.image.load('Assets/Enemy/WALK_000.png')
self.enemy_mask = pygame.mask.from_surface(self.enemy_mask_image)
pygame.display.set_caption("Tower Defense")
width = 1200
height = 840
display_surface = pygame.display.set_mode((width, height))
my_enemy = Enemy(display_surface)
my_tower = Tower(display_surface)
while True:
enemy_sprite = pygame.sprite.GroupSingle(my_enemy.enemy_mask)
tower_sprite = pygame.sprite.GroupSingle(my_tower.tower_mask)
if pygame.sprite.spritecollide(enemy_sprite.sprite,tower_sprite,False,pygame.sprite.collide_mask()):
print("collision")
collide_mask use the .rect and .mask object of the Spirte. Therefore the name of the Mask attribute must be mask, but not tower_mask or enemy_mask.
In addition, the objects must have an attribute named rect, which is a pygame.Rect object with the position of the sprite.
Also the super calls a missing from your class constructors. e.g.:
class TowerCircle(pygame.sprite.Sprite):
def __init__(self, centerx, centery):
super().__init__()
self.image = pygame.image.load('assets/Towers&Projectiles/Collision_Circle/collision_circle.png')
self.mask = pygame.mask.from_surface(self.collision_circle)
self.rect = self.image.get_rect(center = (centerx, centery))
class Enemy(pygame.sprite.Sprite):
def __init__(self, x, y):
super().__init__()
self.image = pygame.image.load('Assets/Enemy/WALK_000.png')
self.mask = pygame.mask.from_surface(self.enemy_mask_image)
self.rect = self.image.get_rect(topleft = (x, y))
See Pygame mask collision and Make a line as a sprite with its own collision in Pygame.

How do I access a variable in a class that was defined before in another class?

I am trying to make a python library:
There is a class (game) in it is a function that defines a variable (display)
Then there is another class (char) in main, I want to access display in char
How do I do it?
In the past I have tried: self.display, global display, and game.display
class game():
def __init__(self, disp, width, height):
self.display = disp # VARIABLE I WANT TO ACCESS
self.height = height
self.width = width
class sprite():
def __init__(self, size, position, image):
self.image = image
self.size = size
self.rect = self.image.get_rect()
self.rect.x = position[0]
self.rect.y = position[1]
self.x = position[0]
self.y = position[1]
self.collisionDirection = 5
self.hasCollided = False
self.mask = pygame.mask.from_surface(self.image)
self.velocity = 0
def render(self):
self.rect.x = self.x
self.rect.y = self.y
self.mask = pygame.mask.from_surface(self.image)
display.blit(self.image, (self.x, self.y)) # WHERE I WANT TO ACCESS IT
I keep getting AttributeError what can I do?
You can pass the Game instance to another class. For example
# instantiate the game
g = game()
# create an instance of char
c = char(game)
assuming the __init__ of char() looks something like this:
class char():
def __init__(self, game):
# save a reference to the game object as an attribute
self.game = game
# now you can access the game's attributes
print(self.game.display)
Allow that the below example is contrived, and not likely the way you would design pen/paper, but it shows that there are a couple different ways that you can accomplish what you ask.
class Pen:
def __init__(self, thickness, color):
self.thickness = thickness
self.color = color
class Paper:
def __init__(self, pen: Pen):
self.pen = pen
def draw(self):
pen_color = self.pen.color
return pen_color
def draw2(self, pen: Pen):
pen_color = pen.color
return pen_color
red_pin = Pen(2, 'Red')
blue_pin = Pen(1, 'Blue')
paper = Paper(red_pin)
print(paper.draw()) # prints Red
print(paper.draw2(blue_pin)) # prints Blue

pygame: replacing image of an item/enemy

I'm trying to write a program where the image for the 'enemy' I have changes once its health goes to 0. I have different classes for each element, but I have this under this class.
class Enemy(pygame.sprite.Sprite):
def __init__(self, gs=None):
pygame.sprite.Sprite.__init__(self)
...initialization stuff...
self.image = pygame.image.load("enemy.png") #enemy image
self.rect = self.image.get_rectangle()
self.hp = 40 #initial health
def damage(self):
if self.rect.colliderect(self.user.rect): #collision/how the health goes down
self.hp = self.hp - 5
Now here's the part I'm curious about, I want to load a new image that replaces the old image I have for this enemy. Can I do/add (in the damage function)
if(self.hp == 0):
self.image = pygame.image.load("dead.png")
Will this replace it? Or just load another image on top of it?
Let me know what I'm missing, thank you!
You should load all your images into your init method when you create the object and then you can do the assigning/changing later using lists. Here is an example:
class Enemy(pygame.sprite.Sprite):
def __init__(self, gs=None):
pygame.sprite.Sprite.__init__(self)
self.images = [pygame.image.load("enemy.png"), pygame.image.load("dead.png")]
self.current_image = self.images[0]
self.rect = self.current_image.get_rectangle()
Then later you can do:
if(self.hp == 0):
self.current_image = self.images[1]
This will in fact, as per your concern, replace the current image instead of just drawing over it. Hope that helps.

Python - how to copy object (so it would not reference old object) without using deepcopy?

I have this code:
import pygame
import block as b
import constants as c
import copy
def walls():
walls_list = []
wall_proto = b.Wall(c.WHITE, 40, 40, 20, 80)
wall = copy.copy(wall_proto)
wall.rect_x = 50
print wall_proto.rect_x
print wall.rect_x
walls()
prints:
50
50
But I want to print it
40
50
I want to create various 'walls' in my game. And those walls maybe have lots of similar data, but some different too. So I want to have some wall object that could be used to copy from and then set different properties on different walls. If I use simple copy, then if I change anything in copied object, all other objects will change that value too. I can use deepcopy, but it is very slow. Is there workaround/better solution than deepcopy?
Update
As requested Wall class:
class Wall(pygame.sprite.Sprite):
def __init__(self, color, x, y, width, height):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface([width, height])
self.image.fill(color)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
#to get/set values
self._width = width
self._height = height
#property
def rect_x(self):
return self.rect.x
#property
def rect_y(self):
return self.rect.y
#property
def width(self):
return self._width
#property
def height(self):
return self._height
#rect_x.setter
def rect_x(self, value):
self.rect.x = value
#rect_y.setter
def rect_x(self, value):
self.rect.y = value
#width.setter
def width(self, value):
self._width = value
#height.setter
def height(self, value):
self._height = value
I would do this:
change all arguments to keyword arguments,
add a wall= keyword arg to the Wall constructor.
If wall is set, copy the values into the new instance, then set them according to the other parameters. Like so:
class Wall(pygame.sprite.Sprite):
def __init__(self, color=None, x=None, y=None, width=None, height=None, wall=None):
pygame.sprite.Sprite.__init__(self)
# check parameters are valid
assert any([color and x and y and width, wall]), "invalid arguments"
# assign parameters from given wall or parameters
x = x or wall.rect_x
y = y or wall.rect_y
color = color or wall.color
height = height or wall.height
width = width or wall.width
...
Then you can write code like:
wall1 = Wall(c.WHITE, 40, 40, 20, 80)
wall2 = Wall(color=c.BLUE, wall=wall1)
if it's a simple list (without any mutable objects), you can use: List[:] or Dict.copy()
Ultimately this just isn't how you should be doing this. Consider instead making a classmethod that will define whole ranges of walls at once. Something like...
class Wall(pygame.sprite.Sprite):
def __init__(self, color, x, y, width, height):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface([width, height])
self.image.fill(color)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
# since your properties are only getters and setters, don't use them
self.width = width
self.height = height
#classmethod
def make_walls(cls, color, coords):
"""Makes a grouping of walls of a specified color, given
a list of (x, y, width, height) tuples"""
list_of_walls = []
for details in coords:
x, y, width, height = details
wall = cls(color, x, y, width, height)
list_of_walls.append(wall)
return list_of_walls
# or MUCH less clear:
# return [cls(color, *details) for details in coords)]
Then you can make any walls you like by doing:
wall_locations = [
(1, 1, 1, 1),
(1, 2, 1, 1),
(1, 3, 1, 1),
(1, 4, 1, 1),
... ]
new_walls = b.Wall.make_walls(c.WHITE, wall_locations)

Trying to create a group of button sprites

Good day,
I have like 15 images I need to be buttons. I have buttons working with a Box() (Box - looks like this)
class Box(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((35, 30))
self.image = self.image.convert()
self.image.fill((255, 0, 0))
self.rect = self.image.get_rect()
self.rect.centerx = 25
self.rect.centery = 505
self.dx = 10
self.dy = 10
I am trying to make the buttons work with image sprites. So I attempted to copy the class style of the box and do the same for my Icons.. code looks like this...
class Icons(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("images/airbrushIC.gif").convert()
self.rect = self.image.get_rect()
self.rect.x = 25
self.rect.y = 550
the code in the main()
rect = image.get_rect()
rect.x = 25
rect.y = 550
ic1 = Icons((screen.get_rect().x, screen.get_rect().y))
screen.blit(ic1.image, ic1.rect)
pygame.display.update()
This code produces a positional (accepts 1 argument but 2 are there) error or an image is not referenced error (inside the Icon class).
I'm unsure if this is the right way to go about this anyways.. I know for sure that I need to load all the images (as sprites)... store them in an array... and then have my mouse check if it is clicking one of the items in the array using a for loop.
Thanks.
You are trying to pass an argument into Icons(), but your __init__() method takes no arguments. If you wanted to pass those onto the Sprite() constructor, then you probably wanted something like:
class Icons(pygame.sprite.Sprite):
def __init__(self, *args):
pygame.sprite.Sprite.__init__(self, *args)
...
This accepts any number of extra arguments (*args) using the star operator, then passes them into the sprite constructor.

Categories

Resources