I'm using Livewires and pygame and one of my objects in the game that gives you extra lives is being mistaken as an asteroid object, and when the extra lives objects collides with the player it returns the 'Extra lives object has no attribute handle_caught' error message, so can I please have some help.
class Extralives(games.Sprite):
global lives
image = games.load_image('lives.png', transparent = True)
speed = 2
def __init__(self,x,y = 10):
""" Initialize a asteroid object. """
super(Extralives, self).__init__(image = Extralives.image,
x = x, y = y,
dy = Extralives.speed)
def update(self):
""" Check if bottom edge has reached screen bottom. """
if self.bottom>games.screen.height:
self.destroy()
self.add_extralives
def add_extralives(self):
lives+=1
The asteroid class:
class Asteroid(games.Sprite):
global lives
global score
"""
A asteroid which falls through space.
"""
image = games.load_image("asteroid_med.bmp")
speed = 1.7
def __init__(self, x,image, y = 10):
""" Initialize a asteroid object. """
super(Asteroid, self).__init__(image = image,
x = x, y = y,
dy = Asteroid.speed)
def update(self):
""" Check if bottom edge has reached screen bottom. """
if self.bottom>games.screen.height:
self.destroy()
score.value+=10
def handle_caught(self):
if lives.value>0:
lives.value-=1
self.destroy_asteroid()
if lives.value <= 0:
self.destroy_asteroid()
self.end_game()
def destroy_asteroid(self):
self.destroy()
part of the player class which handles the collisions:
def update(self):
""" uses A and D keys to move the ship """
if games.keyboard.is_pressed(games.K_a):
self.x-=4
if games.keyboard.is_pressed(games.K_d):
self.x+=4
if self.left < 0:
self.left = 0
if self.right > games.screen.width:
self.right = games.screen.width
self.check_collison()
def ship_destroy(self):
self.destroy()
def check_collison(self):
""" Check if catch pizzas. """
global lives
for asteroid in self.overlapping_sprites:
asteroid.handle_caught()
if lives.value <=0:
self.ship_destroy()
for extralives in self.overlapping_sprites:
extralives.add_extralives()
Here is your problem:
for asteroid in self.overlapping_sprites:
asteroid.handle_caught()
if lives.value <=0:
self.ship_destroy()
The fact that you call your loop variable asteroid does not mean that it's magically only going to ever be an asteroid. Not if you have other kinds of objects you can collide with! overlapping_sprites is all overlapping sprites, not just asteroids. At some point asteroid is an ExtraLives object. When you try to call handle_caught() on it, this obviously fails because ExtraLives doesn't have a handle_caught() method.
The simplest solution here is to rename add_extralives to handle_caught on your ExtraLives class. After all, you're doing the same thing: handling the situation where you collide with (or "catch") the object, it's just a different kind of object so the result needs to be different, which you specify by providing different code. Being able to implement entirely different kinds of behavior by calling the same methods (called "polymorphism") is kinda the whole point of object-oriented programming.
The following loop has a similar problem, in that you're calling add_extralives() on objects that might not be of type ExtraLives. Fortunately you can remove this code since you're already handling this situation by renaming add_extralives to handle_caught.
for extralives in self.overlapping_sprites:
extralives.add_extralives()
Related
I can make no sense of this problem. I have a Player object and some Enemy objects that both inherit from the Actor class. Both Player and Enemy have shoot(self) methods that makes them shoot a bullet. This bullet is supposed to be added to their respective list of projectiles, but when the program calls self.projectiles.append(Projectile()) for an enemy, it adds it to the player's list of projectiles.
I've run the program where the only Actor shooting any bullets was the enemies and I watch as len(player.projectiles) returns greater and greater values, even though it should not be growing. Any help is appreciated. This block runs every time the program updates, it goes through the Game object's list of enemies and updates each one respectively:
for enemy in self.enemies:
enemy.update(self.player)
Here's the Enemy class:
class Enemy(Actor):
def shoot(self):
image = pygame.transform.rotate(ProjectileImage, self.angle)
self.projectiles.append(Projectile(self.getCenter()[0] - 6, self.getCenter()[1] - 16, 12, 32, image, 5, self.angle, True))
shootingSound.play()
def tryToShoot(self):
if self.attackCoolDown >= 30:
self.attackCoolDown = 0
self.shoot()
def update(self, player):
self.pointTowards(player.x, player.y)
Actor.update(self)
self.tryToShoot()
The Actor class initializes projectiles:
class Actor(Entity):
projectiles = []
Your posted code shows projectiles as a class attribute, not an instance attribute. Thus, all Actors share a single projectiles list. To make separate lists, initialize the attribute inside the __init__ method.
When you add projectiles as a class varable,both Enemy and Player inheret the same list object .
Also,when you ref something using self (and it can't be found in the instance dictionary) , it defaults to the class varable, that is why your player's and enemy's lenths are both growing:they share the same list object.
To fix this,put the projectiles=[] line into the __init__ method:
Change:
class Actor(Entity):
projectiles = []
To:
class Actor(Entity):
def __init__(self):
self.projectiles = []
To fix the bug.
PS: You don't seem to have a update method in the Actor class.
And if you do, using super().update() will be more Pythonic than Actor.update(self).
I'm currently coding in some assignment.
I create a class named Ship and created some methods to make the object class ship be able to move.
class Ship():
def __init__(self):
self.center.x = 400
self.center.y = 300
self.velocity.dx = .25
self.velocity.dy = .25
def advance_down(self):
self.center.y = self.center.y - self.velocity.dy
This is my code
def check_keys(self):
"""
This function checks for keys that are being held down.
You will need to put your own method calls in here.
"""
if arcade.key.DOWN in self.held_keys:
self.ship1.advance_down()
advance down just changes the position
def on_key_press(self, key: int, modifiers: int):
"""
Puts the current key in the set of keys that are being held.
You will need to add things here to handle firing the bullet.
"""
if key == arcade.key.DOWN:
self.held_keys.add(key)
This adds the current key being pressed to the set of self.held_keys.
I'm calling an update of the positon.
def update(self, delta_time):
"""
Update each object in the game.
:param delta_time: tells us how much time has actually elapsed
""
self.ship1.advance_down()
when I run the code I'm able to display everything just fine but it's not doing anything once I press my keys.
Any ideas why?
Not sure I am even asking the question correctly, but I will explain. I have created a class, and I have created instances in that class, the problem is I want to be able to use an instance which is chosen by the user via input. Let's say I would like to create a chess game. I want to be able to move a piece forward (possibly backward), left, and right. Suppose I have already created a method inside the class to move the piece and each piece has a name, x-coord, and y-coord. This is the issue I get
class Piece(object):
def __init__(self, x , y):
self.x = x
self.y = y
def move(self, amount, direction):
if direction == 'right':
self.x = self.x + amount
if direction == 'left':
self.x = self.x - amount
if direction == 'up':
self.y = self.y + amount
if direction == 'down':
self.y = self.y - amount
rook = Piece(0,0)
piece = input('What piece would you like to move?')
#Assume that the user types rook after this prompt
piece.move(4,'right')
Now I get an error because the input which is assigned to the variable piece is a string, and when I need rook.move, I am actually getting 'rook'.move
I understand variables can be changed using int(var), float(var), str(var), but how would one go about changing a variable to a class variable.
A way would be to have a dictionary that maps the piece names to your classes.
So
pieces = {
'Queen': Piece(0,0),
'Tower': Piece(0,0),
'Rook' Piece(0,0)
}
inp = input('Which piece would you like to move?')
if inp in pieces:
pieces[inp]().move(4, right)
else:
print('No such piece: %s - Valid pieces: %s' % (inp, pieces.keys()))
I am currently trying to implement an MVC pattern using Python and Pygame, but I can't figure out how to properly handle animations. Let's say we have a model object that can attack:
class ModelObject:
def attack():
if not self.attacking:
self.attacking = True
# Then compute some stuff
def isattacking():
return self.attacking
And a view object that renders the model object by displaying an attack animation:
class ViewObject(pygame.Sprite):
attacking_ressource = [] # List of surfaces for animation
default_ressource = None
def update():
# Detect the model object is attacking
if self.model.isattacking():
# Create animation if needed
if self.attacking_animation is None:
self.attacking_animation = iter(self.attacking_ressource)
# Set image
self.image = next(self.attacking_animation, self.default_ressource)
else:
# Reset animation
self.attacking_animation = None
self.image = self.default_ressource
The question is, how do the model know that it's no longer attacking?
The view could notify the model when the animation is over, but it guess it's not how the MVC pattern is supposed to work. Or, an animation counter could be set in the model, but it doesn't seem right either.
I think you got it the wrong way round.
The attack should not last as long as the animation, but the animation should last as long as the attack.
So the ViewObject is fine as it already asks the model if it is still attacking.
As for the ModelObject, it's up to you how long the attack should last and how to keep track of time. You could for example call pygame.time.get_ticks() to get the number of millisconds since you started your game once in attack, and then periodically check it again, like:
class ModelObject:
def attack():
if not self.attacking:
self.attacking = True
self.started = pygame.time.get_ticks()
# Then compute some stuff
def isattacking():
return self.attacking
def update():
# attack lasts 1000ms
self.attacking &= pygame.time.get_ticks() - self.started < 1000
I'm having another problem with the game I'm making, I want the Asteroids sprite to be randomized every time I new asteroid is created by the spawner class, but I keep getting this error 'non-default argument follows default argument', and I'm pretty much stumped on what to do, The actual randomized image is stored inside the spawner and is then imported to the Asteroid class. Any help would be greatly appreciated, the images list is a global variable.
images = [games.load_image("asteroid_small.bmp"),
games.load_image("asteroid_med.bmp"),
games.load_image("asteroid_big.bmp")]
def check_drop(self):
""" Decrease countdown or drop asteroid and reset countdown. """
if self.time_til_drop > 0:
self.time_til_drop -= 0.7
else:
asteroid_size = random.choice(images)
new_asteroid = Asteroid(x = self.x,image = asteroid_size)
games.screen.add(new_asteroid)
And then this is the part of the asteroid class that the randomized image will be stored in
def __init__(self, x, y = 10,image):
""" Initialize a asteroid object. """
super(Asteroid, self).__init__(image = image,
x = x, y = y,
dy = Asteroid.speed)
Your problem isn't with how you instantiate the asteroid, it is how you define it:
def __init__(self, x, y = 10,image):
If you look, image is last, after y, which has a default argument. In Python you cannot do such things. You have two options:
def __init__(self, x, y = 10, image = None):
# default the argument to some sentinel value
# Test for sentinel and the reassign if not matched.
image = image if image else random.choice(images)
or
def __init__(self, x, image, y = 10):
# re-order your arguments.
# Also note that image, x, y might be a better order
# (#see comment by Micael0x2a)