Hey guys am developing a game with pygame. The idea of the game is that when a user click on start button on the menu(which appear on the first before starting a game) he must see the two balls bouncing on the pygame window.
For this i have two python files.
bounceball.py
This python file makes the two ball bounce on the pygame window which is made by me.The code of bounceball.py is
here.(Sorry for pasting it on pastebin since its a long code)
menu.py
This python file creates a menu which i have found from the internet and this also works fine.The code of menu.py is here
But the Problem is that when a user clicks on the Start button from the menu it didnt do anything .The thing i want is when a user clicks on start button from menu he must see the ball boucing which i have coded in bounceball.py on the pygame window.How can i link my bounceball.py to the menu.
I have tried to acheive this by many methods but it didnt helped me ..
Hope you guys can help me in acheieving this ..Any help would be appreciated ..Thanks in advance
It could be done better but at least it works.
menu.py
#!/usr/bin/python
import sys
import pygame
import bounceball
#----------------------------------------------------------------------
WHITE = (255, 255, 255)
RED = (255, 0, 0)
BLACK = ( 0, 0, 0)
#----------------------------------------------------------------------
class MenuItem(pygame.font.Font):
def __init__(self, text, font=None, font_size=30,
font_color=WHITE, (pos_x, pos_y)=(0, 0)):
pygame.font.Font.__init__(self, font, font_size)
self.text = text
self.font_size = font_size
self.font_color = font_color
self.label = self.render(self.text, 1, self.font_color)
self.width = self.label.get_rect().width
self.height = self.label.get_rect().height
self.dimensions = (self.width, self.height)
self.pos_x = pos_x
self.pos_y = pos_y
self.position = pos_x, pos_y
def is_mouse_selection(self, (posx, posy)):
if (posx >= self.pos_x and posx <= self.pos_x + self.width) and \
(posy >= self.pos_y and posy <= self.pos_y + self.height):
return True
return False
def set_position(self, x, y):
self.position = (x, y)
self.pos_x = x
self.pos_y = y
def set_font_color(self, rgb_tuple):
self.font_color = rgb_tuple
self.label = self.render(self.text, 1, self.font_color)
#----------------------------------------------------------------------
class GameMenu():
def __init__(self, screen, items, funcs, bg_color=BLACK, font=None, font_size=30,
font_color=WHITE):
self.screen = screen
self.scr_width = self.screen.get_rect().width
self.scr_height = self.screen.get_rect().height
self.bg_color = bg_color
self.clock = pygame.time.Clock()
self.funcs = funcs
self.items = []
for index, item in enumerate(items):
menu_item = MenuItem(item, font, font_size, font_color)
# t_h: total height of text block
t_h = len(items) * menu_item.height
pos_x = (self.scr_width / 2) - (menu_item.width / 2)
pos_y = (self.scr_height / 2) - (t_h / 2) + (index * menu_item.height)
menu_item.set_position(pos_x, pos_y)
self.items.append(menu_item)
self.mouse_is_visible = True
self.cur_item = None
def set_mouse_visibility(self):
if self.mouse_is_visible:
pygame.mouse.set_visible(True)
else:
pygame.mouse.set_visible(False)
def set_keyboard_selection(self, key):
"""
Marks the MenuItem chosen via up and down keys.
"""
for item in self.items:
# Return all to neutral
item.set_italic(False)
item.set_font_color(WHITE)
if self.cur_item is None:
self.cur_item = 0
else:
# Find the chosen item
if key == pygame.K_UP and \
self.cur_item > 0:
self.cur_item -= 1
elif key == pygame.K_UP and \
self.cur_item == 0:
self.cur_item = len(self.items) - 1
elif key == pygame.K_DOWN and \
self.cur_item < len(self.items) - 1:
self.cur_item += 1
elif key == pygame.K_DOWN and \
self.cur_item == len(self.items) - 1:
self.cur_item = 0
self.items[self.cur_item].set_italic(True)
self.items[self.cur_item].set_font_color(RED)
# Finally check if Enter or Space is pressed
if key == pygame.K_SPACE or key == pygame.K_RETURN:
text = self.items[self.cur_item].text
self.funcs[text]()
def set_mouse_selection(self, item, mpos):
"""Marks the MenuItem the mouse cursor hovers on."""
if item.is_mouse_selection(mpos):
item.set_font_color(RED)
item.set_italic(True)
else:
item.set_font_color(WHITE)
item.set_italic(False)
def run(self):
mainloop = True
while mainloop:
# Limit frame speed to 50 FPS
self.clock.tick(50)
mpos = pygame.mouse.get_pos()
for event in pygame.event.get():
if event.type == pygame.QUIT:
mainloop = False
if event.type == pygame.KEYDOWN:
self.mouse_is_visible = False
self.set_keyboard_selection(event.key)
if event.type == pygame.MOUSEBUTTONDOWN:
for item in self.items:
if item.is_mouse_selection(mpos):
self.funcs[item.text]()
if pygame.mouse.get_rel() != (0, 0):
self.mouse_is_visible = True
self.cur_item = None
self.set_mouse_visibility()
# Redraw the background
self.screen.fill(self.bg_color)
for item in self.items:
if self.mouse_is_visible:
self.set_mouse_selection(item, mpos)
self.screen.blit(item.label, item.position)
pygame.display.flip()
#----------------------------------------------------------------------
def run_bounceball():
print "run bounceball"
bounceball.run(screen)
#----------------------------------------------------------------------
if __name__ == "__main__":
pygame.init()
# Creating the screen
screen = pygame.display.set_mode((300, 300), 0, 32)
menu_items = ('Start', 'Quit')
funcs = {'Start': run_bounceball,
'Quit': sys.exit}
pygame.display.set_caption('Game Menu')
gm = GameMenu(screen, funcs.keys(), funcs)
gm.run()
bounceball.py
import pygame
import math
from itertools import cycle
#----------------------------------------------------------------------
# some simple vector helper functions, stolen from http://stackoverflow.com/a/4114962/142637
def magnitude(v):
return math.sqrt(sum(v[i]*v[i] for i in range(len(v))))
def add(u, v):
return [ u[i]+v[i] for i in range(len(u)) ]
def sub(u, v):
return [ u[i]-v[i] for i in range(len(u)) ]
def dot(u, v):
return sum(u[i]*v[i] for i in range(len(u)))
def normalize(v):
vmag = magnitude(v)
return [ v[i]/vmag for i in range(len(v)) ]
#----------------------------------------------------------------------
class Ball(object):
def __init__(self, path, screen):
self.x, self.y = (0, 0)
self.speed = 2.5
self.color = (200, 200, 200)
self.path = cycle(path)
self.set_target(next(self.path))
self.screen = screen
#property
def pos(self):
return self.x, self.y
# for drawing, we need the position as tuple of ints
# so lets create a helper property
#property
def int_pos(self):
return map(int, self.pos)
#property
def target(self):
return self.t_x, self.t_y
#property
def int_target(self):
return map(int, self.target)
def next_target(self):
self.set_target(self.pos)
self.set_target(next(self.path))
def set_target(self, pos):
self.t_x, self.t_y = pos
def update(self):
# if we won't move, don't calculate new vectors
if self.int_pos == self.int_target:
return self.next_target()
target_vector = sub(self.target, self.pos)
# a threshold to stop moving if the distance is to small.
# it prevents a 'flickering' between two points
if magnitude(target_vector) < 2:
return self.next_target()
# apply the balls's speed to the vector
move_vector = [c * self.speed for c in normalize(target_vector)]
# update position
self.x, self.y = add(self.pos, move_vector)
def draw(self):
pygame.draw.circle(self.screen, self.color, self.int_pos, 4)
#----------------------------------------------------------------------
def run(screen):
#pygame.init() # no need it - inited in menu.py
#screen = pygame.display.set_mode((300, 300)) # no need it - created in menu.py
clock = pygame.time.Clock()
quit = False
path = [(26, 43),
(105, 110),
(45, 225),
(145, 295),
(266, 211),
(178, 134),
(250, 56),
(147, 12)]
path2 = [(26, 43),
(105, 10),
(45, 125),
(150, 134),
(150, 26),
(107, 12)]
ball = Ball(path, screen)
ball.speed = 1.9
ball2 = Ball(path2, screen)
ball2.color = (200, 200, 0)
balls = [ball, ball2]
while not quit:
quit = pygame.event.get(pygame.QUIT)
pygame.event.poll()
map(Ball.update, balls)
screen.fill((0, 0, 0))
map(Ball.draw, balls)
pygame.display.flip()
clock.tick(60)
Related
I make a program where I generate circles of random size and position, by means of classes in python, I have managed to generate the circles as I wish but all collide with each other, so I have created a method so that this does not happen but it generates an error that I can not identify "'CreaCir' object has no attribute 'radio'", so I do not know what I'm doing wrong, the terminal tells me that the error is in the circle class in the collision method in the return, if someone could mark my error and give me advice to fix it I would appreciate it.
my code is as follows. first my circle class where I contain the collision method:
class Circulo(PosGeo):
def __init__(self, x, y, r):
self.x = x
self.y = y
self.radio = r
self.cx = x+r
self.cy = y+r
def dibujar(self, ventana):
pg.draw.circle(ventana, "white", (self.x, self.y), self.radio, 1)
def update(self):
pass
def colisionC(self, c2):
return self.radio + c2.radio > sqrt(pow(self.cx - c2.cx, 2) + pow(self.cy - c2.cy, 2))#where I get the error
now a part of my main code:
class CreaCir:
def __init__(self, figs):
self.i = 1
self.r = randint(5, 104)
self.x = randint(0, 500 + self.r)
self.y = randint(0, 300 + self.r)
self.creOne = Circulo(self.x, self.y, self.r)
self.figs.append(self.creOne)
def update(self):
if self.i <100:
choca = False
self.r = randint(5, 104)
self.x = randint(0, 500 + self.r)
self.y = randint(0, 300 + self.r)
self.creOne = Circulo(self.x, self.y, self.r)
for j in range (self.i):
choca = self.creOne.colisionC(self.figs[j])#here is where I use the collision method to my object
if choca == True:
break
if choca == False:
self.figs.append(self.creOne)
self.i+=1
def dibujar(self, ventana):
pass
called:
class Ventana:
def __init__(self, Ven_Tam= (700, 500)):
pg.init()
self.ven_tam = Ven_Tam
self.ven = pg.display.set_caption("Linea")
self.ven = pg.display.set_mode(self.ven_tam)
self.ven.fill(pg.Color('#404040'))
self.figs = []
self.figs.append(CreaCir(self.figs))
self.reloj = pg.time.Clock()
def check_events(self):
for event in pg.event.get():
if event.type == pg.QUIT or (event.type == pg.KEYDOWN and event.key == pg.K_ESCAPE):
quit()
pg.display.flip()
def run(self):
while True:
self.check_events()
for fig in self.figs:
fig.update()
for fig in self.figs:
fig.dibujar(self.ven)
self.reloj.tick(30)
if __name__ == '__main__':
ven = Ventana()
ven.run()
The cause of the error is that you have put a CreaCir object in the figs list. So the first item in figs (self.creOne.colisionC(self.figs[j])) is a CreaCir object and this object has no radio.
Just remove that line of code, it is absolutely unnecessary.
self.figs.append(CreaCir(self.figs))
Create the CreaCir in run, but don't put it in the list:
class Ventana:
def __init__(self, Ven_Tam= (700, 500)):
pg.init()
self.ven_tam = Ven_Tam
self.ven = pg.display.set_caption("Linea")
self.ven = pg.display.set_mode(self.ven_tam)
self.ven.fill(pg.Color('#404040'))
self.figs = []
self.reloj = pg.time.Clock()
def check_events(self):
# [...]
def run(self):
cirCreater = CreaCir(self.figs)
while True:
self.check_events()
cirCreater.update()
for fig in self.figs:
fig.dibujar(self.ven)
self.reloj.tick(30)
To detect the collision of 2 circles, you need to calculate the distance between the centers of the circles and check if the distance is greater than the sum of the radii of the circles. The 3rd argument of pygame.draw.circle is the center of the circle, but not the top left of the bounding box of the circle:
pg.draw.circle(ventana, "white", (self.x, self.y), self.radio, 1)
pg.draw.circle(ventana, "white", (self.cx, self.cy), self.radio, 1)
Complete example:
import pygame as pg
import math, random
class Circulo:
def __init__(self, x, y, r):
self.x, self.y = x, y
self.radio = r
self.cx, self.cy = x+r, y+r
def dibujar(self, ventana):
pg.draw.circle(ventana, "white", (self.cx, self.cy), self.radio, 1)
def colisionC(self, c2):
dx = self.cx - c2.cx
dy = self.cy - c2.cy
return self.radio + c2.radio > math.sqrt(dx*dx + dy*dy)
class CreaCir:
def __init__(self, figs):
self.figs = figs
def update(self):
if len(self.figs) < 100:
choca = False
r = random.randint(5, 104)
x = random.randint(0, 700 - r*2)
y = random.randint(0, 500 - r*2)
creOne = Circulo(x, y, r)
for fig in self.figs:
choca = creOne.colisionC(fig)
if choca == True:
break
if choca == False:
self.figs.append(creOne)
class Ventana:
def __init__(self, Ven_Tam= (700, 500)):
pg.init()
self.ven_tam = Ven_Tam
self.ven = pg.display.set_caption("Linea")
self.ven = pg.display.set_mode(self.ven_tam)
self.figs = []
self.reloj = pg.time.Clock()
def check_events(self):
for event in pg.event.get():
if event.type == pg.QUIT or (event.type == pg.KEYDOWN and event.key == pg.K_ESCAPE):
quit()
def run(self):
cirCreater = CreaCir(self.figs)
while True:
self.check_events()
cirCreater.update()
self.ven.fill(pg.Color('#404040'))
for fig in self.figs:
fig.dibujar(self.ven)
pg.display.flip()
self.reloj.tick(30)
if __name__ == '__main__':
ven = Ventana()
ven.run()
I need to create a base rectangle, and the blitted rectangle (default_rect and rect) for both scaling and scrolling purposes. I'm trying to change the y value of the RECT individually, but it somehow changes the value of default_rect also? I have no idea why. Perhaps the issue is coming from somewhere else. I have tried to create a minimum reproducible example to show what I'm talking about.
class Element(pg.sprite.Sprite):
def __init__(self, img, x, y, placement="center"):
super().__init__()
self.image = img
if placement == "topright":
self.rect = self.image.get_rect(topright=(x, y))
if placement == "center":
self.rect = self.image.get_rect(center=(x, y))
self.default_rect = self.rect
print("Test")
def update_rect(self, y_offset):
self.rect.centery = self.default_rect.centery + y_offset
When calling this update_rect() function, I see no reason why the value of self.default_rect.centery should be affected at all. This is the only place I reference default_rect.centery. I do not think that the issue is coming from the __init__ running multiple times because "Test" is only printed when initalising.
Rest of the minimum reproducible example. The rectangle continuously increases it's y position to -1,000,000 in seconds (without fps limit).
import pygame as pg
pg.init()
screen_width, screen_height = 800, 600
screen = pg.display.set_mode((screen_width, screen_height))
grey = (150, 150, 150)
dark_grey = (60, 60, 60)
class Element(pg.sprite.Sprite):
def __init__(self, img, x, y, placement="center"):
super().__init__()
self.image = img
if placement == "topright":
self.rect = self.image.get_rect(topright=(x, y))
if placement == "center":
self.rect = self.image.get_rect(center=(x, y))
self.default_rect = self.rect
print("Test")
def update_rect(self, y_offset):
self.rect.centery = self.default_rect.centery + y_offset
class ScrollBar(Element):
def __init__(self, x, y, total_bar_w, total_bar_h, total_h):
img = pg.Surface((total_bar_w, total_bar_h))
img.fill(dark_grey)
super().__init__(img, x, y, "topright")
bar_w = 0.98*total_bar_w
bar_h = (total_bar_h / total_h) * total_bar_h
bar_img = pg.Surface((bar_w, bar_h))
bar_img.fill(grey)
self.bar = Element(bar_img, x, y, "topright")
self.total_h = total_h
self.ratio = self.total_h / self.rect.h
self.offset = 0
self.y_offset = 0
self.pressed = False
self.active = False
def update(self):
pos = pg.mouse.get_pos()
click = pg.mouse.get_pressed()[0]
# Check if the slider bar was pressed
if click and self.rect.collidepoint(pos):
self.active = True
# Checking if mouse was released or mouse left the allowed zone
if not click or abs(pos[0] - self.rect.midleft[0]) > self.rect.w*3:
self.active = False
if self.active:
self.bar.rect.centery = pos[1]
if self.bar.rect.top < 0:
self.bar.rect.top = 0
if self.bar.rect.bottom > self.rect.top + self.rect.h:
self.bar.rect.bottom = self.rect.top + self.rect.h
# Calculate y offset for elements. Multiply by negative
# to allow us to add the offset rather than subtract
self.y_offset = (self.bar.rect.centery - self.bar.rect.h / 2) * -self.ratio
def draw(self):
screen.blit(self.image, (self.rect.x, self.rect.y))
screen.blit(self.bar.image, (self.bar.rect.x, self.bar.rect.y))
class Button(Element):
def __init__(self, x, y, w, h):
super().__init__(pg.Surface((w, h)), x, y, "center")
self.clicked = False
def update(self):
self.update_rect(scroll_bar.y_offset)
action = False
if self.rect.collidepoint(pg.mouse.get_pos()):
if pg.mouse.get_pressed()[0] and not self.clicked:
self.clicked = True
action = True
if not pg.mouse.get_pressed()[0]:
self.clicked = False
return action
def draw(self):
pg.draw.rect(screen, "blue", self.rect)
button1 = Button(400, 100, 200, 150)
button2 = Button(400, 300, 200, 150)
button3 = Button(400, 500, 200, 150)
buttons = [button1, button2, button3]
total_height = screen_height * 2
scroll_bar = ScrollBar(screen_width, 0, 0.05 * screen_width, screen_height, total_height)
run = True
while run:
screen.fill("white")
for button in buttons:
button.draw()
if button.update():
print("Button's y value is: " + str(button.rect.y))
scroll_bar.update()
scroll_bar.draw()
for e in pg.event.get():
if e.type == pg.QUIT:
run = False
if e.type == pg.KEYDOWN:
if e.key == pg.K_SPACE:
print(f"{button1.rect.y} / {button2.rect.y} / {button3.rect.y}")
if e.key == pg.K_r:
print(scroll_bar.y_offset)
pg.display.update()
When you assign self.default_rect = self.rect in __init__() both variables are references to the same object. So later in update_rect() when you change self.rect.centery, the change is reflected in self.default_rect because they both refer to the same object.
To understand how this works, check out the excelent talk by Ned Batchelder about names and values.
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.
I imagine this is a simple fix and have seen similar questions, but this is really frustrating me. Basically when you run the game, it only advances the simulation on a keypress, and not at a set framerate.
https://pastebin.com/aP6LsMMA
The main code is:
pg.init()
clock = pg.time.Clock()
FPS = 10
# ...
Game = Control()
while not Game.done:
Game.main_loop()
pg.display.update()
clock.tick(FPS)
pg.quit()
and the event handler method - within the Control class - is:
def event_handler(self):
for event in pg.event.get():
if event.type == pg.QUIT:
self.done = True
elif event.type == pg.KEYDOWN:
self.scene.process_event(event)
The weird thing to me is that, at the bottom of the pastebin, is my old testing code that is not done with classes for Scenes/Control. However, to my eye it should work exactly the same. I've tried putting in the clock both in and out of the Control class to no avail.
Any help (and general tips!) greatly appreciated.
Thanks
process_event() should only change variables which depend on events but it shouldn't update other values which should be updated in every frame. You have to move some elements to new method update() and execute it in every loop.
More or less
class Scene:
def process_event(self, event):
pass
def update(self):
pass
class GamePlayState(Scene):
def process_event(self, event):
self.snake.get_key(event)
def update(self):
self.snake.update()
self.snake.food_check(self.apple)
self.snake.collision_check()
if self.snake.alive == False:
print("GAME OVER")
print(self.snake.points)
self.done = True
class Control:
def update(self):
self.scene.update()
def main_loop(self):
self.event_handler()
self.update()
self.scene_checker()
self.draw()
Full working code
import pygame as pg
import sys
import random
import queue
# TODO: Walls, queue for keypress, scene stuff, 2 player, difficulty(?)
""" ######################
PREAMBLE
###################### """
""" Dictionaries for direction/velocity mapping - stolen from https://github.com/Mekire """
DIRECT_DICT = {"left" : (-1, 0), "right" : (1, 0),
"up" : (0,-1), "down" : (0, 1)}
KEY_MAPPING = {pg.K_LEFT : "left", pg.K_RIGHT : "right",
pg.K_UP : "up", pg.K_DOWN : "down"}
OPPOSITES = {"left" : "right", "right" : "left",
"up" : "down", "down" : "up"}
""" Colour Mapping """
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
DARK_GREY = (70, 70, 70)
GREY = (211, 211, 211)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
COLOUR_MAP = {"snake": GREEN, "apple": RED, "wall": BLACK, "surface": GREY, "background": DARK_GREY }
""" ################
CLASSES
################ """
""" ####################### Object Classes ########################## """
class Square:
""" All other objects in the game will be built up from this """
def __init__(self, pos, colour, length):
self.xi, self.yi = pos # i for index, p for pixel
self.colour = colour
self.length = length
def display(self):
xp, yp = self.sq_to_pixs(self.xi, self.yi) # (x = left side, y = top edge)
pg.draw.rect(screen, self.colour, (xp, yp, self.length, self.length), 0)
def sq_to_pixs(self, x, y):
# Converts index of square to pixel coords
px = (x+1)*(2*MARGIN + SQUARE_SIZE) - MARGIN - SQUARE_SIZE
py = (y+1)*(2*MARGIN + SQUARE_SIZE) - MARGIN
return (px, py)
def index_coords(self): # TODO - remove for direct ref?
return (self.xi, self.yi)
class Arena:
""" A grid within which the game takes place """
def __init__(self, size, square_length, colour):
self.size = size # i.e. number of squares = size**2 for square arena
self.length = square_length # i.e. per square dimension
self.colour = colour
self.squares = [ [] for i in range(self.size) ]
for y in range(self.size):
for x in range(self.size):
self.squares[y].append(Square((x,y), self.colour, self.length))
def display(self):
for y in self.squares:
for square in y:
square.display()
class Snake:
""" Class for the agent(s) """
def __init__(self, pos, colour, square_length):
self.xi, self.yi = pos
self.colour = colour
self.size = 3
self.length = square_length
self.direction = "right"
self.direction_queue = queue.Queue(4) # TODO
self.points = 0
self.growing = False
self.alive = True
self.squares = []
for x in range(self.size): # horizontal initial orientation
self.squares.append(Square((self.xi - x, self.yi), self.colour, self.length))
def display(self):
for square in self.squares:
square.display()
def food_check(self, apple):
if self.squares[0].index_coords() == apple.square.index_coords():
self.growing = True
self.points += apple.points_value
apple.respawn([self])
def collision_check(self, walls = None):
xh, yh = self.squares[0].index_coords()
body = self.squares[-1:0:-1] # going backwards thru array as forwards [0:-1:1] didnt work...
def _collide(obstacles):
for sq in obstacles:
_x, _y = sq.index_coords()
if (_x == xh) and (_y == yh):
self.alive = False
_collide(body)
if walls is not None:
_collide(walls)
def update(self):
# Add new head based on velocity and old head
velocity = DIRECT_DICT[self.direction]
head_coords = [ (self.squares[0].index_coords()[i] + velocity[i]) for i in (0,1) ]
# Wrap around screen if reach the end
for i in (0, 1):
if head_coords[i] < 0:
head_coords[i] = SQUARES_PER_ARENA_SIDE - 1
elif head_coords[i] > SQUARES_PER_ARENA_SIDE - 1:
head_coords[i] = 0
self.squares.insert(0, Square(head_coords, self.colour, self.length))
if self.growing:
self.growing = False
else:
del self.squares[-1]
"""
def queue_key_press(self, key):
for keys in KEY_MAPPING:
if key in keys:
try:
self.direction_queue.put(KEY_MAPPING[keys], block=False)
break
except queue.Full:
pass
"""
class Player(Snake):
""" Human controlled snake via arrow keys """
def __init__(self, pos, colour, size):
Snake.__init__(self, pos, colour, size)
def get_key(self, event):
if event.type == pg.KEYDOWN and event.key in KEY_MAPPING:
new_direction = KEY_MAPPING[event.key]
if new_direction != OPPOSITES[self.direction]:
self.direction = new_direction
class Apple:
""" Food our (veggie) snake is greedily after """
def __init__(self, colour, length, points_value, snake):
self.colour = colour
self.length = length
self.xi, self.yi = self._rand_coords()
self.points_value = points_value
self.square = Square((self.xi, self.yi), self.colour, self.length)
def _rand_coords(self):
rand_num = lambda x: random.randint(0, x)
_x = rand_num(SQUARES_PER_ARENA_SIDE-1)
_y = rand_num(SQUARES_PER_ARENA_SIDE-1)
return _x, _y
def respawn(self, obstacles):
_x, _y = self._rand_coords()
for ob in obstacles:
for sq in ob.squares:
while sq.index_coords() == (_x, _y):
_x, _y = self._rand_coords()
self.square.xi, self.square.yi = _x, _y
def display(self):
self.square.display()
""" ################ SCENES ####################### """
class Scene:
""" Overload most of this - barebones structure
A bit pointless in current state but easily expanded """
def __init__(self):
self.done = False
def when_activated(self):
pass
def reset(self):
self.done = False
def render(self):
pass
def process_event(self, event):
pass
def update(self):
pass
class StartUp(Scene):
def __init__(self):
Scene.__init__(self)
def render(self):
# test placeholder
pass
def when_activated(self):
print("Press any key to continue")
def process_event(self, event):
if event.type == pg.KEYDOWN:
self.done = True
class GamePlayState(Scene):
def __init__(self):
Scene.__init__(self)
self.arena = Arena(SQUARES_PER_ARENA_SIDE, SQUARE_SIZE, COLOUR_MAP["surface"])
self.snake = Player(SNAKE_START, COLOUR_MAP["snake"], SQUARE_SIZE)
self.apple = Apple(COLOUR_MAP["apple"], SQUARE_SIZE, 1, self.snake)
self.font = pg.font.SysFont("courier new", 50)
def render(self):
screen.fill(COLOUR_MAP["background"])
self.arena.display()
self.apple.display()
self.snake.display()
text = self.font.render(str(self.snake.points), True, [255,255,255])
screen.blit(text, (500, 400))
def process_event(self, event):
self.snake.get_key(event)
def update(self):
self.snake.update()
self.snake.food_check(self.apple)
self.snake.collision_check()
if self.snake.alive == False:
print("GAME OVER")
print(self.snake.points)
self.done = True
""" ################## CONTROL CLASS #########################"""
class Control:
def __init__(self):
#self.clock = pg.time.Clock()
#self.fps = FPS
self.done = False
self.scene_array = [StartUp(), GamePlayState()]
self.scene_index = 0 # dirty way whilst dict method needs tinkering
#self.scene_dict = {"START": StartUp(), "GAME": GamePlayState()} #TODO
self.scene = self.scene_array[self.scene_index]
#self.scene = self.scene_dict["START"]
self.scene.when_activated()
def event_handler(self):
for event in pg.event.get():
if event.type == pg.QUIT:
self.done = True
elif event.type == pg.KEYDOWN:
self.scene.process_event(event)
def scene_checker(self):
if self.scene.done:
self.scene.reset() # for reuse - TODO
self.scene_index = (self.scene_index + 1) % len(self.scene_array)
self.scene = self.scene_array[self.scene_index]
self.scene.when_activated()
#self.scene = self.scene_dict[self.scene.next]
def update(self):
self.scene.update()
def draw(self):
self.scene.render()
def main_loop(self):
self.event_handler()
self.update()
self.scene_checker()
self.draw()
""" ################ RUN GAME ################ """
""" Game paramaters """
SQUARE_SIZE = 20 # pixels
SQUARES_PER_ARENA_SIDE = 20 # squares
MARGIN = 2 # pixels
SNAKE_START = (int(SQUARES_PER_ARENA_SIDE/2), int(SQUARES_PER_ARENA_SIDE/2)) # square coords
pg.init()
clock = pg.time.Clock()
# Square.display() and a few others need a direct reference to "screen" TODO implement better
w, h = 620, 620
SCREEN_SIZE = [w, h]
FPS = 10
screen = pg.display.set_mode(SCREEN_SIZE)
Game = Control()
while not Game.done:
Game.main_loop()
pg.display.update()
clock.tick(FPS)
pg.quit()
Ok, so I am using pygame.draw to make a stick figure in the class called Entity:
class Entity: #Used with default arguments blited on a 600 by 600 pixel screen
def __init__(self, pos=[300, 300]):
self.pos = pos
self.legR = [10, 25]
self.legL = [-10, -25]
self.armR = [0, 0]
self.armL = [0, 0]
self.body = [30, 5]
self.head = [0, 0, 5]
self.size = [60, 110]
self.color = [0, 0, 0]
self.image = pygame.surface.Surface(self.size)
self.image.fill([255, 255, 255])
def render(self, screen, frame):
self.image = pygame.surface.Surface(self.size)
self.image.fill([255, 255, 255])
pygame.draw.line(self.image, self.color, [self.size[0]/2, self.size[1]/2],
[self.size[0]/2+self.legR[0], self.size[0]/2+self.legR[1]], 5)
pygame.draw.line(self.image, self.color, [self.size[0]/2, self.size[1]/2],
[self.size[0]/2+self.legL[0], self.size[0]/2+self.legL[1]], 5)
pygame.draw.line(self.image, self.color, [self.size[0]/2, self.size[1]/2],
[self.size[0]/2+self.body[0], self.size[0]/2+self.body[1]], self.body[1])
pygame.draw.circle(self.image, self.color,
[self.size[0]/2+self.body[0]+self.head[0], self.size[1]/2+self.body[1]+self.head[1]],
self.head[2])
#pygame.draw.line(self.image, self.color, [self.size/2
screen.blit(self.image, self.pos)
So I run this and it gives me this weird messed up image with a bunch of lines in random directions. It seams to me I do not really understand the function well. Could I please have a example of a render able stick figure with configurable joints? If not, could someone please at least tell me my fatal error? Thanks!
I started writing an example based off your code. For now it just draws 2 legs and his spine:
Note:
Using vectors instead of tuples would let you do return
self.pos+offset vs return (self.pos[0]+offset[0],
self.pos[1]+offset[1])
I use offsets relative a local origin to draw.
code:
import pygame
from pygame.locals import *
pygame.init()
# not normally all global, but simplified demo
color_bg = Color("gray20")
color_fg = Color("gray80")
clock = pygame.time.Clock()
screen = pygame.display.set_mode((600,400))
class Entity():
def __init__(self, pos=(300, 300)):
self.pos = pos
self.armR = (10, 10)
self.armL = (-10, 10)
self.body = (0, -20)
self.head_offset = self.offset(self.body)
def offset(self, offset):
# get offset to draw, relative stickman's hips
return (self.pos[0]+offset[0], self.pos[1]+offset[1])
def render(self):
b = self.pos
#pygame.draw.line( screen, color_fg, (10,10), (20,30) )
o = self.offset( self.armL )
pygame.draw.line( screen, color_fg, b, o )
o = self.offset( self.armR )
pygame.draw.line( screen, color_fg, b, o )
o = self.offset( self.body )
pygame.draw.line( screen, Color("red"), b, o )
class Game():
def __init__(self):
self.e = Entity()
def draw(self):
screen.fill( color_bg )
self.e.render()
pygame.display.flip()
clock.tick(80)
def loop(self):
done=False
while not done:
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT: done = True
# event: keydown
elif event.type == KEYDOWN:
if event.key == K_ESCAPE or event.key == K_SPACE: done = True
elif event.type == MOUSEMOTION:
self.mouse_loc = event.pos
self.draw()
g = Game()
g.loop()
The main problem I see is that you're using the x-value of your entity's size for the y-value of one of the points on your lines:
pygame.draw.line(self.image, self.color, [self.size[0]/2, self.size[1]/2],
[self.size[0]/2+self.legR[0], self.size[0]/2+self.legR[1]], 5)
The second value in the third argument should be:
self.size[1]/2+self.legR[1]
That will get you want you want rendered, but I would also takes monkey's advice and organize a bit and compartmentalize repeated code into functions.