Displaying movement tiles for a Python game - python

I am trying to create a simple tactical RPG in python, similar to Fire Emblem or Advanced Wars, but in ASCII. I have been following this tutorial, which uses the libtcod module, and modifying the tutorial code for my own purposes
Right now I am struck trying to display movement tiles for each character. Basically I want the game to display the tiles each character can move to, whenever the user clicks his mouse over that character. I have created a function (get_total_squares) that generates the coordinates of the tiles to be highlighted based on the character's coordinates and movement range (hard-coded to 5, for now) and a function (show_move_tiles) to highlight these tiles. To record the click coordinates I have another function target_tile and a function to see if the coordinates match with a character's (target_character)
The program runs without errors, but clicking the mouse over a character does nothing (no tiles are shown).
Here is the code:
import libtcodpy as libtcod
#actual size of the window
SCREEN_WIDTH = 80
SCREEN_HEIGHT = 50
#size of the map
MAP_WIDTH = 80
MAP_HEIGHT = 43
LIMIT_FPS = 20 #20 frames-per-second maximum
color_dark_wall = libtcod.Color(0, 0, 50)
color_dark_ground = libtcod.Color(5, 50, 0)
color_move_tile = libtcod.Color (100, 0, 0)
class Tile:
#a tile of the map and its properties
def __init__(self, blocked, block_sight = None):
self.blocked = blocked
#by default, if a tile is blocked, it also blocks sight
if block_sight is None: block_sight = blocked
self.block_sight = block_sight
def target_tile():
#return the position of a tile left-clicked, or (None,None) if right-clicked.
global key, mouse
while True:
libtcod.console_flush()
libtcod.sys_check_for_event(libtcod.EVENT_KEY_PRESS|libtcod.EVENT_MOUSE, key, mouse)
render_all()
(x, y) = (mouse.cx, mouse.cy)
if mouse.rbutton_pressed or key.vk == libtcod.KEY_ESCAPE:
return (None, None) #cancel if the player right-clicked or pressed Escape
if mouse.lbutton_pressed:
return (x, y)
print "works!"
def target_character():
#Shows movement tiles if character is on click-coordinate
(x, y) = target_tile()
#return the first clicked character
for obj in objects:
if obj.x == x and obj.y == y:
obj.show_move_tiles()
def make_map():
global map
#fill map with "unblocked" tiles
map = [[ Tile(False)
for y in range(MAP_HEIGHT) ]
for x in range(MAP_WIDTH) ]
map[30][22].blocked = True
map[30][22].block_sight = True
map[50][22].blocked = True
map[50][22].block_sight = True
class Object:
#this is a generic object: the player, a monster, an item, the stairs...
#it's always represented by a character on screen.
def __init__(self, x, y, char, color):
self.x = x
self.y = y
self.char = char
self.color = color
def move(self, dx, dy):
#move by the given amount
if not map[self.x + dx][self.y + dy].blocked:
self.x += dx
self.y += dy
def draw(self):
#set the color and then draw the character that represents this object at its position
libtcod.console_set_default_foreground(con, self.color)
libtcod.console_put_char(con, self.x, self.y, self.char, libtcod.BKGND_NONE)
def show_move_tiles(self):
global mouse
global color_move_tile
(x,y) = (mouse.cx, mouse.cy)
coord_in_range = get_total_squares(5, self.x, self.y)
for [x,y] in coord_in_range:
if [x,y] is not [self.x, self.y]:
libtcod.console_set_char_background(con, x, y, color_move_tile, libtcod.BKGND_SET)
def clear(self):
#erase the character that represents this object
libtcod.console_put_char_ex(con, self.x, self.y, '.', libtcod.white, libtcod.black)
def handle_keys():
#key = libtcod.console_check_for_keypress() #real-time
global key #turn-based
if key.vk == libtcod.KEY_ENTER and key.lalt:
#Alt+Enter: toggle fullscreen
libtcod.console_set_fullscreen(not libtcod.console_is_fullscreen())
elif key.vk == libtcod.KEY_ESCAPE:
return True #exit game
def get_total_squares(dist, playerx, playery):
coord_in_range = []
for x in range(-dist,dist+1):
for y in range(-dist, dist+1):
if abs(x)+abs(y) <= dist:
coord_in_range.append([playerx+x, playery+y])
return coord_in_range
def render_all():
global color_dark_wall
global color_dark_ground
#go through all tiles, and set their background color
for y in range(MAP_HEIGHT):
for x in range(MAP_WIDTH):
wall = map[x][y].block_sight
if wall:
libtcod.console_put_char_ex(con, x, y, '#', libtcod.white, libtcod.black)
else:
libtcod.console_put_char_ex(con, x, y, '.', libtcod.white, libtcod.black)
#draw all objects in the list and show movement if mouse hovers over player
for item in objects:
item.draw()
if mouse.cx == item.x and mouse.cy == item.y:
item.show_move_tiles()
#blit the contents of "con" to the root console
libtcod.console_blit(con, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, 0)
#############################################
# Initialization & Main Loop
#############################################
libtcod.console_set_custom_font('arial10x10.png', libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_TCOD)
libtcod.console_init_root(SCREEN_WIDTH, SCREEN_HEIGHT, 'python/libtcod tutorial', False)
libtcod.sys_set_fps(LIMIT_FPS)
con = libtcod.console_new(SCREEN_WIDTH, SCREEN_HEIGHT)
#create object representing the player
player = Object(30, 15, '#', libtcod.white)
#create an NPC
npc = Object(SCREEN_WIDTH/2 - 5, SCREEN_HEIGHT/2, '#', libtcod.yellow)
#the list of objects with those two
objects = [npc, player]
make_map()
mouse = libtcod.Mouse()
key = libtcod.Key()
while not libtcod.console_is_window_closed():
libtcod.sys_check_for_event(libtcod.EVENT_KEY_PRESS|libtcod.EVENT_MOUSE,key,mouse)
#render the screen
render_all()
target_tile()
libtcod.console_flush()
#erase all objects at their old locations, before they move
for object in objects:
object.clear()
#handle keys and exit game if needed
exit = handle_keys()
if exit:
break
EDIT: I solved the previous problem of the movement tiles not showing up when I hovered the mouse over the character, but have updated the problem with a new one: trying to toggle the movement tiles on and off with a mouse click

Related

How can I change this surface image to the image I want in pygame?

# Memory V2
# The second version contains the complete tile grid and the black panel on the right, there is score in the black panel. All 8 pairs of two tiles are covered by question mark when the game starts . Each time the game is played, the tiles spawn in random locations in the grid. Player can click tiles to reveal images. Game ends upon clicking close screen or all 16 tiles being exposed. Game occurs on a 4x4 grid.
import pygame,random, time
# User-defined functions
def main():
# initialize all pygame modules (some need initialization)
pygame.init()
# create a pygame display window
pygame.display.set_mode((500, 400))
# set the title of the display window
pygame.display.set_caption('Memory v1')
# get the display surface
w_surface = pygame.display.get_surface()
# create a game object
game = Game(w_surface)
# start the main game loop by calling the play method on the game
#object
game.play()
# quit pygame and clean up the pygame window
pygame.quit()
# User-defined classes
class Game:
# An object in this class represents a complete game.
def __init__(self, surface):
# Initialize a Game.
# - self is the Game to initialize
# - surface is the display window surface object
# === objects that are part of every game that we will discuss
self.surface = surface
self.bg_color = pygame.Color('black')
self.FPS = 60
self.game_Clock = pygame.time.Clock()
self.close_clicked = False
self.continue_game = True
Tile.set_surface(self.surface)
# tell grid to be size 4 meaning 4x4 or 16 squares total
grid_size = 4
self.create_grid(grid_size)
self.score=0
def draw_score(self):
# this method draws the player's score in the top-right corner of the
# game window.
# - self : the game the score is being drawn for
font_color = pygame.Color("white")
font_bg = pygame.Color("black")
font = pygame.font.SysFont("arial", 32)
text_img = font.render(str(self.score), True, font_color, font_bg)
text_pos = (460,0)
self.surface.blit(text_img, text_pos)
def create_grid(self, grid_size):
# Creates a grid of tiles that is grid_size x grid_size in size.
self.grid = [ ]
# Create list of image names to be used on the squares (we just append image(1-9) and the file type bmp
# Then we create image surfaces of each image name and add image surfaces to itself which provides us with two of each image
img_names = ['image' + str(i) + '.bmp' for i in range(1,9)]
image_surfaces = [pygame.image.load(name) for name in img_names]
image_surfaces = image_surfaces + image_surfaces
random.shuffle(image_surfaces)
# this for loop creates each row in our grid
for row_num in range(grid_size):
new_row = self.create_row(row_num, grid_size,image_surfaces)
self.grid.append(new_row)
def create_row(self, row_num, size,images):
# Create one row in a grid. Each row contains size Tiles
# required for calculating the tile's x,y coordinates on screen
# - row_num: the nth row of the grid being created
# - size : the number of tiles in the row
# returns the newly created row'
tile_height = self.surface.get_height() // size
# 3/4 to leave space for black column on side
tile_width = 3/4*self.surface.get_width() // size
new_row = [ ]
for col_num in range(size):
# number of row x tile height produces y position
# number of col x tile_width produces x position
# + 10 so it fits
pos = (row_num*tile_height+10,col_num*tile_width+10)
# assigns one of the images to each tile by pairing it with a unique coordinate
#content=pygame.image.load('image0.bmp')
#content = images[row_num*size+col_num]
one_tile = Tile(pos, tile_width, tile_height)
content=pygame.image.load('image0.bmp')
#content=images[row_num*size+col_num]
if self.handle_mouse_click==True:
content=images[row_num*size+col_num]
one_tile.set_content(content)
#one_tile = Tile(pos, tile_width, tile_height)
#one_tile.set_content(content)
new_row.append(one_tile)
return new_row
def play(self):
# Play the game until the player presses the close box.
# - self is the Game that should be continued or not.
while not self.close_clicked: # until player clicks close box
# play frame
self.handle_events()
self.draw()
if self.continue_game:
self.update()
self.decide_continue()
self.game_Clock.tick(self.FPS) # run at most with FPS listed
def handle_events(self):
# Handle each user event by changing the game state
# - self is the Game whose events will be handled
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
self.close_clicked = True
if event.type == pygame.MOUSEBUTTONDOWN:
self.handle_mouse_click(event)
def handle_mouse_click(self, event):
# responds to one mouse click on screen; that means flipping the
# tile
#print("Screen was clicked at " + str(event.pos))
for row in self.grid:
for tile in row:
if tile.select(event.pos)==True:
print('hi')
#def change_content(self,content):
#return False
#return content_switch
def draw(self):
# Draw all game objects.
# - self is the Game to draw
self.surface.fill(self.bg_color) # clear the display surface
# draws the grid
for row in self.grid:
for tile in row:
tile.draw()
self.draw_score()
pygame.display.update() # updates the display
def update(self):
# Update the game objects for the next frame.
# - self is the Game to update
self.score= pygame.time.get_ticks()//1000
pass
def decide_continue(self):
# Check and remember if the game should continue
filled_tiles = [ ]
for row in self.grid:
for tile in row:
if tile.tile_content==True:
filled_tiles.append(tile)
if len(filled_tiles) == self.grid_size ** 2:
return False
else:
return True
class Tile:
# A tile represents one location on a grid. Tiles hold content
# (in this case, an X or an O).
# class attributes that are common to all tiles
# setting surface to none isnt exactly necessary, however we set class wide attributes here before putting it in a method
surface = None
fg_color = pygame.Color("white")
bg_color = pygame.Color("black")
# set border width for each tile in grid
border_width = 5
#classmethod
def set_surface(cls, surface):
# sets the class attribute, surface
cls.surface = surface
def __init__(self, screen_position, width, height):
# initialize one instance of our Tile class. Tiles represent
# one 'position' in our tic-tac-toe board.
# - self: the tile being initialized
# - screen_position: the [x, y] coordinates to draw the tile at
# - width: the width of the tile
# - height: the height of the tile
self.screen_position = screen_position
#self.content = ''
# create a rectangle defining our boundaries
x, y = screen_position
self.rect = pygame.Rect(x, y, width, height)
def draw_content(self):
# create an rect object of image so we can blit images to surface of grid tiles
image_rect=self.content.get_rect(center=self.rect.center)
Tile.surface.blit(self.content,image_rect)
def draw(self):
# draw the contents of a tile to its surface.
# - self: the tile being drawn
self.draw_content()
pygame.draw.rect(Tile.surface, Tile.bg_color, self.rect,
Tile.border_width)
def set_content(self, new_content):
# change our tile content.
self.content = new_content
def select(self, pos):
selected=False
if self.rect.collidepoint(pos):
if self.content==pygame.image.load('image0.bmp'):
selected=True
return selected
def __eq__(self):
if self.content==pygame.image.load('image0.bmp'):
return True
def tile_content(self):
if self.content == pygame.image.load('image0.bmp'):
return False
else:
return True
main()
So I'm trying to build another version of the game memory where upon clicking one of the tiles the question mark image 'flips' and becomes the actual image I assigned in my code and then the game ends after I flip all of the tiles. However, when I use self.content==pygame.image.load('image0.bmp') it doesn't seem to work or return true or anything even though I set it to do so. My professor said I need to overload the == operator which I think I did but for some reason it still refuses to flip upon me clicking it and I have no idea why? Can someone explain?
This is because pygame.image.load() returns a pygame.Surface(). Any object (instance of a class) is never exactly the same.
So:
pygame.image.load("image.png") == pygame.image.load("image.png") will always be False.
Also, when asking a question, try to put small snippets of code instead of the whole thing.

builtins.AttributeError: 'module' object has no attribute 'one_tile'

HI I keep getting that error message from the line below
pygame.one_tile.blit(self.i_list[img_index], pos)
and this is from the function below
def create_grid(self, grid_size):
self.grid = [ ]
tile_width = self.surface.get_width() / grid_size
tile_height = self.surface.get_height() / grid_size
# this for loop creates each row in our grid
for i in range(grid_size):
one_row = [ ]
img_index = 0
for j in range(grid_size):
y = i * tile_height
x = j * tile_width
pos = (x,y)
one_tile = Tile(pos, tile_width, tile_height, self.surface)
pygame.one_tile.blit(self.i_list[img_index], pos)
img_index += 1
one_row.append(one_tile)
self.grid.append(one_row)
I'm writing a code for the memory game version 1 (the game that has 8 pairs of images and you need to memorize which image was on which card and match a pair) and I keep get that error message but I don;t really know what I should do to solve it. Any help would be appreciated very much. Thank you!!
and my full code is
import pygame, random
# User-defined functions
def main():
# initialize all pygame modules (some need initialization)
pygame.init()
# create a pygame display window
pygame.display.set_mode((500, 400))
# set the title of the display window
pygame.display.set_caption('A template for graphical games with two moving dots')
# get the display surface
w_surface = pygame.display.get_surface()
# create a game object
game = Game(w_surface)
# start the main game loop by calling the play method on the game object
game.play()
# quit pygame and clean up the pygame window
pygame.quit()
# User-defined classes
class Game:
# An object in this class represents a complete game.
def __init__(self, surface):
# Initialize a Game.
# - self is the Game to initialize
# - surface is the display window surface object
# === objects that are part of every game that we will discuss
self.surface = surface
self.bg_color = pygame.Color('black')
self.FPS = 60
self.game_Clock = pygame.time.Clock()
self.close_clicked = False
self.continue_game = True
# === game specific objects
self.max_frames = 150
self.frame_counter = 0
self.i_list = []
self.images = ["image1.bmp", "image2.bmp", "image3.bmp", "image4.bmp", "image5.bmp", "image6.bmp", "image7.bmp", "image8.bmp"]
for i in self.images:
pygame.image.load(i)
self.i_list.append(i)
self.i_list.append(i)
random.shuffle(self.i_list)
self.create_grid(4)
def create_grid(self, grid_size):
self.grid = [ ]
tile_width = self.surface.get_width() / grid_size
tile_height = self.surface.get_height() / grid_size
# this for loop creates each row in our grid
for i in range(grid_size):
one_row = [ ]
img_index = 0
for j in range(grid_size):
y = i * tile_height
x = j * tile_width
pos = (x,y)
one_tile = Tile(pos, tile_width, tile_height, self.surface)
pygame.one_tile.blit(self.i_list[img_index], pos)
img_index += 1
one_row.append(one_tile)
self.grid.append(one_row)
def play(self):
# Play the game until the player presses the close box.
# - self is the Game that should be continued or not.
while not self.close_clicked: # until player clicks close box
# play frame
self.handle_events()
self.draw()
if self.continue_game:
self.update()
self.decide_continue()
self.game_Clock.tick(self.FPS) # run at most with FPS Frames Per Second
def handle_events(self):
# Handle each user event by changing the game state appropriately.
# - self is the Game whose events will be handled
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
self.close_clicked = True
def draw(self):
# Draw all game objects.
# - self is the Game to draw
self.surface.fill(self.bg_color) # clear the display surface first
for row in self.grid:
for tile in row:
tile.draw()
pygame.display.update()
def update(self):
# Update the game objects for the next frame.
# - self is the Game to update
pass
def decide_continue(self):
# Check and remember if the game should continue
# - self is the Game to check
return True
class Tile:
# A tile represents one location on a grid. Tiles hold content
# (in this case, an X or an O).
def __init__(self, screen_position, width, height, surface):
# initialize one instance of our Tile class. Tiles represent
# one 'position' in our tic-tac-toe board.
# - self: the tile being initialized
# - screen_position: the [x, y] coordinates to draw the tile at
# - surface: the surface on which to draw the tile
# - height: the height of the tile when it is drawn
# - width: the width of the tile when it is drawn
self.screen_position = screen_position
self.surface = surface
self.content = ''
self.height = height
self.width = width
def draw(self):
# draw the contents of a tile to its surface.
tile_boundary = pygame.Rect(self.screen_position[0],
self.screen_position[1],
self.width,
self.height)
pygame.draw.rect(self.surface, pygame.Color("white"), tile_boundary, 2)
main()
I recommend to add an .image attribute and .visible attribute to the class Tile. Each tile knows the associated image and has a state if the image in on the tile is visible:
class Tile:
# A tile represents one location on a grid. Tiles hold content
# (in this case, an X or an O).
def __init__(self, screen_position, width, height, surface, image):
# initialize one instance of our Tile class. Tiles represent
# one 'position' in our tic-tac-toe board.
# - self: the tile being initialized
# - screen_position: the [x, y] coordinates to draw the tile at
# - surface: the surface on which to draw the tile
# - height: the height of the tile when it is drawn
# - width: the width of the tile when it is drawn
self.screen_position = screen_position
self.surface = surface
self.content = ''
self.height = height
self.width = width
self.image = image
self.visible = False
def show(self, visible):
self.visible = visible
def draw(self):
# draw the contents of a tile to its surface.
tile_boundary = pygame.Rect(self.screen_position[0],
self.screen_position[1],
self.width,
self.height)
pygame.draw.rect(self.surface, pygame.Color("white"), tile_boundary, 2)
if self.visible:
img_rect = self.image.get_rect(center = tile_boundary.center)
self.surface.blit(self.image, img_rect.topleft)
To create an image list, you've to load the image. The return value of pygame.image.load is a pygame.Surface object which can be append to i_list:
self.i_list = []
self.images = ["image1.bmp", "image2.bmp", "image3.bmp", "image4.bmp", "image5.bmp", "image6.bmp", "image7.bmp", "image8.bmp"]
for imgname in self.images:
img = pygame.image.load(imgname)
self.i_list.append(img)
random.shuffle(self.i_list)
Pass the image to the constructor of Tile:
for i in range(grid_size):
one_row = [ ]
img_index = 0
for j in range(grid_size):
pos = (j * tile_width, i * tile_height)
one_tile = Tile(pos, tile_width, tile_height, self.surface, self.i_list[img_index])
img_index += 1
one_row.append(one_tile)
self.grid.append(one_row)
Note, for debug reasons you can all the state of all the images "visible" (self.visible = True in the constructor of Tiles).

atan2 isn't providing me with the angle I want

I'm trying to write a game in pygame, involving a moving object with a "turret" that swivels to follow a mouse. As of now, I'm mostly trying to expand upon examples, so the code's not entirely mine (credit to Sean J. McKiernan for his sample programs); however, this portion is. Below is my code; I use the center of rect (the "base" shape and the point around which the "turret" swivels) as the base point, and the position of the mouse as the other point. By subtracting the mouse's displacement from the displacement of the "center," I effectively get a vector between the two points and find the angle between that vector and the x-axis with atan2. Below is the code I use to do that:
def get_angle(self, mouse):
x_off = (mouse[0]-self.rect.centerx)
y_off = (mouse[1]-self.rect.centery)
self.angle = math.degrees(math.atan2(-y_off, x_off) % 2*math.pi)
self.hand = pg.transform.rotate(self.original_hand, self.angle)
self.hand_rect = self.hand.get_rect(center=self.hand_rect.center)
According to multiple tutorials I've reviewed, this SHOULD be the correct code; however, I discovered later that those tutorials (and, in fact, this tutorial) were all for Python 2.7, while I am trying to write in Python 3.6. I don't think that should make a difference in this scenario, though. As it stands, the view appears to depend entirely upon the "character's" position on the screen. If the "character" is in one corner, the reaction of the "turret" is different than the reaction if the "character" is in the middle of the screen. However, this shouldn't matter; the position of the "character" relative to the mouse is the exact same no matter where on the screen they are. Any ideas, or do I need to supply more code?
Edit: Apparently, more code is required. Rather than attempt to extricate only the entirely necessary parts, I've provided the entire code sample, so everyone can run it. As a side note, the "Bolt" things (intended to fire simple yellow blocks) don't work either, but I'm just trying to get the arm working before I start in on debugging that.
Edit the second: I have discovered that the "Bolt" system works within a certain distance of the origin (0,0 in the window coordinate system), and that the arm also works within a much lesser distance. I added the line Block(pg.Color("chocolate"), (0,0,100,100)) under the "walls" grouping as a decision point, and the block was positioned in the top left corner. I've corrected Bolt by changing screen_rect to viewport in the control loop; however, I don't know why the "arm" swinging is dependent on adjacency to the origin. The positions of the mouse and "character" SHOULD be absolute. Am I missing something?
"""
Basic moving platforms using only rectangle collision.
-Written by Sean J. McKiernan 'Mekire'
Edited for a test of "arms"
"""
import os
import sys
import math
import pygame as pg
CAPTION = "Moving Platforms"
SCREEN_SIZE = (700,700)
BACKGROUND_COLOR = (50, 50, 50)
COLOR_KEY = (255, 255, 255)
class _Physics(object):
"""A simplified physics class. Psuedo-gravity is often good enough."""
def __init__(self):
"""You can experiment with different gravity here."""
self.x_vel = self.y_vel = 0
self.grav = 0.4
self.fall = False
def physics_update(self):
"""If the player is falling, add gravity to the current y velocity."""
if self.fall:
self.y_vel += self.grav
else:
self.y_vel = 0
class Player(_Physics, object):
def __init__(self,location,speed):
_Physics.__init__(self)
HAND = pg.image.load("playertst2.png").convert()
HAND.set_colorkey(COLOR_KEY)
self.image = pg.image.load('playertst.png').convert()
self.rect = self.image.get_rect(topleft=location)
self.speed = speed
self.jump_power = -9.0
self.jump_cut_magnitude = -3.0
self.on_moving = False
self.collide_below = False
self.original_hand = HAND
self.fake_hand = self.original_hand.copy()
self.hand = self.original_hand.copy()
self.hand_rect = self.hand.get_rect(topleft=location)
self.angle = self.get_angle(pg.mouse.get_pos())
def check_keys(self, keys):
"""Find the player's self.x_vel based on currently held keys."""
self.x_vel = 0
if keys[pg.K_LEFT] or keys[pg.K_a]:
self.x_vel -= self.speed
if keys[pg.K_RIGHT] or keys[pg.K_d]:
self.x_vel += self.speed
def get_position(self, obstacles):
"""Calculate the player's position this frame, including collisions."""
if not self.fall:
self.check_falling(obstacles)
else:
self.fall = self.check_collisions((0,self.y_vel), 1, obstacles)
if self.x_vel:
self.check_collisions((self.x_vel,0), 0, obstacles)
def check_falling(self, obstacles):
"""If player is not contacting the ground, enter fall state."""
if not self.collide_below:
self.fall = True
self.on_moving = False
def check_moving(self,obstacles):
"""
Check if the player is standing on a moving platform.
If the player is in contact with multiple platforms, the prevously
detected platform will take presidence.
"""
if not self.fall:
now_moving = self.on_moving
any_moving, any_non_moving = [], []
for collide in self.collide_below:
if collide.type == "moving":
self.on_moving = collide
any_moving.append(collide)
else:
any_non_moving.append(collide)
if not any_moving:
self.on_moving = False
elif any_non_moving or now_moving in any_moving:
self.on_moving = now_moving
def check_collisions(self, offset, index, obstacles):
"""
This function checks if a collision would occur after moving offset
pixels. If a collision is detected, the position is decremented by one
pixel and retested. This continues until we find exactly how far we can
safely move, or we decide we can't move.
"""
unaltered = True
self.rect[index] += offset[index]
self.hand_rect[index] += offset[index]
while pg.sprite.spritecollideany(self, obstacles):
self.rect[index] += (1 if offset[index]<0 else -1)
self.hand_rect[index] += (1 if offset[index]<0 else -1)
unaltered = False
return unaltered
def check_above(self, obstacles):
"""When jumping, don't enter fall state if there is no room to jump."""
self.rect.move_ip(0, -1)
collide = pg.sprite.spritecollideany(self, obstacles)
self.rect.move_ip(0, 1)
return collide
def check_below(self, obstacles):
"""Check to see if the player is contacting the ground."""
self.rect.move_ip((0,1))
collide = pg.sprite.spritecollide(self, obstacles, False)
self.rect.move_ip((0,-1))
return collide
def jump(self, obstacles):
"""Called when the user presses the jump button."""
if not self.fall and not self.check_above(obstacles):
self.y_vel = self.jump_power
self.fall = True
self.on_moving = False
def jump_cut(self):
"""Called if player releases the jump key before maximum height."""
if self.fall:
if self.y_vel < self.jump_cut_magnitude:
self.y_vel = self.jump_cut_magnitude
def get_angle(self, mouse):
x_off = (mouse[0]-self.rect.centerx)
y_off = (mouse[1]-self.rect.centery)
self.angle = math.degrees(math.atan2(-y_off, x_off) % (2*math.pi))
self.hand = pg.transform.rotate(self.original_hand, self.angle)
self.hand_rect = self.hand.get_rect(center=self.hand_rect.center)
"""
offset = (mouse[1]-self.hand_rect.centery, mouse[0]-self.hand_rect.centerx)
self.angle = math.atan2(-offset[0], offset[1]) % (2 * math.pi)
self.angle = math.degrees(self.angle)
self.hand = pg.transform.rotate(self.original_hand, self.angle)
self.hand_rect = self.hand.get_rect(center=self.rect.center)
self.angle = 135-math.degrees(math.atan2(*offset))
self.hand = pg.transform.rotate(self.original_hand, self.angle)
self.hand_rect = self.hand.get_rect(topleft=self.rect.topleft)
"""
def pre_update(self, obstacles):
"""Ran before platforms are updated."""
self.collide_below = self.check_below(obstacles)
self.check_moving(obstacles)
def update(self, obstacles, keys):
"""Everything we need to stay updated; ran after platforms update."""
self.check_keys(keys)
self.get_position(obstacles)
self.physics_update()
def get_event(self, event, bolts):
if event.type == pg.MOUSEBUTTONDOWN and event.button == 1:
bolts.add(Bolt(self.rect.center))
elif event.type == pg.MOUSEMOTION:
self.get_angle(event.pos)
def draw(self, surface):
"""Blit the player to the target surface."""
surface.blit(self.image, self.rect)
surface.blit(self.hand, self.hand_rect)
class Bolt(pg.sprite.Sprite):
def __init__(self, location):
pg.sprite.Sprite.__init__(self)
"""self.original_bolt = pg.image.load('bolt.png')"""
"""self.angle = -math.radians(angle-135)"""
"""self.image = pg.transform.rotate(self.original_bolt, angle)"""
"""self.image = self.original_bolt"""
self.image=pg.Surface((5,10)).convert()
self.image.fill(pg.Color("yellow"))
self.rect = self.image.get_rect(center=location)
self.move = [self.rect.x, self.rect.y]
self.speed_magnitude = 5
"""self.speed = (self.speed_magnitude*math.cos(self.angle), self.speed_magnitude*math.sin(self.angle))"""
"""self.speed = (5,0)"""
self.done = False
def update(self, screen_rect, obstacles):
self.move[0] += self.speed_magnitude
"""self.move[1] += self.speed[1]"""
self.rect.topleft = self.move
self.remove(screen_rect, obstacles)
def remove(self, screen_rect, obstacles):
if not self.rect.colliderect(screen_rect):
self.kill()
class Block(pg.sprite.Sprite):
"""A class representing solid obstacles."""
def __init__(self, color, rect):
"""The color is an (r,g,b) tuple; rect is a rect-style argument."""
pg.sprite.Sprite.__init__(self)
self.rect = pg.Rect(rect)
self.image = pg.Surface(self.rect.size).convert()
self.image.fill(color)
self.type = "normal"
class MovingBlock(Block):
"""A class to represent horizontally and vertically moving blocks."""
def __init__(self, color, rect, end, axis, delay=500, speed=2, start=None):
"""
The moving block will travel in the direction of axis (0 or 1)
between rect.topleft and end. The delay argument is the amount of time
(in miliseconds) to pause when reaching an endpoint; speed is the
platforms speed in pixels/frame; if specified start is the place
within the blocks path to start (defaulting to rect.topleft).
"""
Block.__init__(self, color, rect)
self.start = self.rect[axis]
if start:
self.rect[axis] = start
self.axis = axis
self.end = end
self.timer = 0.0
self.delay = delay
self.speed = speed
self.waiting = False
self.type = "moving"
def update(self, player, obstacles):
"""Update position. This should be done before moving any actors."""
obstacles = obstacles.copy()
obstacles.remove(self)
now = pg.time.get_ticks()
if not self.waiting:
speed = self.speed
start_passed = self.start >= self.rect[self.axis]+speed
end_passed = self.end <= self.rect[self.axis]+speed
if start_passed or end_passed:
if start_passed:
speed = self.start-self.rect[self.axis]
else:
speed = self.end-self.rect[self.axis]
self.change_direction(now)
self.rect[self.axis] += speed
self.move_player(now, player, obstacles, speed)
elif now-self.timer > self.delay:
self.waiting = False
def move_player(self, now, player, obstacles, speed):
"""
Moves the player both when on top of, or bumped by the platform.
Collision checks are in place to prevent the block pushing the player
through a wall.
"""
if player.on_moving is self or pg.sprite.collide_rect(self,player):
axis = self.axis
offset = (speed, speed)
player.check_collisions(offset, axis, obstacles)
if pg.sprite.collide_rect(self, player):
if self.speed > 0:
self.rect[axis] = player.rect[axis]-self.rect.size[axis]
else:
self.rect[axis] = player.rect[axis]+player.rect.size[axis]
self.change_direction(now)
def change_direction(self, now):
"""Called when the platform reaches an endpoint or has no more room."""
self.waiting = True
self.timer = now
self.speed *= -1
"""class Spell(pg.sprite.Sprite):
def __init__(self, location, angle)"""
class Control(object):
"""Class for managing event loop and game states."""
def __init__(self):
"""Initalize the display and prepare game objects."""
self.screen = pg.display.get_surface()
self.screen_rect = self.screen.get_rect()
self.clock = pg.time.Clock()
self.fps = 60.0
self.keys = pg.key.get_pressed()
self.done = False
self.player = Player((50,875), 4)
self.viewport = self.screen.get_rect()
self.level = pg.Surface((1000,1000)).convert()
self.level_rect = self.level.get_rect()
self.win_text,self.win_rect = self.make_text()
self.obstacles = self.make_obstacles()
self.bolts = pg.sprite.Group()
def make_text(self):
"""Renders a text object. Text is only rendered once."""
font = pg.font.Font(None, 100)
message = "You win. Celebrate."
text = font.render(message, True, (100,100,175))
rect = text.get_rect(centerx=self.level_rect.centerx, y=100)
return text, rect
def make_obstacles(self):
"""Adds some arbitrarily placed obstacles to a sprite.Group."""
walls = [Block(pg.Color("chocolate"), (0,980,1000,20)),
Block(pg.Color("chocolate"), (0,0,20,1000)),
Block(pg.Color("chocolate"), (980,0,20,1000))]
static = [Block(pg.Color("darkgreen"), (250,780,200,100)),
Block(pg.Color("darkgreen"), (600,880,200,100)),
Block(pg.Color("darkgreen"), (20,360,880,40)),
Block(pg.Color("darkgreen"), (950,400,30,20)),
Block(pg.Color("darkgreen"), (20,630,50,20)),
Block(pg.Color("darkgreen"), (80,530,50,20)),
Block(pg.Color("darkgreen"), (130,470,200,215)),
Block(pg.Color("darkgreen"), (20,760,30,20)),
Block(pg.Color("darkgreen"), (400,740,30,40))]
moving = [MovingBlock(pg.Color("olivedrab"), (20,740,75,20), 325, 0),
MovingBlock(pg.Color("olivedrab"), (600,500,100,20), 880, 0),
MovingBlock(pg.Color("olivedrab"),
(420,430,100,20), 550, 1, speed=3, delay=200),
MovingBlock(pg.Color("olivedrab"),
(450,700,50,20), 930, 1, start=930),
MovingBlock(pg.Color("olivedrab"),
(500,700,50,20), 730, 0, start=730),
MovingBlock(pg.Color("olivedrab"),
(780,700,50,20), 895, 0, speed=-1)]
return pg.sprite.Group(walls, static, moving)
def update_viewport(self):
"""
The viewport will stay centered on the player unless the player
approaches the edge of the map.
"""
self.viewport.center = self.player.rect.center
self.viewport.clamp_ip(self.level_rect)
def event_loop(self):
"""We can always quit, and the player can sometimes jump."""
for event in pg.event.get():
if event.type == pg.QUIT or self.keys[pg.K_ESCAPE]:
self.done = True
elif event.type == pg.KEYDOWN:
if event.key == pg.K_SPACE:
self.player.jump(self.obstacles)
elif event.type == pg.KEYUP:
if event.key == pg.K_SPACE:
self.player.jump_cut()
elif event.type == pg.MOUSEMOTION or event.type == pg.MOUSEBUTTONDOWN:
self.player.get_event(event, self.bolts)
def update(self):
"""Update the player, obstacles, and current viewport."""
self.keys = pg.key.get_pressed()
self.player.pre_update(self.obstacles)
self.obstacles.update(self.player, self.obstacles)
self.player.update(self.obstacles, self.keys)
self.update_viewport()
self.bolts.update(self.screen_rect, self.obstacles)
def draw(self):
"""
Draw all necessary objects to the level surface, and then draw
the viewport section of the level to the display surface.
"""
self.level.fill(pg.Color("lightblue"))
self.obstacles.draw(self.level)
self.level.blit(self.win_text, self.win_rect)
self.player.draw(self.level)
self.bolts.draw(self.level)
self.screen.blit(self.level, (0,0), self.viewport)
def display_fps(self):
"""Show the programs FPS in the window handle."""
caption = "{} - FPS: {:.2f}".format(CAPTION, self.clock.get_fps())
pg.display.set_caption(caption)
def main_loop(self):
"""As simple as it gets."""
while not self.done:
self.event_loop()
self.update()
self.draw()
pg.display.update()
self.clock.tick(self.fps)
self.display_fps()
if __name__ == "__main__":
os.environ['SDL_VIDEO_CENTERED'] = '1'
pg.init()
pg.display.set_caption(CAPTION)
pg.display.set_mode(SCREEN_SIZE)
PLAYERIMG = pg.image.load("playertst.png").convert()
PLAYERIMG.set_colorkey(COLOR_KEY)
run_it = Control()
run_it.main_loop()
pg.quit()
sys.exit()
The % 2*pi unnecessary, and your get_angle function has no return value, but you do an assignment to self.angle = self.get_angle, but that is not the issue. The issue is that the mouse position is relative to the screen (i.e. clicking in the top right area of your game screen will always yield (0,480) if your screen is 640x480), while the position of the (character) rectangle is given in your game play area, which is larger than the screen, ergo if you move the character and thus the view shifts, you are getting coordinates in two different coordinate systems. You will have to keep track of where the view is in your game play area and add the offset to the mouse coordinates.

Rendering an Isometric Grid in PyGame [duplicate]

# User-defined functions
def main():
# initialize all pygame modules (some need initialization)
pygame.init()
# create a pygame display window
pygame.display.set_mode((500, 400))
# set the title of the display window
pygame.display.set_caption('Memory')
# get the display surface
w_surface = pygame.display.get_surface()
# create a game object
game = Game(w_surface)
# start the main game loop by calling the play method on the game
#object
game.play()
# quit pygame and clean up the pygame window
pygame.quit()
# User-defined classes
class Game:
# An object in this class represents a complete game.
def __init__(self, surface):
# Initialize a Game.
# - self is the Game to initialize
# - surface is the display window surface object
self.surface = surface
self.bg_color = pygame.Color('black')
self.FPS = 60
self.game_Clock = pygame.time.Clock()
self.close_clicked = False
self.continue_game = True
Tile.set_surface(self.surface)
grid_size = 4
self.create_grid(grid_size)
def create_grid(self, grid_size):
# Creates a grid of tiles that is grid_size x grid_size in size.
self.grid = [ ]
# this for loop creates each row in our grid
for row_num in range(grid_size):
new_row = self.create_row(row_num, grid_size)
self.grid.append(new_row)
def create_row(self, row_num, size):
# Create one row in a grid. Each row contains size Tiles. a
#row_num is
# required for calculating the tile's x,y coordinates on screen
# - row_num: the nth row of the grid being created
# - size : the number of tiles in the row
# returns the newly created row'
image_1=pygame.image.load('image1.bmp')
image_2=pygame.image.load('image2.bmp')
image_3=pygame.image.load('image3.bmp')
image_4=pygame.image.load('image4.bmp')
image_5=pygame.image.load('image5.bmp')
image_6=pygame.image.load('image6.bmp')
image_7=pygame.image.load('image7.bmp')
image_8=pygame.image.load('image8.bmp')
pygame_image_surfaces=[]
pygame_image_surfaces.append(image_1)
pygame_image_surfaces.append(image_2)
pygame_image_surfaces.append(image_3)
pygame_image_surfaces.append(image_4)
pygame_image_surfaces.append(image_5)
pygame_image_surfaces.append(image_6)
pygame_image_surfaces.append(image_7)
pygame_image_surfaces.append(image_8)
pygame_image_surfaces=pygame_image_surfaces+pygame_image_surfaces
random.shuffle(pygame_image_surfaces)
image_surfaces=pygame_image_surfaces
tile_height = self.surface.get_height() // size
tile_width = 3/4*self.surface.get_width() // size
one_row = [ ]
for col_num in range(size):
y = row_num * tile_height
x = col_num * tile_width
pos = (x,y)
one_tile = Tile(pos, tile_width, tile_height)
i=0
content = image_surfaces[i]
i+=1
one_tile.set_content(content)
one_row.append(one_tile)
return one_row
def play(self):
# Play the game until the player presses the close box.
# - self is the Game that should be continued or not.
while not self.close_clicked: # until player clicks close box
# play frame
self.handle_events()
self.draw()
if self.continue_game:
self.update()
self.decide_continue()
self.game_Clock.tick(self.FPS) # run at most with FPS Frames
#Per Second
def handle_events(self):
# Handle each user event by changing the game state
#appropriately.
# - self is the Game whose events will be handled
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
self.close_clicked = True
if event.type == pygame.MOUSEBUTTONDOWN:
self.handle_mouse_click(event)
def handle_mouse_click(self, event):
# responds to one mouse click on screen; that means changing the
# content of a tile if it is empty.
print("Screen was clicked at " + str(event.pos))
def draw(self):
# Draw all game objects.
# - self is the Game to draw
self.surface.fill(self.bg_color) # clear the display surface
#first
for row in self.grid:
for tile in row:
tile.draw()
pygame.display.update() # make the updated surface appear on the
def update(self):
# Update the game objects for the next frame.
# - self is the Game to update
pass
def decide_continue(self):
# Check and remember if the game should continue
# - self is the Game to check
return True
class Tile:
# A tile represents one location on a grid. Tiles hold content
# class attributes that are common to all tiles
surface = None
fg_color = pygame.Color("white")
bg_color = pygame.Color("black")
border_width = 3
#classmethod
def set_surface(cls, surface):
# sets the class attribute, surface
cls.surface = surface
def __init__(self, screen_position, width, height):
# initialize one instance of our Tile class. Tiles represent
# one 'position' in our board.
# - self: the tile being initialized
# - screen_position: the [x, y] coordinates to draw the tile at
# - width: the width of the tile
# - height: the height of the tile
self.screen_position = screen_position
self.content = ''
# create a rectangle defining our boundaries
x, y = screen_position
self.rect = pygame.Rect(x, y, width, height)
def draw_content(self):
image_1=pygame.image.load('image1.bmp')
image_2=pygame.image.load('image2.bmp')
image_3=pygame.image.load('image3.bmp')
image_4=pygame.image.load('image4.bmp')
image_5=pygame.image.load('image5.bmp')
image_6=pygame.image.load('image6.bmp')
image_7=pygame.image.load('image7.bmp')
image_8=pygame.image.load('image8.bmp')
pygame_image_surfaces=[]
pygame_image_surfaces.append(image_1)
pygame_image_surfaces.append(image_2)
pygame_image_surfaces.append(image_3)
pygame_image_surfaces.append(image_4)
pygame_image_surfaces.append(image_5)
pygame_image_surfaces.append(image_6)
pygame_image_surfaces.append(image_7)
pygame_image_surfaces.append(image_8)
pygame_image_surfaces=pygame_image_surfaces+pygame_image_surfaces
random.shuffle(pygame_image_surfaces)
image_surfaces=pygame_image_surfaces
for i in range(len(image_surfaces)):
Tile.surface.blit(i)
#Tile.surface.blit(text_img, text_pos)
def draw(self):
# draw the contents of a tile to its surface.
# - self: the tile being drawn
self.draw_content()
pygame.draw.rect(Tile.surface, Tile.fg_color, self.rect,
Tile.border_width)
def set_content(self, new_content):
# - self: the tile whose content is being updated
self.content = new_content
main()
Trying to create a memory game in pygame but I'm having problems trying to blit the images onto each individual tile and would like some help troubleshooting. What I'm trying to do is from the list of image files that are now surface objects, I would like to blit one onto each tile. For some reason though my logic is wrong. I'm not sure if this stuff should go in my game class or my tile class since its describing the tile rather than the game.
Tldr: don't know how to blit the images onto each tile from a list
First of all, t he code which defines and loads the images, can be simplified a lot:
il = ['image' + str(i) + '.bmp' for i in range(1,9)]
pygame_image_surfaces = [pygame.image.load(os.path.join(path, name)) for name in imagenames]
The image which is associated to a Tile is stored in the instance attribute self.content of the Tile object. Use this attribute to draw the tile:
class Tile:
# [...]
def draw_content(self):
image_rect = self.content.get_rect(center = self.rect.center)
Tile.surface.blit(self.content, image_rect)
def draw(self):
# draw the contents of a tile to its surface.
# - self: the tile being drawn
self.draw_content()
pygame.draw.rect(Tile.surface, Tile.fg_color, self.rect, Tile.border_width)
def set_content(self, new_content):
# - self: the tile whose content is being updated
self.content = new_content
Create 1 random list of images. And use this list to set the images for the entire grid of tiles:
class Game:
# [...]
def create_grid(self, grid_size):
# Creates a grid of tiles that is grid_size x grid_size in size.
imgnames = ['image' + str(i) + '.bmp' for i in range(1,9)]
image_surfaces = [pygame.image.load(os.path.join(path, name)) for name in imgnames]
image_surfaces = image_surfaces + image_surfaces
random.shuffle(image_surfaces)
self.grid = []
# this for loop creates each row in our grid
for row_num in range(grid_size):
new_row = self.create_row(row_num, grid_size, image_surfaces)
self.grid.append(new_row)
def create_row(self, row_num, size, images):
# Create one row in a grid. Each row contains size Tiles.
# required for calculating the tile's x,y coordinates on screen
# - row_num: the nth row of the grid being created
# - size : the number of tiles in the row
# returns the newly created row'
tile_height = self.surface.get_height() // size
tile_width = 3/4*self.surface.get_width() // size
new_row = []
for col_num in range(size):
pos = (row_num * tile_height + 10, col_num * tile_width + 10)
content = images[row_num*size + col_num]
one_tile = Tile(pos, tile_width, tile_height)
one_tile.set_content(content)
new_row.append(one_tile)
return new_row

Pygame Scrolling Map

I'm making a rogue-like and I trying to make a camera follow the player as he moves by the map.
I was able to make the Draw function only happens when the tiles are inside the camera viewport[show in gray], but I can't make the camera stay in the corner of the window.
This is how it is:
And this is How it should be:
Is there a way to 'crop' the screen Surface, or perhaps copy only whats inside the camera viewport and blit it in the screen again.
Probably I'm doing this the hard way.
I'm iterating over the whole map, creating a rectangle for each tile, and checking if it's inside the Camera Viewport Rect using the '.contains()'.
EDIT:
This is how I'm drawing the map:
for x in xrange(mymap.width):
for y in xrange(mymap.height):
lit = field_of_view.lit(x, y)
visited = field_of_view.visited(x, y)
graphic = mymap.tileAt(x, y).graphic
if lit:
color = mymap.tileAt(x, y).color
elif visited:
color = GRAY
else:
color = BLACK
renderedgraphic = myfont.render(graphic, 1, color)
screen.blit(renderedgraphic, (x*TILESIZE, y*TILESIZE))
I do the same thing for the player, monsters, items and etc, but everything in it's own classmethod.
My camera is set like this:
class Camera(Object):
def __init__(self, x, y):
graphic = ''
Object.__init__(self, graphic, x, y)
self.rect = pygame.Rect(x, y, CAMERA_WIDTH * 2 + 5, CAMERA_HEIGHT * 2 + 5)
def update(self):
self.x = PLAYER.x
self.y = PLAYER.y
startx = ((self.x * TILESIZE) - CAMERA_WIDTH) + 5
starty = ((self.y * TILESIZE) - CAMERA_HEIGHT) + 5
self.rect = pygame.Rect(startx, starty, CAMERA_WIDTH * 2 + 5, CAMERA_HEIGHT * 2 + 5)
So I tried what user3762084 said.
in short:
for x in xrange(mymap.width):
for y in xrange(mymap.height):
... # do fov stuff
tile = Tile.At(x, y) # get tile instance
if tile:
tile.update() # update it's relative position
screen.blit(renderedgraphic, (tile.relX * TILESIZE, tile.relX * TILESIZE)) # blit the postion to it's relative position
This is what happens:
It's all squished in the side of the window. And if the player moves it all goes black.
What I have done before to make a scrolling environment was to give each object its coordinates in the world, and then give your camera a position. When you are drawing, you then calculate the position of each object on the screen based on the object's actual coordinates and the camera's coordinates.
class Tile:
def __init__(self, x, y, other_variables):
self.x = x
self.y = y
# relative vars for later
self.relX = None
self.relY = None
# then your camera should have a position as well as its width and height:
class Camera:
def __init__(self, x, y, width, height):
# assign those variables here
# your drawing function:
for tile in tiles:
tile.relX = tile.x - camera.x
tile.relY = tile.y - camera.y
# blit your tiles to the screen at the relative coordinates
In addition, you could also implement checks to see if the tile is completely outside of the camera space and not draw those tiles.
Ok I got what I was looking for, I did a litte hack tho. So if anyone have a better way to do this.
I changede the way my map was drawing
I took the Camera Rect positions [topleft and bottomright];
Converted it to World Position;
Iterate over it, with a enumerator too;
Did any lit/visited FOG calcunations with X and Y;
And Blited in the screen using the enumerators 'i' and 'j'.
Here's the code:
topleft = Map.toWorld(camera.rect.topleft)
bottomright = Map.toWorld(camera.rect.bottomright)
for i, x in enumerate(xrange(topleft[0], bottomright[0])):
for j, y in enumerate(xrange(topleft[1], bottomright[1])):
tile = mymap.tileAt(x, y)
object = [obj for obj in Object.OBJECTS if obj.pos == (x,y)]
if tile:
lit = field_of_view.lit(x, y)
visited = field_of_view.visited(x, y)
graphic = tile.graphic
if lit:
color = tile.color
elif visited:
color = GRAY
else:
color = BLACK
renderedgraphic = myfont.render(ch, 1, graphic)
screen.blit(renderedgraphic, Map.toScreen((i + 1, j)))
if object:
Draw.drawObject(object[0], Map.toScreen((i + 1, j)))
The only issue now is when the player is at the sides of the map it shows a black border.

Categories