Given the position of the creature, and the position of it's target, I'm trying to calculate the path to follow, limited by movement and rotation speed.
The creature has a look-orientation, and can only move forward along that orientation. It can not sidestep.
I am able to calculate the angle of difference between the creature's look-orientation and the target's position with the following code
def calculateAngleDifference(self):
deltaX = self.position_x - self.target[0]
deltaY = self.position_y - self.target[1]
try:
#atan is inverse tan
delta = degrees(atan(deltaY / deltaX)) - self.orientation
except:
delta = degrees(atan(deltaY / 1)) - self.orientation
return delta//1
this gives me a correct result, and stops when the creature is looking directly to the target.
However, I would like to move it during the same time, and thus the creature should walk a curve. It is impossible to have a target that is more than 45° different.
for the position calculation I use
def pointOnCircle(origin, distance=50, degree = 45, method=cos):
return origin + distance * method(radians(degree))
newX = pointOnCircle(self.position_x, distance=1, degree = 1, method=cos)
newY = pointOnCircle(self.position_y, distance=1, degree = 1, method=sin)
# input()
self.position_x = newX
self.position_y = newY
However, this never gets me to the target.
Related
I want to finish my tower defense game as fast as I can.
I have trouble with if the tower is on the path with a given path list.
It will never work even is I try as hard as I can.
I already tried a lot of times to solve that problem like tech with tim's tower defense youtube tutorial. It made almost perfect sense why it was not working.
But no matter how hard I try, It seems to never work properly.
Find the link to the tutorial here.
WARNING:The video is pretty long.
x, y = tower.x, tower.y
for n, point in enumerate(path):
point_x, point_y = point[0], point[1]
dis_x = abs(point_x - x)
dis_y = abs(point_y - y)
dis = math.sqrt((dis_x - x)**2 + (dis_y - y)**2)
print(dis)
if dis < 130:
return False
return True
You might be thinking 'Why did I do this' so I changed it a bit:
import numpy as np
closest = []
x, y = tower.x, tower.y
for n, point in enumerate(path):
point_x, point_y = point[0], point[1]
dis_x = abs(point_x - x)
dis_y = abs(point_y - y)
dis = math.sqrt((dis_x - x)**2 + (dis_y - y)**2)
print(dis)
if len(closest) <= 2:
if dis < 130:
closest.append(point)
p1 = np.array([x, y])
p2 = np.array(closest[0])
p3 = np.array(closest[1])
dis = np.cross(p2-p1,p3-p1)/np.linalg.norm(p2-p1)
if dis < 90:
return False
return True
I did not recive any error messages, but you can still place towers on some spots on the path,
and you can't place towers on some points not on the path, and I was expecting it to be pretty neat.
As #ImperishableNight stated, the issue is that your function only compares each point on the path and checks if the distance is less than a certain threshold (130 pixels in your case). But this is not enough, since we are not only interested in the end points in each segment of the path, but also all of the points in between. For that, we need to calculate the distance between a point and a line segment.
I have written and commented on the following code using a simple Point class to replace whatever pygame provides. I break the problem up into a bunch of tiny functions to solve your problem. Please let me know if any of this is unclear.
import math
import random
class Point:
def __init__(self, x=0, y=0):
"""
A really simple Point class.
Replaces whatever pygame provides for this example.
"""
self.x = x
self.y = y
def __repr__(self):
"""
A convenient representation of a Point class.
So we see the x and y values when printing these objects.
"""
return "({0}, {1})".format(self.x, self.y)
def gen_rand_point(width, height):
"""
Creates random x and y values for a Point object
"""
rand_x = random.randint(0, width)
rand_y = random.randint(0, height)
point = Point(x=rand_x, y=rand_y)
return point
def gen_rand_point_list(width, height, num_points):
"""
Creates a list of random points using the previous function.
"""
points = []
for i in range(num_points):
point = gen_rand_point(width, height)
points.append(point)
return points
def points_to_segments(points, loop=False):
"""
Converts a list of points into a list of segments.
Offsets the point list and zips it to create "segments".
A segment is just a tuple containing two Point objects.
"""
starts = points
ends = points[1:] + [points[0]]
segments = list(zip(starts, ends))
if loop:
return segments
else:
return segments[:-1]
def calc_sqr_dist(point_a, point_b):
"""
Calculates the square distance between two points.
Can be useful to save a wasteful math.sqrt call.
"""
delta_x = point_b.x - point_a.x
delta_y = point_b.y - point_a.y
sqr_dist = (delta_x ** 2) + (delta_y ** 2)
return sqr_dist
def calc_dist(point_a, point_b):
"""
Calculates the distance between two points.
When you need a wasteful math.sqrt call.
"""
sqr_dist = calc_sqr_dist(point_a, point_b)
dist = math.sqrt(sqr_dist)
return dist
def calc_dot_product(segment_a, segment_b):
"""
Calculates the dot product of two segments.
Info about what the dot product represents can be found here:
https://math.stackexchange.com/q/805954
"""
a0, a1 = segment_a
b0, b1 = segment_b
ax = a1.x - a0.x
ay = a1.y - a0.y
bx = b1.x - b0.x
by = b1.y - b0.y
dot = (ax * bx) + (ay * by)
return dot
def calc_point_segment_dist(point, segment):
"""
Gets the distance between a point and a line segment.
Some explanation can be found here:
https://stackoverflow.com/a/1501725/2588654
"""
start, end = segment
sqr_dist = calc_sqr_dist(start, end)
#what if the segment's start and end are the same?
if sqr_dist == 0:
dist = calc_dist(point, start)
return dist
#what if it is not that easy?
else:
segment_a = (start, point)
segment_b = (start, end)#really is just segment...
dot = calc_dot_product(segment_a, segment_b)
t = float(dot) / sqr_dist
clamped_t = max(0, min(1, t))#clamps t to be just within the segment
#the interpolation is basically like a lerp (linear interpolation)
projection = Point(
x = start.x + (t * (end.x - start.x)),
y = start.y + (t * (end.y - start.y)),
)
dist = calc_dist(point, projection)
return dist
def calc_point_path_dist(point, path):
"""
Gets the distances between the point and each segment.
Then returns the minimum distance of all of these distances.
"""
dists = [calc_point_segment_dist(point, segment) for segment in path]
min_dist = min(dists)
return min_dist
if __name__ == "__main__":
"""
A fun example!
"""
width = 800
height = 600
num_path_points = 5
tower_range = 50
tower = gen_rand_point(width, height)
path_points = gen_rand_point_list(width, height, num_path_points)
path = points_to_segments(path_points)
dist = calc_point_path_dist(tower, path)
in_range = dist <= tower_range
print(dist, in_range)
I am trying to code a game that has a red circle in which the user is supposed to click up to 7 times in the window. If the user clicks outside the circle, the circle will change its position to where the user clicked. And the game should end when the user has clicked 3 times inside the circle (does not have to be in a row) or when the user has clicked 7 times in total.
I have coded and done quite most of it I think, its just I cant seem to make it work as I want to.
from graphics import *
def draw_circle(win, c=None):
x = random.randint(0,500)
y = random.randint(0,500)
if var is None:
centa = Point(x,y)
var = Circle(centa,50)
var.setFill(color_rgb(200,0,0))
var.draw(win)
else:
p1 = c.p1
x_dif = (p1.x - x) * -1
y_dif = (p1.y - y) * -1
var.move(x_dif, y_dif)
return (var, x, y)
def main():
win= GraphWin("game",800,800)
score = 0
var,x,y = draw_circle(win)
while score <= 7:
mouseClick2=win.getMouse()
if mouseClick2.y >= y-50 and mouseClick2.y <= y +50 and
mouseClick2.x >= x-50 and mouseClick2.x <= x+50:
score=score + random.randint(0,5)
var,x,y = draw_circle(win, c)
print ("Success!")
print (("the score is, {0}").format(score))
thanks for the help in advance!
I see a couple problems.
your if mouseClick2.y >= y-50... conditional is spread out on two lines, but you don't have a line continuation character.
You never call main().
You don't import random.
You call draw_circle in the while loop with an argument of c, but there is no variable by that name in the global scope. You probably meant to pass in var.
c in draw_circle ostensibly refers to the circle object you want to manipulate, but half the time you manipulate var instead of c.
you assign a value to cvar in the loop, but never use it.
Your else block in draw_circle calculates the movement delta by subtracting the cursor coordinates from c.p1. But c.p1 is the upper-left corner of the circle, not the center of the circle. So your hit detection is off by fifty pixels.
import random
from graphics import *
def draw_circle(win, c=None):
x = random.randint(0,500)
y = random.randint(0,500)
if c is None:
centa = Point(x,y)
c = Circle(centa,50)
c.setFill(color_rgb(200,0,0))
c.draw(win)
else:
center_x = c.p1.x + 50
center_y = c.p1.y + 50
x_dif = (center_x - x) * -1
y_dif = (center_y - y) * -1
c.move(x_dif, y_dif)
return (c, x, y)
def main():
win= GraphWin("game",800,800)
score = 0
var,x,y = draw_circle(win)
while score <= 7:
mouseClick2=win.getMouse()
if mouseClick2.y >= y-50 and mouseClick2.y <= y +50 and \
mouseClick2.x >= x-50 and mouseClick2.x <= x+50:
score=score + random.randint(0,5)
var,x,y = draw_circle(win, var)
print ("Success!")
print (("the score is, {0}").format(score))
main()
Additional possible improvements:
Your hit detection checks whether the cursor is in a 50x50 rectangle centered on the circle. You could instead check whether the cursor is inside the circle if you measured the distance between the cursor and the center, and checked whether it was less than the radius.
var and c could stand to have more descriptive names.
mouseClick2 doesn't make much sense as a name, considering there's no mouseClick1.
The movement delta arithmetic could be simplified: (a-b) * -1 is the same as (b-a).
If you only use a variable's value once, you can sometimes avoid creating the variable at all if you nest expressions.
it might be nice to define constants, such as for the circle's radius, instead of having magic numbers in your code.
You can save five characters by using += to increment the score.
import math
import random
from graphics import *
RADIUS = 50
def draw_circle(win, circle=None):
x = random.randint(0,500)
y = random.randint(0,500)
if circle is None:
circle = Circle(Point(x,y),RADIUS)
circle.setFill(color_rgb(200,0,0))
circle.draw(win)
else:
circle.move(
x - circle.p1.x - RADIUS,
y - circle.p1.y - RADIUS
)
return (circle, x, y)
def main():
win= GraphWin("game",800,800)
score = 0
circle,x,y = draw_circle(win)
while score <= 7:
cursor = win.getMouse()
if math.hypot(cursor.x - x, cursor.y - y) <= RADIUS:
score += random.randint(0,5)
circle,x,y = draw_circle(win, circle)
print ("Success!")
print (("the score is, {0}").format(score))
main()
I'm not really a python guy, but I see that your hitbox is wrong. If there are any other issues then comment it/them to me.
Solving hitbox to be circle:
What you have already written is good to have thing but you should check if click was in circle not square. Pythagoras triangle is solution for this.
Check:
if (math.sqrt(delta_x **2 + delta_y **2) <= circle_radius)
where delta_x and delta_y is center coordinate minus mouse position
I know how to calculate the scalar of the velocity vector after a collision with 2 circles
(as per this link: https://gamedevelopment.tutsplus.com/tutorials/how-to-create-a-custom-2d-physics-engine-the-basics-and-impulse-resolution--gamedev-6331)
These circles cannot rotate and do not have friction but can have different masses, however I cannot seem to find out any way to find the unit vector that I need to multiply the scalar of velocity by to get the new velocity of the particles after the collision.
I also know how to check if 2 circles are colliding.
Also, I am only dealing with this in a purely "maths-sense" (ie. the circles have a center and a radius), and would like to know how I can represent these circles on the screen in python 3.0.
The vector class:
class Vector():
def __init__(self,x,y):
self.x = x
self.y = y
def add(self, newVector):
return Vector(self.x+newVector.x, self.y+newVector.y)
def subtract(self,newVector):
return Vector(self.x-newVector.x, self.y-newVector.y)
def equals(self, newVector):
return Vector(newVector.x,newVector.y)
def scalarMult(self, scalar):
return Vector(self.x*scalar, self.y*scalar)
def dotProduct(self, newVector):
return (self.x*newVector.x)+(self.y*newVector.y
def distance(self):
return math.sqrt((self.x)**2 +(self.y)**2)
The circle class:
class Particles():
def __init__(self,currentPos, oldPos, accel, dt,mass, center, radius):
self.currentPos = currentPos
self.oldPos = oldPos
self.accel = accel
self.dt = dt
self.mass = mass
self.center = center
self.radius = radius
def doVerletPosition(currentPos, oldPos, accel, dt):
a = currentPos.subtract(oldPos)
b = currentPos.add(a)
c = accel.scalarMult(dt)
d = c.scalarMult(dt)
return d.add(b)
def doVerletVelocity(currentPos, oldPos, dt):
deltaD = (currentPos.subtract(oldPos))
return deltaD.scalarMult(1/dt)
def collisionDetection(self, center, radius):
xCenter = (self.radius).xComponent()
yCenter = (self.radius).yComponent()
xOther = radius.xComponent()
yOther = radius.yComponent()
if ((xCenter - xOther)**2 + (yCenter-yOther)**2 < (self.radius + radius)**2):
return True
else:
return False
I do know about AABBs, but I am only using around 10 particles for now, and AABBs are not necessary now.
You know that the force transmitted between the two discs has to go along the "normal vector" for this collision, which is easy to get - it's just the vector along the line connecting the centers of the two discs.
You have four constraints: conservation of momentum (which counts for two constraints since it applies in x and y), conservation of energy, and this "force along normal" constraint. And you have four unknowns, namely the x and y components of the final velocities. Four equations and four unknowns, you can solve for your answer. Due to my background in physics, I've written this out in terms of momentum instead of velocity, but hopefully that's not too hard to parse. (Note, for instance, that kinetic energy is equal to p**2/2m or 1/2 mv**2.)
## conservation of momentum
p_1_x_i + p_2_x_i = p_1_x_f + p_2_x_f ## p_1_x_i := momentum of disc _1_ in _x_ axis intially _i
p_1_y_i + p_2_x_i = p_1_y_f + p_2_y_f
## conservation of energy
(p_1_x_i**2 + p_1_y_i**2)/(2*m_1) + (p_2_x_i**2 + p_2_y_i**2)/(2*m_2) = (p_1_x_f**2 + p_1_y_f**2)/(2*m_1) + (p_2_x_f**2 + p_2_y_f**2)/(2*m_2)
## impulse/force goes along the normal vector
tan(th) := (x_2-x_1)/(y_2-y_1) # tangent of the angle of the collision
j_1_x := p_1_x_i - p_1_x_f # change in momentum aka impulse
j_1_y := p_1_y_i - p_1_y_f
tan(th) = -j_1_x/j_1_y
(I hope the notation is clear. It would be much clearer if I could use latex, but stackoverflow doesn't support it.)
Hope this helps!
I am trying to create a rotating turret. The turret rotates correctly the problem is when I make the turret shoot with the space bar the bullet isn't the same size or shape at every angle. I tried using the angle that the turret is facing to do some trig calculations and find the two corner points needed to create the bullet (which is a circle). Nothing I have tried will work. Here is the code:
Barrel = [260,210,270,210,270,170,260,170]
def RotateBarrel():
global angle
angleChange = 2
mountCenterX = 265
mountCenterY = 215
#Rotate Barrel
cycle = 1
while cycle < len(Barrel):
x = Barrel[cycle-1]-mountCenterX
y = Barrel[cycle]-mountCenterY
Barrel[cycle-1] = (x*math.cos(angleChange*math.pi/180)-y*math.sin(angleChange*math.pi/180))+mountCenterX
Barrel[cycle] = (x*math.sin(angleChange*math.pi/180)+y*math.cos(angleChange*math.pi/180))+mountCenterY
cycle += 2
angle += angleChange
if angle == 360: angle = 0
canvas.coords(barrel,Barrel)
self.after(1,RotateBarrel)
def SpinningShoot(event):
global angle
speed = 10
shotXpos = Barrel[6]+10*(math.cos(angle*math.pi/180))
shotYpos = Barrel[7]-10*(math.sin(angle*math.pi/180))
cornerX = Barrel[6]+10*(math.cos((90-angle)*math.pi/180))
cornerY = Barrel[7]-10*(math.sin((90-angle)*math.pi/180))
shot = canvas.create_oval(shotXpos,shotYpos,cornerX,cornerY,fill="white")
Xmotion = speed*math.cos(angle*math.pi/180)
Ymotion = speed*math.sin(angle*math.pi/180)
Shots.append(shot)
ShotsPos.append(shotXpos)
ShotsPos.append(shotYpos)
ShotsMotion.append(Xmotion)
ShotsMotion.append(Ymotion)
It looks to me that your shot is not going to be centered on the "barrel", but the calculation using (90-angle) is going to give you an angular width for the shot of
angle - ( 90 - angle )
which is 2 * angle - 90
(ie the shot will be wider depending on the size of the angle).
I would have thought use (angle - 45 ) and (angle + 45 ) so your shot is always the same angular width and centered on the barrel.
You would also need to increase the "radius" for the second corner. Im confused about the +10 and -10, not quite sure what they would do.
Probably a better approach is to calculate the "centre" of the bullet and then from that just draw the circle centered on it. Im sure there would be a function that takes the centre and radius. So have
radius=10
centrex= radius * cos ( angle * math.pi /180 )
centrey= radius * sin ( angle * math. pi / 180 )
and then pass those two and a radius to a function that does a circle
One other little thing, I would suggest changing the line
if angle == 360: angle = 0
to
if angle >= 360: angle = angle-360
as if angle was initialized as anything other than an even number or you changed the angle step you could "miss" the 360 and then never wrap back around.
Here is the module I'm using: http://mcsp.wartburg.edu/zelle/python/graphics/graphics.pdf
I want to see whether a user's clicks are within a shape or not. I used the in operator, but I know that is incorrect. Below is a chunk of my code:
win = GraphWin("Click Speed", 700, 700)
theTarget = drawTarget(win, random.randrange(0,685), random.randrange(0,685))
while theTarget in win:
click = win.getMouse()
if click in theTarget:
print("Good job")
I left out the code that draws theTarget shape because it is length and unnecessary. It is a moving circle.
I'm using a while loop so it allows me to constantly get the user's clicks.
How do I go about checking whether or not a user's clicks are in the specified Target shape by using the getMouse() command?
I'm going to have to use this in the future for more abstract shapes (not simple circles).
Circle
For the simple case of a circle, you can determine whether the mouse is inside using the distance formula. For example:
# checks whether pt1 is in circ
def inCircle(pt1, circ):
# get the distance between pt1 and circ using the
# distance formula
dx = pt1.getX() - circ.getCenter().getX()
dy = pt1.getY() - circ.getCenter().getY()
dist = math.sqrt(dx*dx + dy*dy)
# check whether the distance is less than the radius
return dist <= circ.getRadius()
def main():
win = GraphWin("Click Speed", 700, 700)
# create a simple circle
circ = Circle(Point(350,350),50)
circ.setFill("red")
circ.draw(win)
while True:
mouse = win.getMouse()
if inCircle(mouse,circ):
print ("Good job")
main()
Oval
For the more advanced example of an ellipse we will need to use a formula found here. Here is the function implemting that:
def inOval(pt1, oval):
# get the radii
rx = abs(oval.getP1().getX() - oval.getP2().getX())/2
ry = abs(oval.getP1().getY() - oval.getP2().getY())/2
# get the center
h = oval.getCenter().getX()
k = oval.getCenter().getY()
# get the point
x = pt1.getX()
y = pt1.getY()
# use the formula
return (x-h)**2/rx**2 + (y-k)**2/ry**2 <= 1
Polygon
For a polygon of abitrary shape we need to reference this. I have converted that to a python equivalent for you. Check the link to see why it works because I am honestly not sure
def inPoly(pt1, poly):
points = poly.getPoints()
nvert = len(points) #the number of vertices in the polygon
#get x and y of pt1
x = pt1.getX()
y = pt1.getY()
# I don't know why this works
# See the link I provided for details
result = False
for i in range(nvert):
# note: points[-1] will give you the last element
# convenient!
j = i - 1
#get x and y of vertex at index i
vix = points[i].getX()
viy = points[i].getY()
#get x and y of vertex at index j
vjx = points[j].getX()
vjy = points[j].getY()
if (viy > y) != (vjy > y) and (x < (vjx - vix) * (y - viy) / (vjy - viy) + vix):
result = not result
return result