PYGame - Changing bullets bug - python

First of all, my point is making 3 different bullets with different stats and different colors. Shoot them with Space key, selecting them with 1,2 and 3 keys. I'm not putting whole code here but if you need more info feel free to ask.
LaserDict = {
"redLaser": (RED_LASER,4,1), #Bad Velocity #Bad Firerate
"greenLaser": (GREEN_LASER,10,3), #Excellent Velocity #Good Firerate
"blueLaser": (BLUE_LASER,5,2) #Medium Velocity #Good Firerate
}
class Lasers():
def __init__(self,x,y,laserIndex):
self.x = x
self.y = y
self.laserNames = ["redLaser","greenLaser","blueLaser"]
self.laserIndex = laserIndex
self.laserImg, self.laserVel, self.laserCooldown = LaserDict[self.laserNames[self.laserIndex]]
def draw(self,window):
window.blit(self.laserImg,(self.x,self.y))
def move(self,p):
self.y += p
def offScreen(self,height):
return not (self.y>0 and self.y<=height) #if not in screen return True
class Player():
fpsCooldown = 30
def __init__(self,x,y,health,laserIndex):
blah blah...
self.cooldownReady = 0
self.lasers = []
self.laserIndex = laserIndex
def cooldown(self):
if self.cooldownReady >= self.fpsCooldown:
self.cooldownReady = 0
elif self.cooldownReady > 0:
self.cooldownReady += Lasers(self.x,self.y,self.laserIndex).laserCooldown
def shoot(self):
if self.cooldownReady == 0:
laser = Lasers(self.x+int(self.getWidth()/8) ,self.y, self.laserIndex)
self.lasers.append(laser)
self.cooldownReady = 1
def moveLasers(self):
self.cooldown()
for laser in self.lasers:
p = Lasers(self.x,self.y,self.laserIndex).laserVel
laser.move(-p)
if laser.offScreen(height):
self.lasers.remove(laser)
def main():
blah blah...
if keys[pygame.K_SPACE]:
player.shoot()
if keys[pygame.K_1]:
player.laserIndex = 0
if keys[pygame.K_2]:
player.laserIndex = 1
if keys[pygame.K_3]:
player.laserIndex = 2
Code is working fine with one bug. When you change your laser type by
pressing 1,2 or 3 it changes all of your Laser's stats exist in
window. For example if you shoot "Red Lasers" 5 times and if they are
not off the screen yet, and if you change your laser type to "GreenLasers" all of your "Red Lasers" remains red but moving just like
Green ones.

In moveLasers you calculate how much to move each laser with the variable p. This value p is calculated by creating a new Lasers with the current player.laserIndex, meaning that all lasers will move this much. You're better off changing Lasers.move to:
def move(self):
self.y += self.laserVel
rather than re-finding p everytime, which is only the laserVel for the current mode anyway

Create an object for your bullet, with its own stats.
Here, when you modify the bullet stats on the player, it affects all the bullets because you update your bullets inside the Player.
You could also keep a track of every bullet inside the player but it is overwhelming.
In you Bullet Object, just add it a direction, a speed and a color. The update every frame, just check if it reaches the end of the screen to erase it (to not lose memory).
Was this helpful?

Lasers.move() changed to:
def move(self):
self.y -= self.laserVel
def moveEnemyLasers(self,p):
self.y += p
Player.moveLasers() changed to:
def moveLasers(self):
self.cooldown()
for laser in self.lasers:
laser.move()
if laser.offScreen(height):
self.lasers.remove(laser)

Related

I want an if statement to add a rectangle to a list, when a condition is met, but it keeps adding them until the condition is false

I'm very new to pygame and python in general, and I'm trying to make a simple game. In the code, I made a class Enemy, and an instance of it enemy, here.
class Enemy:
def __init__(self):
self.list = []
self.size = 20
self.x = random.randint(0 , WIDTH - self.size)
self.y = random.randint(70 , HEIGHT - self.size)
self.new_enemy = pygame.Rect(self.x , self.y , self.size , self.size)
def add_enemy(self):
if score % 5 == 0 and score != 0:
self.list.append(self.new_enemy)
print(enemy.list)
#to see if it works
enemy = Enemy()
But when I run enemy.add_enemy(), while score == 5 (or 10, 15, etc), it adds not just one but tons of items to the list. Is there a way around this? help would be appreciated.
So i still have doubts about this, since i dont have more of the code.
But fundamentally what's happening here is that self.newenemy is being delared and created, then in add_enemy you run self.list.append(self.newenemy) which is essentially adding the same object into the list. so the list has n objects, but they are all the "same" as in not copy or duplicates, but the exact same. so if you were to "kill" list[0] enemy they all would die.
EDIT: Additionally, your randint will always generate the same values, so x and y will always be the same once the class gets created.
try changing your code to something like this. NOTE the return in add_enemy after the append is done.
class Enemy:
def __init__(self):
self.list = []
self.size = 20
def add_enemy(self):
if score % 5 == 0 and score != 0:
self.list.append(pygame.Rect(self.get_rand_x() , self.get_rand_y() , self.size , self.size))
return
def get_rand_x(self):
return random.randint(0 , WIDTH - self.size)
def get_rand_y(self):
return random.randint(70 , HEIGHT - self.size)
enemy = Enemy()
enemy.add_enemy()
print(enemy.list)

using a variable from a class in python

I think this question has been asked before but I have not found an answer suited to my problem. I basically have a class for different characters, which each have a cost. When creating a character, I want to take their cost away from the players score.
Here is an example of a class:
class Assassin(pygame.sprite.Sprite):
def __init__(self, x, y, row, column):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("assassin.png")
self.x = x
self.type = "assassin"
self.y = y
self.rect = self.image.get_rect(center=(self.x, self.y))
self.damage = 60
self.health = 40
self.speed = 2
self.move = False
self.cost = 4
self.row = row
self.column = column
And here is the code where I would want to use the variable:
if assassin.collidepoint(pygame.mouse.get_pos()) and mouseDown[0]:
for block in blockGroup:
if block.team1Taken == False and block.column ==1:
team1.add(Assassin(block.team1[0], block.team1[1], block.row, block.column))
block.team1Taken = True
score -= Assassin.__init__.cost #Example of what I think you would do
break
I hope I have explained this well enough to understand what I want.
You can't call score -= Assassin.__init__.cost in python.
The init method is the constructor of the Class and should be used to do so.
The value that you want is inside the object that you created, so you could call assassin.cost directly, assuming that assassin is the object.
So, you just need to change to:
if assassin.collidepoint(pygame.mouse.get_pos()) and mouseDown[0]:
for block in blockGroup:
if block.team1Taken == False and block.column ==1:
current_assassin = Assassin(block.team1[0], block.team1[1], block.row, block.column)
team1.add(current_assassin)
block.team1Taken = True
score -= current_assassin.cost
break
You will need to keep a reference to the Assassin instance you create and then access its cost attribute:
if assassin.collidepoint(pygame.mouse.get_pos()) and mouseDown[0]:
for block in blockGroup:
if block.team1Taken == False and block.column == 1:
new_assassin = Assassin(block.team1[0], block.team1[1],
block.row, block.column)
team1.add(new_assassin)
block.team1Taken = True
score -= new_assassin.cost
break

Python How to check if a target object is no longer the same instance or "valid"

OK in my 2d game, I have several 'cell' object instances (sprite objects) that have an empty self.target = [] attribute upon initialization.
They will then find a valid "plant" target object nearby and that will become the cells' "self.target."
The 'cell' will then navigate toward the plant to eventually collide with and eat it, and the plant is then set to ".kill()" and another is respawned at a random new coordinate. There are several plant instances (part of spr_plant_group) and several cells (belonging to spr_cell_group).
Each plant 'lives' for X game ticks (200 I think), and if not eaten by then, is .kill()-ed and a new one spawns at a random coordinate.
The problem is this: If a cell's target plant happens to despawn from age, or is eaten by something, the cell's self.target info is STILL pointing to the old "plant" object's data. This means the cell is chasing a phantom object, still apparently having a valid x and y coordinate.
Question: How do I tell the cell that it's target is dead, gone, invalid? If I can do this, I think it would fix the 'phantom' target object.
Pieces of relevant code:
class Agent(sprite.Sprite):
def __init__(self, sense_range, size, food, maxspeed):
sprite.Sprite.__init__(self)
self.x = randint(0,450)
self.y = randint(0,450)
self.ang = randint(0,359)
self.turn_rate = 2
self.dx = 0
self.dy = 0
self.speed = 1
self.maxspeed = maxspeed
self.food = int(food)
self.max_food = food*1.5
self.target = []
self.sense_range = sense_range
# Snip------This part below is supposed to find a new target, but it only
# works for the first one when the cell spawns, then I can't seem to
# get it to become 'empty' so that the "if self.target == []" can do its
# thing....
def seek_food(self):
if (self.target == []):
#find cell a target within "sense_range" distance (say 200 pixels)
dist = self.sense_range
targ = []
for t in spr_plant_group:
t_dist = abs(self.x - t.x)
if t_dist <= dist:
targ = t
dist = t_dist
self.target = targ
print ("Found target...",dist, self.target)
else:
#already have a target, so move cell toward target
dx = self.target.x - self.x
dy = self.target.y - self.y
rads = atan2(dy,dx)
rads %= 2*pi
degs = degrees(rads)
direction = degs - self.ang
if direction > 0:
self.ang = self.ang + self.turn_rate
elif direction < 0:
self.ang = self.ang - self.turn_rate
# Correct for angle being out of 0-360* range
if self.ang > 360:
self.ang -= 360
elif self.ang < 0:
self.ang += 360
#---This is just a piece of the Plant class for your reference
class Plant (sprite.Sprite):
def __init__(self):
sprite.Sprite.__init__(self)
self.x = randint(0,450)
self.y = randint(0,450)
self.age = 1 + randint(0,50)
For reference, below. Plants age increases until 200 ticks, then they're killed, and a new one respawns...
def update_plants():
for shrub in spr_plant_group:
shrub.age += 1
# Respawn a new plant in a different place
if shrub.age >= 200:
shrub.kill()
plant.append (Plant())
EDIT: Actually, it's a lot simpler. Pygame's Sprite class supports an alive method, so just do this:
def seek_food(self):
if (self.target == [] or not self.target.alive()):
# Find a new target that isn't dead...
else:
# Move to the (alive) target...
You can use some variant of the Observer design pattern. Extend your Plant class with an extra property which keeps track of any Cell instances that are targeting the plant. When a cell targets a plant, it will add itself to the plant's list. Before the plant dies, it will notify all of its cells.
New property in the Plant class:
class Plant (sprite.Sprite):
def __init__(self):
# ...
self.followers = []
# ...
Cells subscribe to the plant's list of followers:
def seek_food(self):
if (self.target == []):
# ...
self.target = targ
self.target.followers.append(self)
# ...
else:
# ...
The Plant class overrides its parent class' kill function so that it notifies its followers before dying:
(Here, the cell class is directly modifying each plant's target, but you could encapsulate the behaviour differently if you want)
def kill(self):
for follower in self.followers:
follower.target = []
self.followers = []
super().kill()

Change the speed of a single unchanging sprite object in pygame

I am doing a pygame program from a book where a chef drops pizzas and you have to catch them with a pan. The chef and pan sprites are only created once, while the pizza sprites obviously keep getting created. I am trying to make it so that as the score gets higher, the chef starts to move around faster (only moves in the x). I think I am running into trouble with class attributes vs instance attributes. I have been unable to find a way to access the score from the chef class, even though I tried making score a global variable or even just assigning it to a dummy global variable (This would allow me to make changes to the chefs speed within the update method). Alternatively, I tried accessing the chefs dx within the pan class since that is where the score is. I have also been unable to access that, even with the getattr method. Would greatly appreciate any suggestions on this one. Here is the code for the pan and chef class. I have commented out parts of stuff that I tried and didn't work.
from livewires import games, color
import random
games.init(screen_width = 640, screen_height = 480, fps = 50)
class Pan(games.Sprite):
"""
A pan controlled by player to catch falling pizzas.
"""
image = games.load_image("pan.bmp")
def __init__(self):
""" Initialize Pan object and create Text object for score. """
super(Pan, self).__init__(image = Pan.image,
x = games.mouse.x,
bottom = games.screen.height)
self.score = games.Text(value = 0, size = 25, color = color.black,
top = 5, right = games.screen.width - 10)
games.screen.add(self.score)
def update(self):
""" Move to mouse x position. """
self.x = games.mouse.x
if self.left < 0:
self.left = 0
if self.right > games.screen.width:
self.right = games.screen.width
self.check_catch()
def check_catch(self):
""" Check if catch pizzas. """
for pizza in self.overlapping_sprites:
self.score.value += 10
#increase the speed of the pizza
# if self.score.value % 100 == 0:
# Pizza.speed += 0.1
# print(Pizza.speed)
#increase the speed of the chef
# if self.score.value % 100 == 0:
# print(Chef.dx)
# print(x)
# y = int(x)*2
# print(y)
self.score.right = games.screen.width - 10
pizza.handle_caught()
class Chef(games.Sprite):
"""
A chef which moves left and right, dropping pizzas.
"""
image = games.load_image("chef.bmp")
speed = 2
def __init__(self, y = 55, odds_change = 200):
""" Initialize the Chef object. """
super(Chef, self).__init__(image = Chef.image,
x = games.screen.width / 2,
y = y,
dx = Chef.speed)
self.odds_change = odds_change
self.time_til_drop = 0
def update(self):
#x = getattr(Pan,"score")
#print(x)
""" Determine if direction needs to be reversed. """
if self.left < 0 or self.right > games.screen.width:
self.dx = -self.dx
elif random.randrange(self.odds_change) == 0:
self.dx = -self.dx
self.check_drop()
def check_drop(self):
""" Decrease countdown or drop pizza and reset countdown. """
if self.time_til_drop > 0:
self.time_til_drop -= 1
else:
new_pizza = Pizza(x = self.x)
games.screen.add(new_pizza)
# set buffer to approx 30% of pizza height, regardless of pizza speed
self.time_til_drop = int(new_pizza.height * 1.3 / Pizza.speed) + 1
def main():
""" Play the game. """
wall_image = games.load_image("wall.jpg", transparent = False)
games.screen.background = wall_image
the_chef = Chef()
games.screen.add(the_chef)
the_pan = Pan()
games.screen.add(the_pan)
games.mouse.is_visible = False
games.screen.event_grab = True
games.screen.mainloop()
# start it up!
main()
As the chef speed is a class variable you need to add the class name to access it, as in: Chef.speed. Where is the pizza class? I don't know Livewire so can't explain why you can't access the score, but surely you can set it as a number somehow and use it that way?
Thanks for your time. I was able to solve it by creating an attribute flag within the Pan class to check the score and then accessing that attribute flag in the Chef's update method, to then be able to change dx. Chef.speed is only the initial chef speed so changing it doesn't update the chef's dx.

Move Car Horizontal - Python classes

I need to create a class for a car that moves on a horizontal line. The constructor must only have one argument and this is where it is throwing me off. I can only have one argument to initialize the initial position of the bug. It should default to a value of 0 and the initial direction should always be one. But i'm not sure i can do this without 2 arguments in the constructor. I also need two mutator methods for moving and turning the car as well as a accessor method that will display the location of the car.
Example: position 5 direction right: .....>; position 2 direction left: ..<
class Bug:
def __init__(self, iPosition=0):
self.position = iPosition
def move(self):
pos = self.postion
def turn(self):
direction = self.position
def display(self):
if direction < 0:
x = '<'
elif direction > 0:
x = '>'
for i in range(pos):
y = '.' + y
return (y,x)
Your code in the display() function should give you the answer. It has the movement being to the left for negative direction. Hence, a negative value in the constructor's parameter could represent movement to the left.
This might be more like what you need:
class Bug:
def __init__(self, iPosition=0):
self.position = iPosition
self.direction = 1
def move(self, distance=1):
self.position += self.direction * distance
def turn(self):
self.direction = -self.direction
def display(self):
if self.direction < 0:
x = '<'
elif self.direction > 0:
x = '>'
y = ''
for i in range(self.position): # this is not right if position is negative; maybe abs()?
y = '.' + y
return (y,x)

Categories

Resources