I've seen several different posts about changing a sprite image.
For the current assignment, I have to construct a packman sprite, then it should follow the mouse. No problem there.
Here is that bit of code:
class Packman(games.Sprite):
"""Create the packman that is conrolled by the mouse"""
#load the packman image
image_right = games.load_image("snap_right.png") # initial value
image_left = games.load_image("snap_left.png")
show_image = image_right
def __init__(self, x=games.mouse.x, y = games.mouse.y):
"""Initialise packman"""
super(Packman, self).__init__(image = Packman.show_image, x = games.mouse.x, y = games.mouse.y)
def update(self):
"""Move packmans coordinates"""
self.x = games.mouse.x
self.y = games.mouse.y
if self.left < 0:
self.left = 0
if self.right > games.screen.width:
self.right = games.screen.width
#change Packman's direction that he is facing`
As you can see I try and load two images, but only one image at a time will be displayed.
(I reckon there is a way of just flipping one image horizontally, in stead of using two images.) As is, I can move Packman around. Now I need to add the bit that makes Packman face left/right depending on the direction the mouse is traveling in. My handbook gives me an example to rotate the image through 180deg with keypresses, which work, but then packman is just upside down, with his eye at the bottom.
Is there another way of flipping packman depending on mouse direction? (I only need horizontal flip, i.e. left and right)
Well I use Pygame for my sprites but the solution is pretty simple.
I basically use something like this. Two pointers pointing to initialized images. And then a pointer to pointers called image which will be what we use to draw the sprites.
# image is the pointer that points to the pointer that points to the image being currently used
image1 = pygame.image.load('left.png')
image2 = pygame.image.load('right.png')
image = image1
Then in draw I just do
screen.blit(image, position)
Now since you are using the mouse to track the position of pacman. What you have to do is this. At EACH frame, store the location of the mouse x and y in a class variable. Call it something like old_x and old_y. On the next frame, simply compare your mouse x position to your old_x. If the mouse position is greater, your pacman is trying to move to the right. image = image2 If your mouse position is less, then your pacman is moving to the left. image = image1 Apply the necessary state changes and change your image accordingly.
Related
I am coding a prototype platformer in pygame. I'm using a .png as a tilesheet, I load it and then get a list tileset.tiles of all the different tile textures in it. I then use three layers of .csv tilemaps to associate every tile in the grid with its own corresponding texture. I bake all of the tile layers onto a map surface once, and then blit this surface at every frame.
The problem is that the outcome is not as expected, apparently not all of the tiles are properly blit onto the surface. The problem seems to arise when the same subsurface has to be blit a second time by the load method in the Room class. It's not clear to me what exactly causes this. I have tried playing around with the .csv files, and it seems that different arrangements of tiles, even across layers, have an influence on what is actually rendered on screen. I've added screenshots to illustrate this better. For reference, the id number 8 corresponds to a blue square texture, which should be the sky. The other numbers correspond to several different textures.
bottom layer csv
middle layer csv
top layer csv
outcome
By changing the first tile of the middle layer no difference is shown (tile 0 corresponds to the flower texture):
alternate middle layer
outcome
Or, if I try and change the first few tiles of the bottom layer:
alternate bottom layer
outcome
Generally, if I change some tile number in a csv file, weird things happen, and other seemingly random tiles get blit. Also, I am able to manually place tiles at any position on the screen without any problems (bypassing the load method and directly blitting subsurfaces from the tileset class) so I think that the tileset class is working properly.
Here's the full code:
import pygame as pg
class Tileset:
def __init__(self,img:pg.Surface):
self.tiles = []
self.img = img
self.loadtiles()
def loadtiles(self):
for i in range(16):
for n in range(16):
currentimg = self.img.subsurface(32*n,32*i,32,32)
self.tiles.append(currentimg.copy())
class Tile(pg.sprite.Sprite):
def __init__(self,image:pg.Surface,position:tuple):
self.img = image
self.pos = position
def draw (self,surface:pg.Surface):
surface.blit(self.img,self.pos)
class Room:
def __init__(self,id,size:tuple):
self.id = id
self.layers = [[],[],[]]
self.size = (size[0]*32,size[1]*32)
# Call when a new room must be loaded: reads room csv, stores tile info, overwrites drawn map with new map
def load(self,map):
map = pg.Surface(self.size)
for layer in self.layers:
with open ('levels/final/room'+str(self.id)+'_'+str(self.layers.index(layer))+'.csv') as file:
data = file.readlines()
for rrrow in data: # unprocessed row
rrow = rrrow.strip('\n').split(',') # semi processed row
row = [] # processed row
for rtile in rrow: # unprocessed tile in row
if rtile != -1:
tileimg = tileset.tiles[int(rtile)]
tile = Tile(tileimg,(rrow.index(rtile)*32, data.index(rrrow)*32)) # process tile
tile.draw(map) # draw tile on current tilemap
row.append(tile) # store tile in row
layer.append(row) # store row in layer (to use later for collisions)
return map
### pygame loop setup (incomplete, shouldn't matter ###
res = (32*30,32*20)
scr = pg.display.set_mode(res)
tileset = Tileset(pg.image.load('graphics\stock.png'))
room0 = Room(0,(30,20))
map = pg.Surface((0,0))
map = room0.load(map)
running = True
while running:
scr.fill((0,0,0))
scr.blit(map,(0,0))
pg.display.flip()
I have just solved it. The problem was in the use of the '.index' method to determine what the position of that tile would be. .index would just return the first occurrence of that tile in the list, and not the actual one, so any time a tile would have to appear for a second time in the same row, or every time two rows looked the same, that tile/row would just be positioned on top of its previous copy. Solved by using i and n as counters for the tile and row positions instead of the .index method.
I am making a game using pygame and Tiled to design the map. I am able to load the tiles and blit them on to the screen. When i flip the tile horizontally/rotate inside of Tiled and run the game, the tiles are not rotated inside of the game.
So i would want to check if the tile is rotated/flipped before blitting the tile onto the screen and i am not sure how exactly to do this or whether if this is even possible and proceed to use the tiles without any rotations or flips inside of Tiled?
I read that there are flags you can check for, but at this point i am not sure of that either, any help would be great. Heres the class used to make the map
import pytmx
class TiledMap:
def __init__(self, filename):
tm = pytmx.load_pygame(filename)
self.width = tm.width * tm.tilewidth
self.height = tm.height * tm.tileheight
self.tmxdata = tm
def render(self, surface):
ti = self.tmxdata.get_tile_image_by_gid
for layer in self.tmxdata.visible_layers:
if isinstance(layer, pytmx.TiledTileLayer):
for x, y, gid, in layer:
tile = ti(gid)
if tile:
# check for flags first.. ie rotating, flips
# do the neccessary transformations... this part i know
# blit the transformed tile
# if no flags just blit the image
surface.blit(tile, (x * self.tmxdata.tilewidth,
y * self.tmxdata.tileheight))
how the same tile flipped horizontally appears inside of Tiled
how the image gets loaded when i run the game
Okay so after reading Tile flipping, here's what i understood from it.
Get the gid
Already have that from this bit of code for x, y, gid, in layer:
Check for the flags
The highest three bits of the gid store the flipped states.
Says that 32nd bit is for horizontal flip. So to check for that we do h_flipped = gid & (1<<31) # correct? But the problem here is when i do that it evaluates to a 0. Printing out the gids gives 1 for the unflipped tile and 2 for the flipped so 2 & (1<<31) # false
The pseudo code was a bit confusing and maybe i understood it incorrectly. So either the gids i am getting is incorrect or the checking for the bit is incorrect? or Both? More help appreciated
I am trying to make a sprite move directly towards the mouse, utilizing the angle between them. This angle is found via the atan2 function. While this angle works fine for rotating the sprite towards the mouse, the sprite moves in the wrong directions depending on the quadrant of the given angle. It will sometimes freeze up in one quadrant, or move directly opposite the mouse.
I am using basic Trig functions to find the angle, and calculate proper additions to the X and Y variables of the sprite. It is also important to note that the angle I calculate, while it doesn't work for movement, does work perfectly for rotation. What's odd is that I pass the X-difference between the two spots, and THEN the Y-difference, which is the opposite of how the inverse tangent function is supposed to be handled. Therefore, I'm not even sure how this angle has been making rotation work correctly.
I've attempted to pass the Y-difference and the X-difference (in that order) into the atan2 function. However, this causes the rotation on my sprite to be wrong, pointing me towards the idea that the angle as a whole is also incorrect. I've also tried following along with numerous other programs, all of which use the same formulas as me. However, these don't work, even when I change the order of the arguments to the atan2 function to match the example programs.
def main():
ExitLoop = False
image = IMAGELOADER.AllImages["Fighter1"]
image2 = IMAGELOADER.AllImages["Fighter2"]
Fighter1 = FighterClass.Fighter(image,(700,700))
Fighter2 = FighterClass.Fighter(image2,(300,300))
while not ExitLoop:
ScreenController.Refresh()
mouse_pos = pygame.mouse.get_pos()
Fighter2.set_x(mouse_pos[0]-32)
Fighter2.set_y(mouse_pos[1]-32)
angle = math.atan2(Fighter1.get_x()-mouse_pos[0]+32, Fighter1.get_y()-mouse_pos[1]+32)
degrees_angle = math.degrees(angle)
Fighter1.rotate(degrees_angle)
xval = Fighter1.get_x()
yval = Fighter1.get_y()
speed = Fighter1.get_speed()
changex = (speed*math.cos(angle))
changey = (speed*math.sin(angle))
Fighter1.set_x(xval+changex)
Fighter1.set_y(yval+changey)
for event in pygame.event.get():
if event.type == pygame.QUIT:
ExitLoop = True
ScreenController.Draw(Fighter1.get_image(),Fighter1.get_rect(),False)
ScreenController.Draw(Fighter2.get_image(),Fighter2.get_rect(),False)
ScreenController.DisplayUpdate()
clock.tick(60)
Class Code (Relevant to the fighter class)
import pygame
import WoodysFunctions
class Fighter(pygame.sprite.Sprite):
def __init__(self,image,XnY):
pygame.sprite.Sprite.__init__(self)
self.image = image
self.__image_source = image
self.rect = self.image.get_rect()
self.__mask = pygame.mask.from_surface(self.image)
self.rect.x = XnY[0]
self.rect.y = XnY[1]
self.__speed = 1
def get_image(self):
return self.image
def get_rect(self):
return self.rect
def get_mask(self):
return self.__mask
def get_x(self):
return self.rect.x
def get_y(self):
return self.rect.y
def get_speed(self):
return self.__speed
def set_image(self,value):
self.image = value
def set_rect(self,value):
self.__rect = value
def set_mask(self,value):
self.__mask = value
def set_x(self,value):
self.rect.x = value
def set_y(self,value):
self.rect.y = value
def set_speed(self,value):
self.__speed = value
def rotate(self,angle):
old_center = self.rect.center
self.image = pygame.transform.rotate(self.__image_source,angle)
self.rect = self.image.get_rect()
self.rect.center = old_center
Expected output: Sprite moves straight towards the mouse
Actual behavior: Sprite moves in wrong directions, with behavior showing patterns depending on quadrant of calculated angle.
Edit:
I changed the program so that the X and Y variables of the sprite are stored in variables separate from the rect object. This prevents decimal truncation. I also recalculated the angle between the sprite and the mouse pointer after the rotation code is finished. In the recalculation, the X and Y difference parameters are swapped to match the inverse tangent function instead of the inverse cotangent function. This recalculated angle is used for angular movement, and the first angle, with the X difference passed first, is used for rotation. It is important to note that after I calculated the changeX and changeY variables using the recalculated angle (with the Y difference passed first), I multiplied them by -1, as otherwise the sprite will move away from the mouse pointer.
I cannot be 100% sure, but I think the problem is that pygame.Rect stores position as integers, because it's supposed to store coordinates and dimensions in pixel unit, and of course you cannot paint half pixel.
Since you are dealing with any angle and trigonometric functions, you end with floats which are truncated when you do:
def set_x(self,value):
self.rect.x = value
Here, if value is 1.4, self.rect.x becomes 1. So you lose "accuracy."
This loss of accuracy is propagated each iteration of the main loop (each frame), resulting in an unexpected motion direction.
The best solution is to store all your value in a separate data structure and update the rect attribute only for drawing in the screen.
I'm making a little platformer game using pygame, and decided that making a level editor for each level would be easier than typing each blocks' coordinate and size.
I'm using a set of lines, horizontally and vertically to make a grid to make plotting points easier.
Here's the code for my grid:
def makeGrid(surface, width, height, spacing):
for x in range(0, width, spacing):
pygame.draw.line(surface, BLACK, (x,0), (x, height))
for y in range(0, height, spacing):
pygame.draw.line(surface, BLACK, (0,y), (width, y))
I want the user's mouse to move at 10px intervals, to move to only the points of intersection. Here's what I tried to force the mouse to snap to the grid.
def snapToGrid(mousePos):
if 0 < mousePos[0] < DISPLAYWIDTH and 0 < mousePos[1] < 700:
pygame.mouse.set_pos(roundCoords(mousePos[0],mousePos[1]))
(BTW, roundCoords() returns the coordinates rounded to the nearest ten unit.)
(Also BTW, snapToGrid() is called inside the main game loop (while not done))
...but this happens, the mouse doesn't want to move anywhere else.
Any suggestions on how to fix this? If I need to, I can change the grid code too.
Thanks a bunch.
P.S. This is using the latest version of PyGame on 64 bit Python 2.7
First of all I think you're not far off.
I think the problem is that the code runs quite fast through each game loop, so your mouse doesn't have time to move far before being set to the position return by your function.
What I would have a look into is rather than to pygame.mouse.set_pos() just return the snapped coordinates to a variable and use this to blit a marker to the screen highlighting the intersection of interest (here I use a circle, but you could just blit the image of a mouse ;) ). And hide your actual mouse using pygame.mouse.set_visible(False):
def snapToGrid(mousePos):
if 0 < mousePos[0] < DISPLAYWIDTH and 0 < mousePos[1] < 700:
return roundCoords(mousePos[0],mousePos[1])
snap_coord = snapToGrid(mousePos)# save snapped coordinates to variable
pygame.draw.circle(Surface, color, snap_coord, radius, 0)# define the remaining arguments, Surface, color, radius as you need
pygame.mouse.set_visible(False)# hide the actual mouse pointer
I hope that works for you !
I'm looking for the easiest way to implement this. I'm trying to implement platforms (with full collision detection) that you can draw in via mouse. Right now I have a line drawing function that actually draws small circles, but they're so close together that they more or less look like a line. Would the best solution be to create little pygame.Rect objects at each circle? That's going to be a lot of rect objects. It's not an image so pixel perfect doesn't seem like an option?
def drawGradientLine(screen, index, start, end, width, color_mode):
#color values change based on index
cvar1 = max(0, min(255, 9 * index-256))
cvar2 = max(0, min(255, 9 * index))
#green(0,255,0), blue(0,0,255), red(255,0,0), yellow(255,255,0)
if color_mode == 'green':
color = (cvar1, cvar2, cvar1)
elif color_mode == 'blue':
color = (cvar1, cvar1, cvar2)
elif color_mode == 'red':
color = (cvar2, cvar1, cvar1)
elif color_mode == 'yellow':
color = (cvar2, cvar2, cvar1)
dx = end[0] - start[0]
dy = end[1] - start[1]
dist = max(abs(dx), abs(dy))
for i in xrange(dist):
x = int(start[0]+float(i)/dist*dx)
y = int(start[1]+float(i)/dist*dy)
pygame.draw.circle(screen, color, (x, y), width)
That's my drawing function. And here's my loop that I have put in my main game event loop.
i = 0
while (i < len(pointList)-1):
drawGradientLine(screen, i, pointList[i], pointList[i + 1], r, mode)
i += 1
Thanks for any help, collision detection is giving me a huge headache right now (still can't get it right for my tiles either..).
Any reason you want to stick with circles?
Rectangles will make the line/rectangle a lot more smooth and will make collision detecting a lot easier unless you want to look into pixel perfect collision.
You also don't seem to save your drawn objects anywhere (like in a list or spritegroup), so how are you going to check for collision?
Here's a leveleditor I did for game awhile back, it's not perfect, but it works:
https://gist.github.com/marcusmoller/bae9ea310999db8d8d95
How it works:
The whole game level is divided up into 10x10px grid for easier drawing
The leveleditor check if the mouse is being clicked and then saves that mouse position
The player now moves the mouse to another position and releases the mouse button, the leveleditor now saves that new position.
You now have two different coordinates and can easily make a rectangle out of them.
Instead of creating a whole bunch of rect objects to test collision against, I'm going to recommend creating something called a mask of the drawn-in collideable object, and test for collision against that. Basically, a mask is a map of which pixels are being used and which are not in an image. You can almost think of it as a shadow or silhouette of a surface.
When you call pygame.draw.circle, you are already passing in a surface. Right now you are drawing directly to the screen, which might not be as useful for what I'm suggesting. I would recommend creating a rect which covers the entire area of the line being drawn, and then creating a surface of that size, and then draw the line to this surface. My code will assume you already know the bounds of the line's points.
line_rect = pygame.Rect(leftmost, topmost, rightmost - leftmost, bottommost - topmost)
line_surf = pygame.Surface((line_rect.width, line_rect.height))
In your drawGradientLine function, you'll have to translate the point coordinates to the object space of the line_surf.
while (i < len(pointList)-1):
drawGradientLine(line_surf, (line_rect.x, line_rect.y), i, pointList[i], pointList[i+1], r, mode)
i += 1
def drawGradientLine(surf, offset, index, start, end, width, color_mode):
# the code leading up to where you draw the circle...
for i in xrange(dist):
x = int(start[0]+float(i)/dist*dx) - offset[0]
y = int(start[1]+float(i)/dist*dy) - offset[1]
pygame.draw.circle(surf, color, (x, y), width)
Now you'll have a surface with the drawn object blitted to it. Note that you might have to add some padding to the surface when you create it if the width of the lines you are drawing is greater than 1.
Now that you have the surface, you will want to create the mask of it.
surf_mask = pygame.mask.from_surface(line_surf)
Hopefully this isn't getting too complicated for you! Now you can either check each "active" point in the mask for collision within a rect from your player (or whatever other objects you want to collide withe drawn-in platforms), or you can create a mask from the surface of such a player object and use the pygame.Mask.overlap_area function to check for pixel-perfect collision.
# player_surf is a surface object I am imagining exists
# player_rect is a rect object I am imagining exists
overlap_count = surf_mask.overlap_area(player_surf, (line_rect.x - player_rect.x, line_rect.y - player_rect.y))
overlap_count should be a count of the number of pixels that are overlapping between the masks. If this is greater than zero, then you know there has been a collision.
Here is the documentation for pygame.Mask.overlap_area: http://www.pygame.org/docs/ref/mask.html#pygame.mask.Mask.overlap_area