Issues with mask multiple of the same identical mob in pygame - python

as discussed in the title I am having issues with masking identical images.
#initalising the masks
Invader1= pygame.image.load('Space_invaders_character_1_1.png').convert_alpha()
Invader1= pygame.transform.scale(Invader11, (40,30))
Invader1_mask = pygame.mask.from_surface(Invader11)
Invader1_mask= Invader11_mask.scale((70,40))
Invader2= pygame.image.load('Space_invaders_character_2_1.png').convert_alpha()
Invader2= pygame.transform.scale(Invader21, (40,30))
Invader2_mask = pygame.mask.from_surface(Invader21)
Invader2_mask= Invader11_mask.scale((70,40))
Invader3= pygame.image.load('Space_invaders_character_3_1.png').convert_alpha()
Invader3= pygame.transform.scale(Invader31, (40,30))
Invader3_mask = pygame.mask.from_surface(Invader31)
Invader3_mask= Invader11_mask.scale((70,40))
#drawing characters
def drawEnemies (invX,invY):
for num in range (1,11):
invX = invX + 50
gameDisplay.blit(Invader32, (invX,invY))
gameDisplay.blit(Invader32, (invX,invY-50))
gameDisplay.blit(Invader22, (invX,invY-100))
gameDisplay.blit(Invader22, (invX,invY-150))
gameDisplay.blit(Invader12, (invX, invY -200))
while lives > 0:
offset = (bulletX -invX, bulletY - invY)
result = Invader11_mask.overlap(bullet_mask, offset)
Of course this isn't all my code, however, I hope you see what I am attempting to do. In essence I am attempting to loop to create a specific Invader (yes from space invaders), however, the masks are either not being created with the other invaders or aren't moving. Can someone please help me?
Thanks.

The meaningful answer to your problem is to stop what your doing right now and start using the Sprite and Group classes together with the collide_mask function.
You don't want to create several global variables for each thingy in your game. You want instances of classes (you usually use Sprite), and add them to a list (usually a Group).
So, create a class for your invaders that inherits from Sprite and give them a mask attribue, something like this:
class Invader(pygame.spriteSprite):
def __init__(self, image, pos):
super().__init__()
self.image = image
self.rect = image.get_rect(topleft=pos)
self.mask = pygame.mask.from_surface(image)
def update(self):
pass # handle movement
Create a Group for your bullets and one for your invaders, then you can check the collision with:
pygame.sprite.groupcollide(bullets, invaders, True, True, pygame.sprite.collide_mask)

Related

Image not moving pygame

When I try to fire my knife in my game, it doesn't work. I am not very experienced in pygame. This is my first game that I'm making. It worked the first time, but now I try to call a new knife in a function and it doesn't work. I have tried a lot but it won't work. Here's my is my code:
# first I do this
realknifeImg = pygame.image.load('realknife.png')
Realknife_state = "ready"
# then this
def fire_realknife(x, y):
global Realknife_state
Realknife_state = "fire"
screen.blit(realknifeImg, (x, y))
# and finally this
def outro():
RealknifeX = 750
RealknifeY = 400
fire_realknife(RealknifeX, RealknifeY)
if Realknife_state == "fire":
fire_realknife(RealknifeX, RealknifeY)
RealknifeX += 1
The position of the knife is continuously initialized in the outro. Set to position before the function and use the global statement to change the position in the function:
RealknifeX = 750
RealknifeY = 400
def outro():
global RealknifeX, RealknifeY
fire_realknife(RealknifeX, RealknifeY)
if Realknife_state == "fire":
RealknifeX += 1
You need to use a rect for this. It is the standard way of moving
images in pygame.
Instead of having all these variables you can just say:
r_knife_rect = realKnifeImg.get_rect()
r_knife_rect.x = 750
r_knife_rect.y = 400
Then whenever you want to move it call:
r_knife_rect.x += 1
Pygame wont like you using a tuple of (x,y) in your blit, it likes
rect style objects instead. To blit just say:
screen.blit(realKnifeImg, real_knife_rect)
Edit:
Using this also means your knife has a premade hitbox.

Pygame/Python: Can't create deepcopy of surfaces for room loading system (tl;dr at bottom)

tl;dr at the bottom. Most of this post is to give context to my problem. If that isn't needed, my main question is down below.
I am currently working on a pygame project (action adventure game) and I currently am working on stuff relating to object persistence and room transitioning. Basically, I want to create a system where I have a dictionary that contains all of the information about a particular room including the images it uses and the objects that are in that room.
My game has a dictionary that contains lists of the current objects that are updating and doing stuff in my game. Let's call this instances. Anytime I add an object to instances, it will appear in game. With that in mind, lets consider how loading the objects in a particular room actually works.
The way my system works is that I have a variable that is called room which contains a string that represents what room I am currently in. I have another dictionary that contains all of the objects within a room. Lets call this room_dict. room_dict would have the keys "objects":[obj1,obj2]. So based on the current value of room, it can access certain objects (i.e,room_dict[room]["objects"] would return a list of all of the objects in the current room).
Now that I've explained the basics of how it works, I have a method that actually knows when I have triggered a room transition (or rather, when the value of room is changed). When this happens, all of the objects existing in the room (that I was just in) are cleared from the instances dictionary. All of the objects from room_dict[room]["objects"] are added to instances so that they now appear in the game. Makes sense so far, right?
The main problem with this is that when objects in the instances dictionary (objects that are currently loaded) are updating, the objects that are in room_dict[room]["objects"] are also updated as well. This means that if I change the position of an enemy in one room and then leave the room and return, the object will be created at that position instead of the original position. So I tried doing instances[list_of_enemies].append(copy.copy(enemy_object)) to add a copy of the object as well. This still didn't work though, and when I tried doing a copy.deepcopy(), the interpreter said that it was unable to serialize the object because one of its attibutes was a pygame.Surface object.
So in other words my main issue is that I want to make a copy of an object that contains a pygame.Surface as its attribute that doesn't reference the original object at all. How would I go about making a deepcopy with an object that has a pygame.Surface type attribute?
tl;dr: I want to make a copy of an object that has an attribute that is a pygame.Surface object but copy.deepcopy() doesn't work. Is there any other way to copy without referencing the original object?
EDIT: Forgot to mention that the project is rather hefty, so it would be quite difficult to give code for context. I personally don't think it is needed, but I thought I'd put this out anyways. Thanks everyone!
One way to solve this is to create your objects from your json file everytime you need a new copy instead of creating the objects beforehand.
Or you could change your copy method: implement __deepcopy__ and/or __copy__ and copy the attributes of your objects without the image attribute, maybe just create a new instance.
Simple example:
import pygame
from copy import deepcopy
from itertools import cycle
# an implementation of something in our game
class Cloud(pygame.sprite.Sprite):
def __init__(self, pos, speed):
super().__init__()
self.pos = pos
self.speed = speed
self.image = pygame.Surface((50, 20))
self.image.set_colorkey((11, 12, 13))
self.image.fill((11, 12, 13))
pygame.draw.ellipse(self.image, 'white', self.image.get_rect())
self.rect = self.image.get_rect(topleft=self.pos)
def update(self):
super().update()
self.rect.move_ip(self.speed, 0)
if not pygame.display.get_surface().get_rect().colliderect(self.rect):
self.rect.right = 0
def __deepcopy__(self, memo):
# just create a new instance
return Cloud(self.pos, self.speed)
# the definition of our game world
game_data = {
'WORLD_A': {
'color': 'lightblue',
'objects': pygame.sprite.Group(Cloud((50, 50), 1))
},
'WORLD_B': {
'color': 'red',
'objects': pygame.sprite.Group(Cloud((100, 100), 2), Cloud((80, 30), 3))
},
}
screen = pygame.display.set_mode((300, 300))
clock = pygame.time.Clock()
keys = cycle(game_data.keys())
# happy deepcopying
current = deepcopy(game_data[next(keys)])
while True:
for e in pygame.event.get():
if e.type == pygame.QUIT:
quit()
if e.type == pygame.KEYDOWN:
# happy deepcopying
current = deepcopy(game_data[next(keys)])
screen.fill(current['color'])
current['objects'].update()
current['objects'].draw(screen)
pygame.display.flip()
clock.tick(30)
Another solution is to lazy load the images and look them up only when needed so you don't need to copy them. Here's a simple example:
... see example above ...
# load/create all images once and store them in a dict
def create_cloud_image():
image = pygame.Surface((50, 20))
image.set_colorkey((11, 12, 13))
image.fill((11, 12, 13))
pygame.draw.ellipse(image, 'white', image.get_rect())
return image
images = {
'cloud': create_cloud_image()
}
# a simple sprite that lazy loads its image
class CopyableActor(pygame.sprite.Sprite):
def __init__(self, image_key, pos):
super().__init__()
self.pos = pos
self.image_key = image_key
def init_image(self):
self.image = images['cloud']
self.rect = self.image.get_rect(topleft=self.pos)
def update(self):
if not hasattr(self, 'image'):
self.init_image()
# an implementation of something in our game
class Cloud(CopyableActor):
def __init__(self, pos, speed):
super().__init__('cloud', pos)
self.speed = speed
def update(self):
super().update()
self.rect.move_ip(self.speed, 0)
if not pygame.display.get_surface().get_rect().colliderect(self.rect):
self.rect.right = 0
... see example above ...

Procedural Generation Using Classes Pygame

I'm in the process of creating a basic game in pygame at the moment, and one part of that is the procedural generation of new areas as you go off the screen. As a test, I'm looking to generate an object once per area by defining its variables, and then save that area's object within the class for if you come back to it later. Here's what I have at the moment:
#area_gen is set to "true" if you move to a new area
#swidth and sheight are set to the size of the screen
#x_area and y_area are defined as you change areas, acting as sector coordinates
#Red is defined in globals
class areas:
def __init__(self, coords):
self.coordinates = coords
self.generated = False
def gen_objects(self):
if not self.generated:
self.objs = []
obj_type = "test object"
center_x = random.randrange(105, swidth-25)
center_y = random.randrange(25, swidth - 175)
self.objs.append([obj_type, center_x, center_y])
self.generated = True
#Within The Game Loop
if area_gen == "true":
coords = str(str(x_area) + " " + str(y_area))
area = areas(coords)
area.gen_objects()
for thing in area.objs:
if thing[0] == "test object":
pygame.draw.rect(screen, Red, (thing[1], thing[2], 250, 250))
#Bottom of the Game Loop
area_gen = "false"
What I thought the self.generated variable would do was stop the new object generation if one already existed, but that doesn't seem to be working. The square still generates at a new location even if the area has already been visited. My knowledge on classes is relatively limited, so I'm a bit stuck as to where to go from here.
Pass area_gen into the constructor for the areas class, and set self.generated = area_gen. Does this work? I can't see enough of your code to know, to be honest.

collision detection error:AttributeError: type object 'Ship_laser' has no attribute 'sprites'

I am trying to write a game using a model, but i get the error:
"File "C:\Python27\lib\site-packages\pygame\sprite.py", line 1514, in spritecollide
for s in group.sprites():AttributeError: type object 'Ship_laser' has no attribute 'sprites'"
when running the script.If i dont call my collision function the script runs, so in that function is the mistake,but i dont understand where's the mistake.Here is the code of the function:
def collisions():
for enemy_ship in classes.Enemy_ship.List:
enemy_laser = pygame.sprite.spritecollide(enemy_ship, classes.Ship_laser, True)
if len(enemy_laser) > 0:
for hit in enemy_laser:
enemy_ship.health -= 25
for laser in classes.Ship_laser.List:
if pygame.sprite.spritecollide(laser, enemy_ship, True):
laser.destroy()
If it's needed i am posting the the Ship_laser class from my classes.py file
class Ship_laser(pygame.sprite.Sprite):
allsprites = pygame.sprite.Group()
def __init__(self, x, y, image_string):
pygame.sprite.Sprite.__init__(self)
Ship_laser.allsprites.add(self)
self.image = pygame.image.load(image_string)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.velx, self.vely = 0, 0
#staticmethod
def laser_movement(SCREENWIDTH, SCREENHEIGHT):
for laser in Ship_laser.List:
laser.rect.x += laser.velx
laser.rect.y += laser.vely
def destroy(self):
Ship_laser.List.remove(self)
del self
Considering that the Laser.ship class it's inheriting the pygame.sprite.Sprite class i dont understand the error.This is my first game.Please help
I can't say I am an expert with pygame, nor do I fully understand how you have built your classes. When I hear ship laser, I think of a single instance that belongs to a ship, yet in your class you define "allsprites" which is mutable type defined to a class instance (shared by all members of the class).
But given this, allsprites will be the same mutable object shared among ever ship_laser, almost like the yellowpages of your class. When you call pygame.sprite.spritecollide (based on pygame docs) it is looking for the sprite.Group and hence, you should pass it the group lookup (the yellowpages aka allsprites) rather than the reference to the class. That should sort your issues. So, here is your code change:
enemy_laser = pygame.sprite.spritecollide(enemy_ship, classes.Ship_laser, True)
to
enemy_laser = pygame.sprite.spritecollide(enemy_ship, classes.Ship_laser.allsprites, True)

Is this an optimal way of updating sprites in pygame?

Cutting through all the unrelated parts this is how I implemented it:
done=False
while(done==False)
#here goes some code for listening to the events
if (some condition fulfilled) then
screen.blit(background, [0,0])
screen.blit(some_sprite,[x,y])
elif (more conditions)
do something else
else
do even more stuff
pygame.display.flip()
Without the background update within this conditional statement this sprite doesn't get deleted, of course, os I get multiple copies on the screen. I have a strong suspicion this is by far not the optimal way of handling the situation, because blitting the image that doesn't change every time I need to do something else seems like a waste of resources.
I would appreciate any suggestions
Here's what I would recommend, based on personal experience. I built a tile-based game and over-engineered it a bit. My final solution looked a bit like this:
class Graphic(object):
def __init__(*some_args):
self.owner = which_object_owns_this_graphic
self.priority = some_int
# if more than one graphic are stacked on each other, which one to display?
self.surface = surface_to_draw_on
self.graphic = sprite_to_draw
#property
def x(self): return self.owner.x
#property
def y(self): return self.owner.y
def draw(self):
self.surface.blit(self.graphic, (self.x, self.y))
class Tile(object):
def __init__(*some_args):
self.x = x
self.y = y
self.graphic = some_default_Graphic_with_priority_minusone
self.contains = [self]
# list of objects that live here right now
#property
def topmost(self):
"""returns the graphic of the object with highest priority that is contained here"""
global_update_list = []
# anytime a tile is moved into or out of, place it here
Then in my event loop:
for tile in global_update_list:
tile.topmost.draw()
global_update_list = []
That prevented me from having to redraw the screen every time something moved, and I could just redraw the tile it moved out of and the tile it moved into.

Categories

Resources