Tkinter Canvas game needs a slight rework - python

Here's the Code firstly - I'll get into the problems after. Although I'm sure you can spot plenty without needing to scroll to far down.
from tkinter import *
import random
import math
import time
test = random.randint(10,40)
print(test)
class Game():
global x0,y0,x1,y1,Game,debug, Player, mousex0, mousey0, mousex1,
mousey1, moveTowardMouse, rayCast, speed, frayCast, fx0, fy0,
fx1, fy1, Food
def move(event):
global x0,y0,x1,y1,mouseX0,mouseY0,mouseX1,mouseY1,fx0, fy0,
fx1, fy1,Food
mouseX0 = event.x - 10
mouseY0 = event.y - 10
mouseX1 = event.x + 10
mouseY1 = event.y + 10
Game.coords(rayCast, x0, y0, mouseX1, mouseY1)
if x0 != mouseX0 and x0 < mouseX0:
x0 = x0 + speed
x1 = x1 + speed
Game.coords(Player, x0, y0, x1, y1)
if x0 != mouseX0 and x0 > mouseX0:
x0 = x0 - speed
x1 = x1 - speed
Game.coords(Player, x0, y0, x1, y1)
if y0 != mouseY0 and y0 < mouseY0:
y0 = y0 + speed
y1 = y1 + speed
Game.coords(Player, x0, y0, x1, y1)
if y0 != mouseY0 and y0 > mouseY0:
y0 = y0 - speed
y1 = y1 - speed
Game.coords(Player, x0, y0, x1, y1)
Game.coords(frayCast, x0,y0, fx0,fy0)
if fx0 > x0 and (fx0 - x0) < 20:
fx0 = fx0 + 0.5
fx1 = fx1 + 0.5
Game.coords(Food, fx0,fy0,fx1,fy1)
if fx0 < x0 and (fx0 + x0) < 20:
fx0 = fx0 - 0.5
fx1 = fx1 - 0.5
Game.coords(Food, fx0,fy0,fx1,fy1)
if fy0 > y0 and (fy0 - y0) < 20:
fy0 = fy0 + 0.5
fy1 = fy1 + 0.5
Game.coords(Food, fx0,fy0,fx1,fy1)
if fy0 < y0 and (fy0 - y0) < 20:
fy0 = fy0 - 0.5
fy1 = fy1 - 0.5
Game.coords(Food, fx0,fy0,fx1,fy1)
if fx0 > x0 and (fx0 - x0) < 5:
if fy0 > y0 and (fy0 - y0) <5:
Game.delete(Food)
x0 = x0 - fx1
y0 = y0 - fy1
Game.coords(Player, x0,y0,x1,y1)
fx0 = 20
fy0 = 20
fx1 = test + 20
fy1 = test + 20
x0 = -50
y0 = -50
x1 = 50
y1 = 50
speed = 1
mouseX0 = 0
mouseY0 = 0
mouseX1 = 0
mouseY1 = 0
debug = "DEBUGGED"
module = Tk()
Game = Canvas(module, width=1000, height=1000)
Player = Game.create_oval(x0,y0,x1,y1,fill="blue")
Food = Game.create_oval(fx0, fy0, fx1, fy1, fill="red")
rayCast = Game.create_line(x0,y0,mouseX1,mouseY1)
frayCast = Game.create_line(x0,y0,mouseX1,mouseY1)
module.bind('<Motion>', move)
Game.pack()
module.mainloop()
So I'm having just a slight "oh snap" just a moment ago when I realised that my code was basically useless.
In the game I'm creating, I'm wanting the Player controlled sprite on the canvas to move at a slow speed towards the mouse. I googled how to get the mouse coordinates, it told me that I could use the event to get the coords in a function. However since getting those coords I've slowly put all the major sprite movement calculations in the same function resulting in a functional game... that only does something as long as your moving the mouse.
The idea is that the NPC-sprite is a random size and spawns in a random space on the canvas. It moves in random directions in a slow speed until it is within "20" of the player controlled sprite, in which case it moves (faster) away from the player-controlled sprite.
Aside from the fact that all this only happens when you move the mouse (and that I'm still using raycasting to get a trajectory for the sprites to follow, there are a few more issues I need help with.
Firstly, the random size of the NPC sprite works great. But it spawns exactly the same place every time.
Secondly, the NPC's "avoid the player" code seems to be... less than functional. Basically it works fine, but then it just keeps working even after the player has moved "20" away from the sprite.
Lastly, I'm having an issue with the coords of the sprites themselves. You see the raycasting reveals that the true coords for the sprites are not in the centre of their canvas representation of a circle, but instead in the top left corner of what would be an invisible square around said circle. I need this to be in the centre of the sprite rather than not otherwise the gameplay mechanics become a little bit buggy.
Back to the biggest issue (With the way updates work for sprite coords) I'm fine with setting up like Updates per Tick within the game and run all my calculations every tick, but then I wouldn't know how to get the mouse coords outside of using that event.
Long story short some help would be much appreciated.

Here is your improved code. I explained everything in the comments.
from tkinter import *
import random
import math
import time
class Game():
# This __init__ will run first when you run Game()
# Learn to always set up your programs this way.
def __init__(self):
test = random.randint(10,40)
print(test)
# Put self. before the name of every variable you might want to use outside this __init__ function.
# this way you don't need to define them separately in every function
# You defined these xs and ys by hand so of course it remains in the same position.
# define them randomly and you'll be fine.
self.fx0 = 20
self.fy0 = 20
self.fx1 = test + 20
self.fy1 = test + 20
self.x0 = -50
self.y0 = -50
self.x1 = 50
self.y1 = 50
self.speed = 1
self.mouseX0 = 0
self.mouseY0 = 0
self.mouseX1 = 0
self.mouseY1 = 0
self.debug = "DEBUGGED"
# Added these two variables
self.tick_intervals = 10
self.food_exists = True
self.module = Tk()
#Game is the name of your class don't use it here: (use game instead)
self.game = Canvas(self.module, width=1000, height=1000)
self.Player = self.game.create_oval(self.x0,self.y0,self.x1,self.y1,fill="blue")
self.Food = self.game.create_oval(self.fx0, self.fy0, self.fx1, self.fy1, fill="red")
self.rayCast = self.game.create_line(self.x0,self.y0,self.mouseX1,self.mouseY1)
self.frayCast = self.game.create_line(self.x0,self.y0,self.mouseX1,self.mouseY1)
self.game.pack()
self.move()
self.module.mainloop()
def move(self):
# To get mouse position on your whole screen: .winfo_pointerx() and .winfo_pointery()
# To get position of widget (self.game) on screen .winfo_rootx()
# x and y below are the same as mouse event.x and event.y without the need to bind anything to self.module
x = self.game.winfo_pointerx()-self.game.winfo_rootx()
y = self.game.winfo_pointery()-self.game.winfo_rooty()
# If you have a value you use more than 1 time,
# it's best to define it first then put that here
# instead of writing it out every time.
# this way you can change it very easily
# better define these in __init__ function with self.something = ...
self.mouseX0 = x - 10 # define var for 10
self.mouseY0 = y - 10
self.mouseX1 = x + 10
self.mouseY1 = y + 10
# You should also design a (visible or invisible) wall around the screen
# so your Player and Food can't run off the screen.
# Basically it's numbers and some if statements.
# If you don't put elif here x and y might get resized then resized again.
# but you only need to resize them once a tick.
# You don't need != here. < > are enough.
# Look up += -= *= /= functions.
if self.x0 < self.mouseX0:
self.x0 += self.speed
self.x1 += self.speed
elif self.x0 > self.mouseX0:
self.x0 -= self.speed
self.x1 -= self.speed
if self.y0 < self.mouseY0:
self.y0 += self.speed
self.y1 += self.speed
elif self.y0 > self.mouseY0:
self.y0 -= self.speed
self.y1 -= self.speed
# Need to call these once a tick and not every time you change x or y
self.game.coords(self.rayCast, self.x0, self.y0, self.mouseX1, self.mouseY1)
self.game.coords(self.Player,self.x0,self.y0,self.x1,self.y1)
# After you eat food this shouldn't run any more.
# This is why Player kept getting bigger and bigger
if self.food_exists:
if self.fx0 > self.x0 and (self.fx0 - self.x0) < 20: # define var for 20
self.fx0 += 0.5 # define var for 0.5
self.fx1 += 0.5
elif self.fx0 < self.x0 and (self.fx0 + self.x0) < 20:
self.fx0 -= 0.5
self.fx1 -= 0.5
if self.fy0 > self.y0 and (self.fy0 - self.y0) < 20:
self.fy0 += 0.5
self.fy1 += 0.5
elif self.fy0 < self.y0 and (self.fy0 - self.y0) < 20:
self.fy0 -= 0.5
self.fy1 -= 0.5
if self.fx0 > self.x0 and (self.fx0 - self.x0) < 5: # define var for 5
if self.fy0 > self.y0 and (self.fy0 - self.y0) <5:
self.game.delete(self.Food)
self.x0 -= self.fx1
self.y0 -= self.fy1
self.food_exists = False
self.game.coords(self.Food,self.fx0,self.fy0,self.fx1,self.fy1)
self.game.coords(self.frayCast, self.x0,self.y0, self.fx0,self.fy0)
# This automatically runs self.move after (self.tick_intevals) miliseconds
self.game.after(self.tick_intervals,self.move)
# This IF runs the Game() only if you run the script yourself.
# This way if you imported this script into another program it wouldn't run Game()
# Learn to always use this if for your programs
if __name__=='__main__':
Game()

Related

Why won't my Spaceship point toward the planet? [duplicate]

This question already has answers here:
How to know the angle between two vectors?
(7 answers)
How to rotate an image(player) to the mouse direction?
(2 answers)
How do I rotate an image around its center using Pygame?
(6 answers)
Closed 2 years ago.
Small gif showing problem but I don't have 10 reputation yet (this is only my 2nd question) and thus have to use link
I have a simple test program with a Spaceship which is supposed to point toward a planet, but it points in odd directions instead.
The purpose of the program is to test whether my test program can get the angle from the spaceship to the planet, so an alternate method won't work, since I need the angle to determine which direction to apply "gravity" in (which is what this test is for).
This is also why the script repeats multiple times but only the final angle will be graphically displayed.
My program uses code gotten from SO question Calculate angle (clockwise) between two points
The answer by "Chris St Pierre" just gave me an error for attempting to divide a float by zero (?).
The answer by "ali_m" just gave me a problem like this one.
I'm using the answer by "Colin Basnett", which doesn't work for me either, but it's my favorite method so far because it doesn't require plugins (and because it's short and doesn't just straight-away throw an error at me).
I adapted it into the function below:
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def get_angle_between(x0,y0,x1,y1):
v1 = Vector(x0, y0)
v2 = Vector(x1, y1)
v1_theta = math.atan2(v1.y, v1.x)
v2_theta = math.atan2(v2.y, v2.x)
r = (v2_theta - v1_theta) * (180.0 / math.pi)
return r
It is called by this script in the Spaceship sprite's "move" function:
if gravityJsonQ:
for item in planets:
centreOfGravityX = planets[item]["x"] + (planets[item]["s"] / 2)
centreOfGravityY = planets[item]["y"] + (planets[item]["s"] / 2)
centreOfGravityGravity = float(planets[item]["g"])
pendingUtil = get_points(prevSubPositionX,prevSubPositionY,subPositionX,subPositionY)
for item2 in pendingUtil:
cfx,cfy = item2
circular_percentage = get_angle_between(cfx,cfy,centreOfGravityX,centreOfGravityY) / 3.6
circular_percentage (cp) is essentially degrees / 3.6 (anticlockwise, which, although the link is for clockwise angles, I tried subtracting it from 100cp (360deg) to no avail, so I doubt that's the problem)
get_points() is "Bresenham's line algorithm", and it works fine.
planets is the following dictionary:
{"Earthlike": {"x": 375, "y": 375, "s": 200, "i": "earthlike_1_beveled.png", "g": 11}}
I've tried fiddling with it a bit to see if it would start working, but the main problem is I don't understand any of the math involved, so the linked Wikipedia article(s) went right over my head.
I have (frankly) not a clue to what's causing the problem or how to solve it.
Here's a link to download all 194KB (it's actually 10KB smaller when unzipped) of the program and it's textures. (Use WASD/arrow keys to move, the problem is in either lines 49 to 63 or 100 to 108 (the first line is #1 not #0)):
https://www.filehosting.org/file/details/920907/SOQ.zip
There might be some unnecessary code since I just got my main program and cut out most of the bits that weren't needed.
Just in case, here's the code (it's in the zip, but I figured I'm probably supposed to put it here anyway even though it is unrunable (real word?) without the textures):
#See lines (this line is #1) - 49 to 63 - and - 100 to 108
import json, math, os, pygame, sys, time
from pygame.locals import *
pygame.init()
baseFolder = __file__[:-10]
FPS = 30
FramePerSec = pygame.time.Clock()
xVelocity = yVelocity = rVelocity = float(0)
def get_points(x0,y0,x1,y1):
pointlist = []
x0,y0 = int(x0),int(y0)
x1,y1 = int(x1),int(y1)
dx = abs(x1-x0)
dy = abs(y1-y0)
if x0 < x1: sx = 1
else: sx = -1
if y0 < y1: sy = 1
else: sy = -1
err = dx-dy
while True:
pointlist.append((x0,y0))
if x0 == x1 and y0 == y1: return pointlist
e2 = 2 * err
if e2 > -dy:
err = err - dy
x0 += sx
if e2 < dx:
err = err + dx
y0 += sy
screen_size = 750
spaceship_texture = "spaceship.png"
spaceship_texture = spaceship_texture.replace("\n","")
spaceship_size = 60
gravityJsonQ = True
planets = {"Earthlike": {"x": 375, "y": 375, "s": 200, "i": "earthlike_1_beveled.png", "g": 11}}
displaySurf = pygame.display.set_mode((screen_size,screen_size))
displaySurf.fill((0,0,0))
subPositionX = subPositionY = float(screen_size / 2)
circular_percentage = 0
#Problem:
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def get_angle_between(x0,y0,x1,y1):
v1 = Vector(x0, y0)
v2 = Vector(x1, y1)
v1_theta = math.atan2(v1.y, v1.x)
v2_theta = math.atan2(v2.y, v2.x)
r = (v2_theta - v1_theta) * (180.0 / math.pi)
return r
#or mabye...
class Spaceship(pygame.sprite.Sprite):
def __init__(self):
global baseFolder, screen_size, spaceship_images, spaceship_size, spaceship_texture
super().__init__()
spaceship_images = {}
for pendingUtil in range(0,100): spaceship_images[str(pendingUtil)] = pygame.image.load(baseFolder + "\\" + spaceship_texture + ".texture_map\\" + str(pendingUtil) + ".png")
self.image = spaceship_images["0"]
self.surf = pygame.Surface((int(spaceship_size), int(spaceship_size)))
self.rect = self.surf.get_rect(center = (int(screen_size / 2),int(screen_size / 2)))
self.image = pygame.transform.scale(self.image,(spaceship_size,spaceship_size))
def move(self):
global circular_percentage, rVelocity, prevSubPositionX, prevSubPositionY, subPositionX, subPositionY, xVelocity, yVelocity
pressed_keys = pygame.key.get_pressed()
if pressed_keys[K_UP] or pressed_keys[K_w]:
yVelocity -= 0.1
if pressed_keys[K_DOWN] or pressed_keys[K_s]:
yVelocity += 0.1
if pressed_keys[K_LEFT] or pressed_keys[K_a]:
xVelocity -= 0.1
if pressed_keys[K_RIGHT] or pressed_keys[K_d]:
xVelocity += 0.1
prevSubPositionX,prevSubPositionY = subPositionX,subPositionY
subPositionX += xVelocity
subPositionY += yVelocity
#Problem:
if gravityJsonQ:
for item in planets:
centreOfGravityX = planets[item]["x"] + (planets[item]["s"] / 2)
centreOfGravityY = planets[item]["y"] + (planets[item]["s"] / 2)
centreOfGravityGravity = float(planets[item]["g"])
pendingUtil = get_points(prevSubPositionX,prevSubPositionY,subPositionX,subPositionY)
for item2 in pendingUtil:
cfx,cfy = item2
circular_percentage = get_angle_between(cfx,cfy,centreOfGravityX,centreOfGravityY) / 3.6
#Problem will (very likely) be either here or noted area above
while circular_percentage < 0: circular_percentage += 100
while circular_percentage > 99: circular_percentage -= 100
self.rect = self.surf.get_rect(center = (int(subPositionX),int(subPositionY)))
self.image = spaceship_images[str(int(circular_percentage))]
self.image = pygame.transform.scale(self.image,(spaceship_size,spaceship_size))
Player = Spaceship()
all_sprites = pygame.sprite.Group()
all_sprites.add(Player)
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
displaySurf.fill((0,0,0))
for item in planets:
current_planet_image = pygame.image.load(planets[item]["i"])
current_planet_image = pygame.transform.scale(current_planet_image,(planets[item]["s"],planets[item]["s"]))
displaySurf.blit(current_planet_image,(planets[item]["x"],planets[item]["y"]))
for entity in all_sprites:
displaySurf.blit(entity.image,entity.rect)
entity.move()
pygame.display.update()
FramePerSec.tick(FPS)
Note, Pygame provides the pygame.math.Vector2 class. It is not necessary to paint 100 images of the space ship with different angles. You can rotate an image with pygame.transform.rotate. See How do I rotate an image around its center using PyGame?.
The vector from the point (x0, y0) to the point (x1, y1) is:
v = Vector(x1-x0, y1-y0)
The angle of the vector is (see How to know the angle between two points?):
math.atan2(y1-y0, x1-x0)
The top left of the pygame coordinate system is (0, 0). Therefore the y-axis points downwards. Hence you have to invert the y-axis for the calculation of the angle.
get_angle_between function:
def get_angle_between(x0, y0, x1, y1):
v = Vector(x1-x0, y1-y0)
return math.degrees(math.atan2(-v.y, v.x))
In the above formula, an angle of 0 means that the spaceship is pointing to the right. If your spaceship is pointing up at a 0 angle, you'll need to add a correction angle (see How to rotate an image(player) to the mouse direction?):
def get_angle_between(x0, y0, x1, y1):
v = Vector(x1-x0, y1-y0)
return math.degrees(math.atan2(-v.y, v.x)) - 90
In this answer I want to explain the steps for the calculation. I want to keep it comprehensible. Of course, you can skip constructing the Vector object and put everything in one line of code:
def get_angle_between(x0, y0, x1, y1):
return math.degrees(math.atan2(y0-y1, x1-x0)) - 90
However, the bottleneck in the calculation is the function math.atan2.

Pygame: How do I apply Separating Axis Theorem (SAT) to Rotating Squares?

I don't want the code (I really do want the code), but can someone explain to me how I can create the diagonal line to see if there's a gap? I know we have to use vectors, but I don't know how to do that using python
So, using the logic of Separating Axis Theorem that if you cant draw a line in between 2 squares then they are overlapping and colliding. I made something close, its not perfect but its close, I also haven't accounted for rotation of squares but if you find a way to find the vertices/corners of the square, then this could easily work. The way i did it was that i turned the squares into lines and drew a line directly in the middle of the squares and at the normal of the line in between the squares, its a bit confusing but it makes sense once you see it. I then used line intersecting maths to find if they intersect.
import pygame
from pygame.locals import *
from pygame import Vector2
pygame.init()
screen = pygame.display.set_mode((500,500))
#check if 2 lines are intersecting
def LineIntersect(line1, line2):
#the math is from wikipedia
x1 = line1[0].x
y1 = line1[0].y
x2 = line1[1].x
y2 = line1[1].y
x3 = line2[0].x
y3 = line2[0].y
x4 = line2[1].x
y4 = line2[1].y
den = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
if den == 0:
return
t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / den
u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / den
if t > 0 and t < 1 and u > 0 and u < 1:
pt = Vector2()
pt.x = x1 + t * (x2 - x1)
pt.y = y1 + t * (y2 - y1)
return pt
return
#class for sqaure
class square:
def __init__(self,x,y,w):
self.x = x
self.y = y
self.w = w
self.centerx = self.x + w//2
self.centery = self.y + w//2
self.col = (255,0,0)
def Draw(self, outline = False):
if outline:
self.Outline()
else:
pygame.draw.rect(screen,self.col,(self.x,self.y,self.w,self.w))
def Outline(self):
for point1, point2 in self.Lines():
pygame.draw.line(screen,sqr2.col,point1,point2,1)
#get the lines that make up the square, the outline/perameter
def Lines(self):
lines = []
lines.append((Vector2(self.x,self.y),Vector2(self.x+self.w,self.y)))
lines.append((Vector2(self.x,self.y),Vector2(self.x,self.y + self.w)))
lines.append((Vector2(self.x + self.w,self.y + self.w),Vector2(self.x+self.w,self.y)))
lines.append((Vector2(self.x + self.w,self.y + self.w),Vector2(self.x,self.y + self.w)))
return lines
#draw a line inbetween the 2 squares
def DrawLineInBetween():
#draw a line between the 2 squares, get gradient
#to avoid divide by zero
if abs(sqr1.x - sqr2.x) == 0:
gradient = "infinity"
else:
#rise over run
#left - right = run
left = sqr1 if sqr1.x < sqr2.x else sqr2
right = sqr1 if left == sqr2 else sqr2
gradient = ((left.y - right.y)/abs(sqr1.x - sqr2.x))
#print("gradient:",gradient)
#get the middle point between the centers of the squares
middle = (max(sqr1.x + sqr1.w//2, sqr2.x + sqr2.w//2) - abs(sqr1.x - sqr2.x)//2,
max(sqr1.y + sqr1.w//2, sqr2.y + sqr2.w//2) - abs(sqr1.y - sqr2.y)//2)
#to avoid divide by 0
if gradient == 0:
point1 = Vector2(middle[0], middle[1] + 100)
point2 = Vector2(middle[0], middle[1] - 100)
elif gradient == "infinity":
point1 = Vector2(middle[0] - 100, middle[1])
point2 = Vector2(middle[0] + 100, middle[1])
else:
#get normal of line
gradient = -1/gradient
#print("normal:",gradient)
point1 = Vector2(middle[0] + 100, middle[1] + int(-100 * gradient))
point2 = Vector2(middle[0] - 100, middle[1] + int(100 * gradient))
#print(point1)
#print(point2)
#print(middle)
pygame.draw.line(screen,(0,255,0),point1,point2,1)
line = (point1, point2)
return line
sqr1 = square(100,100,50)
sqr2 = square(200,100,50)
Clock = pygame.time.Clock()
running = True
key = ""
while running:
screen.fill((0,0,0))
sqr1.Draw(outline=True)
sqr2.Draw()
line = DrawLineInBetween()
for sqr_line in sqr1.Lines():
pt = LineIntersect(line,sqr_line)
if pt:
pygame.draw.circle(screen,(0,255,255),(int(pt.x),int(pt.y)),5)
if key == "s":
sqr1.y += 1
elif key == "w":
sqr1.y -= 1
if key == "d":
sqr1.x += 1
if key == "a":
sqr1.x -= 1
pygame.display.update()
Clock.tick(60)
for e in pygame.event.get():
if e.type == pygame.QUIT:
pygame.quit()
running = False
if e.type == MOUSEBUTTONDOWN:
print(e.pos)
if e.type == KEYDOWN:
key = e.unicode
if e.type == KEYUP:
key = ""
doing rotating squares:
added rotation variable in square class, i used this answer to find the corners of the square, then once i have the corners, used the line intersetion.
Here is new class:
#class for sqaure
class square:
def __init__(self,x,y,w):
self.x = x
self.y = y
self.w = w
self.centerx = self.x + w//2
self.centery = self.y + w//2
self.col = (255,0,0)
self.rotation_angle = 0
def Draw(self, outline = False):
if outline:
self.Outline()
else:
pygame.draw.rect(screen,self.col,(self.x,self.y,self.w,self.w))
#this used the normal coordinate of an unrotated square to find new coordinates of rotated sqaure
def GetCorner(self,tempX,tempY):
angle = math.radians(self.rotation_angle)
rotatedX = tempX*math.cos(angle) - tempY*math.sin(angle);
rotatedY = tempX*math.sin(angle) + tempY*math.cos(angle);
x = rotatedX + self.centerx;
y = rotatedY + self.centery;
return Vector2(x,y)
def Outline(self):
for point1, point2 in self.Lines():
pygame.draw.line(screen,sqr2.col,point1,point2,1)
#new lines method, only changed to GetCorner()
def Lines(self):
lines = []
top_left = self.GetCorner(self.x - self.centerx, self.y - self.centery)
top_right = self.GetCorner(self.x + self.w - self.centerx, self.y - self.centery)
bottom_left = self.GetCorner(self.x - self.centerx, self.y + self.w - self.centery)
bottom_right = self.GetCorner(self.x + self.w - self.centerx, self.y + self.w - self.centery)
lines.append((top_left,top_right))
lines.append((top_left,bottom_left))
lines.append((bottom_right,top_right))
lines.append((bottom_right,bottom_left))
return lines
#chnaged to this as rotation rotates around center, so need to update both x and centerx
def Move(self,x =None, y = None):
if x:
self.x += x
self.centerx += x
if y:
self.y += y
self.centery += y
#get the lines that make up the square, the outline/perameter
#def Lines(self):
#lines = []
#lines.append((Vector2(self.x,self.y),Vector2(self.x+self.w,self.y)))
#lines.append((Vector2(self.x,self.y),Vector2(self.x,self.y + self.w)))
#lines.append((Vector2(self.x + self.w,self.y + self.w),Vector2(self.x+self.w,self.y)))
#lines.append((Vector2(self.x + self.w,self.y + self.w),Vector2(self.x,self.y + self.w)))
#return lines

Bouncing ball game tkinter canvas

I write a game in python in which the goal is to bounce the ball off the platform.
Everything works pretty well, but the platform's movement is not that smooth. Could you help me make the platform movement more smooth? If the code isn't too clear, I'm sorry, but I'm new in python
import tkinter as tk
import random
root = tk.Tk()
width = 900
height = 500
canvas = tk.Canvas(root, bg='white', width=width, height=height)
canvas.pack()
x = random.randrange(700)
ball = canvas.create_oval(x+10, 10, x+50, 50, fill='green')
platform_y = height - 20
platform = canvas.create_rectangle(width//2-50, platform_y, width//2+50, platform_y+10, fill='black')
xspeed = 2
yspeed = 2
skore = 0
body = 0
def move_ball():
global xspeed
global yspeed
x1, y1, x2, y2 = canvas.coords(ball)
if x1 <= 0 or x2 >= width:
xspeed = -xspeed
if y1 <= 0:
yspeed = 10
elif y2 == platform_y:
cx = (x1 + x2) // 2
px1, _, px2, _ = canvas.coords(platform)
if px1 <= cx <= px2:
yspeed = -10
else:
canvas.create_text(width//2, height//2, text='Game Over', font=('Arial Bold', 32), fill='red')
return
canvas.move(ball, xspeed, yspeed)
canvas.after(20, move_ball)
def board_right(event):
x1, y1, x2, y2 = canvas.coords(platform)
if x2 < width:
dx = min(width-x2, 10)
canvas.move(platform, dx, 0)
def board_left(event):
x1, y1, x2, y2 = canvas.coords(platform)
if x1 > 0:
dx = min(x1, 10)
canvas.move(platform, -dx, 0)
canvas.bind_all('<Right>', board_right)
canvas.bind_all('<Left>', board_left)
move_ball()
root.mainloop()
The problem is that the speed of the platform is dependent on the auto-repeat speed of your keyboard.
Instead of moving once for each <Right> or <Left> event, use a key press to start the platform moving in the desired direction and a key release to stop the platform moving. Then, use after to repeatedly move the platform in the given direction.
Example:
after_id = None
def platform_move(direction):
"""
direction should be -1 to move left, +1 to move right,
or 0 to stop moving
"""
global after_id
speed = 10
if direction == 0:
canvas.after_cancel(after_id)
after_id = None
else:
canvas.move(platform, direction*speed, 0)
after_id = canvas.after(5, platform_move, direction)
canvas.bind_all("<KeyPress-Right>", lambda event: platform_move(1))
canvas.bind_all("<KeyRelease-Right>", lambda event: platform_move(0))
canvas.bind_all("<KeyPress-Left>", lambda event: platform_move(-1))
canvas.bind_all("<KeyRelease-Left>", lambda event: platform_move(0))
The above code doesn't handle the case where you might press both keys at the same time, but that can be handled with a little additional logic. The point is to show how you can use the keys to start and stop an animation.

Snake segments that "flow" rather than "snap" to the snake's head position

I am trying to make a snake game in python and I would like the segments of the snake to flow when the user presses the WASD keys rather than the segments snapping to the user's desired direction
import pygame
import random
import time
pygame.init()
win = pygame.display.set_mode((800,600))
pygame.display.set_caption("Pygame")
clock = pygame.time.Clock()
x = 30
y = 30
x2 = x
y2 = random.randrange(1,601-30)
vel = 2
run = True
facing = 0
direction = 0
text = pygame.font.SysFont('Times New Roman',30)
score = 0
segments = []
green = ((0,128,0))
white = ((255,255,255))
counting = 0
segmentTime = time.time()
class segmentClass():
def __init__(self,x,y,pos,color):
self.x = x
self.y = y
self.pos = pos
self.color = color
def draw(self,win):
pygame.draw.rect(win,(self.color),(self.x,self.y,30,30))
def gameOver():
global run
run = False
def segmentGrowth():
global x2
global y2
global score
global vel
global ammount
segments.append(segmentClass(x,y,len(segments)+1,green))
ammount = 0
x2 = random.randrange(1,801-30)
y2 = random.randrange(1,601-30)
score += 1
print(vel)
while run:
currentTime = time.time()
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
vel += (score*0.0001)
keys = pygame.key.get_pressed()
if keys[pygame.K_w]:
if direction != 1:
direction = 1
facing = -1
if keys[pygame.K_s]:
if direction != 1:
direction = 1
facing = 1
if keys[pygame.K_a]:
if direction != 0:
direction = 0
facing = -1
if keys[pygame.K_d]:
if direction != 0:
direction = 0
facing = 1
if direction == 1:
y += (vel*facing)
else:
x += (vel*facing)
if x > x2 and x < x2 + 30 or x + 30 > x2 and x + 30 < x2 + 30:
if y == y2:
segmentGrowth()
if y > y2 and y < y2 + 30 or y + 30 > y2 and y + 30 < y2 + 30:
segmentGrowth()
if y > y2 and y < y2 + 30 or y + 30 > y2 and y + 30 < y2 + 30:
if x == x2:
segmentGrowth()
if x > x2 and x < x2 + 30 or x + 30 > x2 and x + 30 < x2 + 30:
segmentGrowth()
if x > 800-30 or y > 600-30 or x < 0 or y < 0:
gameOver()
win.fill((0,0,0))
for segment in segments:
if direction == 0: #X value
if facing == 1: #Right
segment.x = x - (35 * segment.pos)
segment.y = y
else: #Left
segment.x = x + (35 * segment.pos)
segment.y = y
else: #Y value
if facing == -1: #Up
segment.y = y + (35 * segment.pos)
segment.x = x
else:#Down
segment.y = y - (35 * segment.pos)
segment.x = x
for segment in segments:
segment.draw(win)
scoreDisplay = text.render(str(score),1,(255,255,255))
win.blit(scoreDisplay,(760,0))
pygame.draw.rect(win,(0,128,0),(x,y,30,30))
pygame.draw.rect(win,(255,0,0),(x2,y2,30,30))
pygame.display.update()
pygame.quit()
How it works is there is a list of segments and a class for information of each segment (ie x, y, etc). I append to that list an instance of the segment class whenever the user has collided with the red cube. I have this code:
for segment in segments:
if direction == 0: #X value
if facing == 1: #Right
segment.x = x - (35 * segment.pos)
segment.y = y
else: #Left
segment.x = x + (35 * segment.pos)
segment.y = y
else: #Y value
if facing == -1: #Up
segment.y = y + (35 * segment.pos)
segment.x = x
else:#Down
segment.y = y - (35 * segment.pos)
segment.x = x
That will move all segments of the snake all at once when the player decides what direction they want the snake to move. However, the segments are snapping immediately to the x position of the head rather than moving one at a time, smoothly. If someone could help me out with this that would be great. Thanks!
Nice game. I recommend to create a list of points, which is a list of tuples of the snakes head positions ((x, y)). Add every position to the list:
pts = []
while run:
# [...]
pts.append((x, y))
Create a function which calculates the position of a part of the snake by its index (i) counted to the head of the snake. The distance to the head has to be lenToI = i * 35.
The distance between to points can be calculated by the Euclidean distance (math.sqrt((px-pnx)*(px-pnx) + (py-pny)*(py-pny)), where the points are (px, py) and (pnx, pny). If the sum of the distances between the points (lenAct) exceeds the length to point (lenToI), then the position of part i is found:
def getPos(i):
global pts
lenToI = i * 35
lenAct = 0
px, py = pts[-1]
for j in reversed(range(len(pts)-1)):
px, py = pts[j]
pnx, pny = pts[j+1]
lenAct += math.sqrt((px-pnx)*(px-pnx) + (py-pny)*(py-pny))
if lenAct >= lenToI:
break
return (px, py)
Write another function cutPts, which deletes the points from the list, which ar not further required:
def cutPts(i):
global pts
lenToI = i * 35
lenAct = 0
cut_i = 0
px, py = pts[0]
for j in reversed(range(len(pts)-1)):
px, py = pts[j]
pnx, pny = pts[j+1]
lenAct += math.sqrt((px-pnx)*(px-pnx) + (py-pny)*(py-pny))
if lenAct >= lenToI:
break
cut_i = j
del pts[:cut_i]
Update the positions of the segments in a loop:
pts.append((x, y))
for i in range(len(segments)):
segments[i].x, segments[i].y = getPos(len(segments)-i)
cutPts(len(segments)+1)
Regarding the comment:
how would I go about calling the gameOver() function if the head of the snake touches any of its segments? I tried using an if statement to check for collision (the same way I did for the apple) using segment.x and segment.y but this won't work since the second segment of the snake always overlaps the head when the snake moves.
Note, the head can never "touch" the first segment, except the direction is changed to the reverse direction, but this case can be handled by an extra test, with ease.
It is sufficient to check if the head "hits" any segment except the fist one which connects to the head.
Use pygame.Rect.colliderect to check for the intersection of rectangular segments:
def selfCollide():
for i in range(len(segments)-1):
s = segments[i]
if pygame.Rect(x, y, 30, 30).colliderect(pygame.Rect(s.x, s.y, 30, 30)):
return True
return False
if selfCollide():
gameOver()

How can i make sprites spawn on random locations using pygame?

Ive been trying to make a twist on a dodger-like game, where some blocks make you grow and others make you shrink. I'm also planning to add a red one of which is supposed to make you lose a life(you will have 3 lives starting out), and various others with their own attributes. However Ive hit a bump in the road in having the falling blocks spawn randomly, which is something that would be required in a broad range of games.
My plan is basically that i would want to have the blocks re-spawn at random locations each time and at some point i want the amount of falling blocks to increase as well to further mount the difficulty.
Here is my current progress. Any input greatly appreciated:
import pygame
import random
pygame.init()
win_width = 800
win_height = 600
window = pygame.display.set_mode((win_width,win_height))
pygame.display.set_caption("Roger Dodger")
black = (0,0,0)
white = (255,255,255)
red = (255,0,0)
orange = (255,127,0)
yellow = (255,255,0)
blue = (0,0,255)
clock = pygame.time.Clock()
class Collisions:
def __init__(self, x1,y1,w1,h1,x2,y2,w2,h2):
self.x1 = x1
self.y1 = y1
self.w1 = w1
self.h1 = h1
self.x2 = x2
self.y2 = y2
self.w2 = w2
self.h2 = h2
def checkCol(self):
if (self.x2 + self.w2 >= self.x1 >= self.x2 and self.y2 + self.h2 >= self.y1 >= self.y2):
return True
elif (self.x2 + self.w2 >= self.x1 + self.w1 >= self.x2 and self.y2 + self.h2 >= self.y1 >= self.y2):
return True
elif (self.x2 + self.w2 >= self.x1 >= self.x2 and self.y2 + self.h2 >= self.y1 + self.h1 >= self.y2):
return True
elif (self.x2 + self.w2 >= self.x1 + self.w1 >= self.x2 and self.y2 + self.h2 >= self.y1 + self.h1 >= self.y2):
return True
else:
return False
def yel_col(self):
if Collisions.checkCol(self):
return True
def ora_col(self):
if Collisions.checkCol(self):
return True
class Sprite:
def __init__(self,x,y,width,height, color):
self.x = x
self.y = y
self.width = width
self.height = height
self.color = color
def render(self,):
pygame.draw.rect(window,self.color,(self.x,self.y,self.width,self.height))
Sprite1=Sprite(win_width/2 ,win_height-60,30,30, blue)
moveX = 0
sprite2x = random.randrange(30, win_width, 30)
sprite3x = random.randrange(30, win_width, 30)
falling_pos = 0
gameLoop=True
while gameLoop:
for event in pygame.event.get():
if (event.type==pygame.QUIT):
gameLoop=False
if (event.type==pygame.KEYDOWN):
if (event.key==pygame.K_LEFT):
moveX = -3
if (event.key==pygame.K_RIGHT):
moveX = 3
window.fill(white)
ground = pygame.draw.rect(window, black, ((0, win_height-30), (win_width, 30)))
Sprite1.x+=moveX
falling_pos += 3
Sprite2=Sprite(sprite2x,falling_pos,30,30, orange)
Sprite3=Sprite(sprite3x, falling_pos, 30, 30, yellow)
collisions1=Collisions(Sprite1.x,Sprite1.y,Sprite1.width,Sprite1.height,Sprite2.x,Sprite2.y,Sprite2.width,Sprite2.height)
collisions2=Collisions(Sprite1.x,Sprite1.y,Sprite1.width,Sprite1.height,Sprite3.x,Sprite3.y,Sprite3.width,Sprite3.height)
Sprite1.render()
Sprite2.render()
Sprite3.render()
if collisions2.checkCol() and collisions2.yel_col():
if Sprite1.width and Sprite1.height > 30:
Sprite1.width -= 5
Sprite1.height -= 5
Sprite1.y += 5
if collisions1.checkCol() and collisions1.ora_col():
if Sprite1.width and Sprite1.height < 300:
Sprite1.width += 5
Sprite1.height += 5
Sprite1.y -= 5
if Sprite1.x < 0:
Sprite1.x = win_width
elif Sprite1.x > win_width:
Sprite1.x = 0
if falling_pos > win_height:
falling_pos = 0
pygame.display.flip()
clock.tick(120)
pygame.quit()
To make them spawn in random locations use random.randint(a, b) to assign the start position.

Categories