Accessing an object attribute in an event function - python

I've been trying to use def on_mouse_click to change a value from another object. I'm using pyglet to make a game. It looks like this:
import random, math, time
import pyglet
from pyglet.gl import *
window = pyglet.window.Window(640, 480, visible=False, caption="Pong")
window.set_location(window.screen.width/2 - window.width/2, window.screen.height/2 - window.height/2)
batch = pyglet.graphics.Batch()
class Ball(pyglet.sprite.Sprite):
def __init__(self):
pattern = pyglet.image.SolidColorImagePattern((139, 90, 43, 120))
image = pyglet.image.create(8,8,pattern)
image.anchor_x, image.anchor_y = 20, 0
pyglet.sprite.Sprite.__init__(self, image, batch=batch)
def reset(self):
self.x, self.y = 0 ,0
angle = random.random()*math.pi/2
self.vx, self.vy = math.sin(angle)*300, math.cos(angle)*300
#window.event
def on_resize(width, height):
glViewport(0, 0, width, height)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glOrtho(0, 800, 0, 600, -1, 1)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
return pyglet.event.EVENT_HANDLED
keymap = pyglet.window.key.KeyStateHandler()
window.push_handlers(keymap)
states = []
class PausedState:
def update(self, dt):
if keymap[pyglet.window.key.SPACE]:
states.pop()
class GameState:
def __init__(self):
self.b = Ball()
self.b.reset()
def update(self, dt):
#window.event
def on_mouse_press(x, y, button, modifiers):
self.b.angle = 0
self.b.x += self.b.vx *dt
self.b.y += self.b.vy *dt
#window.event
def on_draw():
window.clear()
batch.draw()
def update(dt):
if len(states):
states[-1].update(dt)
else:
pyglet.app.exit()
states.append(GameState())
states.append(PausedState())
pyglet.clock.schedule_interval(update, 1.0/60.0)
window.clear()
window.flip()
window.set_visible(True)
pyglet.app.run()
I'm getting an error that says global 'self is not defined'. I'm expecting to change the angle of the ball by clicking the left mouse button. Thanks in advance! :)

Did you try to declare a setter method like follow ? :
#in your class
def setB(new_b):
self.b = new_b

Related

looping image with fade between them [duplicate]

So I am trying to fade my screen out and back in after completing a level using PyGame. My problem is that only the fadeout() works and not the fadein(). When calling the fadein() the screen turns black for a few seconds then suddenly shows the next level. I can't find the problem, any ideas?
def fadeout():
fadeout = pg.Surface((screen_width, screen_height))
fadeout = fadeout.convert()
fadeout.fill(black)
for i in range(255):
fadeout.set_alpha(i)
screen.blit(fadeout, (0, 0))
pg.display.update()
def fadein():
fadein = pg.Surface((screen_width, screen_height))
fadein = fadein.convert()
fadein.fill(black)
for i in range(255):
fadein.set_alpha(255-i)
screen.blit(fadein, (0, 0))
pg.display.update()
Your problem is that you fade in to a black screen, so you don't see any effect. A black screen with a black half-translucent Surface drawn on top is still a black Surface.
You should render the first frame of your level, and blit that Surface to the screen before blitting the fadein surface onto the screen.
Here's a simple example that I hacked together. Press a key to switch from one scene to the next.
import pygame
import random
from itertools import cycle
class Cloud(pygame.sprite.Sprite):
def __init__(self, x, y):
super().__init__()
self.image = pygame.Surface((50, 20))
self.image.set_colorkey((11, 12, 13))
self.image.fill((11, 12, 13))
pygame.draw.ellipse(self.image, pygame.Color('white'), self.image.get_rect())
self.rect = self.image.get_rect(topleft=(x,y))
def update(self, dt, events):
self.rect.move_ip(dt/10, 0)
if self.rect.left >= pygame.display.get_surface().get_rect().width:
self.rect.right = 0
class DayScene:
def __init__(self):
self.clouds = pygame.sprite.Group(Cloud(0, 30), Cloud(100, 40), Cloud(400, 50))
def draw(self, screen):
screen.fill(pygame.Color('lightblue'))
self.clouds.draw(screen)
def update(self, dt, events):
self.clouds.update(dt, events)
class NightScene:
def __init__(self):
sr = pygame.display.get_surface().get_rect()
self.sky = pygame.Surface(sr.size)
self.sky.fill((50,0,50))
for x in random.sample(range(sr.width), 50):
pygame.draw.circle(self.sky, (200, 200, 0), (x, random.randint(0, sr.height)), 1)
self.clouds = pygame.sprite.Group(Cloud(70, 70), Cloud(60, 40), Cloud(0, 50), Cloud(140, 10), Cloud(100, 20))
def draw(self, screen):
screen.blit(self.sky, (0, 0))
self.clouds.draw(screen)
def update(self, dt, events):
self.clouds.update(dt, events)
class Fader:
def __init__(self, scenes):
self.scenes = cycle(scenes)
self.scene = next(self.scenes)
self.fading = None
self.alpha = 0
sr = pygame.display.get_surface().get_rect()
self.veil = pygame.Surface(sr.size)
self.veil.fill((0, 0, 0))
def next(self):
if not self.fading:
self.fading = 'OUT'
self.alpha = 0
def draw(self, screen):
self.scene.draw(screen)
if self.fading:
self.veil.set_alpha(self.alpha)
screen.blit(self.veil, (0, 0))
def update(self, dt, events):
self.scene.update(dt, events)
if self.fading == 'OUT':
self.alpha += 8
if self.alpha >= 255:
self.fading = 'IN'
self.scene = next(self.scenes)
else:
self.alpha -= 8
if self.alpha <= 0:
self.fading = None
def main():
screen_width, screen_height = 300, 300
screen = pygame.display.set_mode((screen_width, screen_height))
clock = pygame.time.Clock()
dt = 0
fader = Fader([DayScene(), NightScene()])
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
if e.type == pygame.KEYDOWN:
fader.next()
fader.draw(screen)
fader.update(dt, events)
pygame.display.flip()
dt = clock.tick(30)
main()
By abstracting each scene into it's own class and delegating the scene change to the Fader class, we're able to let the scenes continue (or add a simple if statement to prevent that) and to handle events while fading.

scrolling a background image in pyglet

I am new to pyglet.I am trying to a have a seamlessly scrolling background.
I have tried everything i could find on stack and google. I turned vsync off because it improved the performance in my computer. I have even used batch to draw on my screen. Used the preload function to load my image and in my Game class convert it to sprite.
What else can i do to make my background scroll smoothly?
Thanks in Advance.
This is my code:
import pyglet
from pyglet.gl import *
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glEnable(GL_LINE_SMOOTH)
glHint(GL_LINE_SMOOTH_HINT, GL_DONT_CARE)
pyglet.clock.set_fps_limit(60)
WIDTH = 800
HEIGHT = 600
TITLE = 'Bunny'
def preload_image(img):
pyglet.resource.path = ['../res']
pyglet.resource.reindex()
image = pyglet.resource.image(img)
return image
class main(pyglet.window.Window):
def __init__(self):
super(main, self).__init__(800, 600, fullscreen=False, vsync=False)
self.x, self.y = 0, 0
self.sprites = {}
self.batches = {}
self.subgroups = {}
self._handles = {}
self.batches['main'] = pyglet.graphics.Batch()
self.subgroups['base'] = pyglet.graphics.OrderedGroup(0)
self.background = preload_image('mountains.png')
self.background.width = WIDTH
self.background.height = HEIGHT
self.background_sprite = pyglet.sprite.Sprite(self.background, 0, 0, batch=self.batches['main'])
self.background_sprite1 = pyglet.sprite.Sprite(self.background, WIDTH, 0, batch=self.batches['main'])
self.speed = 30
self.frame_rate = 60.0
pyglet.clock.schedule_interval(self.run, 1.0 / self.frame_rate)
pyglet.clock.set_fps_limit(self.frame_rate)
self.alive = 1
def on_draw(self):
self.render()
def on_close(self):
self.alive = 0
def render(self):
self.clear()
for batch_name, batch in self.batches.items():
batch.draw()
for sprite_name, sprite in self.sprites.items():
sprite._draw()
self.flip()
def run(self):
while self.alive == 1:
self.render()
dt = 1/pyglet.clock.get_fps_limit()
print(1/dt)
self.background_sprite.x -= self.speed * dt
self.background_sprite1.x -= self.speed * dt
if self.background_sprite1.x < -WIDTH: self.background_sprite1.x = WIDTH
if self.background_sprite.x < -WIDTH: self.background_sprite.x = WIDTH
event = self.dispatch_events()
x = main()
x.run()
As you can see in the image, there is a small black line in between the two background scrolling images

Python Class Syntax Error

I have been following a guide to make my game. However, when I try to run the program I get a syntax Error with the Class Circle and not sure why? I can't figure out the reason and I'm sure there are more issue afterward the Class Circle but can't fix it.
import pygame
from pygame.locals import *
from uagame import Window
import time
import random
def main():
window = Window('Pong', 500, 400)
window.set_auto_update(False)
game = Game(window)
game.play()
window.close()
class Rect:
def __init__(self, center, radius, color, window):
self.center = center
self.radius = radius
self.color = color
self.window = window
def draw(self):
pygame.draw.rect(self.window.get_surface(), self.color, Rect((100, 200), (20, 30))
class Circle: # Syntax Error: class Circle:: <string>, line 28, pos 5
def __init__(self, center, radius, color, window):
self.center = center
self.radius = radius
self.color = color
self.window = window
def draw(self):
pygame.draw.circle(self.window.get_surface(), self.color, self.center,self.radius)
def get_color(self):
return self.color
def move(self):
window_size = (self.window.get_width() , self.window.get_height())
for index in range(0,2):
self.center[index] = (self.center[index] + 1) % window_size[index]
def enlarge(self,increment):
self.radius = self.radius + increment
class Game:
def __init__(self, window):
self.window = window
self.bg_color = pygame.Color('black')
self.fg_color = 'green'
self.rect_color = 'green'
self.pause_time = 0.02
self.close_clicked = False
self.continue_game = True
self.circle = Ball([200,200], 20, pygame.Color(self.fg_color),window)
self.rect = Rect([100,200], 50 , pygame.Color(self.fg_color),window)
self.radius_increment = 5
def play(self):
while not self.close_clicked:
self.handle_event()
self.draw()
if self.continue_game:
self.update()
self.decide_continue()
time.sleep(self.pause_time)
def handle_event(self):
event = pygame.event.poll()
if event.type == QUIT:
self.close_clicked = True
elif event.type == MOUSEBUTTONUP and self.continue_game:
self.handle_mouse_up()
def handle_mouse_up(self):
self.circle.enlarge(self.radius_increment)
def draw(self):
self.window.clear()
self.circle.draw()
self.rect.draw()
if not self.continue_game:
self.window.set_bg_color(self.fg_color)
self.window.clear()
self.window.update()
def update(self):
self.circle.move()
def decide_continue(self):
pass
main()
Your error actually lies in the line above. See that your code is missing a parenthesis to close the rect() function. Always remember to count the parenthesis on long functions like this.
def draw(self):
pygame.draw.rect(self.window.get_surface(), self.color, Rect((100, 200), (20, 30))) #<-

Sprite in Pyglet not doing what I want

I am trying to have a circle that, when clicked, moves somewhere else on the screen. However, when I click the circle, nothing happens.
#IMPORT STUFF
import pyglet as pg
from random import randint
mouse = pg.window.mouse
#VARS
window = pg.window.Window(width = 640, height = 480)
score = 0
circleImg = pg.image.load("circle.png")
circle = pg.sprite.Sprite(circleImg, randint(1, window.width), randint(1, window.height))
text = pg.text.Label("Click red!", font_name = "Times New Roman", font_size = 18, x = 260, y = 10)
#DETECT MOUSE PRESS ON CIRCLE
#window.event
def on_mouse_press(x, y, button, modifiers):
if x == circle.x and y == circle.y:
circle.x = randint(1, window.width)
circle.y = randint(1, window.height)
#window.event
def on_draw():
window.clear()
text.draw()
circle.draw()
pg.app.run()
import pyglet
from pyglet.gl import *
from random import randint
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
class Circle(pyglet.sprite.Sprite):
def __init__(self, radiance=5, x=0, y=0):
self.texture = pyglet.image.load('circle.png')
super(Circle, self).__init__(self.texture)
def click(self, x, y):
if x >= self.x and y >= self.y:
if x <= self.x + self.texture.width and y <= self.y + self.texture.height:
return self
mouse = pyglet.window.mouse
#VARS
window = pyglet.window.Window(width = 640, height = 480)
score = 0
#circleImg = pyglet.image.load("circle.png")
#circle = pyglet.sprite.Sprite(circleImg, randint(1, window.width), randint(1, window.height))
circle = Circle(x=50, y=50)
text = pyglet.text.Label("Click red!", font_name = "Times New Roman", font_size = 18, x = 260, y = 10)
#DETECT MOUSE PRESS ON CIRCLE
#window.event
def on_mouse_press(x, y, button, modifiers):
if circle.click(x, y):
print('Clicked in circle')
circle.x = randint(0, window.width - 10)
circle.y = randint(0, window.height - 10)
#window.event
def on_draw():
window.clear()
text.draw()
circle.draw()
pyglet.app.run()
A short description of what this does is it creates a custom class called Circle that inherits the Sprite class. It loads the circle.png as a texture with a alpha channel that gets blended by the GL library.
We add a custom function called click that checks if the lowest x,y coordinates are higher than the circles lowest x,y, then we check if the cursor is below x+width and same for y of the image region.
If that's the case, we return the circle sprite class as a True value in case we want to use the sprite.
Future enhancements:
You should draw the circle using gl functions, hence why I've defined radiance in the class definitions. However radiance here is never used, it's a placeholder for the future.
This is so you can use math to defined if you actually clicked within the circle, but this is beyond my scope of quick answers.. I would have to do a lot of debugging myself in order to get the math to add up (it's not my strong side).
What makes it work now is that we use the image width, height, x and y data to crudely check if we're within the image, aka "the circle".
trying to draw over sprite or change picture pyglet
As a bonus, I'll add this answer to the list of enhancements because it contains some stuff that might be useful. One would be to replace 90% of your code with a custom pyglet.window.Window class to replace global variables and decorators and stuff.
And it would look something like this:
import pyglet
from pyglet.gl import *
from random import randint
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
key = pyglet.window.key
class Circle(pyglet.sprite.Sprite):
def __init__(self, radiance=5, x=0, y=0):
self.texture = pyglet.image.load('circle.png')
super(Circle, self).__init__(self.texture)
def click(self, x, y):
if x >= self.x and y >= self.y:
if x <= self.x + self.texture.width and y <= self.y + self.texture.height:
return self
class MainScreen(pyglet.window.Window):
def __init__ (self):
super(MainScreen, self).__init__(800, 600, fullscreen = False)
self.x, self.y = 0, 0
self.bg = pyglet.sprite.Sprite(pyglet.image.load('background.jpg'))
self.sprites = {}
self.sprites['circle'] = Circle(x=50, y=50)
self.sprites['label'] = pyglet.text.Label("Click red!", font_name = "Times New Roman", font_size = 18, x = 260, y = 10)
self.alive = 1
def on_draw(self):
self.render()
def on_close(self):
self.alive = 0
def on_mouse_press(self, x, y, button, modifiers):
if self.sprites['circle'].click(x, y):
print('Clicked in circle')
self.sprites['circle'].x = randint(0, self.width - 10)
self.sprites['circle'].y = randint(0, self.height - 10)
def on_key_press(self, symbol, modifiers):
if symbol == key.ESCAPE: # [ESC]
self.alive = 0
def render(self):
self.clear()
self.bg.draw()
for sprite_name, sprite_obj in self.sprites.items():
sprite_obj.draw()
self.flip()
def run(self):
while self.alive == 1:
self.render()
# -----------> This is key <----------
# This is what replaces pyglet.app.run()
# but is required for the GUI to not freeze
#
event = self.dispatch_events()
x = MainScreen()
x.run()
I'm not familiar with pyglet, but I'm guessing the problem is that you're checking whether x == circle.x etc, which means it only moves when you click the single pixel at the exact centre of the circle. Try some kind of maximum distance from the centre (e.g. a hypotenuse math.sqrt( (x-circle.x)**2 + (y-circle.y)**2) < circle.radius

Using pymunk gravity with pyglet

This code displays the image assassin1.png on a black screen standing still at position (50, 80).
The goal is to induce the pymunk gravity on that image so that it falls down. I followed the pymunk tutorial which is written using pygame and tried to adapt that. I don't know why my code isn't applying gravity to the my image. Could someone tell me what I am doing wrong and what I should change to make it work properly?
import pyglet
import pymunk
class PymunkSpace(object):
def assassin_space(self, space):
self.space = space
self.mass = 91
self.radius = 14
self.inertia = pymunk.moment_for_circle(self.mass, 0, self.radius)
self.body = pymunk.Body(self.mass, self.inertia)
self.body.position = 50, 80
self.shape = pymunk.Circle(self.body, self.radius)
self.space.add(self.body, self.shape)
return self.shape
class Assassin(pyglet.sprite.Sprite):
def __init__(self, batch, img, x, y):
pyglet.sprite.Sprite.__init__(self, img, x , y )
class Game(pyglet.window.Window):
def __init__(self):
pyglet.window.Window.__init__(self, width = 315, height = 220)
self.batch_draw = pyglet.graphics.Batch()
self.a_space = PymunkSpace().assassin_space(space)
self.player1 = Assassin(batch = self.batch_draw, img = pyglet.image.load("assassin1.png"), x = self.a_space.body.position.x ,y = self.a_space.body.position.y )
def on_draw(self):
self.clear()
self.batch_draw.draw()
self.player1.draw()
space.step(1/50.0)
if __name__ == "__main__":
space = pymunk.Space()
space.gravity = (0.0, -900.)
window = Game()
pyglet.app.run()
The problem is that you set the location of your Sprite once:
self.player1 = Assassin(batch = self.batch_draw, img = pyglet.image.load("assassin1.png"), x = self.a_space.body.position.x ,y = self.a_space.body.position.y )
class Assassin(pyglet.sprite.Sprite):
def __init__(self, batch, img, x, y):
pyglet.sprite.Sprite.__init__(self, img, x , y )
but this location never gets updated.
Another problem is that in the pygame example, space.step(1/50.0) is called every iteration of the main loop, while your's only gets called when the window is drawn.
So, first, you should ensure space.step gets called every frame. Do so by using a clock:
class Game(pyglet.window.Window):
def __init__(self):
...
pyglet.clock.schedule(self.update)
def on_draw(self):
self.clear()
self.batch_draw.draw()
self.player1.draw()
def update(self, dt):
space.step(dt)
The next step is ensuring the location of the sprite is keeped in sync with the pyglet object.
Change your Assassin class:
class Assassin(pyglet.sprite.Sprite):
def __init__(self, batch, img, space):
self.space = space
pyglet.sprite.Sprite.__init__(self, img, self.space.body.position.x , self.space.body.position.y)
def update(self):
self.x = self.space.body.position.x
self.y = self.space.body.position.y
Create your Assassin instance like this:
class Game(pyglet.window.Window):
def __init__(self):
pyglet.window.Window.__init__(self, width = 315, height = 220)
self.batch_draw = pyglet.graphics.Batch()
self.a_space = PymunkSpace().assassin_space(space)
self.player1 = Assassin(batch = self.batch_draw, img = pyglet.image.load("assassin1.png"), space = self.a_space)
pyglet.clock.schedule(self.update)
and call self.player1.update() in your new update method:
class Game(pyglet.window.Window):
def __init__(self):
...
def on_draw(self):
...
def update(self, dt):
self.player1.update()
space.step(dt)
(And you should probably get rid of some unneeded instance members, but that's another topic)

Categories

Resources