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.
Related
This question already has answers here:
How do I fix wall warping in my raycaster?
(1 answer)
Problem with recognising where a ray in raycaster intersects a wall along the horizontal axis
(1 answer)
cant get raycast to work from angles 90 to 270 pygame
(1 answer)
Why my raycasting keeps going through walls?
(1 answer)
Closed 3 months ago.
I am trying to create a raycast visualizer. The lines are supposed to shoot out and stop when they collide with a wall. Currently the length is entirely random and sometimes the rays point in directions that are even behind me. I am using an scale of 47 when i draw things to the screen for tiling purposes. I have tried for 10 or so hours every scale in the raycast code and I can't see what I am missing.
import pygame
import numpy
from PygameEngine import GameEngine
import sys
import math
class RayCasting:
FOV = numpy.pi/5
HALF_FOV = FOV/2
NUM_RAYS = GameEngine.WIDTH//2
HALF_NUM_RAYS = NUM_RAYS//2
DELTA_ANGLE = FOV/NUM_RAYS
MAX_DEPTH = 20
def __init__(self, game):
self.game = game
def rayCast(self):
ox, oy = self.game.wasd.pos
x_map = int(ox)
y_map = int(oy)
ray_angle = self.game.wasd.angle - self.HALF_FOV + 0.000001
for ray in range(self.NUM_RAYS):
sin_a = math.sin(ray_angle)
cos_a = math.cos(ray_angle)
# horizontals
y_hor, dy = (y_map + 1, 1) if sin_a > 0 else (y_map - 1e-6, -1)
depth_hor = (y_hor - oy) / sin_a
x_hor = ox + depth_hor * cos_a
delta_depth = dy / sin_a
dx = delta_depth * cos_a
print("YHor: ",y_hor, " DY:", dy, " Depth Hor: ", depth_hor, "X Hor: ", x_hor,
" Delta Depth: ", delta_depth, " DX: ", dx)
for i in range(self.MAX_DEPTH):
tile_hor = int(x_hor), int(y_hor)
if tile_hor in self.game.MAP.wallMap:
# print("INSIDE HOR")
break
x_hor += dx
y_hor += dy
depth_hor += delta_depth
# verticals
x_vert, dx = (x_map + 1, 1) if cos_a > 0 else (x_map - 1e-6, -1)
depth_vert = (x_vert - ox) / cos_a
y_vert = oy + depth_vert * sin_a
delta_depth = dx / cos_a
dy = delta_depth * sin_a
for i in range(self.MAX_DEPTH):
tile_vert = int(x_vert), int(y_vert)
if tile_vert in self.game.MAP.wallMap:
# print("INSIDE VERT")
break
x_vert += dx
y_vert += dy
depth_vert += delta_depth
# depth, texture offset
if depth_vert < depth_hor:
depth = depth_vert
#y_vert %= 1
#offset = y_vert if cos_a > 0 else (1 - y_vert)
else:
depth = depth_hor
#x_hor %= 1
#offset = (1 - x_hor) if sin_a > 0 else x_hor
# remove fishbowl effect
#depth *= math.cos(self.game.wasd.angle - ray_angle)
# projection
#proj_height = SCREEN_DIST / (depth + 0.0001)
# ray casting result
#self.ray_casting_result.append((depth, proj_height, texture, offset))
ray_angle += self.DELTA_ANGLE
pygame.draw.line(self.game.screen, "yellow", (ox*self.game.CELLSIZE,oy*self.game.CELLSIZE), (ox*self.game.CELLSIZE+depth*cos_a, oy*self.game.CELLSIZE+depth*sin_a), 1)
def update(self):
self.rayCast()
from PygameEngine import GameEngine
from Circle import Circle
import pygame
from pygame.locals import *
import sys
import numpy
from map import Map
from RaycastFunction import RayCasting
class RaycastGame(GameEngine):
# Space bar to place this circle which will connect to the WASD with a line
planet = Circle((0,0,0))
planet.keyX = 5
planet.keyY = 5
# Grid set up
def __init__(self):
super().__init__()
self.load()
self.MAP = Map()
self.CELLSIZE = self.MAP.CELLSIZE
# Circle controllable with WASD
self.wasd = Circle((123, 255, 123))
self.raycast = RayCasting(self)
def DDA(self):
# -
# * |
# Remember the Plane is - --m-- +
# * = target |
# m = mouse +
distX = self.wasd.keyX - self.planet.pos[0]
distY = self.wasd.keyY - self.planet.pos[1]
#hypotenuse = numpy.sqrt(distX**2+distY**2)
theta = numpy.arctan((distY/(distX+.0001)))
theta += numpy.deg2rad(90)
# print(numpy.rad2deg(theta), " THETA")
collisionPos = (0,0)
def draw(self):
# Draw MAP array
self.MAP.drawMap()
self.MAP.drawGrid()
# Draw mouse character
#pygame.draw.circle(self.screen, (0, 0, 0),
#(self.plane), Circle.radius)
# Draw planet
# self.planet.draw(self.screen)
# Draw wasd character
self.wasd.draw(self.screen)
# Connect mouse and wasd characters with a line
#pygame.draw.line(self.screen, (255, 255, 255), self.planet.pos, (self.wasd.keyX, self.wasd.keyY), 5)
def update(self):
self.planet.placePlanet()
self.wasd.move()
self.DDA()
self.raycast.update()
def run(self):
# Game loop.
while True:
#This gets written over. Only for clearing screen before each draw
self.screen.fill((0, 0, 0))
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
# Update.
self.update()
# Draw
self.draw()
pygame.display.flip()
self.fpsClock.tick(self.FPS)
I do not understand why the rays are not stopping in the proper area.
I have to create a visualization where I have to create a car (I considered to represent it as a rectangle) and develop trajectories for that car depending on its lateral & longitudinal acceleration. My problem is I am unable to create trajectories. I am doing this as a part of own project. Hence I have no idea if I am in the right direction or not. Guide me if I am wrong.
Problem1 : How do I show predicted path trajectories (beginner step to create only 8 trajectories and not many) ? Is a possible way to also create trajectories also like a curve using Pygame? If not, can I use a mix pf pygame and tkinter to create a mix of curved and straight line trajectories?
Problem2 : Make my rectangular car run along the chosen trajectory (curved line or straight line) or make it follow the path defined in trajectory.
Solutions Tried:
I have taken a rectangular sprite as a car with an idea that I can make it move around the desired trajectory. I am successful with sprite creation and moving it. But I am unable to show display any trajectory curve and make the car follow the path. I am not sure if I have to take trajectories also as Sprite or only car as Sprite is sufficient and trajectories as lines/curves?
Code of the above same explanation is as follows:
My Sprite Class :
class Vehicle(pygame.sprite.Sprite):
def __init__(self,x,y,slip_angle,length=4):
super().__init__()
width = 50
height = 50
self.image = pygame.Surface([width,height])
#Create a car
self.image.fill(RED)
self.image.set_colorkey(RED)
pygame.draw.rect(self.image, WHITE, [0, 0, width, height])
self.rect = self.image.get_rect()
#Parameters of car
self.position = position = Vector2(x, y)
self.velocity = Vector2(0.0, 0.0)
self.slip_angle = slip_angle
self.length = length
self.max_velocity = 20
self.brake_deceleration = 10
self.free_deceleration = 2
self.initial_velocity = 0
self.long_acceleration = 0.0
self.lat_acceleration = 0.0
self.acceleration = 0.0
self.steering = 0.0
def update(self,dt):
self.velocity += (self.acceleration * dt, 0)
self.velocity.x = max(-self.max_velocity, min(self.velocity.x, self.max_velocity))
if self.steering:
turning_radius = self.length / sin(radians(self.steering))
angular_velocity = self.velocity.x / turning_radius
else:
angular_velocity = 0
self.position += self.velocity.rotate(-self.angle) * dt
self.slip_angle += degrees(angular_velocity) * dt
Trajectory Class:
class trajectory(object):
def __init__(self,x,y):
self.x = x
self.y = y
def draw():
x_value = []
time = []
for acc in range(10):
for dt in range(0,10,0.05):
x = vehicle1.initial_velocity + (vehicle1.velocity*dt) + (0.5 * vehicle1.long_acceleration * dt* dt)
x_value.append(x)
time.append(dt)
coordinates = (x_value,time)
canvas_1 = Canvas(root,700,600,background='pink')
canvas_1.grid(row=0,column=1)
x1 = coordinates[acc]
y1 = coordinates[time]
canvas_1.create_line(x1,y1)
#pygame.draw.line(screen,GREEN,list(coordinates),(700, 600))
def update(self):
#self.angle = vehicle1.slip_angle*pi / 180
self.velocity_h = vehicle1.velocity*cos(angle)
self.velocity_v = vehicle1.velocity*sin(angle)
Pygame Main Logic :
#main Logic
proceed = True
#Capturing events till exit
while proceed:
for event in pygame.event.get():
if event.type == pygame.QUIT:
proceed = False
vehicle1.update(dt)
sprites_list.update(dt)
screen.fill(WHITE)
#screen.pygame.Surface.fill(color, rect=None, special_flags=0)
pygame.draw.line(screen, BLACK, [0, 0], [700, 300], 5)
# self.screen.blit(rotated, [20,30])
sprites_list.draw(screen)
pygame.display.flip()
clock.tick(60)
pygame.quit()
I thank you for your advices and support. Happy to accept any guidance
A simple re-working of your trajectory.draw() should sort it out. I noticed the initial x and y passed to the constructor don't seem to be used. Is it intended that the trajectory is always from ( 0, 0 ) or suchlike?
def draw( screen, initial_vel, vel, accel ):
coordinates = []
for acc in range(10):
for dt in range( 0, 10, 0.05 ):
x = initial_vel + ( vel * dt ) + ( 0.5 * accel * dt * dt )
coordinates.append( ( x, dt ) )
if ( len( coordinates ) > 1 ):
PINK = ( 255, 192, 203 )
pygame.draw.lines( screen, PINK, False, coordinates )
Pass your Window and Vehicle Velocities as a parameters when calling trajectory.draw(), rather than relying on global variables.
my_trajectory.draw( screen, vehicle1.initial_velocity, vehicle1.velocity, vehicle1.long_acceleration )
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()
I have a gravity vector (in the form [r, theta]) which I add to my ball's velocity vector. For some reason, the ball doesn't return to the same height after bouncing, but instead slowly loses height sporadically. I am guessing there's some rounding error or something in a calculation I'm using, but I can't isolate the issue.
Here is my code. You need both files and pygame to run it. Sorry if it's a little confusing. I can comment anything some more if you want.
I added a marker whenever the ball reaches its max height so you guys what I mean. I want the ball to return to exactly the same height every time it bounces.
I took a little bit of unnecessary code out. The full program is under the pastebin links.
https://pastebin.com/FyejMCmg - PhysicsSim
import pygame, sys, math, tools, random, time
from pygame.locals import *
clock = pygame.time.Clock()
lines = []
class Particle:
def __init__(self,screen,colour, mass, loc, vel):
self.screen = screen
self.colour = colour
self.mass = mass
self.x = loc[0]
self.y = loc[1]
self.location = self.x,self.y
self.speed = vel[0]
self.angle = vel[1]
def update(self):
global lines
# add gravity
self.speed,self.angle = tools.add_vectors2([self.speed,self.angle], tools.GRAVITY)
# update position
dt = clock.tick(60)
self.x += self.speed * tools.SCALE * math.cos(self.angle) * dt
self.y -= self.speed * tools.SCALE * math.sin(self.angle) * dt
self.location = int(self.x),int(self.y)
# border checking
do = False
n=[]
if ((self.y+self.mass) > tools.SCREEN_HEIGHT):
self.y = tools.SCREEN_HEIGHT-self.mass
n = [0,1]
do = True
# adds position to array so max height so max height can be recorded
if (self.speed==0):
lines.append([self.screen, self.location, self.mass])
# bounce
if do:
#init, convert everything to cartesian
v = tools.polarToCartesian([self.speed, self.angle])
#final -> initial minus twice the projection onto n, where n is the normal to the surface
a = tools.scalarP(2*abs(tools.dotP(v,n)),n) #vector to be added to v
v = tools.add_vectors(v,a)
self.angle = tools.cartesianToPolar(v)[1] # does not set magnitude
# drawing
pygame.draw.circle(self.screen, self.colour, self.location, self.mass, 0)
# draws max height line
def draw_line(l):
screen = l[0]
location = l[1]
radius = l[2]
pygame.draw.line(screen, tools.BLACK, [location[0] + 15, location[1]-radius],[location[0] - 15, location[1]-radius])
def main():
pygame.init()
DISPLAY = pygame.display.set_mode(tools.SCREEN_SIZE,0,32)
DISPLAY.fill(tools.WHITE)
particles = []
particles.append(Particle(DISPLAY, tools.GREEN, 10, [100,100], [0,0]))
done = False
while not done:
global lines
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
DISPLAY.fill(tools.WHITE)
for i in particles:
i.update()
for l in lines:
draw_line(l)
pygame.display.update()
main()
https://pastebin.com/Epgqka31 - tools
import math
#colours
WHITE = (255, 255, 255)
BLUE = ( 0, 0, 255)
GREEN = ( 0, 255, 0)
RED = ( 255, 0, 0)
BLACK = ( 0, 0, 0)
COLOURS = [WHITE,BLUE,GREEN,RED,BLACK]
#screen
SCREEN_SIZE = SCREEN_WIDTH,SCREEN_HEIGHT = 1000,700
#vectors
GRAVITY = [5.0, 3*math.pi/2] # not 9.8 because it seems too high
SCALE = 0.01
# converts polar coordinates to cartesian coordinates in R2
def polarToCartesian(v):
return [v[0]*math.cos(v[1]), v[0]*math.sin(v[1])]
# converts cartesian coordinates to polar coordinates in R2
def cartesianToPolar(v):
return [math.sqrt(v[0]**2 + v[1]**2), math.atan2(v[1],v[0])]
# dots two cartesian vectors in R2
def dotP(v1, v2):
return v1[0]*v2[0] + v1[1]*v2[1]
# multiplies cartesian vector v by scalar s in Rn
def scalarP(s,v):
v_=[]
for i in v:
v_.append(s*i)
return v_
# returns the sum of two cartesian vectors in R2
def add_vectors(v1, v2):
return [v1[0]+v2[0], v1[1]+v2[1]]
# returns the sum of two polar vectors in R2, equations from https://math.stackexchange.com/questions/1365622/adding-two-polar-vectors
def add_vectors2(v1,v2):
r1,r2,t1,t2 = v1[0],v2[0],v1[1],v2[1]
return [math.sqrt(r1**2 + r2**2 + 2*r1*r2*math.cos(t2-t1)), t1 + math.atan2(r2*math.sin(t2 - t1), r1 + r2*math.cos(t2 - t1))]
Your time interval, dt = clock.tick(60), is not a constant. If you change it to dt = 60 your program runs as expected.
Have a look a the Verlet Algorithm and implement it in your code. You are on the right track!
This question already has answers here:
How do I detect collision in pygame?
(5 answers)
Closed 1 year ago.
I was only just introduced to Python and I am having a bit of trouble on detecting collision between two rectangles. I want a one rectangle to bounce off the other when they collide
Here is my code so far:
import pygame
import sys
from pygame.locals import *
pygame.init()
ww = 400
wh = 300
w = pygame.display.set_mode((ww,wh))
pygame.display.set_caption('OFK')
s = pygame.display.get_surface()
green = pygame.Color(152,251,152)
blue = pygame.Color(135,206,250)
white = pygame.Color(255,255,240)
clock = pygame.time.Clock()
background_file = 'ice-in-water.jpg'
background_image = pygame.image.load(background_file).convert()
rx = ww/2
ry = wh/2
rw = 30
rh = 20
px = ww/2
py = wh - 40
pw = 60
ph = 10
vdirection = -1
hdirection = 1
while True:
clock.tick(60)
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
quit()
if event.type == KEYDOWN:
#print (event.key,event.unicode)
if event.key == 275:
px = px + 30
if px > ww - pw:
px = ww - pw
if event.key == 276:
px = px - 30
if px < 0:
px = 0
s.fill(green)
pygame.draw.rect(s,blue,(rx,ry,rw,rh),0)
pygame.draw.rect(s,white,(px,py,pw,ph),0)
#w.blit(background_image,(-80,0))
pygame.display.update()
#mx,my = pygame.mouse.get_pos()
#print ("x: ", mx, "y: ", my)
#print ("ry: ", ry, "wh: ", wh)
rx = rx + 1.5 * hdirection
if rx > ww - rw:
hdirection = -hdirection
if rx < 0:
hdirection = -hdirection
ry = ry + 1.5 * vdirection
if ry > ww:
rx = ww/2
ry = wh/2
vdirection = -vdirection
if ry < 0:
vdirection = -vdirection
I have tried finding tutorials on how to do this, but I can't understand very well. A little help would be appreciated.
In the Rect class of pygame there is a method called colliderect.
You can also refer to this previous answer for some hints.
I calculate the angle between the two center points of the circles. Also remember arctan returns an angle between -pi/2 and pi/2 meaning you need to account for the other side of the unit circle.
then I use the angle to determine the exact points of the circles which point toward the other circle if you were to draw a line from center point to center point.
finally, I'm casting them to int as an easy way of checking to see if the two points end up being equal.
def detectCollision(self, game):
for planet in game.planets:
directionX = self.x - planet.x
directionY = self.y - planet.y
theta = atan(directionY / directionX)
if directionX < 0:
theta += pi
if theta < 0:
theta += 2 * pi
# point on player circle
point1 = [self.x + self.radius*cos(theta+pi),self.y + self.radius*sin(theta+pi)]
# point on planet circle
point2 = [planet.x + planet.radius*cos(theta), planet.y + planet.radius*sin(theta)]
if int(point1[0]) == int(point2[0]) and int(point1[1]) == int(point2[1]):
print("collided!")
This technically works, but will probably have problems if the movement steps are greater than a unit of 1 integer, since we're only checking that they are equal, not whether they've overlapped. A better way might be to leave them as floats and check that the difference comes within a certain bound, possibly dependent on the player velocity to account for larger jumps between frames.