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

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

Related

Why is my sprite getting stretched during look_at function?

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.

my car is running away, how can I do that the car could only go back and forth?

the car should go in a straight line (blue arrow):
My code is:
extends VehicleBody
const STEER_SPEED = 1.5
const STEER_LIMIT = 0.4
var steer_target = 0
export var engine_force_value = 40
func _physics_process(delta):
var fwd_mps = transform.basis.xform_inv(linear_velocity).x
if Input.is_action_pressed("accelerate"):
# Increase engine force at low speeds to make the initial acceleration faster.
var speed = linear_velocity.length()
if speed < 5 and speed != 0:
engine_force = clamp(engine_force_value * 5 / speed, 0, 100)
else:
engine_force = engine_force_value
else:
engine_force = 0
if Input.is_action_pressed("reverse"):
# Increase engine force at low speeds to make the initial acceleration faster.
if fwd_mps >= -1:
var speed = linear_velocity.length()
if speed < 5 and speed != 0:
engine_force = -clamp(engine_force_value * 5 / speed, 0, 100)
else:
engine_force = -engine_force_value
else:
brake = 1
else:
brake = 0.0
steering = move_toward(steering, steer_target, STEER_SPEED * delta)
I'm not convinced the code would work as written.
However, to the point: if you want a VehicleBody to not turn left or right, you need to keep steering = 0.

Animation plays only the first frame Godot

Im making a simple platformer. I have 3 movements that the player can do, run, jump and walk. The walking and jumping functions run fine but I have a problem with the run function, it seems to work but the animation only plays the first frame, which does not happen for any other animation.
extends KinematicBody2D
var speed : int = 200
var jumpforce: int = 500
var gravity: int = 1000
var vel: Vector2 = Vector2()
onready var sprite: Sprite = get_node("Sprite")
onready var anim = get_node("AnimationPlayer").get_animation("walk")
onready var anim2 = get_node("AnimationPlayer").get_animation("run")
func _physics_process(delta):
vel.x = 0
#movement
if Input.is_action_pressed("move_left"):
vel.x -= speed
anim.set_loop(true)
get_node("AnimationPlayer").play("walk")
if Input.is_action_pressed("move_right"):
vel.x += speed
anim.set_loop(true)
get_node("AnimationPlayer").play("walk")
#return to idle
if Input.is_action_just_released("move_right") or Input.is_action_just_released("move_left"):
$AnimationPlayer.play("idle")
#run
if Input.is_action_pressed("move_left") and Input.is_action_pressed("shift"):
vel.x -= speed
anim2.set_loop(true)
get_node("AnimationPlayer").play("run")
if Input.is_action_pressed("move_right") and Input.is_action_pressed("shift"):
vel.x += speed
anim2.set_loop(true)
get_node("AnimationPlayer").play("run")
#physic and jump
vel.y += gravity * delta
if Input.is_action_pressed("jump") and is_on_floor():
vel.y -= jumpforce
vel = move_and_slide(vel, Vector2.UP)
if vel.x < 0:
sprite.flip_h = true
elif vel.x > 0:
sprite.flip_h = false
Hi Man i am not sure if you still need Help but here is my solution:
extends KinematicBody2D
var speed : int = 200
var jumpforce: int = 500
var gravity: int = 1000
var vel: Vector2 = Vector2()
onready var sprite: Sprite = get_node("Sprite")
func _physics_process(delta):
vel.x = 0
#movement
#run
if Input.is_action_pressed("move_left") and Input.is_action_pressed("shift"):
vel.x -= speed
$AnimationPlayer.play("run")
elif Input.is_action_pressed("move_right") and Input.is_action_pressed("shift"):
vel.x += speed
$AnimationPlayer.play("run")
elif Input.is_action_pressed("move_left"):
vel.x -= speed
$AnimationPlayer.play("walk")
elif Input.is_action_pressed("move_right"):
vel.x += speed
$AnimationPlayer.play("walk")
#return to idle
else:
$AnimationPlayer.play("idle")
#physic and jump
vel.y += gravity * delta
if Input.is_action_pressed("jump") and is_on_floor():
vel.y -= jumpforce
vel = move_and_slide(vel, Vector2.UP)
if vel.x < 0:
sprite.flip_h = true
elif vel.x > 0:
sprite.flip_h = false
The problem was that you had a bunch of if statements and every tick both the run and walk if statements were true. So for example you press left and shift. The walk left if statement is true and the animation gets set to walk. Then the run if statement is also true and your animation gets set to run. The next frame the walk animation if statement is true again which means it gets set to the walk animation again. Then the run animation is true which sets the animation to the run animation but because we are switching the animation it sets the frame to 0 and starts the animation again.

Why is this simple pygame program lagging?

So, I've been studying programming for a short time and decided to make the snake game in pygame. However, while making the base of the program I realized that the rectangle (snake) controlled by the player is teleporting (maybe by the lag) every second while moving. Here is the code:
import pygame
pygame.init()
# Window
window = (1280, 720)
center = (window[0]//2, window[1]//2)
screen = pygame.display.set_mode(window)
pygame.display.set_caption("Snake")
# Colors
COLOR_LIGHT_GREY = (200, 200, 200)
COLOR_DARK_GREY = pygame.Color('gray12')
# Game loop
game_loop = True
game_clock = pygame.time.Clock()
# Create image
def img(name):
img_path = "./assets/natanael.lucena_" + name + ".png"
return pygame.image.load(img_path).convert_alpha()
# Set object coordinates
def set_obj_coordinates(obj, x, y):
obj.x = x
obj.y = y
# Check player key press
def check_player_key(b):
global snake_direction
if event.key == pygame.K_w or event.key == pygame.K_s or event.key == pygame.K_a or event.key == pygame.K_d:
snake_direction[event.key] = b
# Check key events in-game
def event_conditional():
global game_loop
if event.type == pygame.QUIT:
game_loop = False
elif event.type == pygame.KEYDOWN:
check_player_key(True)
elif event.type == pygame.KEYUP:
check_player_key(False)
# Check if the snake collided and the game is over
def game_over():
if snake.y < 0 or snake.y > 720 or snake.x < 0 or snake. x > 1280:
return True
# Snake
snake_img = img("snake")
snake = snake_img.get_rect()
move_keys = [pygame.K_w, pygame.K_d, pygame.K_s, pygame.K_a]
snake_direction = {k: False for k in move_keys}
snake_score = 0
snake_vel = 10
set_obj_coordinates(snake, center[0], center[1])
# Apple
apple_img = img("apple")
apple = apple_img.get_rect()
apple_eaten = False
set_obj_coordinates(apple, 40, 40)
# Main game loop
while game_loop:
for event in pygame.event.get():
event_conditional()
# score_text = text_render(snake_score)
if not game_over():
for i in range(4):
if i % 2:
coord_aux = "x "
else:
coord_aux = "y "
if i % 3:
op = "+= "
else:
op = "-= "
if snake_direction[move_keys[i]]:
exec("snake." + coord_aux + op + "snake_vel")
# the for loop above is equivalent to :
# if snake_direction[move_keys[0]]:
# snake.y -= snake_vel
# if snake_direction[move_keys[1]]:
# snake.x += snake_vel
# if snake_direction[move_keys[2]]:
# snake.y += snake_vel
# if snake_direction[move_keys[3]]:
# snake.x -= snake_vel
screen.fill(COLOR_DARK_GREY)
screen.blit(snake_img, snake)
screen.blit(apple_img, apple)
# Update screen
pygame.display.flip()
game_clock.tick(60)
pygame.quit()
If someone can tell me the reason for the problem, I really appreciate it.
Edit: looks like the problem is just happening with me
Thats a common problem with pygame, especially since pygame 2/sdl 2 where you can't use the directx video driver and enable vsync anymore.
You should do the following:
keep track of the sub-pixel coordinates of your moving game objects. A Rect can only store integer values in its x and y attributes, so you'll need another variable to store this. I usually use a Vector2 because it's easy to use and the performance hit usually does not matter anyway.
use an accurate clock. Pygame's clock object only uses milliseconds, which is not accurate enough for really smooth movement. If you're on windows, usually the best method for timing is the GetSystemTimePreciseAsFileTime function.
use a delta timing.
You could also use different threads for the parts of your game that needs different timing methods (e.g. your game logic expects fixed 30 or 60 FPS and your drawing code wants to run as fast as possible), but that's overkill for your small game.
So here's an example I hacked together that gives you smooth movement (at least that's what I use usually since it works fine for me. Note that it's windows specific):
import pygame
import ctypes.wintypes
pygame.init()
screen = pygame.display.set_mode((1280, 720))
center = screen.get_rect().center
pygame.display.set_caption("Snake")
game_loop = True
# https://stackoverflow.com/a/28574340/142637
def utcnow_microseconds():
system_time = ctypes.wintypes.FILETIME()
ctypes.windll.kernel32.GetSystemTimePreciseAsFileTime(ctypes.byref(system_time))
large = (system_time.dwHighDateTime << 32) + system_time.dwLowDateTime
return large // 10 - 11644473600000000
# Snake
snake_img = pygame.Surface((40, 40))
snake_img.fill('white')
snake = snake_img.get_rect()
snake_vel = 10
snake_pos = pygame.Vector2(center[0], center[1])
snake.topleft = snake_pos.x, snake_pos.y
# Apple
apple_img = pygame.Surface((40, 40))
apple_img.fill('red')
apple = apple_img.get_rect(topleft=(40, 40))
dt = 0
while game_loop:
t1 = utcnow_microseconds()
for event in pygame.event.get():
if event.type == pygame.QUIT:
game_loop = False
keys = pygame.key.get_pressed()
snake_pos.x += (keys[pygame.K_d] - keys[pygame.K_a]) * snake_vel * dt
snake_pos.y += (keys[pygame.K_s] - keys[pygame.K_w]) * snake_vel * dt
snake.topleft = snake_pos.x, snake_pos.y
screen.fill('darkgrey')
screen.blit(snake_img, snake)
screen.blit(apple_img, apple)
pygame.display.flip()
t2 = utcnow_microseconds()
dt = (t2 - t1) / 1000. / 1000. * 30
pygame.quit()
Further reading.
Very likely the bottleneck is the line
exec("snake." + coord_aux + op + "snake_vel")
exec has to parse and interpret the text in the argument.
This code can be easily improved
if not game_over():
for i in range(4):
if snake_direction[move_keys[i]]:
sign = 1 if i % 3 else -1
if i % 2:
snake.x += sign * snake_vel
else:
snake.y += sign * snake_vel
Since snake is a pygame.Rect object, you can even do the following:
if not game_over():
for i in range(4):
if snake_direction[move_keys[i]]:
sign = 1 if i % 3 else -1
snake[(i+1) % 2] += sign * snake_vel
However:
The keyboard events (see pygame.event module) occur only once when the state of a key changes. The KEYDOWN event occurs once every time a key is pressed. KEYUP occurs once every time a key is released. Use the keyboard events for a single action or a step-by-step movement.
pygame.key.get_pressed() returns a list with the state of each key. If a key is held down, the state for the key is True, otherwise False. Use pygame.key.get_pressed() to evaluate the current state of a button and get continuous movement.
Use pygame.key.get_pressed() for a smooth continuous movement:
# Main game loop
while game_loop:
for event in pygame.event.get():
event_conditional()
# score_text = text_render(snake_score)
if not game_over():
keys = pygame.key.get_pressed()
snake.x += (keys[pygame.K_d] - keys[pygame.K_a]) * snake_vel
snake.y += (keys[pygame.K_s] - keys[pygame.K_w]) * snake_vel
# [...]

i was making a Godot platformer with the help of a video on Youtube I've done everything same as him but my character refuses to move left

I'm trying to make a 2d platformer in godot with help from one of the the videos. video link: https://www.youtube.com/watch?v=PG0tfoPraE4. I have very little experience working with Godot.
I'm stuck and am not sure what to do, I've tried looking at some more videos as a last resort but all of them use another way of movement.
Here is the code
GDSCRIPT
extends KinematicBody2D
const MOVESPEED = 70
const JUMPFORCE = -200
const GRAVITY = 600
var motion = Vector2()
func _physics_process(delta):
if is_on_floor():
if Input.is_action_just_pressed("ui_up"):
motion.y = JUMPFORCE
if Input.is_action_pressed("ui_left"):
motion.x = -MOVESPEED
$Sprite.flip_h = true
$AnimationPlayer.play("walk")
if Input.is_action_pressed("ui_right"):
motion.x = MOVESPEED
$Sprite.flip_h = false
$AnimationPlayer.play("walk")
else:
motion.x = 0
$AnimationPlayer.stop()
motion.y += GRAVITY * delta
motion = move_and_slide(motion, Vector2(0,-1))
Your code path is falling into the else clause of the right movement. So preventing it to move.
Just add and else to the right if, like:
if Input.is_action_pressed("ui_left"):
motion.x = -MOVESPEED
$Sprite.flip_h = true
$AnimationPlayer.play("walk")
elif Input.is_action_pressed("ui_right"):
motion.x = MOVESPEED
$Sprite.flip_h = false
$AnimationPlayer.play("walk")
else:
motion.x = 0
$AnimationPlayer.stop()

Resources