I am currently working on a pygame, trying to animate my character so that as the player moves him the program will cycle through four sprite image subsurfaces. I already setup a class for this:
import pygame
from pygame.locaks import *
class Prota:
def __init__(self, sheet):
self.sheet = pygame.image.load(sheet).convert_alpha()
self.image_rect = self.sheet.get_rect()
self.image_rect_h = (self.image_rect.height) #num of rows
self.image_rect_w = (self.image_rect.width/16) #num of columns
self.image_reel = self.fetch()
self.image_cue = 0 #index: 0 - 3 (Right), 4 - 7 (Left), 8 - 11 (Front), 12 - 15 (Back)
self.clock = pygame.time.Clock()
def draw(self, screen):
self.clock.tick(60)
screen.blit(self.image_reel[self.image_cue], (400, 300))
def fetch(self):
sprites = []
for x in range(0, 15):
self.sheet.set_clip(pygame.Rect(self.image_rect_h*x, 0, self.image_rect_w, self.image_rect_h))
sprite = self.sheet.subsurface(self.sheet.get_clip())
sprites.append(sprite)
return sprites
And it worked perfectly when I used a dummy sprite sheet (just a simple 50 x 50 square sprite that changes colors), but when I tried to implement my (partially complete) actually character sheet, I got back
ValueError: subsurface rectangle outside surface area
I am not sure if it's the size of sheets (the dummy sheet was 832 x 52px, and the character sheet is 1008 x 79px), or what, and I can't seem to find any article that addresses this issue. (The closest I could find in a quick search was How to rotate images in pygame
Any ideas?
The mistake was to use self.image_rect_h*x as the first argument (the x-coord) of the pygame.Rect. Changing it to self.image_rect_w*x fixed it.
self.sheet.set_clip(pygame.Rect(self.image_rect_w*x, 0, self.image_rect_w, self.image_rect_h))
I'm not sure why the error occurs, because pygame.Surface.set_clip and .get_clip should be restricted to the surface area. You should post the full traceback (error message).
I actually think it would be better to let the program crash if something is wrong with the image loading and cutting, so that it's easier to find the error, otherwise you would get incorrect sprites anyway. So you could just use pygame.Surface.subsurface instead of set_clip andget_clip.
rect = (self.image_rect_w*x, 0, self.image_rect_w, self.image_rect_h)
sprite = self.sheet.subsurface(rect)
Related
I am making a game where there are two players and they can shoot each other. Their movement will be defined by a rotation around a fixed point, the point will be(600, 300), which is the center of our screen. The player will keep rotating around the point as long as they are pressing a certain button(which is keep providing force to our player) else they will fall(due to gravity). I think it would help to think of it as a ball attached to a point using a string. The string is attached as long as a button is pressed and gets unattached as soon as the button is released and the ball flies off. Here is my player class
class Player:
def __init__(self):
self.pos = [500, 200]
self.width = 30
self.height = 30
self.player = pygame.image.load("player.png").convert_alpha()
self.player = pygame.transform.scale(self.player, (self.width, self.height))
self.rect = self.player.get_rect()
self.rotated_player = None
self.anguler_vel = 0
self.Fg = 0.05
self.Fp = 0
self.arm_length = 0
Fp is the force perpendicular to the force of gravityFg. Fg is the force which is pulling it down on our player. Fp is defined by math.sin(theta) * Fg. I am keeping track of Fp because i want the player to keep moving in the direction of rotation after its unattatched from the string. arm_length is the length of the string.
I have a Point class, which is the point about which our player will rotate. Here's the point class.
class Point:
def __init__(self,x, y):
self.pos = [x, y]
dx = self.pos[0] - player.pos[0]
dy = self.pos[1] - player.pos[1]
self.angle = math.atan2(dy, dx)
Now, i need help with the actual rotation itself. I am aware that adding a certain value to the angle every single frame would make it go around. But how would i make it go around a certain point that i specify and how would the arm length tie into this?. I find that it is really difficult to implement all of this because the y-axis is flipped and all the positional values have to be scaled down when using them in calculations because of the FPS rate. Any help on how this is done would be appreciated as well. Thanks
When you use pygame.transform.rotate the size of the new rotated image is increased compared to the size of the original image. You must make sure that the rotated image is placed so that its center remains in the center of the non-rotated image. To do this, get the rectangle of the original image and set the position. Get the rectangle of the rotated image and set the center position through the center of the original rectangle. e.g.:
def rotate_center(image, rect, angle):
rotated_image = pygame.transform.rotate(image, angle)
new_rect = rotated_image.get_rect(center = rect.center)
return rotated_image, new_rect
screen.blit(*rotate_center(image, image_rect, angle))
Alos see How do I rotate an image around its center using PyGame? and How to rotate an image(player) to the mouse direction?
I have an Agar.io like test game where the square player touches the "bit"s and grows. The problem is the bit generation. The bit generates every second and has its own surface. But, the program will not blit the bit (no pun intended) into the initial surface.
Here is the code:
import pygame
import random
pygame.init()
clock = pygame.time.Clock()
display = pygame.display
screen = display.set_mode([640,480])
rect_x = 295
rect_y = 215
display.set_caption("Agar")
d = False
bits = []
size = 50
steps = 0
class Bit:
cscreen = display.set_mode([640,480])
circ = pygame.draw.circle(cscreen, (65, 76, 150), (random.randrange(40, 600), random.randrange(40, 440)), 5)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
d = True
if d == True:
break
# Fill background
screen.fill((5, 58, 0))
# Create player object
player = pygame.draw.rect(screen, (250, 197, 255), [rect_x, rect_y, size, size])
# Moving
if pygame.key.get_pressed()[pygame.K_UP]:
rect_y -= 2
if pygame.key.get_pressed()[pygame.K_DOWN]:
rect_y += 2
if pygame.key.get_pressed()[pygame.K_RIGHT]:
rect_x += 2
if pygame.key.get_pressed()[pygame.K_LEFT]:
rect_x -= 2
# Bit generation
if steps == 60:
new_bit = Bit()
bits.append(new_bit.circ)
screen.blit(new_bit.cscreen, (0,0))
steps = 0
steps += 1
# Collision detection
collision = player.collidelist(bits)
if collision != -1:
bits[collision].cscreen.fill((0,0,0,0))
# Make player larger
size += 10
# FPS limit
clock.tick(60)
# Refresh
display.flip()
P.S. There is no error message.
Thanks in advance.
To have each Bit object have different locations and instance variables, you need to have an __init__ method in your bit class. By adding an __init__, and adding self. to the variables, the blitting works.
But a word of warning. When you make a separate cscreen for each object, and then blit that cscreen to your actual screen, you get rid of every other cscreen thats been blitted, making only 1 Bit viewable at a time. Even if you had cscreen outside of __init__, you would have an issue erasing older ones now, so I advise you take a different approach.
My recommendation is to find a small picture of a dot, and write these lines in the Bit's __init__ method.
self.image = pygame.image.load("reddot.png")
self.rect = self.image.get_rect()
Once you create your bit, add it to a pygame.sprite.Group(). These groups have very handy built in methods. One of them being .draw(). It will go through your Group of Bits and use each's self.image and self.rect to draw them in the correct place every time, without you having to worry about it. When a Bit is eaten, you can delete the Bit from the Group and it is now erased for you.
A random side note: I recommend you change your event loop to just break when it event.type == pygame.QUIT. That way, Python does not need to check the if d == True statement on every frame, while making the code slightly more readable. If it only breaks out of the for loop (for me it exited fine), you can use sys.exit() after importing sys
I've created a surface that I've used pixel array to put pixels on, but i want to make the surface transparent but leaving the pixels opaque, I've tried making the surface transparent then drawing the pixels tot he surface but that just makes the pixels also transparent, any help or something I've missed?
-Edit- Hopefully this'll help in some way, this is the class object that creates the surface that is the galaxy
Also I have stated what I've tried, there's not much more to tell
class Galaxy(object):
def __init__(self,posx=0,posy=0,radius=0,depth=0):
radius = int(radius)
self.size = [radius*2,radius*2,depth]
self.posx = posx
self.posy = posy
self.radius = radius
#create array for stars
self.starArray = []
#create surface for stars
self.surface = pygame.Surface([radius*2,radius*2])
self.starPixel = pygame.PixelArray(self.surface)
#populate
for x in range(radius*2):
for y in range(radius*2):
#generate stars
num1 = noise.snoise2(x+posx,y+posy,repeatx=radius*10,repeaty=radius*10)
distance = math.sqrt(math.pow((x-radius),2)+math.pow((y-radius),2))
if distance < 0:
distance = distance * -1
#print(x,y,"is",distance,"from",radius,radius)
val = 5
#glaxy density algorithm
num = (num1 / ( ((distance+0.0001)/radius)*(val*10) )) * 10
#density
if num > (1/val):
#create star
self.starArray.append(Stars(x,y,seed=num1*100000,distance=distance))
#print(num*1000)
self.addPixels()
#adds all star pixels to pixel array on surface
def addPixels(self):
for i in self.starArray:
self.starPixel[i.x,i.y] = i.colour
del self.starPixel
#sends to screen to await rendering
def display(self):
screen.displaySurface(self.surface,[self.posx+camPosX,self.posy+camPosY])
Use MyGalaxy.set_colorkey(SomeUnusedRGB) to define a zero-alpha (invisible) background colour, fill MyGalaxy with that colour, then draw the pixels on top of that. You can use pixelArray functions to draw to that surface, but you're probably better to use MyGalaxy.set_at(pixelLocationXY, pixelColourRGB) instead, for reasons of managability and performance.
Make sure that SomeUnusedRGB is never the same as any pixelColourRGB, or those pixels won't appear (since pygame will interpret them as invisible). When you blit MyGalaxy to wherever you want it, it ought to only blit the non-SomeUnusedRGB-coloured pixels, leaving the rest unaltered.
(This is the best I can offer you without knowing more about your code; revise the question to include what you're already trying, and I'll update this answer.)
I want to add a grid to my level that stays with the terrain and not the screen. The way I thought of doing it is to add all the lines that form the grid as sprites and move them with the terrain, but I can't figure out how to represent the line as an image.
I tried to do this myself, but had no success.
EDIT: Here's what I've tried
class Grid():
def __init__(self):
self.grid = pygame.Surface(size)
self.grid.set_colorkey((0,0,0))
def draw(self):
# DRAW TILE LINES ----------------------------------------------------------
grid_x = 0
grid_y = 0
for i in range(total_level_width // TILE_SIZE):
pygame.draw.aaline(self.grid,BLACK,[grid_x,0],[grid_x,total_level_height])
pygame.draw.aaline(self.grid,BLACK,[0,grid_x],[total_level_width,grid_y])
grid_x += TILE_SIZE
grid_y += TILE_SIZE
# tile test
pygame.draw.rect(screen,BLACK,(49*TILE_SIZE,34*TILE_SIZE,TILE_SIZE,TILE_SIZE))
screen.blit(self.grid,(0,0))
Creating the object:
grid = Grid()
Calling class: (in main program loop)
grid.draw()
I had a similar problem while i was trying to do a project. I used the following code to get a line onto a surface and then bliting it onto my screen. I hope this entire function might help you.
def blitBoundary(self):
""" helper function to blit boundary on screen """
# create a surface
self.boundSurf=pygame.Surface((1024,768))
self.boundSurf.set_colorkey((0,0,0))
"""
if not self.boundary.closePoly:
(x,y)=pygame.mouse.get_pos()
pointList=self.boundary.pointList +[[x,y]]
else:
pointList=self.boundary.pointList"""
if len(pointList)>1:
pygame.draw.aalines(self.boundSurf, (255,255,255),
self.boundary.closePoly , pointList, 1)
self.screen.blit(self.boundSurf,(0,0))
I was trying to draw a polygon. The commented out the if statement that would be most probably not useful for you.
All my lines were in a polygon class object.
You might want to look into pygame.draw.aalines function.
I thought you guys might be able to help me wrap my head around this. I want to be able to generate rects and assign images to those rects. I've been doing this for the whole project and isn't too hard. The hard part here is that I want this particular function to be able to generate as many different rects as I want. The project is a game that takes place on like a chess board. I figure I can write like... if statements for every single space and then have like a bazillion parameters in the function that dictate which rects get generated and where, but I was hoping someone might be able to think of a more elegant solution.
You could use two nested "for" loops --
def make_chessboard(upper_x=0, upper_y=0, size=30):
chessboard = []
for y in range(8):
row = []
for x in range(8):
coords = (upper_x + x * size, upper_y + y * size)
row.append(pygame.Rect(coords, (size, size)))
chessboard.append(row)
return chessboard
Then, to get the rect that's in the top-left corner, you could do chessboard[0][0]. To get the rect that's in the top-right corner, you could do chessboard[0][7].
You wouldn't be able to explicitly name each rect, but then again, you really wouldn't need to.
Note: I'm assuming that you wanted to create a chessboard-like pattern of rects of some kind. I can edit my question if you detail specifically what you're trying to do.
class ChessTile(pygame.sprite.Sprite):
def __init__(self, image, location):
pygame.sprite.Sprite.__init__(self)
self.image = image.convert()
self.mask = pygame.mask.from_surface(self.image)
self.rect = pygame.Rect(location, self.image.get_size())
Then make another method called like "MakeBoard". Call MakeBoad and have a loop setup with the size of the board. so the pseudo code would be:
(let's assume "img" is a 32x32 white or black square)
for y in range(0,7):
for x in range(0,7):
# alternate the tile image from black/white before calling ChessTile with it
# the location parameter is going to be x*32,y*32.. something like that
# so you'd have a tile at (0,0) then at (32,0), then (64,0), etc...
# after the first "j" run is done, "i" increments so now we have
# (0, 32), (32, 32), etc etc.
#
tile = ChessTile(img, (x,y))
then just draw the tile object as you normall would in some render method!
hope that helps!!!