Related
I'm having an issue with my snakegame on the collision detection side. What I wrote originally to do so was this:
snakecollisionchecklist = len(snake.snake_coord_list)
snakecollisionchecklistset = len(set(snake.snake_coord_list))
if snakecollisionchecklist != snakecollisionchecklistset:
return
The idea being that if any segment position in the snake was equal to another segment position it would abort the program. The problem I found was that the .position() function I was using to find the position of each segment returned a 10 decimal float value, and for whatever reason this wasn't consistent even if the snake was sharing the same place. Converting it to int doesnt work either as that sometimes will make 60 59 etc. if the variance is high/low. To deal with this I put this together, but I feel like this isn't the most efficient way to handle this:
values_to_convert = segments.position()
xvaluestoconvert = round(values_to_convert[0],2)
yvaluestoconvert = round(values_to_convert[1],2)
self.snake_coord_list[segloc] = (xvaluestoconvert, yvaluestoconvert)
This just takes the individual pieces of the position() and forces it to be rounded. I also had a similar issue with trying to make food not spawn inside the snake, which ended up looking like this:
newloc= positionlist[0]
while newloc in positionlist:
randomx = round((random.randint(-7,7) * 20))
randomy = round((random.randint(-7,7) * 20))
newloc = [randomx, randomy]
self.goto(float(randomx),float(randomy))
print(f"{randomx} and {randomy}")
But then I still get overlap.
Is there a better way to do this? If you're curious about full code it's here:
gamesetup.py
import turtle as t
class FullSnake:
def __init__(self) -> None:
self.snake_part_list = []
self.snake_coord_list = [(-40,0),(-60,0), (-80,0)]
self.moment_prior_coord_list = [(-40,0),(-60,0), (-80,0)]
for nums in range(3):
new_segment = t.Turtle("circle")
new_segment.penup()
new_segment.color("white")
self.snake_part_list.append(new_segment)
new_segment.goto(self.snake_coord_list[nums])
self.snake_head = self.snake_part_list[0]
self.headingverification = 0
def add_segment(self,):
"""This adds a segment to the snake. the segment added should be initialized to be add the end of the list."""
new_segment = t.Turtle("circle")
new_segment.penup()
new_segment.color("white")
self.snake_part_list.append(new_segment)
#self.snake_coord_list.append((self.snake_part_list[0].xcor(),self.snake_part_list[0].ycor()))
#self.snake_coord_list.append(new_segment)
# current_final_seg = self.snake_part_list[-2]
current_final_seg_pos = self.moment_prior_coord_list[-1]
#self.move_snake()
new_segment.goto(current_final_seg_pos[0],current_final_seg_pos[1])
self.snake_coord_list.append(current_final_seg_pos)
def right(self):
if self.headingverification != 180:
self.snake_head.setheading(0)
# time.sleep(0.031)
# self.move_snake()
#ime.sleep(0.05)
def up(self):
if self.headingverification != 270:
self.snake_head.setheading(90)
# time.sleep(0.031)
#self.move_snake()
def left(self):
if self.headingverification != 0:
self.snake_head.setheading(180)
# time.sleep(0.031)
# self.move_snake()
def down(self):
if self.headingverification != 90:
self.snake_head.setheading(270)
#time.sleep(0.031)
# self.move_snake()
def move_snake(self):
"""moves snake. snake moves forward 20 units, and prior units get updated"""
self.moment_prior_coord_list = list(self.snake_coord_list)
for seg_num in range(len(self.snake_part_list)-1,0,-1):
new_x = round(self.snake_part_list[seg_num-1].xcor(),2)
new_y = round(self.snake_part_list[seg_num-1].ycor(),2)
self.snake_part_list[seg_num].goto(new_x, new_y)
self.snake_head.forward(20)
#print(self.snake_head.position())
for segments in self.snake_part_list:
segloc = self.snake_part_list.index(segments)
#for some reason segments.position() a varied float, so this just forces it to be samesies
values_to_convert = segments.position()
xvaluestoconvert = round(values_to_convert[0],2)
yvaluestoconvert = round(values_to_convert[1],2)
self.snake_coord_list[segloc] = (xvaluestoconvert, yvaluestoconvert)
print(self.snake_coord_list)
main.py:
import turtle as t
from gamesetup import FullSnake
import time
import food
import score_board as sb
screen = t.Screen()
screen.setup(food.screensize[0],food.screensize[1])
screen.bgcolor("Black")
screen.title("My Snake Game")
screen.tracer(0)
snakefood = food.Food()
snake = FullSnake()
scoreboard = sb.ScoreBoard()
screen.listen()
screen.onkey(snake.up,"Up")
screen.onkey(snake.down,"Down")
screen.onkey(snake.right,"Right")
screen.onkey(snake.left,"Left")
#game_is_on = True
#while game_is_on:
def snakemovefct():
snake.move_snake()
screen.update()
#what happens when you hit food, add to length of snake, increase score and move pellet to place that snake isnt
if snake.snake_head.distance(snakefood) <5:
snake.add_segment()
snakefood.refresh(snake.snake_coord_list)
scoreboard.score_event()
screen.update()
time.sleep(0.1)
#set gameover if you hit boundary
if snake.snake_head.xcor() > 150 or snake.snake_head.xcor() < -150 or snake.snake_head.ycor() > 150 or snake.snake_head.ycor() < -150:
scoreboard.game_over()
return
#check collision
snakecollisionchecklist = len(snake.snake_coord_list)
snakecollisionchecklistset = len(set(snake.snake_coord_list))
if snakecollisionchecklist != snakecollisionchecklistset:
scoreboard.game_over()
return
#this makes sure you cant press up and left when moving right to go left
snake.headingverification = snake.snake_head.heading()
#keep snake moving in loop, if no recursion it only moves once
screen.ontimer(snakemovefct,150)
screen.update()
screen.ontimer(snakemovefct,150)
screen.mainloop()
food.py
from turtle import Turtle
import random
screensize = (340, 340)
class Food(Turtle):
def __init__(self) -> None:
super().__init__()
self.shape("square")
self.penup()
#self.shapesize(stretch_len=0.5, stretch_wid=0.5)
self.color("red")
self.speed("fastest")
self.refresh([(20,0),(0,0), (-20,0)])
def refresh(self, positionlist):
newloc= positionlist[0]
while newloc in positionlist:
randomx = "{:.2f}".format(random.randint(-7,7) * 20)
randomy = "{:.2f}".format(random.randint(-7,7) * 20)
newloc = [randomx, randomy]
self.goto(float(randomx),float(randomy))
print(f"{randomx} and {randomy}")
scoreboard.py
import turtle as t
class ScoreBoard(t.Turtle):
def __init__(self) -> None:
super().__init__()
self.hideturtle()
self.penup()
self.pencolor("white")
self.speed("fastest")
self.score = 0
self.goto(0,120)
self.write(f"Current score: {self.score}", False, align="center")
def score_event(self):
self.score +=1
self.clear()
self.write(f"Current score: {self.score}", False, align="center")
def game_over(self):
self.goto(0,0)
self.write("GAME OVER", False, align="center")
I created a Flappy Bird game with NEAT systems built by python, following this tutorial from YouTube: Tech With Tim
When I finished all of the code, where he ran it and had had the game run normally, mine didn't show pipes on screen. My code is provided below:
import pygame, neat, time, os, random
##import all of the variables we'll need for AI running
pygame.font.init()
##enables using fonts/text on screen.
WIN_WIDTH = 500
WIN_HEIGHT = 800
##define the size of the window. We're working with a GUI for flappy bird AI.
BIRD_IMGS = [pygame.transform.scale2x(pygame.image.load(os.path.join("imgs", "bird1.png"))), pygame.transform.scale2x(pygame.image.load(os.path.join("imgs", "bird2.png"))), pygame.transform.scale2x(pygame.image.load(os.path.join("imgs", "bird3.png")))]
##import the images of the bird from the images directory. There are three as to make up the states of flapping.
PIPE_IMG = pygame.transform.scale2x(pygame.image.load(os.path.join("imgs", "pipe.png")))
##import the texture for the pipes from the same directory.
BASE_IMG = pygame.transform.scale2x(pygame.image.load(os.path.join("imgs", "base.png")))
BG_IMG = pygame.transform.scale2x(pygame.image.load(os.path.join("imgs", "bg.png")))
##repeat for the ground and the background. They are seperate pieces as to allow for handling the collision with the ground being death.
STAT_FONT = pygame.font.SysFont("comicsans", 50)
class Bird: ##define the bird and its system as a class.
IMGS = BIRD_IMGS ##easier system for calling back to our list of bird images
MAX_ROTATION = 25
ROT_VEL = 20
ANIMATION_TIME = 5
##some definitions to do with making sure the bird can't just start spinning around aimlessly instead of looking like it should be going forward.
def __init__(self, x, y): ##essentially telling the AI/game what the bird starts out as, and how to define each thing.
self.x = x ##the distance forward on the screen
self.y = y ##how high up on the screen the bird is
self.tilt = 0 ##the angle the bird is at
self.tick_count = 0 ##essentially handles all of the physics
self.vel = 0 ##speed.
self.height = self.y ##a bit more about the height
self.img_count = 0 ##which image of the bird to be automatically loading (pre-flight)
self.img = self.IMGS[0] ##references back to the IMGS variable at the start of the class, defining to the system that the 0 on the previous line means bird1.png
def jump(self):
self.vel = -10.5 ##tells the game where to start. 0,0 is in the top left. This is near to half way down.
self.tick_count = 0 ##keeps track of when the last jump was. All for physics of bird.
self.height = self.y ##where bird started from, where the bird is now.
def move(self):
self.tick_count += 1 ##it's jumped one, now the tick registers that.
d = self.vel*self.tick_count + 1.5*self.tick_count**2 ##this is literally just a physics equation to calculate where the bird is going. Tells game how much player is moving up/down and how long player has been moving for.
if d >= 16: ##assigning terminal velocity. Going much faster can make the game unplayable. If going faster than 16 pixels per second, just start going 16 pixels per second.
d = 16 ##if d has gone higher than 16, just make it 16 again. Terminal velocity.
if d < 0: ##if for some reason, there's no velocity, such as at the very start of the game, give a very slight jump boost of -2 pixels (upwards 2)
d -= 2
self.y = self.y + d ##make sure the new Y position is calculated with the variable we just made.
if d < 0 or self.y < self.height + 50: ##assign bird tilting for model
if self.tilt < self.MAX_ROTATION:
self.tilt = self.MAX_ROTATION
else:
if self.tilt > -90: ##eventually, nosedive when going down.
self.tilt -= self.ROT_VEL
def draw(self, win): ##this one draws the bird and window onto the screen. It creates the window, adds the bird to it, etc. Kind of confusing, but I will try to keep the comments simple enough.
self.img_count += 1 ##this just checks how many times/how long the current image of the bird has been on screen.
if self.img_count < self.ANIMATION_TIME:
self.img = self.IMGS[0]
elif self.img_count < self.ANIMATION_TIME*2:
self.img = self.IMGS[1]
elif self.img_count < self.ANIMATION_TIME*3:
self.img = self.IMGS[2]
elif self.img_count == self.ANIMATION_TIME*4 + 1:
self.img = self.IMGS[0]
self.img_count = 0 ##essentially, this just does a check for how long each anim has been playing, and if it should change the image. It gets really complicated to actually explain though, so sorry.
if self.tilt <= -80:
self.img = self.IMGS[1]
self.img_count = self.ANIMATION_TIME*2 ##this prevents frames being skipped.
rotated_image = pygame.transform.rotate(self.img, self.tilt) ##this will rotate the bird using all of the logic we just defined above.
new_rect = rotated_image.get_rect(center=self.img.get_rect(topleft = (self.x, self.y)).center) ##changes the center of rotation for the bird from the top left to the center of the screen.
win.blit(rotated_image, new_rect.topleft)
def get_mask(self):
return pygame.mask.from_surface(self.img) ##to be used for accurate collision later.
class Pipe: ##now we make the pipes.
GAP = 200 ##distance between each other.
VEL = 5 ##how fast they're moving towards the left.
def __init__(self, x):
self.x = x ##position as last time.
self.height = 0 ##height to be defined by a random system later.
self.gap = 100 ##gap again to be updated by a random system later.
self.top = 0 ##where the top pipe is. To be defined by random system later.
self.bottom = 0 ##same as prior, but for the bottom pipe instead.
self.PIPE_TOP = pygame.transform.flip(PIPE_IMG, False, True) ##flipping the top pipe. By default the model is upright, we need one going the other way as well.
self.PIPE_BOTTOM = PIPE_IMG ##here's the bottom pipe now imported.
self.passed = False ##has bird passed pipe? Default false
self.set_height() ##set the height for the pipe, dealt with later.
def set_height(self):
self.height = random.randrange(50, 450) ##random height for the pipes.
self.top = self.height - self.PIPE_TOP.get_height() ##defining the top pipe's height using the bottom pipe.
self.bottom = self.height + self.GAP ##defining the distance between the pipes
def move(self): ##defining the movement, just moving the pipes to the left.
self.x -= self.VEL
def draw(self, win): ##drawing the pipes as we want them to appear on screen
win.blit(self.PIPE_TOP, (self.x, self.top))
win.blit(self.PIPE_BOTTOM, (self.x, self.bottom))
def collide(self, bird): ##this is all the code for PIXEL PERFECT collision. Instead of box collision, it checks to see if coloured pixels are overlapping to avoid deaths that didn't deserve to be deaths.
bird_mask = bird.get_mask()
top_mask = pygame.mask.from_surface(self.PIPE_TOP)
bottom_mask = pygame.mask.from_surface(self.PIPE_BOTTOM) ##masks the images, allowing to spot pixels from blank space.
top_offset = (self.x - bird.x, self.top - round(bird.y)) ##checks distance from bird, round is because we cannot have decimal numbers, so it rounds to nearest whole
bottom_offset = (self.x - bird.x, self.bottom - round(bird.y))
b_point = bird_mask.overlap(bottom_mask, bottom_offset) ##checks the first point of overlapping for bird and bottom pipe
t_point = bird_mask.overlap(top_mask, top_offset) ##repeat for top pipe
if t_point or b_point:
return True
return False
class Base:
VEL = 5
WIDTH = BASE_IMG.get_width()
IMG = BASE_IMG
def __init__(self, y): ##same as pipe and bird, defining all of the variables.
self.y = y
self.x1 = 0
self.x2 = self.WIDTH
def move(self): ##defining movement speed to velocity.
self.x1 -= self.VEL
self.x2 -= self.VEL
if self.x1 + self.WIDTH < 0: ##it creates two versions of the image, one directly behind the other, therefore it looks like one continuous image. If one has left the screen completely, move it back to behind the other one and get going again.
self.x1 = self.x2 + self.WIDTH
if self.x2 + self.WIDTH < 0:
self.x2 = self.x1 + self.WIDTH
def draw(self, win):
win.blit(self.IMG, (self.x1, self.y))
win.blit(self.IMG, (self.x2, self.y))
def draw_window(win, birds, pipes, base, score):
win.blit(BG_IMG, (0,0)) ##set up the background image for the game
for pipe in pipes:
pipe.draw(win) ##draw the pipes
base.draw(win) ##draw the floor into the window
for bird in birds:
bird.draw(win) ##draw the bird into the window
pygame.display.update() ##update the display
text = STAT_FONT.render("Score: " + str(score), 1, (255,255,255))
win.blit(text, (WIN_WIDTH - text.get_width() - 15, 10))
def main(genomes, config):
nets = []
ge = []
birds = []
for _, g in genomes:
net = neat.nn.FeedForwardNetwork.create(g, config)
nets.append(net)
birds.append(Bird(230, 350))
g.fitness = 0
ge.append(g)
base = Base(730) ##place the base of the window at 730
pipes = [Pipe(600)]
score = 0
win = pygame.display.set_mode((WIN_WIDTH, WIN_HEIGHT)) ##define the window.
clock = pygame.time.Clock() ##start a clock so that the game runs at a consistent speed and is not relative to FPS(*COUGH* BETHESDA AND FALLOUT 76 *COUGH*)
run = True ##by default of course, the game is running.
while run: ##while the game is running. Pygame doesn't like to close without being informed prior.
clock.tick(30) ##define the speed of that clock.
for event in pygame.event.get(): ##keeps track of whenever something is triggered, such as user clicking mouse, or AI activating output
if event.type == pygame.QUIT: ##check if the active event is a quit-based event to close the game.
run = False ##tell run it is now false as to allow the while loop to stop.
pygame.quit() ##when any quit function, such as clicking the red X is triggered (windows) it will close the game, and tell pygame and python to shut off.
quit()
pipe_ind = 0
if len(birds) > 0:
if len(pipes) > 1 and birds[0].x > pipes[0].x + pipes[0].PIPE_TOP.get_width():
pipe_ind = 1
else:
run = False
break
for x, bird in enumerate(birds):
bird.move()
ge[x].fitness += 0.1
output = nets[x].activate((bird.y, abs(bird.y - pipes[pipe_ind].height), abs(bird.y - pipes[pipe_ind].bottom)))
if output[0] > 0.5:
bird.jump()
add_pipe = False
base.move() ##moves the base.
rem = []
for pipe in pipes: ##it gets each pipe option in the list of pipes
for x, bird in enumerate(birds):
if pipe.collide(bird): ##if the pipe collides with the bird using specified collision in that section, kill player
ge[x].fitness -= 1
birds.pop(x)
nets.pop(x)
ge.pop(x)
if not pipe.passed and pipe.x < bird.x: ##once the pipe has been passed by the bird, add a new one.
pipe.passed = True
add_pipe = True
if pipe.x + pipe.PIPE_TOP.get_width() < 0: ##once the pipes has left the screen, remove the pipe, add it to a list of removed pipes
rem.append(pipe)
pipe.move() ##enable pipe movement.
if add_pipe: ##add one score to the player once the pipe has been passed.
score += 1
for g in ge:
g.fitness += 5
pipes.append(Pipe(600))
for r in rem:
pipes.remove(r)
for x, bird in enumerate(birds):
if bird.y + bird.img.get_height() >= 730 or bird.y < 0: ##handling for bird hitting floor.
birds.pop(x)
nets.pop(x)
ge.pop(x)
draw_window(win, birds, pipes, base, score) ##calls the above draw window stuff
def run(config_path): ##start running the new config.
config = neat.config.Config(neat.DefaultGenome, neat.DefaultReproduction, neat.DefaultSpeciesSet, neat.DefaultStagnation, config_path) ##pull in a ton of stuff for NEAT to work with
p = neat.Population(config) ##set our population (how many AIs will go per generation/how many birds are on screen at once. The config is 100, as this is best for evolution-performance.)
p.add_reporter(neat.StdOutReporter(True))
stats = neat.StatisticsReporter()
p.add_reporter(stats)
winner = p.run(main,50)
if __name__ == "__main__":##loading in the config file to create the network.
local_dir = os.path.dirname(__file__)
config_path = os.path.join(local_dir, "config-feedforward.txt")
run(config_path)
Any ideas why this is not working? Thanks.
you have indented the pipe.move() one too many times.
It should be
if pipe.x + pipe.PIPE_TOP.get_width() < 0:
rem.append(pipe)
pipe.move() #enable pipe movement.
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.
I'm learning python from a beginners book. Below is an extract of code from that book (Python Programming for the absolute beginner, 3rd edition) for a game.
My question is a fairly simple one. The update() methods appear never to be invoked, yet still function. How does this work?
I've pasted the whole block of code, so nothing is missing.
# Pizza Panic
# Player must catch falling pizzas before they hit the ground
from livewires import games, color
import random
games.init(screen_width = 640, screen_height = 480, fps = 50)
class Pan(games.Sprite):
"""
A pan controlled by player to catch falling pizzas.
"""
image = games.load_image("pan.bmp")
def __init__(self):
""" Initialize Pan object and create Text object for score. """
super(Pan, self).__init__(image = Pan.image,
x = games.mouse.x,
bottom = games.screen.height)
self.score = games.Text(value = 0, size = 25, color = color.black,
top = 5, right = games.screen.width - 10)
games.screen.add(self.score)
def update(self):
""" Move to mouse x position. """
self.x = games.mouse.x
if self.left < 0:
self.left = 0
if self.right > games.screen.width:
self.right = games.screen.width
self.check_catch()
def check_catch(self):
""" Check if catch pizzas. """
for pizza in self.overlapping_sprites:
self.score.value += 10
self.score.right = games.screen.width - 10
pizza.handle_caught()
class Pizza(games.Sprite):
"""
A pizza which falls to the ground.
"""
image = games.load_image("pizza.bmp")
speed = 1
def __init__(self, x, y = 90):
""" Initialize a Pizza object. """
super(Pizza, self).__init__(image = Pizza.image,
x = x, y = y,
dy = Pizza.speed)
def update(self):
""" Check if bottom edge has reached screen bottom. """
if self.bottom > games.screen.height:
self.end_game()
self.destroy()
def handle_caught(self):
""" Destroy self if caught. """
self.destroy()
def end_game(self):
""" End the game. """
end_message = games.Message(value = "Game Over",
size = 90,
color = color.red,
x = games.screen.width/2,
y = games.screen.height/2,
lifetime = 5 * games.screen.fps,
after_death = games.screen.quit)
games.screen.add(end_message)
class Chef(games.Sprite):
"""
A chef which moves left and right, dropping pizzas.
"""
image = games.load_image("chef.bmp")
def __init__(self, y = 55, speed = 2, odds_change = 200):
""" Initialize the Chef object. """
super(Chef, self).__init__(image = Chef.image,
x = games.screen.width / 2,
y = y,
dx = speed)
self.odds_change = odds_change
self.time_til_drop = 0
def update(self):
""" Determine if direction needs to be reversed. """
if self.left < 0 or self.right > games.screen.width:
self.dx = -self.dx
elif random.randrange(self.odds_change) == 0:
self.dx = -self.dx
self.check_drop()
def check_drop(self):
""" Decrease countdown or drop pizza and reset countdown. """
if self.time_til_drop > 0:
self.time_til_drop -= 1
else:
new_pizza = Pizza(x = self.x)
games.screen.add(new_pizza)
# set buffer to approx 30% of pizza height, regardless of pizza speed
self.time_til_drop = int(new_pizza.height * 1.3 / Pizza.speed) + 1
def main():
""" Play the game. """
wall_image = games.load_image("wall.jpg", transparent = False)
games.screen.background = wall_image
the_chef = Chef()
games.screen.add(the_chef)
the_pan = Pan()
games.screen.add(the_pan)
games.mouse.is_visible = False
games.screen.event_grab = True
games.screen.mainloop()
# start it up!
main()
The update() methods of each objects are called by the game module you are importing at the top:
from livewires import games, color
The GUI that these modules are handling runs in a loop that manages events and callbacks.
If you are curious, you could open load and read these files, and find out how the code works. You will find that each object is, in turn, calling its own update() method.
You are using a framework livewires which you have imported at the top. That framework takes your objects and then invokes methods on them, instead of you calling the methods.
See this for a high level view of the difference between Frameworks and Libraries: Framework vs. Toolkit vs. Library
I am writing a game where a rock falls down and you use the mouse to have your chef not get crushed. When the rock falls off the screen, two additional rocks spawn and fall. This continues until your character gets smooshed.
However I got the error:
TypeError: unbound method additonal_drop() must be called with Dropper instance as first argument (got nothing instead)
I'm not sure what I should be putting in the (). Can someone explain this error to me?
In addition, how do I get the Dropper sprite to not be visible?
Here's my code:
from livewires import games, color
import random
games.init(screen_width = 640, screen_height = 480, fps = 50)
class Chef(games.Sprite):
image = games.load_image("chef.bmp")
def __init__(self):
super(Chef, self).__init__(image = Chef.image,
x = games.mouse.x,
bottom = games.screen.height)
def update(self):
""" Move to mouse x position. """
self.x = games.mouse.x
if self.left < 0:
self.left = 0
if self.right > games.screen.width:
self.right = games.screen.width
self.check()
def check(self):
""" Check if hit by rocks. """
for rock in self.overlapping_sprites:
rock.end_game()
class Rock(games.Sprite):
"""
A rock which falls to the ground.
"""
image = games.load_image("rock.bmp")
speed = 1
def __init__(self, x = 320, y = 90):
""" Initialize a rock object. """
super(Rock, self).__init__(image = Rock.image,
x = x, y = y,
dy = Rock.speed)
def end_game(self):
""" End the game. """
end_message = games.Message(value = "Game Over",
size = 90,
color = color.red,
x = games.screen.width/2,
y = games.screen.height/2,
lifetime = 2 * games.screen.fps,
after_death = games.screen.quit)
games.screen.add(end_message)
def update(self):
""" Check if bottom edge has reached screen bottom. """
if self.bottom > games.screen.height:
self.destroy()
Dropper.additonal_drop()
class Dropper(games.Sprite):
"""
A invisible sprite that drops the rocks.
"""
image = games.load_image("rock.bmp")
def __init__(self, y = 55, speed = 2, odds_change = 200):
""" Initialize the dropper object. """
super(Dropper, self).__init__(image = Dropper.image,
x = games.screen.width / 2, y = y,
dx = speed)
self.odds_change = odds_change
self.time_til_drop = 0
def update(self):
""" Determine if direction needs to be reversed. """
if self.left < 0 or self.right > games.screen.width:
self.dx = -self.dx
elif random.randrange(self.odds_change) == 0:
self.dx = -self.dx
def additonal_drop(self):
new_rock = Rock(x = self.x)
games.screen.add(new_rock)
new_rock = Rock(x = self.x)
games.screen.add(new_rock)
def main():
""" Play the game. """
wall_image = games.load_image("wall.jpg", transparent = False)
games.screen.background = wall_image
the_chef = Chef()
games.screen.add(the_chef)
the_rock = Rock()
games.screen.add(the_rock)
the_dropper = Dropper()
games.screen.add(the_dropper)
games.mouse.is_visible = False
games.screen.event_grab = True
games.screen.mainloop()
# start it up!
main()
Try defining the Rock class with an additional parameter dropper:
def __init__(self, x = 320, y = 90, dropper=Dropper()):
dropper will be the Dropper instance. Then create Rock instances from inside Dropper as follows:
Rock(x=self.x, dropper=self)
This will pass the Dropper instance itself to each Rock instance that the Dropper instance creates. In Rock's __init__(), save a reference to the Dropper instance:
self.dropper = dropper
Call additional_drop() with:
self.dropper.additional_drop()