I'm making an Asteroids clone and I have a ship that moves fluidly with the arrow keys. The ship has an xv
and yv
velocity, and to move it I simply change the sprite's rect.x
and rect.y
by xv
and yv
, respectively. Once I let go of the arrow keys though, I would like it to decelerate smoothly. To do this, I am currently multiplying xv
and yv
by .96 every frame, which both decelerates the ship and enforces a maximum velocity.
At first glance, this seems to work fine, but when you look closely the ship will have strange movement patterns; for example, sometimes it will slow down a bit before it stops moving down, but it will continue at a constant speed in the x direction. Sometimes, the sprite will stop or slow down significantly on the edges of the screen while it is in this behavior, which shouldn't happen because I have code in place so that it should loop around to the other side.
I suspect these strange behaviors have to do with rounding within the rect
or sprite
parts of Pygame, but I am not sure how to fix this. I have tried automatically setting the velocity to zero once it gets low enough, but the problem persists and sometimes worsens.
Here is my code for the Player
class:
import pygame as pg
import Settings as s
import math
class Player(pg.sprite.Sprite):
global rect, r, xv, yv, v, track, images, pthrust
def __init__(self, x = (s.w-30)/2, y = (s.h-45)/2, r = math.pi, xv = 0, yv = 0, v = .5):
pg.sprite.DirtySprite.__init__(self)
self.r = r
self.v = v
self.xv = xv
self.yv = yv
self.rect = pg.Rect(x, y, 30, 45)
self.radius = 1
self.images = [pg.image.load('Player.png').convert_alpha(), pg.image.load('PThrust.png').convert_alpha()]
self.imagec = self.images[0]
self.image = self.imagec
self.pthrust = False
def thrust(self): # Called every frame while up arrow is down
self.xv += self.v*math.sin(self.r) # Accelerate the player
self.yv += self.v*math.cos(self.r)
self.imagec = self.images[1]
self.pthrust = True
def unthrust(self): # Called when up arrow is released
self.imagec = self.images[0]
self.pthrust = False
def turnLeft(self): # Called every frame while left arrow is down
self.r += math.pi/48
def turnRight(self): # Called every frame while right arrow is down
self.r -= math.pi/48
def move(self): # Called every frame
self.xv *= .96 # Decelerates the Player
self.yv *= .96
self.rect.left += self.xv # Changes the rect position
self.rect.top += self.yv
if self.rect.centerx < 0: self.rect.centerx = s.w+self.rect.centerx # Loop around edges of screen
if self.rect.centery < 0: self.rect.centery = s.h+self.rect.centery
if self.rect.centerx > s.w: self.rect.centerx = self.rect.centerx-s.w
if self.rect.centery > s.h: self.rect.centery = self.rect.centery-s.h
self.image = pg.transform.rotate(self.imagec, math.degrees(self.r)-180) # Rotate image
self.rect = self.image.get_rect(center=self.rect.center)
def reset(self): # Return to center if player dies
self.rect.x = (s.w-30)/2
self.rect.y = (s.h-45)/2
self.r = math.pi
self.xv = 0
self.yv = 0
If anyone has any ideas, they'd be greatly appreciated. Thanks in advance!
Edit: If the code for the main class would be helpful I can provide it, but I didn't think it was relevant to the issue and would take up a lot of space.
I don't observe this behavior when i change your code to use model view controller . eg instead of storing the x and y in Rect. player should store its own x and y and construct the Rect using that x and y.
I don't think pygame objects (such as Rect) should store your model directly. in general code seems to work more correct when you separate the Model from the View.
I would also recommend sepperating frame rate from game speed. eg instead of using 0.5 distance / frame
use 0.5 meters / second
.
I suspect these strange behaviors have to do with rounding within the
rect
[...]
You have to do all the computations with floating point coordinates. Add attributes self.x
respectively self.y
which track the position of the object. This attributes have to be used to compute the current position of the object. Round the position and synchronize it with the location of the .rect
attribute, after a new position is computed. eg:
class Player(pg.sprite.Sprite):
# [...]
def __init__(self, x = (s.w-30)/2, y = (s.h-45)/2, r = math.pi, xv = 0, yv = 0, v = .5):
pg.sprite.DirtySprite.__init__(self)
# [...]
self.x = x
self.y = y
self.rect = pg.Rect(self.x, self.y, 30, 45)
# [...]
# [...]
def move(self): # Called every frame
self.xv *= .96 # Decelerates the Player
self.yv *= .96
# Changes the rect position
self.x += self.xv
self.y += self.yv
self.rect.topleft = round(self.x), round(self.y)
# bounds check
x_changed, y_changed = False, False
if self.rect.centerx < 0:
self.rect.centerx = s.w+self.rect.centerx
x_changed = True
if self.rect.centery < 0:
self.rect.centery = s.h+self.rect.centery
y_changed = True
if self.rect.centerx > s.w:
self.rect.centerx = self.rect.centerx-s.w
x_changed = True
if self.rect.centery > s.h:
self.rect.centery = self.rect.centery-s.h
y_changed = True
# update self.x, self.y after collision with bounds
if x_changed:
self.x = self.rect.x
if y_changed:
self.y = self.rect.y
# [...]
def reset(self): # Return to center if player dies
self.x = (s.w-30)/2
self.y = (s.h-45)/2
self.rect.topleft = round(self.x), round(self.y)
# [...]
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.