Having problems with thrust and acceleration in python turtle module - python-3.x

I am trying to code a boat that keeps its momentum after letting go of "w". However, i also want it to slow down gradually after letting go of "w". I have gotten a decent boat working but sometimes when I press "w" after going a certain direction, the boat goes flying. Is there any way to add a top speed or make it slow down?
self.thrust = 0.0
self.acceleration = 0.0001
self.dx += math.cos(math.radians(self.heading)) * self.thrust
self.dy += math.sin(math.radians(self.heading)) * self.thrust
def accelerate(self):
self.thrust += self.acceleration
def decelerate(self):
self.thrust = 0

If you want it to slow down gradually when you aren't pressing "w", then you may want it to go at the same speed, or faster. This code will work for it to go at the same speed. This should work in any Python IDE.
Suppose your boat is a turtle.
import turtle
Win = turtle.screen()
Boat = turtle.Turtle()
If you want it to also look like a Boat, you can do:
screen.adshape(Saved Document Direction)
Boat.shape(Saved Document Direction)
Otherwise, you can create your own shapes.
Now, we want it to go at a constant speed, while you press "w":
So:
def Constant_Speed():
Boat.speed(Speed You Want it to Go)
Win.listen()
Win.onkeypress(Constant_Speed, "w")
Otherwise, it should slow down.
Initial_Speed = Initial Speed
Boat.speed(Initial_Speed)
Initial_Speed *= Fraction of what you want it to be in/decimal
So, this should work if you replace with your variables:
import turtle
Win = turtle.screen()
Boat = turtle.Turtle()
screen.adshape(Saved Document Direction)
Boat.shape(Saved Document Direction)
def Constant_Speed():
Boat.speed(Speed You Want it to Go)
Win.listen()
Win.onkeypress(Constant_Speed, "w")
Initial_Speed = Initial Speed
Boat.speed(Initial_Speed)
Initial_Speed *= Fraction of what you want it to be in/decimal and must be less than 1
But what if you want it to go faster, not at a constant speed?
Then go to the def Constant_Speed() part.
You can rename it: def Go_Faster()
Now, the code:
Speed = Initial_Speed
Boat.speed(Speed)
Speed *= Times you want to increase speed by
Also:
Change the last line to:
Initial_Speed2 = Initial Speed * Fraction of what you want it to be in/decimal and must be less than 1
With your variables.
Now, this should work:
import turtle
Win = turtle.screen()
Boat = turtle.Turtle()
screen.adshape(Saved Document Direction)
Boat.shape(Saved Document Direction)
def Go_Faster():
Speed = Initial_Speed()
Boat.speed(Speed)
Speed *= Times you want to increase by
Win.listen()
Win.onkeypress(Constant_Speed, "w")
Initial_Speed = Initial Speed
Boat.speed(Initial_Speed)
Initial_Speed2 = Initial Speed * Fraction of what you want it to be in/decimal and must be less than 1

Related

Get Rigidbody2D collision velocity without the sliding factor

I have a flying car that I want to lose some HP when colliding with objects.
I connected car's RigidBody2D to this function to do that
func _on_Car_body_entered(body):
var force = linear_velocity.length()
var dmg = pow(force / 100, 2) - 0.25
if dmg <= 0: return
Health = Health - dmg
Now, since I don't have to be precise I'm just using current velocity as the force, though this is up for change.
After getting my 'force of impact', I put it into damage calculating formula and if damage is above 0, decrease HP by damage.
This works fine in most cases
BUT
I noticed that if car's going fast horizontally and just barely touch the ground (that's perfectly horizontal), car gets hit with a lot of damage, because I'm using the length of the velocity vector.
Ofcourse, this case can be managed by using just the Y component of the velocity vector, but then it removes any horizontal collisions, and vice versa, and it also leads me on to the path of programming vertical and horizontal collisions, and ofcourse those are not the only 2 directions of colisions I need.
Is there a way to remove the sliding factor from this equation?
You can get the sine of the angle between your velocity and the collision normal, and then take the absolute of that.
# 0 When sliding along the wall. 1 when hitting the wall head on
var slide_factor = abs(cos(vel_last_frame.angle_to(collision_normal)))
This will give you a value from 0 to 1. When you are just sliding along the wall, this value will be 0, and when you hit the wall straight on, it will be 1.
I am using the velocity from the last frame here so that it gets the velocity just before the collision. I get it by setting vel_last_frame to linear_velocity inside the _physics_process function.
You can only get the collision normal inside the _integrate_forces function using PhysicsDirectBodyState.get_local_contact_normal(), so you need to make a variable that can be accessed in this function and the _on_Car_body_entered function. Note that you need to set contact_monitor to true and contacts_reported to at least 1 for this function to work.
var collision_normal
func _integrate_forces(state):
# Check if there is a collision
if state.get_contact_count():
# contact_monitor must be true and contacts_reported must be at least 1 for this to work
collision_normal = state.get_contact_local_normal(0)
Now in the _on_Car_body_entered_function, you can multiply dmg by sliding_factor to scale it less depending on how much you are sliding against the wall.
func _on_Car_body_entered(body):
var force = linear_velocity.length()
# 0 When sliding along the wall. 1 when hitting the wall head on
var slide_factor = abs(cos(vel_last_frame.angle_to(collision_normal)))
var dmg = pow(force / 100, 2) - 0.25
# Reduce dmg depending on how much you are sliding against the wall
dmg *= slide_factor
if dmg <= 0: return
Health = Health - dmg
Found a solution for my problem here
This gives me a predictable force range to work with.
I copied all code for 2D collision, just added damage calculation
Range of forces my objects produce is <3000 for small collisions like scratches and bumps, ~10k for beginner friendly damage, and 20k+ for when I really slam the gas pedal, so I just convert that force to damage that I want.
Best part is that I don't have to use the body_entered from RigidBody2D, because now all my cars have this calculation in them, so when 2 of them collide they both get damaged.
extends RigidBody2D
var collision_force : Vector2 = Vector2.ZERO
var previous_linear_velocity : Vector2 = Vector2.ZERO
func _integrate_forces(state : Physics2DDirectBodyState)->void:
collision_force = Vector2.ZERO
if state.get_contact_count() > 0:
var dv : Vector2 = state.linear_velocity - previous_linear_velocity
collision_force = dv / (state.inverse_mass * state.step)
var dmg = collision_force.length() / 2000 - 2
if dmg > 0:
set_hp(Health - dmg)
emit_signal("Damaged")
previous_linear_velocity = state.linear_velocity
**OLD ANSWER**
RUBBER DUCK HERE
In script for car I added a new variable var last_linear_velocity = Vector2()
Then stored the last velocity in _process
func _process(delta):
last_linear_velocity = linear_velocity
Not in _integrate_forces because if I put it there then the new and last velocities are the same.
And just changed how force is calculated in the function mentioned above, so it looks like this
func _on_Car_body_entered(body):
var force = last_linear_velocity.length() - linear_velocity.length()
var dmg = pow(force / 100, 2) - 0.25
if dmg <= 0: return
Health = Health - dmg
Now I get a nice predicable range of values and can transform that to damage.
NOTE
I noticed that sometimes when collision occures the difference between the last and current velocity lengths is negative, as in - car is accelerating.
Anyway, this works for me for now.
If you find a better solutions do post it, as I couldn't find a solution to this problem online elswhere.

Saving a large turtle graphics image from the entire canvas

I've been looking around for a few hours, and I couldn't find any solutions for this specific problem. I did see one person who asked the same question, but they didn't provide code, and so didn't get an answer. My problem is this:
I have a program that sends the turtle left and right randomly for a set number of turns. The turning part works, but when I try to save the canvas, it only saves the visible part, which often cuts things off. I have tried increasing the size of the setup canvas, as well as the screen capture, but the saved file always ends up in the top left corner, and so gets cut off. I'm attaching my entire program (it's short, don't worry), because I'm not sure exactly what aspect needs to change.
Any help would be greatly appreciated!
import turtle
import random
'''
Takes a variable, number, of how many random turns the turtle should make and a variable filenumber,
which is used to save multiple different iterations of the program per program execution
'''
def left_or_right(number,filenumber):
# Counters to make sure the turtle doesn't turn too many times
count = 0
subright = 0
subleft = 0
#sets up screen
win_width, win_height, bg_color = 10000, 10000,'white'
turtle.setup()
turtle.screensize(win_width, win_height,bg_color)
# Clears the screen, sets the turtle in the middle of the screen, drops the pen and hides the
# Turtle. If you want to see the turtles do the drawing, turn turtle.tracer to True, and
# uncomment out turtle.speed('fastest')
turtle.clear()
turtle.home()
turtle.hideturtle()
turtle.pendown()
turtle.speed('fastest')
turtle.tracer(False)
# A for loop for the turtle's turns
for i in range(number):
# Calls decider to you can get a random direction every time
decider = random.randint(0, 1)
# in order to minimize the turtle just going around in circles,
# if the turtle has made more that 3 of the same turn, turn it the other way
# the next to if statements deal with the turtle turning right too many times
if decider == 1 and subright <= 3:
turtle.right(90)
turtle.forward(20)
count += 1
subright += 1
elif decider == 1 and subright > 3:
turtle.left(90)
turtle.forward(20)
count += 1
subright = 0
# The next two if statements make sure the turtle doesn't turn right too many times
elif decider == 0 and subleft <= 3:
turtle.left(90)
turtle.forward(20)
count += 1
subleft += 1
elif decider == 0 and subleft > 3:
turtle.right(90)
turtle.forward(20)
count += 1
subleft = 0
# if the number of turns is equal to what the user inputed, stop making the turns
# and update the screen
if count == number:
turtle.penup()
turtle.update()
# Take a screen capture of the screen and saves it to where the python file is
ts = turtle.getscreen()
ts.getcanvas().postscript(file=filenumber,height=10000, width=10000)
# turtle.done() is here as a debugging tool. If you set the number of files you're
# generating to one, you can keep the turtle window open to check that the file correctly
# copied the turtle screen
#turtle.done()
''' Takes a variable, filenumber, which is the number of generated files you want'''
def filegen(filenumber):
for i in range(filenumber):
# Calls the left or right function as many times as you told it to
# Note: this is where the left or right function get's its turn number parameters
left_or_right(10000,i)
def main():
# calls filegen as many times as you want it to
filegen(4)
if __name__ == '__main__':
main()

Tkinter Canvas Slow

I'm trying to make a moving bullet in Tkinter on a canvas. I delete it and redraw it every frame. The canvas still gets slow after only around 4/5 drawings. What am I missing?
class Bullet:
def __init__(self,x,y,r,vx,vy):
# realX/Y represent the real location of the bullet as a float
self.realX = x
self.realY = y
self.vx = vx
self.vy = vy
self.r = r # Radius
self.speed = 0.96 # Pixels per frame
def level1_loop():
move_bullets()
draw()
window.after(1,level1_loop)
def move_bullets():
global timer
global canvas
if time() > timer + 1:
# Create a new bullet every second
newbul = Bullet(rand(0,1920),0,20,0,0)
diffX = window.winfo_pointerx() - newbul.realX
diffY = window.winfo_pointery() - newbul.realY
scale = newbul.speed / (diffX ** 2 + diffY ** 2) ** 0.5
newbul.vx = diffX*scale
newbul.vy = diffY*scale
bullets.append(newbul)
timer = time()
for bullet in bullets:
# Update bullet position
bullet.realX += bullet.vx
bullet.realY += bullet.vy
def draw():
global canvas
canvas.delete('bullet')
for bullet in bullets:
canvas.create_oval(bullet.realX-bullet.r,bullet.realY-bullet.r,bullet.realX+bullet.r,bullet.realY+bullet.r,tag="bullet")
if bullet.realX > 1920 or bullet.realY > 1080:
bullets.remove(bullet)
It is slow specifically because you are deleting and redrawing the bullets. Instead of that, you should be moving the existing bullets rather than redrawing them. The canvas has known performance problems when you create lots of canvas items, even if you delete the canvas items. Canvas item ids are not recycled, so the list of item ids that the canvas must maintain grows without bounds and makes the canvas slower on each iteration.
It's also slow because you are trying to redraw 1000 times per second even though your eye can't perceive anything close to th at. You are wasting a lot of cpu cycles doing something which you cannot perceive.
Here is similar advice from someone who has worked on the underlying Canvas implementation: Tkinter GUI app runs slower as time goes on

How to make multiple objects work at same time?

I have been using Python to control some instruments, which I created a Class for. I have multiple instruments of the same kind, so my script has multiple instances of the same class.
Let's say the class is Arm, and it has methods move_left, move_right and reset. Right now I have script like this:
arm1 = Arm()
arm2 = Arm()
arm1.move_left()
arm2.move_left()
arm1.move_right()
arm2.move_right()
arm1.reset()
arm2.reset()
It's completely in serial. I have to wait for arm1 to finish move_left, then start arm2 to move_left. This is very inefficient. I would like arm1 and arm2 to move at the same time. They don't have to be exact same time, because arm1 and arm2 are quite independent and there's not much synchronization requirement. I just don't want to waste time in the serialization in the code.
I've done some searching and learned a little about threading, but what I found is all about putting a function in a Thread target, which doesn't really apply to my situation here.
One way to approach the problem would be to implement a state machine. That is, instead of defining the problem through commands like move_left() and move_right(), instead you can have some variables that represent the final position that you want each arm to end up at, and a second set of variables that represent the current position of the arm. Then at each time-step, you simply move the arms by a small amount towards their target-destination.
Here's a very simple toy program to demonstrate the idea. Note that it moves each "arm" by no more than 0.1 units every 100mS time-step (you can of course use any time-step and maximum-movement values you want instead):
import time
class Robot:
def __init__(self):
self._leftArmCurrentPos = 0.0
self._leftArmTargetPos = 0.0
self._rightArmCurrentPos = 0.0
self._rightArmTargetPos = 0.0
def setLeftArmTargetPos(self, newPos):
self._leftArmTargetPos = newPos
def setRightArmTargetPos(self, newPos):
self._rightArmTargetPos = newPos
# Returns the closest value to (deltaVal) in the range [-0.1, +0.1]
def clamp(self, deltaVal):
aLittleBit = 0.1 # or however much you want
if (deltaVal > aLittleBit):
return aLittleBit
elif (deltaVal < -aLittleBit):
return -aLittleBit
else:
return deltaVal
def moveArmsTowardsTargetPositions(self):
leftArmDelta = self.clamp(self._leftArmTargetPos - self._leftArmCurrentPos)
if (leftArmDelta != 0.0):
self._leftArmCurrentPos += leftArmDelta
print("Moved left arm by %f towards %f, new left arm pos is %f" % (leftArmDelta, self._leftArmTargetPos, self._leftArmCurrentPos))
rightArmDelta = self.clamp(self._rightArmTargetPos - self._rightArmCurrentPos)
if (rightArmDelta != 0.0):
self._rightArmCurrentPos += rightArmDelta
print("Moved right arm by %f towards %f, new right arm pos is %f" % (rightArmDelta, self._rightArmTargetPos, self._rightArmCurrentPos))
if __name__ == "__main__":
r = Robot()
r.setLeftArmTargetPos(10.0)
r.setRightArmTargetPos(-3.0)
while True:
r.moveArmsTowardsTargetPositions()
time.sleep(0.1)
A nice side-effect of this approach is that you if change your mind at any time about where you want the arms to be, you can simply call setLeftArmTargetPos() or setRightArmTargetPos() to give the arms new/different destination values, and they will immediately start moving from (wherever they currently are at) towards the new target positions -- there's no need to wait for them to arrive at the old destinations first.

How can I fix the wrong deceleration when moving to left?

I just started learning game development in pygame and I want the player object to have a deceleration when the player stops pressing the key.
This is what I have at the moment:
def update(self):
self.accel_x = 0
keys = pg.key.get_pressed()
if keys[pg.K_LEFT]:
self.accel_x = -0.2
if keys[pg.K_RIGHT]:
self.accel_x = 0.2
if abs(self.vx) >= max_speed:
self.vx = self.vx/abs(self.vx) * max_speed
if self.accel_x == 0:
self.vx *= 0.91
self.vx += self.accel_x
self.vy += self.accel_y
self.rect.x += self.vx
self.rect.y += self.vy
It's works fine while moving to right but the object doesn't stop on time while going to left. Instead it decelerates to a point and then keeps going with a really slow speed for some time, then stops.
First, let see the math behind the algorithm.
When the button is pressed, the speed and position change based on the acceleration a, at t (number of times the function run), initial values being v0 and x0
v = v0 + a * t
x = x0 + Σ(i=1 to t) i * a
or
x = x0 + (t2+t) * a/2
And when the button is released (accel is 0) the speed v decreases geometrically
v = v0 * 0.91t
after 10 calls, we have ~0.39 v, after 100 calls ~10-5 v. Meaning that, visually, the position x decelerates and stops, v being too small to make a difference after some time.
The math is consistent with what is seen in games.
The question is why that algorithm doesn't work left side.
While it should work the same, left and right.
The difference is, left side,
speed v more likely to be negative after LEFT was pressed
position x might become negative at some point (and has to be checked)
Since the code provided (probably) does not cover the part to be changed, some recommendations:
You could force the speed to 0 if abs(v) is less than, say, 10-5 or another small values from which the position doesn't change visually (less than a pixel).
Ensure x values are checked at the limit, especially for negative values.
Debug: display/log v and x values especially after LEFT is released. This way when the whole program is running you'll identify more easily when does the problem come from.
If that doesn't address your problem, you could edit your question and add more relevant code.

Resources