Why is my sprite getting stretched during look_at function? - godot

When my sprite is facing up/down it gets warped (gets longer) and when it is facing left/right it looks normal. Could this potentially coming from my sprite rotation? I'm using a .png image for my sprite.
Below is my code:
Player.gs
func _physics_process(delta):
if Input.is_mouse_button_pressed(BUTTON_LEFT):
destination = get_global_mouse_position()
# if position.distance_to(destination) > 100:
speed += acceleration * delta
if speed > max_speed:
speed = max_speed
velocity = position.direction_to(destination) * speed
# move_direction = rad2deg(destination.angle_to_point(position))
$Sprite.look_at(destination)
else:
speed = 0
velocity = move_and_slide(velocity)
velocity.x = lerp(velocity.x, 0, 0.025)
velocity.y = lerp(velocity.y, 0, 0.025)

Turns out having the method $Sprite.look_at(destination) was causing my issue. What I should be doing is only look_at(destination). This solved my issue.

Related

Godot 3: How to walk on walls using Gravity and Gravity Direction?

I want to make a 3D game on Godot with the player who can walk on walls and ceiling but I've tried to make this but it didn't work :(
This is the code of my Player First Person Controller:
extends KinematicBody
export var GravityDirection = Vector3(-9.8, 0, 0)
var held_object: Object
var velocity = Vector3.ZERO
var speed = 10
var MAX_SPEED = 30
var GravityStrength = 9.8
var throw_force: float = 200
var wall_one = Vector3(-9.8, 0, 0)
var wall_two = Vector3(0, 0, -9.8)
var wall_three = Vector3(9.8, 0, 0)
var wall_four = Vector3(0, 0, 9.8)
var ground = Vector3(0, -9.8, 0)
onready var ray = $"Camera/Hand/RayCast"
onready var hold_position = $"Camera/Hand/HoldPosition"
# Camera
onready var player_camera = $"Camera"
var spin = 0.1
export var mouse_sensitivity = 5
func _ready():
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
func _physics_process(delta):
var Gravity = GravityStrength * GravityDirection
var Velocity = Gravity * delta
move_and_collide(Velocity)
var run_once = 0
while 1:
if run_once == 0:
if GravityDirection == wall_one:
rotate_x(90)
run_once = 1
player_camera.rotation_degrees.y = 180
player_camera.rotation_degrees.z = 0
if Input.is_action_just_pressed("player_fire"):
fire()
if not is_on_floor():
Velocity.y += -GravityStrength
movement(delta)
velocity = move_and_slide(velocity, Vector3.ZERO)
if Input.is_action_just_pressed("player_pick"):
if held_object:
held_object.mode = RigidBody.MODE_RIGID
held_object.collision_mask = 1
held_object = null
else:
if ray.get_collider():
held_object = ray.get_collider()
held_object.mode = RigidBody.MODE_KINEMATIC
held_object.collision_mask = 0
if held_object:
held_object.global_transform.origin = hold_position.global_transform.origin
#_process_input()
#_process_gravity()
# Mouvement
func movement(_delta):
var dir = Vector3.ZERO
var vel_y = velocity.y
velocity = Vector3.ZERO
# Movement forward and backward
if Input.is_action_pressed("player_forward"):
dir += transform.basis.z
elif Input.is_action_pressed("player_backward"):
dir -= transform.basis.z
# Movement Left and Right
if Input.is_action_pressed("player_left"):
dir += transform.basis.x
elif Input.is_action_pressed("player_right"):
dir -= transform.basis.x
velocity = dir.normalized() * speed
velocity.y = vel_y
func _input(event):
if event is InputEventMouseMotion and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
rotate_y(lerp(0, -spin, event.relative.x * (mouse_sensitivity * 0.01) ))
player_camera.rotate_x(lerp(0,spin, event.relative.y * (mouse_sensitivity * 0.01)) )
#Clamp vertical rotation
var curr_rot = player_camera.rotation_degrees
curr_rot.x = clamp(curr_rot.x, -60, 60)
player_camera.rotation_degrees = curr_rot
func fire():
print("fire")
if ray.get_collider() != null and ray.get_collider().is_in_group("enemy"):
print(ray.get_collider())
ray.get_collider().hp -= 10
Nodes Configuration
In the code, you can find fonctions for the camera and gravity system. Also, there is a fonction to pick up rigid bodies. I want to make a system where when the Gravity has a specific direction, the mesh rotate to 90°. I've made a "GlobalRay" with 6 RayCast to detect collisions (walls) and register face blablabla... you've understood but I don't know how to make a system like this!!!
I think there is a way to optimize the script, so, I need help :D
If you can perform my code it's nice! Have a nice code!
It looks like you're subtracting GravityStrength from the player's y velocity if they are not on the floor, while you should be adding the Velocity to the player's velocity. Otherwise, this question here is very general and could be implemented in a lot of ways. wall_one, wall_two, wall_three etc. I think would be better off with a magnitude of one so they could undergo the same GravityStrength modifier as everything else in later implementations, but that's just me.

Godot player not walking in the direction

So I am new to godot and I was having a problem. The players body doesnt follow the camera when the camera moves When I look around with the camera the players body stays the same e.g. if i use wasd if i turn to the right it would be aswd. the player movement is hard but i got help. if you could help that will be super cool, i am just trying to learn coding. this is hard for me.
p.s sorry for the bad grammar
extends KinematicBody
signal hit
# How fast the player moves in meters per second.
export var speed = 14
# The downward acceleration when in the air, in meters per second squared.
export var fall_acceleration = 50
# Vertical impulse applied to the character upon jumping in meters per second.
export var jump_impulse = 30
# Vertical impulse applied to the character upon bouncing over a mob in meters per second.
export var bounce_impulse = 16
# stats
var curHP : int = 10
var maxHP : int = 10
var ammo : int = 15
var score : int = 0
# cam look
var minLookAngle : float = -90.0
var maxLookAngle : float = 90.0
var lookSensitivity : float = 10.0
# vectors
var vel : Vector3 = Vector3()
var mouseDelta : Vector2 = Vector2()
# components
onready var camera : Camera = get_node("Camera")
onready var muzzle : Spatial = get_node("Camera/Muzzle")
# Emitted when a mob hit the player.
var velocity = Vector3.ZERO
func _physics_process(delta):
var direction = Vector3.ZERO
if Input.is_action_pressed("move_right"):
direction.x += 1
if Input.is_action_pressed("move_left"):
direction.x -= 1
if Input.is_action_pressed("move_back"):
direction.z += 1
if Input.is_action_pressed("move_forward"):
direction.z -= 1
#sprinting
if Input.is_action_pressed("move_sprint"):
speed = 50
if Input.is_action_just_released("move_sprint"):
speed = 14
velocity.x = direction.x * speed
velocity.z = direction.z * speed
# Jumping.
if is_on_floor() and Input.is_action_just_pressed("move_jump"):
velocity.y += jump_impulse
velocity.y -= fall_acceleration * delta
velocity = move_and_slide(velocity, Vector3.UP)
func _ready():
# hide and lock the mouse cursor
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
func _process(delta):
# rotate the camera along the x axis
camera.rotation_degrees.x -= mouseDelta.y * lookSensitivity * delta
# clamp camera x rotation axis
camera.rotation_degrees.x = clamp(camera.rotation_degrees.x, minLookAngle, maxLookAngle)
# rotate the player along their y-axis
rotation_degrees.y -= mouseDelta.x * lookSensitivity * delta
# reset the mouseDelta vector
mouseDelta = Vector2()
func _input(event):
if event is InputEventMouseMotion:
mouseDelta = event.relative
If you want the movement to be based on the orientation of camera...
Then base the movement:
var direction = Vector3.ZERO
if Input.is_action_pressed("move_right"):
direction.x += 1
if Input.is_action_pressed("move_left"):
direction.x -= 1
if Input.is_action_pressed("move_back"):
direction.z += 1
if Input.is_action_pressed("move_forward"):
direction.z -= 1
On the orientation of the camera.
We will use camera.global_transform.basis. The basis of a transform gives us a set of vectors that are aligned to the axis of the transformed space.
Using camera.transform instead of camera.global_transform will NOT work, because the Camera is a child of the KinematicBody.
Then your code ends up like this:
var direction = Vector3.ZERO
var camera_x = camera.global_transform.basis.x
var camera_z = camera.global_transform.basis.z
if Input.is_action_pressed("move_right"):
direction += camera_x
if Input.is_action_pressed("move_left"):
direction -= camera_x
if Input.is_action_pressed("move_back"):
direction += camera_z
if Input.is_action_pressed("move_forward"):
direction -= camera_z
Complete script on OP request:
extends KinematicBody
#signal hit
# How fast the player moves in meters per second.
export var speed = 14
# The downward acceleration when in the air, in meters per second squared.
export var fall_acceleration = 50
# Vertical impulse applied to the character upon jumping in meters per second.
export var jump_impulse = 30
# Vertical impulse applied to the character upon bouncing over a mob in meters per second.
# export var bounce_impulse = 16
# stats
# var curHP : int = 10
# var maxHP : int = 10
# var ammo : int = 15
# var score : int = 0
# cam look
var minLookAngle : float = -90.0
var maxLookAngle : float = 90.0
var lookSensitivity : float = 10.0
# vectors
# var vel : Vector3 = Vector3()
var mouseDelta : Vector2 = Vector2()
# components
onready var camera : Camera = get_node("Camera")
# onready var muzzle : Spatial = get_node("Camera/Muzzle")
# Emitted when a mob hit the player.
var velocity = Vector3.ZERO
func _physics_process(delta):
var direction = Vector3.ZERO
var camera_x = camera.global_transform.basis.x
var camera_z = camera.global_transform.basis.z
if Input.is_action_pressed("move_right"):
direction += camera_x
if Input.is_action_pressed("move_left"):
direction -= camera_x
if Input.is_action_pressed("move_back"):
direction += camera_z
if Input.is_action_pressed("move_forward"):
direction -= camera_z
#sprinting
if Input.is_action_pressed("move_sprint"):
speed = 50
if Input.is_action_just_released("move_sprint"):
speed = 14
velocity.x = direction.x * speed
velocity.z = direction.z * speed
# Jumping.
if is_on_floor() and Input.is_action_just_pressed("move_jump"):
velocity.y += jump_impulse
velocity.y -= fall_acceleration * delta
velocity = move_and_slide(velocity, Vector3.UP)
func _ready():
# hide and lock the mouse cursor
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
func _process(delta):
# rotate the camera along the x axis
camera.rotation_degrees.x -= mouseDelta.y * lookSensitivity * delta
# clamp camera x rotation axis
camera.rotation_degrees.x = clamp(camera.rotation_degrees.x, minLookAngle, maxLookAngle)
# rotate the player along their y-axis
rotation_degrees.y -= mouseDelta.x * lookSensitivity * delta
# reset the mouseDelta vector
mouseDelta = Vector2()
func _input(event):
if event is InputEventMouseMotion:
mouseDelta = event.relative

How to translate points on image after cropping it and resizing it?

I am creating a program which allows a user to annotate images with points.
This program allows user to zoom in an image so user can annotate more precisely.
Program zooms in an image doing the following:
Find the center of image
Find minimum and maximum coordinates of new cropped image relative to center
Crop image
Resize the image to original size
For this I have written the following Python code:
import cv2
def zoom_image(original_image, cut_off_percentage, list_of_points):
height, width = original_image.shape[:2]
center_x, center_y = int(width/2), int(height/2)
half_new_width = center_x - int(center_x * cut_off_percentage)
half_new_height = center_y - int(center_y * cut_off_percentage)
min_x, max_x = center_x - half_new_width, center_x + half_new_width
min_y, max_y = center_y - half_new_height, center_y + half_new_height
#I want to include max coordinates in new image, hence +1
cropped = original_image[min_y:max_y+1, min_x:max_x+1]
new_height, new_width = cropped.shape[:2]
resized = cv2.resize(cropped, (width, height))
translate_points(list_of_points, height, width, new_height, new_width, min_x, min_y)
I want to resize the image to original width and height so user always works on same "surface"
regardless of how zoomed image is.
The problem I encounter is how to correctly scale points (annotations) when doing this. My algorithm to do so was following:
Translate points on original image by subtracting min_x from x coordinate and min_y from y coordinate
Calculate constants for scaling x and y coordinates of points
Multiply coordinates by constants
For this I use the following Python code:
import cv2
def translate_points(list_of_points, height, width, new_height, new_width, min_x, min_y):
#Calculate constants for scaling points
scale_x, scale_y = width / new_width, height / new_height
#Translate and scale points
for point in list_of_points:
point.x = (point.x - min_x) * scale_x
point.y = (point.y - min_y) * scale_y
This code doesn't work. If I zoom in once, it is hard to detect the offset of pixels but it happens. If I keep zooming in, it will be much easier to detect the "drift" of points. Here are images to provide examples. On original image (1440x850) I places a point in the middle of blue crosshair. The more I zoom in the image it is easier to see that algorithm doesn't work with bigger cut-ofs.
Original image. Blue crosshair is middle point of an image. Red angles indicate what will be borders after image is zoomed once
Image after zooming in once.
Image after zooming in 5 times. Clearly, green point is no longer in the middle of image
The cut_off_percentage I used is 15% (meaning that I keep 85% of width and height of original image, calculated from the center).
I have also tried the following library: Augmentit python library
Library has functions for cropping images and resizing them together with points. Library also causes the points to drift. This is expected since the code I implemented and library's functions use the same algorithm.
Additionally, I have checked whether this is a rounding problem. It is not. Library rounds the points after multiplying coordinates with scales. Regardless on how they are rounded, points are still off by 4-5 px. This increases the more I zoom in the picture.
EDIT: A more detailed explanation is given here since I didn't understand a given answer.
The following is an image of right human hand.
Image of a hand in my program
Original dimension of this image is 1440 pixels in width and 850 pixels in height. As you can see in this image, I have annotated right wrist at location (756.0, 685.0). To check whether my program works correctly, I have opened this exact image in GIMP and placed a white point at location (756.0, 685.0). The result is following:
Image of a hand in GIMP
Coordinates in program work correctly. Now, if I were to calculate parameters given in first answer according to code given in first answer I get following:
vec = [756, 685]
hh = 425
hw = 720
cov = [720, 425]
These parameters make sense to me. Now I want to zoom the image to scale of 1.15. I crop the image by choosing center point and calculating low and high values which indicate what rectangle of image to keep and what to cut. On the following image you can see what is kept after cutting (everything inside red rectangle).
What is kept when cutting
Lows and highs when cutting are:
xb = [95,1349]
yb = [56,794]
Size of cropped image: 1254 x 738
This cropped image will be resized back to original image. However, when I do that my annotation gets completely wrong coordinates when using parameters described above.
After zoom
This is the code I used to crop, resize and rescale points, based on the first answer:
width, height = image.shape[:2]
center_x, center_y = int(width / 2), int(height / 2)
scale = 1.15
scaled_width = int(center_x / scale)
scaled_height = int(center_y / scale)
xlow = center_x - scaled_width
xhigh = center_x + scaled_width
ylow = center_y - scaled_height
yhigh = center_y + scaled_height
xb = [xlow, xhigh]
yb = [ylow, yhigh]
cropped = image[yb[0]:yb[1], xb[0]:xb[1]]
resized = cv2.resize(cropped, (width, height), cv2.INTER_CUBIC)
#Rescaling poitns
cov = (width / 2, height / 2)
width, height = resized.shape[:2]
hw = width / 2
hh = height / 2
for point in points:
x, y = point.scx, point.scy
x -= xlow
y -= ylow
x -= cov[0] - (hw / scale)
y -= cov[1] - (hh / scale)
x *= scale
y *= scale
x = int(x)
y = int(y)
point.set_coordinates(x, y)
So this really is an integer rounding issue. It's magnified at high zoom levels because being off by 1 pixel at 20x zoom throws you off much further. I tried out two versions of my crop-n-zoom gui. One with int rounding, another without.
You can see that the one with int rounding keeps approaching the correct position as the zoom grows, but as soon as the zoom takes another step, it rebounds back to being wrong. The non-rounded version sticks right up against the mid-lines (denoting the proper position) the whole time.
Note that the resized rectangle (the one drawn on the non-zoomed image) blurs past the midlines. This is because of the resize interpolation from OpenCV. The yellow rectangle that I'm using to check that my points are correctly scaling is redrawn on the zoomed frame so it stays crisp.
With Int Rounding
Without Int Rounding
I have the center-of-view locked to the bottom right corner of the rectangle for this demo.
import cv2
import numpy as np
# clamp value
def clamp(val, low, high):
if val < low:
return low;
if val > high:
return high;
return val;
# bound the center-of-view
def boundCenter(cov, scale, hh, hw):
# scale half res
scaled_hw = int(hw / scale);
scaled_hh = int(hh / scale);
# bound
xlow = scaled_hw;
xhigh = (2*hw) - scaled_hw;
ylow = scaled_hh;
yhigh = (2*hh) - scaled_hh;
cov[0] = clamp(cov[0], xlow, xhigh);
cov[1] = clamp(cov[1], ylow, yhigh);
# do a zoomed view
def zoomView(orig, cov, scale, hh, hw):
# calculate crop
scaled_hh = int(hh / scale);
scaled_hw = int(hw / scale);
xlow = cov[0] - scaled_hw;
xhigh = cov[0] + scaled_hw;
ylow = cov[1] - scaled_hh;
yhigh = cov[1] + scaled_hh;
xb = [xlow, xhigh];
yb = [ylow, yhigh];
# crop and resize
copy = np.copy(orig);
crop = copy[yb[0]:yb[1], xb[0]:xb[1]];
display = cv2.resize(crop, (width, height), cv2.INTER_CUBIC);
return display;
# draw vector shape
def drawVec(img, vec, pos, cov, hh, hw, scale):
con = [];
for point in vec:
# unpack point
x,y = point;
x += pos[0];
y += pos[1];
# here's the int version
# Note: this is the same as xlow and ylow from the above function
# x -= cov[0] - int(hw / scale);
# y -= cov[1] - int(hh / scale);
# rescale point
x -= cov[0] - (hw / scale);
y -= cov[1] - (hh / scale);
x *= scale;
y *= scale;
x = int(x);
y = int(y);
# add
con.append([x,y]);
con = np.array(con);
cv2.drawContours(img, [con], -1, (0,200,200), -1);
# font stuff
font = cv2.FONT_HERSHEY_SIMPLEX;
fontScale = 1;
fontColor = (255, 100, 0);
thickness = 2;
# draw blank
res = (800,1200,3);
blank = np.zeros(res, np.uint8);
print(blank.shape);
# draw a rectangle on the original
cv2.rectangle(blank, (100,100), (400,200), (200,150,0), -1);
# vectored shape
# comparison shape
bshape = [[100,100], [400,100], [400,200], [100,200]];
bpos = [0,0]; # offset
# random shape
vshape = [[148, 89], [245, 179], [299, 67], [326, 171], [385, 222], [291, 235], [291, 340], [229, 267], [89, 358], [151, 251], [57, 167], [167, 164]];
vpos = [100,100]; # offset
# get original image res
height, width = blank.shape[:2];
hh = int(height / 2);
hw = int(width / 2);
# center of view
cov = [600, 400];
camera_spd = 5;
# scale
scale = 1;
scale_step = 0.2;
# loop
done = False;
while not done:
# crop and show image
display = zoomView(blank, cov, scale, hh, hw);
# drawVec(display, vshape, vpos, cov, hh, hw, scale);
drawVec(display, bshape, bpos, cov, hh, hw, scale);
# draw a dot in the middle
cv2.circle(display, (hw, hh), 4, (0,0,255), -1);
# draw center lines
cv2.line(display, (hw,0), (hw,height), (0,0,255), 1);
cv2.line(display, (0,hh), (width,hh), (0,0,255), 1);
# draw zoom text
cv2.putText(display, "Zoom: " + str(scale), (15,40), font,
fontScale, fontColor, thickness, cv2.LINE_AA);
# show
cv2.imshow("Display", display);
key = cv2.waitKey(1);
# check keys
done = key == ord('q');
# Note: if you're actually gonna make a GUI
# use the keyboard module or something else for this
# wasd to move center-of-view
if key == ord('d'):
cov[0] += camera_spd;
if key == ord('a'):
cov[0] -= camera_spd;
if key == ord('w'):
cov[1] -= camera_spd;
if key == ord('s'):
cov[1] += camera_spd;
# z,x to decrease/increase zoom (lower bound is 1.0)
if key == ord('x'):
scale += scale_step;
if key == ord('z'):
scale -= scale_step;
scale = round(scale, 2);
# bound cov
boundCenter(cov, scale, hh, hw);
Edit: Explanation of the drawVec parameters
img: The OpenCV image to be drawn on
vec: A list of [x,y] points
pos: The offset to draw those points at
cov: Center-Of-View, where the middle of our zoomed display is pointed at
hh: Half-Height, the height of "img" divided by 2
hw: Half-Width, the width of "img" divided by 2
I have looked through my code and realized where I was making a mistake which caused points to be offset.
In my program, I have a canvas of specific size. The size of canvas is a constant and is always larger than images being drawn on canvas. When program draws an image on canvas it first resizes that image so it could fit on canvas. The size of resized image is somewhat smaller than size of canvas. Image is usually drawn starting from top left corner of canvas. Since I wanted to always draw image in the center of canvas, I shifted the location from top left corner of canvas to another point. This is what I didn't account when doing image zooming.
def zoom(image, ratio, points, canvas_off_x, canvas_off_y):
width, height = image.shape[:2]
new_width, new_height = int(ratio * width), int(ratio * height)
center_x, center_y = int(new_width / 2), int(new_height / 2)
radius_x, radius_y = int(width / 2), int(height / 2)
min_x, max_x = center_x - radius_x, center_x + radius_x
min_y, max_y = center_y - radius_y, center_y + radius_y
img_resized = cv2.resize(image, (new_width,new_height), interpolation=cv2.INTER_LINEAR)
img_cropped = img_resized[min_y:max_y+1, min_x:max_x+1]
for point in points:
x, y = point.get_original_coordinates()
x -= canvas_off_x
y -= canvas_off_y
x = int((x * ratio) - min_x + canvas_off_x)
y = int((y * ratio) - min_y + canvas_off_y)
point.set_scaled_coordinates(x, y)
In the code below canvas_off_x and canvas_off_y is the location of offset from top left corner of canvas

Python3 Tkinter and Pillow: How to rotate an image while it is on a canvas

I want to move a turtle around a canvas by clicking on the canvas, and the turtle should point in the direction it is moving. The moving part works, but the rotate function causes the image to become distorted and mangled.
What am I doing wrong and how do I fix this?
I have a class that adds an image of a turtle to a canvas:
class TurtleImage:
"""
A Turtle image that will be placed on the canvas that is given in the ctor.
The turtle can be moved around the canvas with the move() method.
"""
def __init__(self, canvas : Canvas):
self.__turtle_file : str = self.__find_file("turtle_example/turtle.png")
self.__canvas = canvas
self.__pilImage : PngImagePlugin.PngImageFile = PILImage.open(self.__turtle_file)
self.__pilTkImage : ImageTk.PhotoImage = ImageTk.PhotoImage(self.__pilImage)
self.__turtle_id : int = canvas.create_image(100,100, image=self.__pilTkImage)
self.__is_moving = False
This class also has a method to animate the turtle moving around the canvas. It moves the turtle by 1 pixel in the x direction, y direction, or both, and then scehdules itself to be called again after a time delay determined by the speed parameter. It also should rotate the turtle so it is pointing in the right direction:
def move(self, dest_x : int, dest_y :int, speed : float = 0.1):
self.__is_moving = True
delay_ms = math.floor(1/speed)
current_x, current_y = self.__canvas.coords(self.__turtle_id)
delta_x = 1 if current_x < dest_x else -1 if current_x > dest_x else 0
delta_y = 1 if current_y < dest_y else -1 if current_y > dest_y else 0
angle = math.atan2(delta_y,delta_x)
self.__rotate(angle)
if (delta_x, delta_y) != (0, 0):
self.__canvas.move(self.__turtle_id, delta_x, delta_y)
if (current_x, current_y) != (dest_x, dest_y):
self.__canvas.after(delay_ms, self.move, dest_x, dest_y, speed)
else:
self.__is_moving = False
Because the canvas does not have the ability to rotate its objects, I must replace the object with a rotated version of itself:
def __rotate(self, angle : float):
self.__pilImage = self.__pilImage.rotate(angle)
self.__pilTkImage = ImageTk.PhotoImage(self.__pilImage)
self.__replace_image(self.__pilTkImage)
def __replace_image(self, new_image : ImageTk.PhotoImage):
self.__canvas.itemconfig(self.__turtle_id, image = new_image)
The moving around works fine, but the rotate function causes the image to become distorted and mangled, and it gets worse every time it is called.
Can you tell me why this isn't working, and what I need to do to fix it?
Here's a screenshot of said turtle, before and after rotating:
I've found the sollution, I have to reopen the file:
def __rotate(self, angle : float):
self.__pilImage = PILImage.open(self.__turtle_file)
self.__pilImage = self.__pilImage.rotate(angle)
self.__pilTkImage = ImageTk.PhotoImage(self.__pilImage)
self.__replace_image(self.__pilTkImage)
I don't know why though.

How can I get a 2D sprite to slow to a stop?

I'm trying to create a playable sprite in Godot using GDScript. I've got my character moving left and right and then coming to a halt when no input is being pressed.
However, instead of coming to a dead stop, I want the sprite to slow down. How would I write that?
extends KinematicBody2D
var motion = Vector2()
func _physics_process(delta):
if Input.is_action_pressed("ui_right"):
motion.x = 250
elif Input.is_action_pressed("ui_left"):
motion.x = -250
else:
motion.x = 0
move_and_slide(motion)
pass
Okay I figured it out:
extends KinematicBody2D
var motion = Vector2()
func _physics_process(delta):
if Input.is_action_pressed("ui_right"):
motion.x = 250
elif Input.is_action_pressed("ui_left"):
motion.x = -250
else:
motion.x = motion.x * .9
move_and_slide(motion)
A very simple method is to dampen the speed at every update - a concept borrowed from physical drag.
extends KinematicBody2D
var motion = Vector2()
func _physics_process(delta):
if Input.is_action_pressed("ui_right"):
motion.x = 250
elif Input.is_action_pressed("ui_left"):
motion.x = -250
else:
motion.x *= 0.95 # slow down by 5% each frame - play with this value
# i.e. make it dependent on delta to account for different fps
# or slow down faster each frame
# or clamp to zero under a certain threshold
move_and_slide(motion)
pass
Again this is very simple. To tune the drag value can be quite finicky if you want to give certain guarantees (like the player should come to a complete stop after 6 frames) especially when you're dealing with unlocked fps.
In such cases instead of having a single motion vector, have a last_motion and a target_motion vector and interpolate between them accordingly to get the current motion vector each frame (you can do this for acceleration too). This requires you to think about state transitions like "when a player stops pressing a button", starting timers to track the progress between the start and the target frame for the interpolation, etc... What kind of interpolation function you choose will affect how responsive or sluggish the movement feels to the player.
Here's an example of slowing down within 0.2 seconds using linear interpolation:
enum MotionState {IDLE, MOVING, DECELERATING}
extends KinematicBody2D
var state = IDLE
var last_motion = Vector2()
var time_motion = 0.0
func _physics_process(delta):
var motion = Vector2();
if Input.is_action_pressed("ui_right"):
motion.x = 250
state = MOVING
last_motion = motion
elif Input.is_action_pressed("ui_left"):
motion.x = -250
state = MOVING
last_motion = motion
elif state == IDLE
motion.x = 0
else:
if state == MOVING:
# start a smooth transition from MOVING through DECELERATING to IDLE
state = DECELERATING
# last_motion already holds last motion from latest MOVING state
time_motion = 0.2
# accumulate frame times
time_motion -= delta
if time_motion < 0.0:
# reached last frame of DECELERATING, transitioning to IDLE
state = IDLE
motion.x = 0
else:
var t = (0.2 - time_motion) / 0.2 # t goes from 0 to 1
# we're linearly interpolating between last_motion.x and 0 here
motion.x = (1 - t) * last_motion.x
move_and_slide(motion)
pass
Linear deceleration will feel off, but you can easily replace that with any other function, e.g. try cubic for faster deceleration and thus more responsive slow-down: motion.x = (1 - (t * t * t)) * last_motion.x

Resources