My program allows the an image to follow my mouse cursor but I am unable to draw the circle with the the Attack method because i have to suface.fill in the move method ( the move method is followMeLittleBoy) I can get the circle to draw for a split second but only while moving and it just barley. This is my full code other than my imports and such
class Hero ():
def __init__(self):
self.dead = False
pygame.mouse.set_visible(False)
self.X_MOVE_AMT = 5
self.Y_MOVE_AMT = 5
self.space = pygame.image.load ("hero_sprite.jpg")
self.spaceRect = self.space.get_rect()
self.spaceRect.topleft = (100,100)
surface.blit (self.space, self.spaceRect)
pygame.display.update()
def followMeLittleBoy (self):
amntTuple = pygame.mouse.get_pos()
self.X_MOVE_AMT = math.ceil((amntTuple[0] - self.spaceRect.left)*0.2)
self.Y_MOVE_AMT = math.ceil((amntTuple[1] - self.spaceRect.top)*0.2)
self.spaceRect.left += self.X_MOVE_AMT
self.spaceRect.top += self.Y_MOVE_AMT
surface.blit (self.space, self.spaceRect)
pygame.display.update()
def Attack (self):
surface.fill ((255,255,255))
amntTuple = pygame.mouse.get_pos()
pygame.draw.circle(surface, pygame.Color(0,0,255), amntTuple, 20, 2)
surface.blit (self.space, self.spaceRect)
var = Hero ()
while True:
surface.fill((255,255,255))
for event in pygame.event.get():
var.followMeLittleBoy ()
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == KEYDOWN:
if event.key == K_SPACE:
var.Attack()
In a previous question I recommended you to update the state during events and call the draw routines from the main loop only once per iteration, this is specially important for the surface.fill call.
Now I strongly recommend to follow that aproach, otherwise this kind of problems will continue to arise.
There are few things that you need to fix.
var.followMeLittleBoy() should be called once per loop, not for every event.
You should have a seperate method for drawing in your Hero class.
Call pygame.display.update() only once per loop.
I am not sure what you are trying to accomplish, but you could create a list of circles that are to be drawn, and when you press spacebar, a circle is added to a list.
Then you can loop your circle list,and draw each of them without them dissapearing.
Related
I'm building a pong game trying to get better at programming but Im having trouble moving the ball. When the move_right method is called the ellipse stretches to the right instead of moving to the right. I've tried putting the ball variable in the init method but that just makes it not move at all even though the variables should be changing on account of the move_right method. I have also tried setting the x and y positions as parameters in the Ball class,but that just stretches it also.
I don't understand why when I run the following code the ball I'm trying to move stretches to the right instead of moves to the right. Can someone explain why this is happening? I have tried everything I can think of but i can't get it to do what I want.
import pygame,sys
import random
class Ball:
def __init__(self):
self.size = 30
self.color = light_grey
self.x_pos = width/2 -15
self.y_pos = height/2 -15
self.speed = 1
#self.ball = pygame.Rect(self.x_pos, self.y_pos,self.size,self.size)
def draw_ball(self):
ball = pygame.Rect(self.x_pos, self.y_pos,self.size,self.size)
pygame.draw.ellipse(screen,self.color,ball)
def move_right(self):
self.x_pos += self.speed
class Player:
def __init__(self,x_pos,y_pos,width,height):
self.x_pos = x_pos
self.y_pos = y_pos
self.width = width
self.height = height
self.color = light_grey
def draw_player(self):
player = pygame.Rect(self.x_pos,self.y_pos,self.width,self.height)
pygame.draw.rect(screen,self.color,player)
class Main:
def __init__(self):
self.ball=Ball()
self.player=Player(width-20,height/2 -70,10,140)
self.opponent= Player(10,height/2-70,10,140)
def draw_elements(self):
self.ball.draw_ball()
self.player.draw_player()
self.opponent.draw_player()
def move_ball(self):
self.ball.move_right()
pygame.init()
size = 30
clock = pygame.time.Clock()
pygame.display.set_caption("Pong")
width = 1000
height = 600
screen = pygame.display.set_mode((width,height))
bg_color = pygame.Color('grey12')
light_grey = (200,200,200)
main = Main()
#ball = pygame.Rect(main.ball.x_pos, main.ball.y_pos,main.ball.size,main.ball.size)
#player = pygame.Rect(width-20,height/2 -70,10,140)
#opponent = pygame.Rect(10,height/2-70,10,140)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
#ball = pygame.Rect(main.ball.x_pos, main.ball.y_pos,main.ball.size,main.ball.size)
#pygame.draw.rect(screen,light_grey,player)
#pygame.draw.rect(screen,light_grey,opponent)
#pygame.draw.ellipse(screen,light_grey,ball)
main.draw_elements()
main.move_ball()
main.ball.x_pos += main.ball.speed
pygame.display.flip()
clock.tick(60)
You have to clear the display in every frame with pygame.Surface.fill:
while True:
# [...]
screen.fill(0) # <---
main.draw_elements()
main.move_ball()
main.ball.x_pos += main.ball.speed
pygame.display.flip()
# [...]
Everything that is drawn is drawn on the target surface. The entire scene is redraw in each frame. Therefore the display needs to be cleared at the begin of every frame in the application loop. The typical PyGame application loop has to:
handle the events by either pygame.event.pump() or pygame.event.get().
update the game states and positions of objects dependent on the input events and time (respectively frames)
clear the entire display or draw the background
draw the entire scene (blit all the objects)
update the display by either pygame.display.update() or pygame.display.flip()
For context, I've just gotten into Pygame, and I'm trying to learn how to use buttons. Of course, there's making the classes or functions and likewise calling them to work. However, when I open a new screen, I'd like there to be a new button, which then only works there.
My issue comes in after the second scene, where I'm unsure how to call a second button, or a third etc. If I put a return in a function where my button is, and check whether that function has collided with a point, the whole screen becomes a button which isn't ideal. So I'm looking for a fix.
Here's the chunk causing issues, and I'd love any advice on optimising it. My guess is that HeroCreation()[1] is causing issues, but I'm not entirely sure how else to make multiple buttons work simultaenously.
def TextCreation(font, text, coords, screen):
label = font.render(text, True, BLACK)
labelRect = label.get_rect()
labelRect.topright = coords
window.blit(label, labelRect)
return(label, labelRect)
StartButton = TextCreation(largeFont, 'Start Game', (rect_centerx+140, rect_centery - 250), window)
QuitButton = TextCreation(largeFont, 'Quit Game', (rect_centerx+140, rect_centery + 150), window)
def HeroCreation():
window.fill(WHITE)
Greeting = TextCreation(largeFont, 'Welcome, Hero!', (rect_centerx+210, rect_centery-200), window)
Intro1 = TextCreation(paraFont, "I presume you're caught up, but just in case:", (rect_centerx+250, rect_centery + 50), window)
Intro2 = TextCreation(paraFont, "The Rat King's wrecking our kingdom, and you're the only one who can find the Orb of Power.", (rect_centerx+500, rect_centery + 100), window)
Intro3 = TextCreation(paraFont, "Deal with him once and for all! Do us proud, hero.",(rect_centerx+260, rect_centery + 150), window)
pygame.draw.rect(window, LIGHT_GREEN, (rect_centerx - 170, rect_centery-80, 400, 70), 0)
ProceedtoMapButton = TextCreation(paraFont, "Are you ready?", (rect_centerx+110, rect_centery-70), window)
return (ProceedtoMapButton)
pygame.display.flip()
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
mouse = pygame.mouse.get_pos()
if QuitButton[1].collidepoint(mouse):
pygame.quit()
sys.exit()
if StartButton[1].collidepoint(mouse):
HeroCreation()
if HeroCreation()[1].collidepoint(mouse):
drawGrid(width, height, window)
if event.type == QUIT:
pygame.quit()
sys.exit()
For each screen, create new buttons, then remove them when the screen exits. In your loop, check if the button is on the screen before the collision check.
Try this code:
HeroButton = None
while run:
for event in pygame.event.get():
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
mouse = pygame.mouse.get_pos()
if QuitButton[1].collidepoint(mouse): # quit button on every screen ?
pygame.quit()
sys.exit()
if StartButton != None and StartButton[1].collidepoint(mouse):
StartButton = None # done with this button
HeroButton = HeroCreation()
elif HeroButton != None and HeroButton[1].collidepoint(mouse):
HeroButton = None # done with this button
drawGrid(width, height, window) # start game?
if event.type == QUIT:
pygame.quit()
sys.exit()
I'm creating a button class for a game, and I'm using the pygame event loop to detect mouse clicks (specifically when the mouse is released) (I hear it is better the method of using pygame.mousemget_pressed()[0]). However, the event loop seems to be slow, not responding and performing the buttons function when it is clicked. I think it may be because relate to how I created the event loop in a class, but I'm not sure. Here's a sample of my code:
class Button:
"""A Button class, built for all kinds of purposes"""
def __init__(self, window, rect, message, off_color, on_color, message_color, message_font_size):
pass # just a bunch of variables that use the parameters given
def in_button(self):
mouse_pos = pygame.mouse.get_pos()
if pygame.Rect(self.rect).collidepoint(mouse_pos):
return True
def clicked(self):
if self.in_button():
pygame.event.pump()
for e in pygame.event.get():
if e.type == pygame.MOUSEBUTTONUP:
return True
# I proceed to create 5 instances using this class.
I removed some unnecessary method information in my code. If you need anything more, please help me.
You have to implement the clicked method in a different way, because there should be only one event loop in your application, not one in every button instance. pygame.event.get() empties the event queue, so calling it multiple times per frame will cause problems.
I suggest to pass the events to the buttons. In this (very simple) example I pass the pygame.MOUSEBUTTONDOWN events to the clicked method and then check if the event.pos (mouse position) of the event collides with the rect. If it returns True, I do something in the event loop in the main function.
import pygame as pg
class Button:
def __init__(self, pos):
self.image = pg.Surface((100, 40))
self.image.fill(pg.Color('dodgerblue1'))
self.rect = self.image.get_rect(center=pos)
def clicked(self, event):
"""Check if the user clicked the button."""
# pygame.MOUSE* events have an `event.pos` attribute, the mouse
# position. You can use `pygame.mouse.get_pos()` as well.
return self.rect.collidepoint(event.pos)
def main():
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
button = Button((100, 60))
number = 0
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
elif event.type == pg.MOUSEBUTTONDOWN:
# Pass the MOUSEBUTTONDOWN event to the buttons.
if button.clicked(event):
number += 1 # Do something.
print('clicked', number)
screen.fill((30, 30, 30))
screen.blit(button.image, button.rect)
pg.display.flip()
clock.tick(60)
if __name__ == '__main__':
pg.init()
main()
pg.quit()
If you want a button with several images, you can use something similar to the button class here (the first addendum) or search for more sophisticated Button classes for pygame.
The problem that I cannot figure how to sort is that when I drag a piece over another the second piece also is dragged along with the first. I have tried several ways to limit the mouse selection to one piece at at time, but all have failed. Can anyone help - no doubt there is a simple way! The code shorn of all my failed attempts follows:
# In main loop:
# Watch for keyboard and mouse events
for event in pygame.event.get():
if event.type == pygame.MOUSEBUTTONDOWN:
mouse_held = True
if event.type == pygame.MOUSEBUTTONUP:
mouse_held = False
# Update pieces that are in a sprite.Group()
pieces.update(mouse_held)
# In sprite class:
def update(self, mouse_held):
if mouse_held == True:
self.mouse_coordinates = pygame.mouse.get_pos()
if self.rect.collidepoint(self.mouse_coordinates) == True:
self.rect.centerx = self.mouse_coordinates[0]
self.rect.centery = self.mouse_coordinates[1]
Your question is fairly difficult but by doing all of this you should be able t achieve what you want.
You should change your sprite class to have a new variable of type int named depth (the higher the value the 'deeper' it is).
Considering you have a list of all the sprite objects you want to check for clicks called spriteList you should add this:
from operator import attrgetter
then change these lines:
if event.type == pygame.MOUSEBUTTONDOWN:
mouse_held = True
to:
if event.type == pygame.MOUSEBUTTONDOWN:
sprites = []
for sprite in spriteList:
if sprite.rect.collidepoint(event.pos) == True:
sprites.append(sprite)
active = min(lists, key=attrgetter('depth'))
mouse_held = True
You should replace the sprite's update function to:
def update(self):
self.mouse_coordinates = pygame.mouse.get_pos()
if self.rect.collidepoint(self.mouse_coordinates) == True:
self.rect.centerx = self.mouse_coordinates[0]
self.rect.centery = self.mouse_coordinates[1]
And finally when you want to update the sprite's position simply type:
if mouse_held:
active.update()
So Ive been having issues with getting a sprite to stay withing the bounds of the screen. I got it to work with a simple rect(0,0,16,16), but i cant seem to get it to work with a sprite being blit onto the screen. What do i need to change in order to keep my sprite clamped within the screen res? I only just started today using classes to orgonize code so any input is appreciated and helpful.
import pygame
from pygame.locals import *
from pygame import Color
class Game():
""" Lets try to get this going by simple steps
One by one. First step, lets figure how to make a class
that can do the display stuff. Lord have mercy on my soul"""
def __init__(self, wi=256, hi=224, multii=3):
"""Initialization"""
pygame.init()
self.runGame = True
self.width = wi*multii
self.height = hi*multii
self.spritesize = 16*multii
self.clock = pygame.time.Clock()
self.fps = self.clock.get_fps()
self.screen = pygame.display.set_mode((self.width, self.height))
self.kl = []
self.walk = [0, 0]
self.speed = multii*1.5
self.x,self.y = self.width/2, self.height/2
self.playerSpr = pygame.image.load('images/'+'link1.png').convert_alpha()
self.playerRec = Rect(self.playerSpr.get_rect())
def mainLoop(self):
"""Loop through the main game routines
1. Drawing 2. Input handling 3. Updating
Then loop through it until user quits"""
while self.runGame:
self.clock.tick(60)
self.events()
self.draw()
def events(self):
"""Time to handle some events"""
for e in pygame.event.get():
if (e.type == pygame.QUIT) or (e.type == KEYDOWN and e.key == K_ESCAPE):
self.runGame = False
break
if e.type==KEYDOWN:
if e.key==pygame.K_a: self.kl.append(1)
if e.key==pygame.K_d: self.kl.append(2)
if e.key==pygame.K_w: self.kl.append(3)
if e.key==pygame.K_s: self.kl.append(4)
if e.type==pygame.KEYUP:
if e.key==pygame.K_a: self.kl.remove(1)
if e.key==pygame.K_d: self.kl.remove(2)
if e.key==pygame.K_w: self.kl.remove(3)
if e.key==pygame.K_s: self.kl.remove(4)
if self.kl[-1:]==[1]: self.walk=[-self.speed, 0]
elif self.kl[-1:]==[2]: self.walk=[ self.speed, 0]
elif self.kl[-1:]==[3]: self.walk=[0,-self.speed]
elif self.kl[-1:]==[4]: self.walk=[0, self.speed]
else: self.walk=[0, 0]
self.x+=self.walk[0]
self.y+=self.walk[1]
def draw(self):
"""Draw and update the main screen"""
self.fps = self.clock.get_fps()
self.screen.fill(Color('purple'))
#print self.screen.get_rect()
#print player_rect
self.playerSpr.clamp_ip(self.screen.get_rect())
#pygame.draw.rect(self.screen, (255, 255, 255), self.playerrect)
self.screen.blit(self.playerSpr, (self.x,self.y), self.playerRec)
pygame.display.set_caption('Grid2. FPS: '+str(self.fps))
pygame.display.update()
game = Game()
game.mainLoop()
Why not use playerRec to keep track of the position of your player instead of the additional x and y attributes?
I suggest also using the move method (or move_ip):
def events(self):
for e in pygame.event.get():
...
self.playerRec.move_ip(*self.walk) # instead of self.x+=self.walk[0] / self.y+=self.walk[1]
def draw(self):
...
# probably do this right after 'move_ip'
self.playerRec.clamp_ip(self.screen.get_rect())
# note that 'blit' accepts a 'Rect' as second parameter
self.screen.blit(self.playerSpr, self.playerRec)
as a side note: You should consider using a Sprite, since it basically combines an Image and a Rect.
Two things:
You are not stopping movement of the sprite when it comes off the screen.
Make an move functions that will get a direction and will decide if it can move more to the side. That way when the right side of the sprite will be of screen, you will not move more to the right.
Since you put your direction keys in a list that works like a stack, you are only getting 1 direction per keypress. If you also want to move diagonally either make two lists one for both directions or use a easier method such as this:
if KEYDOWN == K_LEFT: direction_x = -1
if KEYUP == K_LEFT AND direction_x == -1: direction_x = 0
do this for every key.