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)
Related
I am trying to make a python library:
There is a class (game) in it is a function that defines a variable (display)
Then there is another class (char) in main, I want to access display in char
How do I do it?
In the past I have tried: self.display, global display, and game.display
class game():
def __init__(self, disp, width, height):
self.display = disp # VARIABLE I WANT TO ACCESS
self.height = height
self.width = width
class sprite():
def __init__(self, size, position, image):
self.image = image
self.size = size
self.rect = self.image.get_rect()
self.rect.x = position[0]
self.rect.y = position[1]
self.x = position[0]
self.y = position[1]
self.collisionDirection = 5
self.hasCollided = False
self.mask = pygame.mask.from_surface(self.image)
self.velocity = 0
def render(self):
self.rect.x = self.x
self.rect.y = self.y
self.mask = pygame.mask.from_surface(self.image)
display.blit(self.image, (self.x, self.y)) # WHERE I WANT TO ACCESS IT
I keep getting AttributeError what can I do?
You can pass the Game instance to another class. For example
# instantiate the game
g = game()
# create an instance of char
c = char(game)
assuming the __init__ of char() looks something like this:
class char():
def __init__(self, game):
# save a reference to the game object as an attribute
self.game = game
# now you can access the game's attributes
print(self.game.display)
Allow that the below example is contrived, and not likely the way you would design pen/paper, but it shows that there are a couple different ways that you can accomplish what you ask.
class Pen:
def __init__(self, thickness, color):
self.thickness = thickness
self.color = color
class Paper:
def __init__(self, pen: Pen):
self.pen = pen
def draw(self):
pen_color = self.pen.color
return pen_color
def draw2(self, pen: Pen):
pen_color = pen.color
return pen_color
red_pin = Pen(2, 'Red')
blue_pin = Pen(1, 'Blue')
paper = Paper(red_pin)
print(paper.draw()) # prints Red
print(paper.draw2(blue_pin)) # prints Blue
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
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))) #<-
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
How do you make this code work: Just have pyglet installed and change assassin1.png,assassin2.png and assassin3.png with the name of images stored in the directory where you saved this code to a file.
import pyglet
def sprite_type(type_ = "standing"):
if type_ == "moving-forward":
moving_forward_image_list = [pyglet.image.load('assassin2.png'), pyglet.image.load('assassin3.png')]
moving_forward_animation = pyglet.image.Animation.from_image_sequence(moving_forward_image_list, 0.3)
return moving_forward_animation
if type_ == "standing":
standing_animation = pyglet.image.load("assassin1.png")
return standing_animation
class Assassin(pyglet.sprite.Sprite):
def __init__(self, batch, img):
pyglet.sprite.Sprite.__init__(self, img, x = 50, y = 30)
class Game(pyglet.window.Window):
def __init__(self):
pyglet.window.Window.__init__(self, width = 315, height = 220)
self.batch_draw = pyglet.graphics.Batch()
self.player_dict = {"standing": Assassin(batch = self.batch_draw, img = sprite_type()), "moving-forward": Assassin(batch = self.batch_draw, img = sprite_type(type_ = "moving-forward"))}
self.player = self.player_dict["standing"]
self.fps_display = pyglet.clock.ClockDisplay()
self.keys_held = []
self.schedule = pyglet.clock.schedule_interval(func = self.update, interval = 1/60.)
def on_draw(self):
self.clear()
self.fps_display.draw()
self.batch_draw.draw()
self.player.draw()
def on_key_press(self, symbol, modifiers):
self.keys_held.append(symbol)
if symbol == pyglet.window.key.RIGHT:
self.player = self.player_dict["moving-forward"]
print "The 'RIGHT' key was pressed"
def on_key_release(self, symbol, modifiers):
self.keys_held.pop(self.keys_held.index(symbol))
self.player = self.player_dict["standing"]
def update(self, interval):
if pyglet.window.key.RIGHT in self.keys_held:
self.player.x += 50 * interval
if __name__ == "__main__":
window = Game()
pyglet.app.run()
This code displays assassin1.png while no key is held at position (50 30), when the right key is held the character moves forward and every 0.3 seconds the 2 images assassin2.png and assassin3.png are switched as to give the illusion of a walking character. Also the fps are shown.
Currently when you release the key the character will return to the original position (50, 30). When you press the key again the character moves from the position where the walking character stopped last.
I understand why this happens because the walking animation character and the standing character are two different instances of the same class.
I first thought using class variables I would be able to store the position. But I can't seem to come up with a working solution. Does anyone have any ideas what I could do?
Edit 1 (#Hugh Bothwell)
import pyglet
def sprite_type(type_ = "standing"):
if type_ == "moving-forward":
moving_forward_image_list = [pyglet.image.load('assassin2.png'), pyglet.image.load('assassin3.png')]
moving_forward_animation = pyglet.image.Animation.from_image_sequence(moving_forward_image_list, 0.3)
return moving_forward_animation
if type_ == "standing":
standing_animation = pyglet.image.load("assassin1.png")
return standing_animation
class Assassin(pyglet.sprite.Sprite):
def __init__(self, batch, img):
pyglet.sprite.Sprite.__init__(self, img, x = 50, y = 30)
def stand(self, batch, img):
self.batch = batch
self.img = img
def move(self, batch, img):
self.batch = batch
self.img = img
class Game(pyglet.window.Window):
def __init__(self):
pyglet.window.Window.__init__(self, width = 315, height = 220)
self.batch_draw = pyglet.graphics.Batch()
self.player = Assassin(batch = self.batch_draw, img = sprite_type())
self.fps_display = pyglet.clock.ClockDisplay()
self.keys_held = []
self.schedule = pyglet.clock.schedule_interval(func = self.update, interval = 1/60.)
def on_draw(self):
self.clear()
self.fps_display.draw()
self.batch_draw.draw()
self.player.draw()
def on_key_press(self, symbol, modifiers):
self.keys_held.append(symbol)
if symbol == pyglet.window.key.RIGHT:
self.player.move(batch = self.batch_draw, img = sprite_type("moving-forward"))
# if I put the following line here instead:
#self.player = self.player.move(batch = self.batch_draw, img = sprite_type("moving-forward"))
# I get this error when pressing the <right-direction> key:
#self.player.draw(), AttributeError: 'NoneType' object has no attribute 'draw'
print "The 'RIGHT' key was pressed"
def on_key_release(self, symbol, modifiers):
self.keys_held.pop(self.keys_held.index(symbol))
self.player.stand(batch = self.batch_draw, img = sprite_type("standing"))
def update(self, interval):
if pyglet.window.key.RIGHT in self.keys_held:
self.player.x += 50 * interval
if __name__ == "__main__":
window = Game()
pyglet.app.run()
Instead of having a standing-character and a walking-character, you need one character who has a movement-state.