How do I make a class follow another, instead of moving in a single direction?
for example:
class Character(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
char_walk1 = pygame.image.load("").convert_alpha()
char_walk2 = pygame.image.load("").convert_alpha()
self.char_walk = [char_walk1, char_walk2]
self.char_index = 0
self.image = self.char_walk[self.char_index]
self.rect = self.image.get_rect(midbottom=(100, 100))
def update(self):
key_input = pygame.key.get_pressed()
if key_input[pygame.K_d]:
self.rect.x += 3
if key_input[pygame.K_a]:
self.rect.x -= 3
Here the Character is moving left or right per key press and,
class Mobs(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
mob_walk1 = pygame.image.load("").convert_alpha()
mob_walk2 = pygame.image.load("").convert_alpha()
self.frames = [mob_walk1, mob_walk2]
pos = 452
self.mob_index = 0
self.image = self.frames[self.mob_index]
self.rect = self.image.get_rect(midbottom=(randint(200, 200), pos))
def update(self):
self.rect.x -= 3
Here the mob is moving right, How do I make it follow the character around on screen?
Character is sprite.Groupsingle and Mobs is sprite.Group
You'll probably need a reference to the character as an attribute in the Mobs class. then in the update method you'll have to decide the direction of the movement based on the relative position of the character.
def update(self):
if self.character.rect.x < self.rect.x:
self.rect.x -= 3
else:
self.sect.x += 3
It's been a while since i fooled around with pygame so I'm not sure about the best way to implement it but I hope it at least serves as guidance.
Related
I am fairly new to OOP and pygame, so these may be some stupid and basic questions - but I've been stuck on this for days so anything would help.
I am creating a variable called position3 within Gun.shoot(), I want this variable to move to Bullet.reposition() as Bullet.reposition is called upon. I then want the position3 variable to move to the Bullet.update() function, which gets called upon by a different process elsewhere in the code. During this whole process, the position3 variable should not change but should stay the same. I have managed to get the position3 variable to move to Bullet.reposition() from Gun.shoot(), however I can now not get it into Bullet.update(). Help!
class Bullet(pg.sprite.Sprite):
def __init__(self):
pg.sprite.Sprite.__init__(self)
self.image = pg.Surface((5,5))
self.image.fill(red)
self.rect = self.image.get_rect()
# self.rect.center = (200,200)
self.positionofm = (23,0)
self.pos = vec(300,300)
self.centerlocation = vec(0,0)
self.position3 = vec(0,0)
def update(self):
self.position3 = reposition.position3
print("update",self.position3)
# self.rect.center = self.position3
self.centerlocation = random.randint(200,400),random.randint(200,400)
self.rect.center =(self.centerlocation)
def reposition(self,position3):
print("repositioning")
self.centerlocation = random.randint(200,400),random.randint(200,400)
self.rect.move(position3)
print("regular",position3)
self.position3 = position3
print("First update",self.position3)
class Gun(pg.sprite.Sprite):
def __init__(self):
pg.sprite.Sprite.__init__(self)
self.image = pg.Surface((30,5), pg.SRCALPHA)
self.image.fill(black)
self.rect = self.image.get_rect()
self.original_image = self.image
self.rect.center = (WIDTH/2 , HEIGHT/2)
self.pos = vec(WIDTH / 2, HEIGHT / 2)
self.offset = vec(20, 0)
self.angle=0
self.position2=vec(0,0)
# self.bullet = Bullet()
def shoot(self):
self.BulletEndPos=vec(0,0)
self.BulletEndPos=vec(pg.mouse.get_pos())
# print(self.BulletEndPos,"akshgdjasgdas")
position2x=self.position2[0]
position2y=self.position2[1]
position3=vec(0,0)
position3=(math.floor(position2x)),(math.floor(position2y))
Bullet.reposition(self, position3)
Well your code snippet already has everything you need there you just need to remove the line
self.position3 = reposition.position3
Given that reposition is not an object and will not hold a attribute
The value for position3 is already updated for the object on the reposition method and written in the Bullet object attribute. Another way you could do it would be to rewrite update() somewhat like this:
def update(self, position3= None):
position_3 = position3 if position3 is not None else self.position3
print("update",position_3)
# self.rect.center = position_3
self.centerlocation = random.randint(200,400),random.randint(200,400)
self.rect.center =(self.centerlocation)
This gives you more freedom to pass position3 somewhere else in the code if needed while retaining the logic.
Now just to clarify a few things:
When you write a class you are just declaring the overall structure of the class and not creating any instance of it.
The self keyword refers to the referred instance of the class object, so you need to create an instance of the object that can keep those variables.
Keeping that in mind on your last line of method shoot you are doing nothing, has there is no bullet created to be repositioned and updated. So you kinda need to change your Gun class to this:
class Gun(pg.sprite.Sprite):
def __init__(self):
pg.sprite.Sprite.__init__(self)
self.image = pg.Surface((30,5), pg.SRCALPHA)
self.image.fill(black)
self.rect = self.image.get_rect()
self.original_image = self.image
self.rect.center = (WIDTH/2 , HEIGHT/2)
self.pos = vec(WIDTH / 2, HEIGHT / 2)
self.offset = vec(20, 0)
self.angle=0
self.position2=vec(0,0)
self.bullet = Bullet()
def shoot(self):
self.BulletEndPos=vec(0,0)
self.BulletEndPos=vec(pg.mouse.get_pos())
# print(self.BulletEndPos,"akshgdjasgdas")
position2x=self.position2[0]
position2y=self.position2[1]
position3=vec(0,0)
position3=(math.floor(position2x)),(math.floor(position2y))
self.bullet.reposition(self, position3)
OOP might be confusing at times especially at the beginning so you can try some other resources online (e.g. https://www.tutorialspoint.com/python3/python_classes_objects.htm)
I want to change an image of the object worker each time when it stops.
The class Worker is created based on the answer of #sloth in this thread.
class Worker(pygame.sprite.Sprite):
def __init__(self, image_file, location, *groups):
# we set a _layer attribute before adding this sprite to the sprite groups
# we want the workers on top
self._layer = 1
pygame.sprite.Sprite.__init__(self, groups)
self.image = pygame.transform.scale(pygame.image.load(image_file).convert_alpha(), (40, 40))
self.rect = self.image.get_rect(topleft=location)
# let's call this handy function to set a random direction for the worker
self.change_direction()
# speed is also random
self.speed = random.randint(1, 3)
def change_direction(self):
# let's create a random vector as direction, so we can move in every direction
self.direction = pygame.math.Vector2(random.randint(-1,1), random.randint(-1,1))
# we don't want a vector of length 0, because we want to actually move
# it's not enough to account for rounding errors, but let's ignore that for now
while self.direction.length() == 0:
self.direction = pygame.math.Vector2(random.randint(-1,1), random.randint(-1,1))
# always normalize the vector, so we always move at a constant speed at all directions
self.direction = self.direction.normalize()
def update(self, screen):
# there is a less than 1% chance every time that direction is changed
if random.uniform(0,1)<0.005:
self.change_direction()
# now let's multiply our direction with our speed and move the rect
vec = [int(v) for v in self.direction * self.speed]
self.rect.move_ip(*vec)
# if we're going outside the screen, move back and change direction
if not screen.get_rect().contains(self.rect):
self.change_direction()
self.rect.clamp_ip(screen.get_rect())
I try to create a cache of pre-loaded images
image_cache = {}
def get_image(key):
if not key in image_cache:
image_cache[key] = pygame.image.load(key)
return image_cache[key]
Then I assume that it is necessary to add the following code into def __init__:
images = ["worker.png", "worker_stopped.png"]
for i in range(0,len(images)):
self.images[i] = get_image(images[i])
and the following code into def update(self):
if self.direction.length() == 0:
self.image = self.images[1]
else:
self.image = self.images[0]
However, it does not seem to work properly. The old image worker.png does not disappear and the whole animation gets locked.
I think you should introduce some kind of state to indicate that the worker is running or not. Here's an example. Note the comments:
class Worker(pygame.sprite.Sprite):
# we introduce to possible states: RUNNING and IDLE
RUNNING = 0
IDLE = 1
def __init__(self, location, *groups):
# each state has it's own image
self.images = {
Worker.RUNNING: pygame.transform.scale(get_image("worker.png"), (40, 40)),
Worker.IDLE: pygame.transform.scale(get_image("worker_stopped.png"), (40, 40))
}
self._layer = 1
pygame.sprite.Sprite.__init__(self, groups)
# let's keep track of the state and how long we are in this state already
self.state = Worker.IDLE
self.ticks_in_state = 0
self.image = self.images[self.state]
self.rect = self.image.get_rect(topleft=location)
self.direction = pygame.math.Vector2(0, 0)
self.speed = random.randint(2, 4)
self.set_random_direction()
def set_random_direction(self):
# random new direction or standing still
vec = pygame.math.Vector2(random.randint(-100,100), random.randint(-100,100)) if random.randint(0, 5) > 1 else pygame.math.Vector2(0, 0)
# check the new vector and decide if we are running or fooling around
length = vec.length()
speed = sum(abs(int(v)) for v in vec.normalize() * self.speed) if length > 0 else 0
if length == 0 or speed == 0:
new_state = Worker.IDLE
self.direction = pygame.math.Vector2(0, 0)
else:
new_state = Worker.RUNNING
self.direction = vec.normalize()
self.ticks_in_state = 0
self.state = new_state
# use the right image for the current state
self.image = self.images[self.state]
def update(self, screen):
self.ticks_in_state += 1
# the longer we are in a certain state, the more likely is we change direction
if random.randint(0, self.ticks_in_state) > 30:
self.set_random_direction()
# now let's multiply our direction with our speed and move the rect
vec = [int(v) for v in self.direction * self.speed]
self.rect.move_ip(*vec)
# if we're going outside the screen, change direction
if not screen.get_rect().contains(self.rect):
self.direction = self.direction * -1
self.rect.clamp_ip(screen.get_rect())
This question already has answers here:
How do I create animated sprites using Sprite Sheets in Pygame?
(1 answer)
Invalid destination position for blit error, not seeing how
(1 answer)
Closed 2 years ago.
Alright, so I got some extremely simple code going on here, any comments/suggestions are recommended. Keep in mind, SIMPLE, in other words short and concise. thnx.
My problem is loading an image from a png file. So for example i got a couple of images in the file, and i want only one row to be loaded when the user presses for example, the right arrow key. Basically i have 4 rows, 2 or 3 columns 4 rows for each arrow key respectively
#
import pygame, time, random
pygame.init()
HEIGHT = 700
WIDTH = 1350
GRIDSIZE = HEIGHT/28
BLACK = ( 0, 0, 0)
WHITE = (255,255,255)
RED = (255, 0, 0)
screen=pygame.display.set_mode((WIDTH,HEIGHT))
font = pygame.font.SysFont("arial", 36)
shift = 10
#---------------------------------------#
# classes #
#---------------------------------------#
class Sprite(pygame.sprite.Sprite):
""" (fileName)
Visible game object.
Inherits Sprite to use its Rect property.
"""
def __init__(self, picture=None):
pygame.sprite.Sprite.__init__(self)
self.x = 0
self.y = 0
self.visible = False
self.image = self.blend_image(picture)
self.rect = self.image.get_rect()
self.width, self.height = self.rect.width, self.rect.height
self.update()
def spawn(self, x, y):
""" Assign coordinates to the object and make it visible.
"""
self.x, self.y = x,y
self.rect = pygame.Rect(self.x, self.y, self.width, self.height)
self.visible = True
def draw(self, surface):
surface.blit(self.image, self.rect)
def update(self):
# after moving a sprite, the rect attribute must be updated
self.rect = self.image.get_rect()
self.rect = pygame.Rect(self.x,self.y,self.rect.width,self.rect.height)
def moveLeft(self):
self.x -= shift
self.update()
def moveRight(self):
self.x += shift
self.update()
def moveUp(self):
self.y -= shift
self.update()
def moveDown(self):
self.y += shift
self.update()
def blend_image(self, file_name):
""" Remove background colour of an image.
Need to use it when a picture is stored in BMP or JPG format, with opaque background.
"""
image_surface = pygame.image.load(file_name)
image_surface = image_surface.convert()
colorkey = image_surface.get_at((0,0))
image_surface.set_colorkey(colorkey)
return image_surface
#---------------------------------------#
# functions #
#---------------------------------------#
def redraw_screen():
screen.fill(BLACK)
world.draw(screen)
if player.visible:
player.draw(screen)
pygame.display.update()
#---------------------------------------#
# main program #
#---------------------------------------#
world = Sprite("town.png")
world.spawn(0,0)
player = Sprite("player.png")
player.spawn(100,400)
LEFT_BORDER = 0
RIGHT_BORDER = WIDTH-1100
TOP_BORDER = 0
BOTTOM_BORDER = HEIGHT-480
#---------------------------------------#
clock = pygame.time.Clock()
FPS = 10
inPlay = True
while inPlay:
clock.tick(FPS)
# keyboard handler
pygame.event.get()
keys = pygame.key.get_pressed()
if keys[pygame.K_ESCAPE]:
inPlay = False
# world moves opposite to the arrow
if keys[pygame.K_LEFT] and world.x < LEFT_BORDER:
world.moveRight()
if keys[pygame.K_RIGHT] and world.x > RIGHT_BORDER:
world.moveLeft()
if keys[pygame.K_UP] and world.y < TOP_BORDER:
world.moveDown()
if keys[pygame.K_DOWN] and world.y > -BOTTOM_BORDER:
world.moveUp()
redraw_screen()
#---------------------------------------#
pygame.quit()
Right, so this is using python 2.7 btw. Any help is appreciated thnx again.
And also, there are some things that are added that have no use, but thats for later code, and vice-versa. Some tweaks i could do, like naming, that will come later.
If the player presses right, i have an image of a guy facing right.
Left, up, and down same concept.
if he holds the button, the guy "runs"
Now i hav normal position, and running position.
4 rows, 2 columns in a png file, how to load 1 row for respective key?
This is a the class of a sprite that goes left and right on the screen, when it hits the boundaries, it makes a "boing" sound and goes the opposite direction, everything works perfectly fine except for there is no boing sound when it hits the edge
class MySprite(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("vegetables.gif")
self.image = self.image.convert()
self.rect = self.image.get_rect()
self.rect.left = 0
self.rect.top = 167
self.__direction = 10
def update(self):
self.rect.left += self.__direction
sound = pygame.mixer.Sound("Bounce.mp3")
sound.set_volume(0.5)
if (self.rect.left < 0) or (self.rect.right > screen.get_width()):
sound.play()
self.__direction = -self.__direction
If you want the class to play its own sound, just load it like any attribute on __init__.
class MySprite(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("vegetables.gif")
self.image = self.image.convert()
self.sound = pygame.mixer.Sound("Bounce.mp3") #like this
self.rect = self.image.get_rect()
self.rect.left = 0
self.rect.top = 167
self.__direction = 10
Then whenever it's correct to do so, just call self.sound.play().
def update(self):
self.rect.left += self.__direction
if (self.rect.left < 0) or (self.rect.right > screen.get_width()):
self.sound.play() #as seen here
self.__direction = -self.__direction
For whatever it's worth - if you're going to do it in this way (have the sprite play its own sounds, etc), I would recommend loading them beforehand and then passing them as arguments (perhaps default arguments to avoid errors), such that each instance of the class might call a unique sound if need be.
So in your code prior to these classes, one could do something like:
JumpSound = pygame.Mixer.Sound("jump.wav")
BonkSound = pygame.Mixer.Sound("bonk.wav")
#etc etc etc...
...and then later on, pass the sounds as arguments:
class MySprite(pygame.sprite.Sprite):
def __init__(self, jumpsound, bonksound):
#...your other code precedes...
self.jumpsound = jumpsound
self.bonksound = bonksound
#...your other code continues...
myHero = MySprite(JumpSound, BonkSound)
The names are a little bit lousy b/c they are the same barring the CamelCasing, but forgetting that, this is probably a much cleaner approach. You can set your volume on the sounds way before they are passed into the sprites, along with whatever other changes you feel are necessary before the sprite gets ahold of them.
Specifically, I would like each sprite in sg_fireball to have 'bounces', without giving 'bounces' to every sprite from Spell(). Is there a clean way to do this without making 'bounces' an argument of Spell(), or looping through sg_fireball?
The relevant code snippets:
self.sg_fireball = pygame.sprite.Group()
self.sg_fireball.speed = 6.0
self.sg_fireball.image = pygame.image.load("fireball.png")
self.sg_fireball.bounces = 1
if event.type == pygame.MOUSEBUTTONDOWN:
self.character.cast(self.sg_fireball)
def cast(self, sg):
sg.add(Spell(self.rect.center, sg.speed, self.dir, sg.image))
class Spell(pygame.sprite.Sprite):
def __init__(self,pos, speed, direction, img, bounces):
pygame.sprite.Sprite.__init__(self)
self.bounces = bounces
self.image = img
self.rect = pygame.Rect(pos, (8,8))
self.posx = self.rect.x
self.posy = self.rect.y
self.speed = speed
self.dir = direction
self.velx = self.speed*math.cos(self.dir)
self.vely = self.speed*math.sin(self.dir)
If I understood correctly, you wish some of the sprites to have a certain attribute, while others won't. This is a perfect example of polimorphism and inheritance.
This is one of the options that you can do:
You subclass a normal spell as a bouncy spell. You can then have another update function where you will take care of bouncing. You can normally add a BouncySpell in the same sprite group as the NormalSpell.