cjjJasonchen 发表于 2023-7-23 20:56:16

【pygame】 教程:模拟车辆运动(持续更新)————7/31

本帖最后由 cjjJasonchen 于 2023-7-31 12:08 编辑

Python——pygame游戏开发教程:模拟车辆移动




前言:
最近贴住发现论坛里还有网上基本都很难找到做这种的,所以就做了{:10_279:}


本来想等几天再写这个帖子的,没想到预告竟然让大家这么迫不及待。。。{:10_297:}


当然呐,先说一下前言终于开始了吗,




在阅读这篇帖子之前,请注意:


1、请先保证你拥有python入门水平(怎么说也要看完百分之70小甲鱼教程吧~)


2、其次,你需要一定的pygame基础:不说你能写个打飞机,至少你的电脑上要有pygame吧~


3、本节课会涉及到一丢丢的三角函数,作者菜鸡一个,自己弄不太懂,也说不太明白,
   所以大家可以看看我写的公式或者查查百度,这节课是游戏开发课不是数学课的说~
4、这篇帖子的阅读和学习可能需要比较长的时间{:10_257:}

5、有些输入法的中文模式可能会阻断pygame接受键盘事件,
请点击shift切换成英文模式,最好在点击”Capslock“大写锁定,防止误触切换成中文{:10_275:}
(大写锁定也不会阻断键盘事件)




好啦~话不多说,教程开始!

教程1:旋转一个方块




好的,我们先看看这一页学完你可以做到什么:



(通过键盘”A“和”D“顺时针旋转)


首先我们需要一个Car类,表示玩家操作的方块,继承pygame.sprite.Spriteclass Car(pygame.sprite.Sprite):
    def __init__(self):
      super().__init__()
      self.width,self.height = 50,100
然后,老规矩,他要有透明度:self.image = pygame.Surface().convert_alpha()
一个需要旋转的Sprite对象,最重要的是:self.o = self.image # 备份原图 :看过小甲鱼教程的都知道,旋转会破坏图片
      self.orect = self.o.get_rect() #备份原图位置:图片旋转后位置会发生变化
      self.rect = self.orect
然后剩下的一些属性:# 设置位置
      self.orect.centerx = 400
      self.orect.centery = 300
      
      # 绘制
      pygame.draw.rect(self.image,(255,255,255),)

      self.angle = 0
      self.turn = 0 # 为了方便,在主循环里改



接着,是一个方法,这里看不懂的先别急,往下看,后面会讲:
def update(self):
      # 转动
      self.angle += self.turn

      # 防止角度>360°,方便后面的计算
      if self.angle > 360:
            self.angle -= 360

      # 防止角度<0°, 方便后面的计算
      if self.angle < 0:
            self.angle += 360

      # 得到角度的弧度
      self.radius = math.pi/180*self.angle

      # 旋转图像,得到新的图像和图像位置
      self.image = pygame.transform.rotate(self.o,self.angle)
      self.rect = self.image.get_rect()

      # 保证图像中心位置不变
      self.rect.centerx = self.orect.centerx
      self.rect.centery = self.orect.centery



相信聪明的鱼油一定可以看懂的对吧~{:10_297:}
https://fishc.com.cn/static/image/hrline/line7.pnghttps://fishc.com.cn/static/image/hrline/line7.pnghttps://fishc.com.cn/static/image/hrline/line7.pnghttps://fishc.com.cn/static/image/hrline/line7.pnghttps://fishc.com.cn/static/image/hrline/line7.pnghttps://fishc.com.cn/static/image/hrline/line7.pnghttps://fishc.com.cn/static/image/hrline/line7.pnghttps://fishc.com.cn/static/image/hrline/line7.pnghttps://fishc.com.cn/static/image/hrline/line7.pnghttps://fishc.com.cn/static/image/hrline/line7.pnghttps://fishc.com.cn/static/image/hrline/line7.pnghttps://fishc.com.cn/static/image/hrline/line7.png
接下来我们先把这些连起来,放到大循环里面(大家跑一下试试,然后我们再看看前面的方法):import pygame
import sys
import math
from pygame.locals import *

class Car(pygame.sprite.Sprite):
    def __init__(self):
      super().__init__()
      self.width,self.height = 50,100
      self.image = pygame.Surface().convert_alpha()
      self.o = self.image # 备份原图 :看过小甲鱼教程的都知道,旋转会破坏图片
      self.orect = self.o.get_rect() #备份原图位置:图片旋转后位置会发生变化
      self.rect = self.orect

      # 设置位置
      self.orect.centerx = 400
      self.orect.centery = 300
      
      # 绘制
      pygame.draw.rect(self.image,(255,255,255),)

      self.angle = 0
      self.turn = 0 # 为了方便,在主循环里改
      
      self.rigth = False
      self.left= False


    def update(self):
      # 转动
      self.angle += self.turn

      # 防止角度>360°,方便后面的计算
      if self.angle > 360:
            self.angle -= 360

      # 防止角度<0°, 方便后面的计算
      if self.angle < 0:
            self.angle += 360

      # 得到角度的弧度
      self.radius = math.pi/180*self.angle

      # 旋转图像,得到新的图像和图像位置
      self.image = pygame.transform.rotate(self.o,self.angle)
      self.rect = self.image.get_rect()

      # 保证图像中心位置不变
      self.rect.centerx = self.orect.centerx
      self.rect.centery = self.orect.centery

      


if __name__ == "__main__":
    size = width, height = 800,600

    screen = pygame.display.set_mode(size)

    pygame.display.set_caption("title")

    clock = pygame.time.Clock()

    delay = 60 # 延时计时器
    time = 0

    # 背景颜色
    bg = (85,85,85)

    # 是否全屏
    fullscreen = False
    screen_change = False

    running = True

    #实例化对象
    sprites = pygame.sprite.Group()
    car = Car()
    sprites.add(car)

    while running:
      clock.tick(60)

      # 检测是否全屏
      if fullscreen and screen_change:
            screen = pygame.display.set_mode(size,FULLSCREEN,HWSURFACE)
            screen_change = False
      elif screen_change:
            screen = pygame.display.set_mode(size)
            screen_change = False

      for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
               
            if event.type == MOUSEBUTTONDOWN:
                if event.button == 1:
                  print("左键按下")

            if event.type == KEYDOWN:
                if event.key == K_ESCAPE:
                  pygame.quit()
                  sys.exit()
                  
                #F11切换全屏
                elif event.key == K_F11:
                  fullscreen = not fullscreen
                  screen_change = True

                elif event.key == K_a:
                  car.turn = 5

                elif event.key == K_d:
                  car.turn = -5

                elif event.key == K_w:
                  car.w = True
                  
                elif event.key == K_s:
                  car.s = True

                  
            elif event.type == KEYUP:
                if event.key == K_a:
                  if car.turn >0:
                        car.turn = 0

                elif event.key == K_d:
                  if car.turn <0:
                        car.turn = 0

                elif event.key == K_w:
                  car.w = False

                elif event.key == K_s:
                  car.s = False


      #画背景
      screen.fill(bg)

      #画 xxxx
      sprites.draw(screen)
      sprites.update()

      # 刷新界面
      pygame.display.update()




static/image/hrline/line7.pnghttps://fishc.com.cn/static/image/hrline/line7.pnghttps://fishc.com.cn/static/image/hrline/line7.pnghttps://fishc.com.cn/static/image/hrline/line7.pnghttps://fishc.com.cn/static/image/hrline/line7.pnghttps://fishc.com.cn/static/image/hrline/line7.pnghttps://fishc.com.cn/static/image/hrline/line7.pnghttps://fishc.com.cn/static/image/hrline/line7.pnghttps://fishc.com.cn/static/image/hrline/line7.png


现在我们已经达到前面的效果了,我来讲讲前面方法里面大家可能看不懂的地方
      # 防止角度>360°,方便后面的计算
      if self.angle > 360:
            self.angle -= 360

      # 防止角度<0°, 方便后面的计算
      if self.angle < 0:
            self.angle += 360
这里学过用计算器算山叫含树的同学应该可以想到,有很多时候,计算器算负角度转换弧度啊,三角函数的时候可能会出问题。。。虽然不知道什么问题,但这样肯定可以减少错误

https://fishc.com.cn/static/image/hrline/line7.pnghttps://fishc.com.cn/static/image/hrline/line7.pnghttps://fishc.com.cn/static/image/hrline/line7.pnghttps://fishc.com.cn/static/image/hrline/line7.pnghttps://fishc.com.cn/static/image/hrline/line7.pnghttps://fishc.com.cn/static/image/hrline/line7.png这个弧度有什么用呢?————答案是这里暂时没用,但是当你想要移动这个长方形的时候会用到它,所以先跟他混个眼熟吧~{:10_282:}# 得到角度的弧度
      self.radian = math.pi/180*self.angle



# 保证图像中心位置不变
      self.rect.centerx = self.orect.centerx
      self.rect.centery = self.orect.centery那么我们为什么要重新算一边中心位置呢?
给大家演示一下我们先吧透明度注释掉self.image = pygame.Surface()#.convert_alpha() 改一下这里:# 绘制
      self.image.fill((0,0,0))
      pygame.draw.rect(self.image,(255,255,255),)# 注意看左边0改成了1还有注释这个:# 旋转图像,得到新的图像和图像位置
self.image = pygame.transform.rotate(self.o,self.angle)
#self.rect = self.image.get_rect()
然后把上面保持图像不变也注释掉# 保证图像中心位置不变
      #self.rect.centerx = self.orect.centerx
      #self.rect.centery = self.orect.centery
跑一遍试试:

发现了吗? 只有rect.x和rect.y是不变的,但我们希望的是旋转中心不变对吧~
ok此页已结束,请看回到目录点击后面一页{:10_264:}
教程2:移动这个长方体




好啦,我们来看看如何移动这个长方体吧~


首先,在__init__()里面加上四个属性:
      self.speedall = 10 # ==== xy轴的总速度
      self.speed = # ==== x,y速度的列表
      self.w = False # ====w是否按下
      self.s = False # ====s是否按下



然后,在update()方法里,增加:
      if self.w or self.s: # 计算xy方向的移动速度===
            self.speedx = -math.sin(self.radian)*10
            self.speedy = -math.cos(self.radian)*10
            
      if self.w:# 前进===
            self.orect.centerx += self.speedx
            self.orect.centery += self.speedy
      elif self.s:# 后退===
            self.orect.centerx -= self.speedx
            self.orect.centery -= self.speedy
接下来,你就可以运行这些代码了!
你会发现,我们的目标好像已经完成了!
当然啦~还有很多问题:

正常的车辆在不移动时是不可以转向的(能转的是坦克吧~)并且,车辆的旋转速度是与车辆行驶速度挂钩的(对比左上和右上图)                                                            (右上)



正确的车辆在方向盘不动的情况下,向前和向后的旋转方向应该相反(向前的时候是顺时针向后就应该逆时针)(左上图中我一直按着向左旋转,并且前后移动,一直在逆时针旋转)(右上图是正确的)

还有一个小细节——车辆的旋转中心并不是真正的在它的中心(几何重心)不知道有没有鱼油发现呢~{:10_305:}

上面的问题先留个悬念,下一节在讲



好啦,先看看完整的代码吧~import pygame
import sys
import math
from pygame.locals import *

class Car(pygame.sprite.Sprite):
    def __init__(self):
      super().__init__()
      self.width,self.height = 50,100
      self.image = pygame.Surface().convert_alpha()
      self.o = self.image # 备份原图 :看过小甲鱼教程的都知道,旋转会破坏图片
      self.orect = self.o.get_rect() #备份原图位置:图片旋转后位置会发生变化
      self.rect = self.orect

      # 设置位置
      self.orect.centerx = 400
      self.orect.centery = 300

         # 绘制
      pygame.draw.rect(self.image,(255,255,255),)

      self.speedall = 10 # ==== xy轴的总速度
      self.speed = # ==== x,y速度的列表

      self.angle = 0
      self.turn = 0 # 为了方便,在主循环里改
      
      self.w = False # ====w是否按下
      self.s = False # ====s是否按下

    def update(self):
      # 转动
      self.angle += self.turn
      
      # 防止角度>360°,方便后面的计算
      if self.angle > 360:
            self.angle -= 360

      # 防止角度<0°, 方便后面的计算
      if self.angle < 0:
            self.angle += 360

      
         # 得到角度的弧度
      self.radian = math.pi/180*self.angle

      # 旋转图像,得到新的图像和图像位置
      self.image = pygame.transform.rotate(self.o,self.angle)
      self.rect = self.image.get_rect()
      
      # 保证图像中心位置不变
      self.rect.centerx = self.orect.centerx
      self.rect.centery = self.orect.centery
      
      if self.w or self.s: # 计算xy方向的移动速度===
            self.speedx = -math.sin(self.radian)*10
            self.speedy = -math.cos(self.radian)*10
            
      if self.w:# 前进===
            self.orect.centerx += self.speedx
            self.orect.centery += self.speedy
      elif self.s:# 后退===
            self.orect.centerx -= self.speedx
            self.orect.centery -= self.speedy


      


if __name__ == "__main__":
    size = width, height = 800,600

    screen = pygame.display.set_mode(size)

    pygame.display.set_caption("title")

    clock = pygame.time.Clock()

    delay = 60 # 延时计时器
    time = 0

    # 背景颜色
    bg = (85,85,85)

    # 是否全屏
    fullscreen = False
    screen_change = False

    running = True

    #实例化对象
    sprites = pygame.sprite.Group()
    car = Car()
    sprites.add(car)

    while running:
      clock.tick(60)

      # 检测是否全屏
      if fullscreen and screen_change:
            screen = pygame.display.set_mode(size,FULLSCREEN,HWSURFACE)
            screen_change = False
      elif screen_change:
            screen = pygame.display.set_mode(size)
            screen_change = False

      for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
               
            if event.type == MOUSEBUTTONDOWN:
                if event.button == 1:
                  print("左键按下")

            if event.type == KEYDOWN:
                if event.key == K_ESCAPE:
                  pygame.quit()
                  sys.exit()
                  
                #F11切换全屏
                elif event.key == K_F11:
                  fullscreen = not fullscreen
                  screen_change = True

                elif event.key == K_a:
                  car.turn = 3

                elif event.key == K_d:
                  car.turn = -3

                elif event.key == K_w:
                  car.w = True
                  
                elif event.key == K_s:
                  car.s = True

                  
            elif event.type == KEYUP:
                if event.key == K_a:
                  if car.turn >0:
                        car.turn = 0

                elif event.key == K_d:
                  if car.turn <0:
                        car.turn = 0

                elif event.key == K_w:
                  car.w = False

                elif event.key == K_s:
                  car.s = False

      #画背景
      screen.fill(bg)

      #画 xxxx
      sprites.draw(screen)
      sprites.update()

      # 刷新界面
      pygame.display.update()





各位鱼油可以自己跑一跑程序,体会一下
整理代码
当然啦,在下一页之前,先把代码整理一下,顺便避免在不移动时出现的转向操作,要更改的地方都用“+++”,“---”标出来了

class Car(pygame.sprite.Sprite):
    def __init__(self):
      super().__init__()
      self.width,self.height = 50,100
      self.image = pygame.Surface().convert_alpha() # ===
      self.o = self.image # 备份原图 :看过小甲鱼教程的都知道,旋转会破坏图片
      self.orect = self.o.get_rect() #备份原图位置:图片旋转后位置会发生变化
      self.rect = self.orect

      # 设置位置
      self.orect.centerx = 400
      self.orect.centery = 300

         # 绘制
      self.image.fill((0,0,0,0))
      pygame.draw.rect(self.image,(255,255,255),)

         # 画四个轮子
      pygame.draw.rect(self.image,(0,0,0),)
      pygame.draw.rect(self.image,(0,0,0),)
      pygame.draw.rect(self.image,(0,0,0),)
      pygame.draw.rect(self.image,(0,0,0),)

      self.total_speed = 10 # xy轴的总速度
      self.speed = # x,y速度的列表

      self.angle = 0
      self.turn = 0 # 为了方便,在主循环里改
      
      self.w = False # w是否按下
      self.s = False # 是否按下

    def update(self):
      # 转动----
      
      # 防止角度>360°,方便后面的计算
      if self.angle > 360:
            self.angle -= 360

      # 防止角度<0°, 方便后面的计算
      if self.angle < 0:
            self.angle += 360

      
         # 得到角度的弧度------

      # 旋转图像,得到新的图像和图像位置----
      
      # 保证图像中心位置不变-----
      
         # 计算xy方向的移动速度-----

         
      if self.w:# 前进
            self.angle += self.turn # 计算角度+++
            self.radius = math.pi/180*self.angle # 转化弧度+++

            #旋转图像,得到新的图像和图像位置+++
            self.image = pygame.transform.rotate(self.o,self.angle)
            self.rect = self.image.get_rect()

            # 保证图像中心位置不变+++
            self.rect.centerx = self.orect.centerx
            self.rect.centery = self.orect.centery

            # 计算xy方向的移动速度+++
            self.speed = -math.sin(self.radius)*self.total_speed
            self.speed = -math.cos(self.radius)*self.total_speed

            # 移动图像
            self.orect.centerx += self.speed # 这里把speedx改成speed
            self.orect.centery += self.speed # 这里把speedx改成speed
      elif self.s:# 后退
            self.angle -= self.turn # 计算角度+++
            self.radius = math.pi/180*self.angle # 转化弧度+++

            #旋转图像,得到新的图像和图像位置+++
            self.image = pygame.transform.rotate(self.o,self.angle)
            self.rect = self.image.get_rect()

             # 保证图像中心位置不变+++
            self.rect.centerx = self.orect.centerx
            self.rect.centery = self.orect.centery

            # 计算xy方向的移动速度+++
            self.speed = -math.sin(self.radius)*self.total_speed
            self.speed = -math.cos(self.radius)*self.total_speed
            
            self.orect.centerx -= self.speed # 这里把speedx改成speed
            self.orect.centery -= self.speed # 这里把speedx改成speed
      print(self.angle)


这样在刷新的时候就可以避免在不移动时的转向操作了!


教程3:平滑的手感是如何实现的


其实这一节应该会很简单,


只要多设置几个转向变量和移动变量就可以啦!




简单来说就是:


转向加速,当前帧转向速度,转向最大速度


移动加速度,当前移动速度,移动最大速度


怎么样!是不是已经很清晰啦!{:10_279:}


self.all_speed = 0 # 当前帧x速度+y速度
self.max_speed = 30 # 最大速度
self.speed = #x,y速度
self.speed_up = 0.2 # 每帧增加速度
self.total_speed = 0 # x,y方向的总速度加速减速的时候更改total_speed就可以啦~speed的值每帧都会通过total_speed重新计算
self.angle = 0 # 现在的角度
self.turn = 0 # 这帧旋转速度(随速度增加)
self.turn_speed = 0.2 # 转向加速度
self.max_turn = max_turn # 最大时速时最大旋转速度

(大家不要学我瞎给变量取名,如果不会起名字还不如用中文,都是血和泪的教训)

当然啦,要像车子平滑的减速,必须要有阻力,而不是一松油门直接停车

self.resist = 0.1 # 无操作时每帧减速(阻力)

这时候,我们判断车辆前进还是后退就不可以继续使用W 、S是否按下了,


那么要如何减速呢? 给你0.5秒思考一下!{:10_256:}






嘿嘿嘿聪明的鱼油们一定想到了吧!没错!判断当前帧总移动速度->total_speed是否大于阻力就可以啦~

小提示1:# 判断油门刹车是否踩下和是否达到最高速度
      if self.w and self.total_speed < self.max_speed:
            self.total_speed += self.speed_up
      elif self.s and self.total_speed > -self.max_speed/2:
            self.total_speed -= self.speed_up
小提示2:# 如果向前移动
      if self.total_speed > self.resist+0.01: # 0.01是为了防止闪烁
               
            # 模拟方向盘自动回正(车头转动速度平滑归零)
            if not self.d and self.turn < 0:# 转向速度每帧降低
                self.turn += self.turn_speed
                if self.turn >= -self.turn_speed+0.01: # 保证转向角度成功归零(没有小数)
                  self.turn = 0
                  
               
            elif not self.a and self.turn > 0:
                self.turn -= self.turn_speed
                if self.turn <= self.turn_speed-0.01: # 保证转向角度成功归零(没有小数)
                  self.turn = 0




{:10_319:}
接下来,加上一个简简单单的背景类:class Thing(pygame.sprite.Sprite):
    def __init__(self):
      # 背景
      super().__init__()
      self.width,self.height = randint(10,300),randint(10,300)
      self.image = pygame.Surface().convert_alpha()
      self.rect = self.image.get_rect()
      self.rect.x, self.rect.y = randint(-800,1600),randint(-800,1600)
      pygame.draw.rect(self.image,
                         (randint(100,255),randint(100,255),randint(100,255)),
                         )




使摄像机跟随车辆移动————车辆始终保持在屏幕中心,移动其他对象。
更多关于摄像机移动的教程可以先看看隔壁 @歌者文明清理员的贴子:[作品展示] 【歌者-Pygame】pygame 天体运动模拟(附教程)的 鼠标event 教程
来看看这一小节的成果吧~


注意:      下方代码仅供参考及体验,更优秀的代码等着各位鱼油来写我总是把代码直接给你们,这可不行,各位鱼油一定要有自己写代码的能力!
      因为下面的代码是楼主在研究车辆移动的时候写的,变量名可能和上面提到的有所出入,各位鱼油在自己写的时候请以自己电脑的为准!
绝对不是我懒的再把代码重新优化好发上来。。。{:10_297:}

看看代码吧~




import pygame
import sys
import math
from random import randint
from pygame.locals import *

class Car(pygame.sprite.Sprite):
    def __init__(self):
      super().__init__()
      self.width,self.height = 50,100
      self.image = pygame.Surface().convert_alpha()
      self.o = self.image # 备份原图
      self.orect = self.o.get_rect()
      self.rect = self.orect

      # 设定初始位置
      self.orect.centerx = 400
      self.orect.centery = 300
      
      # 透明背景和主题
      pygame.draw.rect(self.image,(0,0,0,0),)
      pygame.draw.rect(self.image,(255,255,255),)

      # 画四个轮子
      pygame.draw.rect(self.image,(0,0,0),)
      pygame.draw.rect(self.image,(0,0,0),)
      pygame.draw.rect(self.image,(0,0,0),)
      pygame.draw.rect(self.image,(0,0,0),)
      
      self.all_speed = 0 # 当前帧x速度+y速度
      self.max_speed = 30
      self.speed =
      self.speed_up = 0.2 # 每帧增加速度
      self.angle = 0
      self.turn = 5 # 最大时速时旋转速度
      self.resist = 0.1 # 无操作时每帧减速(阻力)
      
      self.w = False
      self.s = False
      self.forward = False


    def update(self):

      # 保证角度不会超过360
      if self.angle >= 360:
            self.angle -= 360

      # 保证角度不会是负数,为后面的计算提供方便
      if self.angle < 0:
            self.angle = 360+self.angle

      self.radius = math.pi/180*self.angle # 转化为弧度

      # 旋转车
      self.image = pygame.transform.rotate(self.o,self.angle)
      self.rect = self.image.get_rect()
      self.rect.centerx = self.orect.centerx
      self.rect.centery = self.orect.centery

      # 判断油门刹车是否踩下和是否达到最高速度
      if self.w and self.all_speed < self.max_speed:
            self.all_speed += self.speed_up
      elif self.s and self.all_speed > -self.max_speed/2:
            self.all_speed -= self.speed_up

      # 计算移动
      if self.all_speed > 0:
            self.speed = -math.sin(self.radius)*self.all_speed
            self.speed = -math.cos(self.radius)*self.all_speed
            if self.all_speed > self.resist:
                self.angle += self.turn*(self.all_speed/self.max_speed)
                self.all_speed -= self.resist

      elif self.all_speed < 0:
            self.speed = -math.sin(self.radius)*self.all_speed
            self.speed = -math.cos(self.radius)*self.all_speed
            
            if self.all_speed < self.resist:
                self.angle += self.turn*(self.all_speed/self.max_speed)
                self.all_speed += self.resist

      

      

class Thing(pygame.sprite.Sprite):
    def __init__(self):
      # 背景
      super().__init__()
      self.width,self.height = randint(10,300),randint(10,300)
      self.image = pygame.Surface().convert_alpha()
      self.rect = self.image.get_rect()
      self.rect.x, self.rect.y = randint(-800,1600),randint(-800,1600)
      pygame.draw.rect(self.image,
                         (randint(100,255),randint(100,255),randint(100,255)),
                         )


if __name__ == "__main__":
    size = width, height = 800,600

    screen = pygame.display.set_mode(size)

    pygame.display.set_caption("title")

    clock = pygame.time.Clock()

    delay = 60 # 延时计时器
    time = 0

    # 背景颜色
    bg = (85,85,85)

    # 是否全屏
    fullscreen = False
    screen_change = False

    running = True

    #实例化对象
    sprites = pygame.sprite.Group()
    bgthings = pygame.sprite.Group()
    car = Car()

    # 生成背景
    for i in range(300):
      #sprites.add(Thing())
      bgthings.add(Thing())
      

    sprites.add(car)

    while running:
      clock.tick(60)

      # 检测是否全屏
      if fullscreen and screen_change:
            screen = pygame.display.set_mode(size,FULLSCREEN,HWSURFACE)
            screen_change = False
      elif screen_change:
            screen = pygame.display.set_mode(size)
            screen_change = False

      for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
               
            if event.type == MOUSEBUTTONDOWN:
                if event.button == 1:
                  print("左键按下")

            if event.type == KEYDOWN:
                if event.key == K_ESCAPE:
                  pygame.quit()
                  sys.exit()
                  
                #F11切换全屏
                elif event.key == K_F11:
                  fullscreen = not fullscreen
                  screen_change = True

                elif event.key == K_a:
                  car.turn = 5

                elif event.key == K_d:
                  car.turn = -5

                elif event.key == K_w:
                  car.w = True
                  
                elif event.key == K_s:
                  car.s = True

                elif event.key == K_q:
                  car.q = True
                  
                elif event.key == K_e:
                  car.e = True

                  
            elif event.type == KEYUP:
                if event.key == K_a:
                  if car.turn >0:
                        car.turn = 0

                elif event.key == K_d:
                  if car.turn <0:
                        car.turn = 0

                elif event.key == K_w:
                  car.w = False

                elif event.key == K_s:
                  car.s = False

                elif event.key == K_q:
                  car.q = False

                elif event.key == K_e:
                  car.e = False

      # 移动镜头
      for bgthing in bgthings:
            bgthing.rect.x -= car.speed
            bgthing.rect.y -= car.speed

      #画背景
      screen.fill(bg)

      #画 xxxx
      sprites.update()
      bgthings.update()
      bgthings.draw(screen)
      sprites.draw(screen)

      # 刷新界面
      pygame.display.update()







漂移的实现理论


这个看起来很牛*,但是实现起来是很简单的。{:10_256:}


这里我用的方法是把移动方向的角度和车体的角度分开计算,使漂移时的移动角度比车体角度大一点就可以啦~

当然啦,在此之前,还要修正一个bug,不知道有没有聪明的鱼油发现呢~

当车辆移动速度非常低的时候,操作手感非常差,(把最高速度改成<5的就可以感受到啦)



(注意看车辆的拖尾,一直在摇晃,这个bug现在还不太明显,但是如果车做的很小就无法忽略它的影响了)

这是因为pygame的移动最小单位是1像素,四舍五入,如果移动速度小于1就会出现问题
(鱼油要自己思考一下再看上面的答案哦)



没错,所以我们就要自己搓一个计算小数的移动算法啦!
代码如下:# 计算移动
      self.speed = -math.sin(self.move_radius)*self.total_speed
      self.speed = -math.cos(self.move_radius)*self.total_speed

                # 计算移速小数
      self.small_speed += self.speed - int(self.speed)
      self.speed = int(self.speed)
      self.small_speed += self.speed - int(self.speed)
      self.speed = int(self.speed)

      # 计算移动(计算小数)
      if self.small_speed >1:
            self.speed += 1
            self.small_speed -= 1
      elif self.small_speed < -1:
            self.speed -= 1
            self.small_speed += 1

      if self.small_speed >1:
            self.speed += 1
            self.small_speed -= 1

      elif self.small_speed < -1:
            self.speed -= 1
            self.small_speed += 1
然后来看看漂移的算法:#计算角度
            
            if self.space and ((self.a and self.move_angle < self.angle+0.1) or (self.d and self.move_angle > self.angle-0.1)):
                # 如果空格按下,并且车头的角度大于移动的角度 开始主动漂移
                self.move_angle += self.turn*(self.total_speed/self.max_speed)*1.1
                self.angle += self.turn*(self.total_speed/self.max_speed)*1.5
                        
            else:# 不漂移时,车头转向速度与移动方向转向速度相同
                # 如果没有主动漂移
                self.angle += self.turn*(self.total_speed/self.max_speed)

                # 移动方向自动向车头方向靠拢 打滑
                if self.move_angle > self.angle+0.1:
                  self.move_angle-= self.turn_restore
                        
                elif self.move_angle < self.angle-0.1:
                  self.move_angle+= self.turn_restore

            # 前进方向和车头方向夹角大于100,锁定角度
            if self.move_angle - self.angle > self.esp:
                self.move_angle = self.angle + self.esp

            elif self.angle - self.move_angle > self.esp:
                  self.move_angle = self.angle - self.esp

            #阻力
            self.total_speed -= self.resist(这是车辆前进时的,后退时有一些量要改成负的)
各位鱼油有看不懂的地方及时提问哦!我会把缺少的再更新上来的!毕竟我的注释基本上每行都写了所以就 不再细讲代码了

好啦!看看完整代码吧:



import pygame
import sys
import math
from random import randint
from pygame.locals import *

class Trace(pygame.sprite.Sprite):
    def __init__(self,color=(255,255,255,255),position=,radius=0,time=-1):
      super().__init__()
      self.position = position
      self.image = pygame.Surface((radius*2,radius*2), pygame.SRCALPHA).convert_alpha()
      self.rect = self.image.get_rect(center=position)
      self.color = color
      self.radius = radius
      self.time = time
      self.total_time = time
      self.alpha = self.color
      self.total_alpha = self.color
      
      pygame.draw.circle(
            self.image,
            color=self.color,
            center=(self.radius,self.radius),
            radius=self.radius
            )

    def update(self):
      self.time -= 1
      self.alpha = self.total_alpha*(self.time/self.total_time)
      self.image.set_alpha(self.alpha)


      
      

class Car(pygame.sprite.Sprite):
    def __init__(self):
      super().__init__()
      self.width,self.height = 50,100
      self.image = pygame.Surface(, pygame.SRCALPHA)#pygame.SRCALPHA 填充透明背景
      self.o = self.image # 备份原图
      self.orect = self.o.get_rect()
      self.rect = self.orect

      # 设定初始位置
      self.orect.centerx = 400
      self.orect.centery = 300
      
      # 主体
      pygame.draw.rect(self.image,(255,255,255),)

      # 画四个轮子
      pygame.draw.rect(self.image,(0,0,0),)
      pygame.draw.rect(self.image,(0,0,0),)
      pygame.draw.rect(self.image,(0,0,0),)
      pygame.draw.rect(self.image,(0,0,0),)
      
      self.total_speed = 0 # 当前帧x速度+y速度
      self.small_speed = # 存放移速零头
      self.max_speed = 10
      self.speed =
      self.speed_up = 0.2 # 每帧增加速度
      self.angle = 0 # 先在的角度
      self.turn = 0 # 这帧旋转速度
      self.move_angle = 0 # 移动方向的角度
      self.max_turn = 3 # 最大时速时最大旋转速度
      self.turn_speed = 0.2 # 转向加速度
      self.resist = 0.1 # 无操作时每帧减速(阻力)
      self.gears = [(10,1),(20,0.2),(30,0.12)] # [(最大时速,加速度)]
      self.radius = 0
      self.control_ability = 0.9 # 操纵性能(打滑、漂移回正速度)
      self.turn_restore = self.max_turn*self.control_ability # 移动速度的方向改变速度
      self.esp = 100 # 最大漂移角度

      # 按键状态
      self.w = False
      self.s = False
      self.a = False
      self.d = False
      self.space=False

      # 车辆状态
      self.forward = False


    def update(self):

      # 保证角度不会超过360
      if self.angle >= 360 and self.move_angle >= 360:
            self.angle -= 360
            self.move_angle -= 360

      # 保证角度不会是负数,为后面的计算提供方便
      if self.angle < 0 and self.move_angle < 0:
            self.angle = 360+self.angle
            self.move_angle = 360+self.move_angle

      self.radius = math.pi/180*self.angle # 转化为弧度
      self.move_radius = math.pi/180*self.move_angle

      # 旋转车
      self.image = pygame.transform.rotate(self.o,self.angle)
      self.rect = self.image.get_rect()
      self.rect.centerx = self.orect.centerx
      self.rect.centery = self.orect.centery

      # 判断油门刹车是否踩下和是否达到最高速度
      if self.w and self.total_speed < self.max_speed:
            self.total_speed += self.speed_up
      elif self.s and self.total_speed > -self.max_speed/2:
            self.total_speed -= self.speed_up

      # 计算移动
      self.speed = -math.sin(self.move_radius)*self.total_speed
      self.speed = -math.cos(self.move_radius)*self.total_speed

                # 计算移速小数
      self.small_speed += self.speed - int(self.speed)
      self.speed = int(self.speed)
      self.small_speed += self.speed - int(self.speed)
      self.speed = int(self.speed)

      # 计算移动(计算小数)
      if self.small_speed >1:
            self.speed += 1
            self.small_speed -= 1
      elif self.small_speed < -1:
            self.speed -= 1
            self.small_speed += 1

      if self.small_speed >1:
            self.speed += 1
            self.small_speed -= 1

      elif self.small_speed < -1:
            self.speed -= 1
            self.small_speed += 1


      # 转向 模拟方向盘转动过程
      if self.d and self.turn > -self.max_turn:# 转向速度每帧提高
            self.turn -= self.turn_speed

      elif self.a and self.turn < self.max_turn:
            self.turn += self.turn_speed
      

      # 如果向前移动
      if self.total_speed > self.resist+0.01: # 0.01是为了防止闪烁
               
            # 模拟方向盘自动回正(车头转动速度平滑归零)
            if not self.d and self.turn < 0:# 转向速度每帧降低
                self.turn += self.turn_speed
                if self.turn >= -self.turn_speed+0.01: # 保证转向角度成功归零(没有小数)
                  self.turn = 0
                  
               
            elif not self.a and self.turn > 0:
                self.turn -= self.turn_speed
                if self.turn <= self.turn_speed-0.01: # 保证转向角度成功归零(没有小数)
                  self.turn = 0
                  
            

            #计算角度
            
            if self.space and ((self.a and self.move_angle < self.angle+0.1) or (self.d and self.move_angle > self.angle-0.1)):
                # 如果空格按下,并且车头的角度大于移动的角度 开始主动漂移
                self.move_angle += self.turn*(self.total_speed/self.max_speed)*1.1
                self.angle += self.turn*(self.total_speed/self.max_speed)*1.5
                        
            else:# 不漂移时,车头转向速度与移动方向转向速度相同
                # 如果没有主动漂移
                self.angle += self.turn*(self.total_speed/self.max_speed)

                # 移动方向自动向车头方向靠拢 打滑
                if self.move_angle > self.angle+0.1:
                  self.move_angle-= self.turn_restore
                        
                elif self.move_angle < self.angle-0.1:
                  self.move_angle+= self.turn_restore

            # 前进方向和车头方向夹角大于100,锁定角度
            if self.move_angle - self.angle > self.esp:
                self.move_angle = self.angle + self.esp

            elif self.angle - self.move_angle > self.esp:
                  self.move_angle = self.angle - self.esp

            #阻力
            self.total_speed -= self.resist

            
            

      # 如果倒车
      elif self.total_speed < -self.resist-0.01:

            # 模拟方向盘自动回正 (平滑归零)
            if not self.d and self.turn < 0:# 转向速度每帧降低
                self.turn += self.turn_speed
                if self.turn >= -self.turn_speed-0.01:# 保证转向角度成功归零(没有小数)
                  self.turn = 0

            elif not self.a and self.turn > 0:
                self.turn -= self.turn_speed
                if self.turn <= self.turn_speed+0.01:# 保证转向角度成功归零(没有小数)
                  self.turn = 0

            #计算角度
            if self.space and ((self.a and self.move_angle > self.angle+0.1) or (self.d and self.move_angle < self.angle-0.1)):
                # 漂移
                self.move_angle += self.turn*(self.total_speed/self.max_speed)*1.1
                self.angle += self.turn*(self.total_speed/self.max_speed)*1.5

            else:# 不漂移时,车头转向速度与移动方向转向速度相同
                self.angle += self.turn*(self.total_speed/self.max_speed)

                # 移动方向自动向车头方向靠拢 打滑
                if self.move_angle > self.angle-0.01:
                  self.move_angle -= self.turn_restore
                elif self.move_angle < self.angle+0.01:
                  self.move_angle += self.turn_restore

            # 后退方向和车头方向夹角大于100,锁定角度
            if self.move_angle - self.angle > self.esp/2:
                self.move_angle = self.angle + self.esp/2

            elif self.angle - self.move_angle > self.esp/2:
                  self.move_angle = self.angle - self.esp/2
            
            #阻力
            self.total_speed += self.resist
            

    def create_trace(self):
      
      def count(position):
            r=((position-self.rect.centerx)**2 + (position-self.rect.centery)**2)**0.5
            if position > self.rect.centerx:
                sin = (position-self.rect.centery)/r
                radian = math.asin(sin)
                radian -= self.radius
                x = self.rect.centerx + r*math.cos(radian)
                y = self.rect.centery + r*math.sin(radian)
            elif position < self.rect.centerx:
                sin = -(position-self.rect.centery)/r
                radian = math.asin(sin)
                radian -= self.radius
                x = self.rect.centerx - r*math.cos(radian)
                y = self.rect.centery - r*math.sin(radian)
            return
      
      
      trace1 = Trace(color=,
                        position=count(),
                        radius=5,
                        time=10)
      
      trace2 = Trace(color=,
                        position=count(),
                        radius=5,
                        time=10)
      
      trace3 = Trace(color=,
                        position=count(),
                        radius=5,
                        time=10)
      
      trace4 = Trace(color=,
                        position=count(),
                        radius=5,
                        time=10)
      
      return

      

class Thing(pygame.sprite.Sprite):
    def __init__(self):
      # 背景
      super().__init__()
      self.time = -1
      self.width,self.height = randint(10,300),randint(10,300)
      self.image = pygame.Surface().convert_alpha()
      self.rect = self.image.get_rect()
      self.rect.x, self.rect.y = randint(-800,1600),randint(-800,1600)
      pygame.draw.rect(self.image,
                         (randint(100,255),randint(100,255),randint(100,255)),
                         )


if __name__ == "__main__":
    size = width, height = 800,600

    screen = pygame.display.set_mode(size)

    pygame.display.set_caption("漂移")

    clock = pygame.time.Clock()

    delay = 60 # 延时计时器
    time = 0

    # 背景颜色
    bg = (85,85,85)

    # 是否全屏
    fullscreen = False
    screen_change = False

    running = True

    #实例化对象
    sprites = pygame.sprite.Group()
    bgthings = pygame.sprite.Group()
    car = Car()

    # 生成背景
    for i in range(300):
      #sprites.add(Thing())
      bgthings.add(Thing())
      

    sprites.add(car)

    while running:
      clock.tick(60)

      # 检测是否全屏
      if fullscreen and screen_change:
            screen = pygame.display.set_mode(size,FULLSCREEN,HWSURFACE)
            screen_change = False
      elif screen_change:
            screen = pygame.display.set_mode(size)
            screen_change = False

      for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
               
            if event.type == MOUSEBUTTONDOWN:
                if event.button == 1:
                  print("左键按下")

            if event.type == KEYDOWN:
                if event.key == K_ESCAPE:
                  pygame.quit()
                  sys.exit()
                  
                #F11切换全屏
                elif event.key == K_F11:
                  fullscreen = not fullscreen
                  screen_change = True

                elif event.key == K_a:
                  car.a = True

                elif event.key == K_d:
                  car.d = True

                elif event.key == K_w:
                  car.w = True
                  
                elif event.key == K_s:
                  car.s = True

                elif event.key == K_q:
                  car.q = True
                  
                elif event.key == K_e:
                  car.e = True

                elif event.key == K_1:
                  car.max_speed = car.gears
                  car.speed_up = car.gears

                elif event.key == K_2:
                  car.max_speed = car.gears
                  car.speed_up = car.gears

                elif event.key == K_3:
                  car.max_speed = car.gears
                  car.speed_up = car.gears

                elif event.key == K_SPACE:
                  car.space=True

                  
            elif event.type == KEYUP:
                if event.key == K_a:
                  car.a = False
                  #if car.turn >0:
                        #car.turn = 0

                elif event.key == K_d:
                  car.d = False
                  #if car.turn <0:
                        #car.turn = 0

                elif event.key == K_w:
                  car.w = False

                elif event.key == K_s:
                  car.s = False

                elif event.key == K_q:
                  car.q = False

                elif event.key == K_e:
                  car.e = False

                elif event.key == K_SPACE:
                  car.space=False

      # 生成轮胎印记
      for trace in car.create_trace():
            bgthings.add(trace)
      for trace in bgthings:
            if trace.time == 0:
                bgthings.remove(trace)

      # 移动镜头
      for bgthing in bgthings:
            bgthing.rect.x -= car.speed
            bgthing.rect.y -= car.speed

      #画背景
      screen.fill(bg)

      #画 xxxx
      sprites.update()
      bgthings.update()
      bgthings.draw(screen)
      sprites.draw(screen)

      # 刷新界面
      pygame.display.update()



学会了吗!评论区见哦~




https://fishc.com.cn/static/image/hrline/line7.png这里可能要先跟大家伙道个歉,模拟车辆移动现在还不成熟,所以更新可能会很慢,但是一定会努力给大家带来更高质量的帖子https://fishc.com.cn/static/image/hrline/line7.png

更新中。。。。这个帖子车不多完结了,但以后还是会看情况更新的 感觉这教程写的一塌糊涂啊



评分!!!评分!!!评分!!!

求评分!求千斤顶!求制定卡!求帮忙@!求推荐!求评论!






感谢


[*]首先感谢各位支持我的鱼油,是你们的支持让我一直走到今天
[*]感谢各位版主,管理员和不二老师
[*]感谢@歌者文明清理员 ,@python爱好者. 等同志帮我做的宣传和提供的帮助帮我修复了 · bug 虽然最后我自己解决了bug
[*]感谢小甲鱼老师和闪耀捕捉器鱼C闪耀屏幕捕捉器为我制作帖子提供了很大很大的方便

cjjJasonchen 发表于 2023-7-23 20:59:21

@Mike_python小 @中英文泡椒 @学习编程中的Ben @sfqxx @python爱好者. @学习学习在研究 @zhangjinxuan @liuhongrun2022

python爱好者. 发表于 2023-7-23 21:02:22

激动人心,无以言表,难得的好帖啊
期待后面的更新

cjjJasonchen 发表于 2023-7-23 21:04:31

python爱好者. 发表于 2023-7-23 21:02
激动人心,无以言表,难得的好帖啊
期待后面的更新

谢谢支持,我一定不会辜负你的期望{:10_297:}

以后一定会努力学习,多学知识跟大家分享,带来跟多优秀的帖子!{:10_257:}

Mike_python小 发表于 2023-7-23 21:05:05

我草,这么好的帖子,今天没有评分了,明天必须补上!!!

cjjJasonchen 发表于 2023-7-23 21:06:12

Mike_python小 发表于 2023-7-23 21:05
我草,这么好的帖子,今天没有评分了,明天必须补上!!!

谢谢谢谢

Mike_python小 发表于 2023-7-23 21:07:21

不是,这么牛逼,上一次见到这么好的帖子还是Twilight6 大神的杰作

学习编程中的Ben 发表于 2023-7-23 21:09:37

支持!

cjjJasonchen 发表于 2023-7-23 21:10:18

Mike_python小 发表于 2023-7-23 21:07
不是,这么牛逼,上一次见到这么好的帖子还是Twilight6 大神的杰作

没有没有,我的水平是远远比不上那位大佬的,大牛排行榜第一可是我们全论坛的神{:10_328:}

不过我会努力向他学习的

python爱好者. 发表于 2023-7-23 21:11:43

cjjJasonchen 发表于 2023-7-23 21:04
谢谢支持,我一定不会辜负你的期望

以后一定会努力学习,多学知识跟大家分享,带来跟多优秀 ...

@不二如是 @isdkz @歌者文明清理员 @sfqxx @Twilight6 @liuzhengyuan @人造人 @学习编程中的Ben @小伤口 @tommyyu 支持一下,这是个人才

cjjJasonchen 发表于 2023-7-23 21:15:19

python爱好者. 发表于 2023-7-23 21:11
@不二如是 @isdkz @歌者文明清理员 @sfqxx @Twilight6 @liuzhengyuan @人造人 @学习编程中的Ben @小伤口...

真的是非常感谢你,帮我@{:10_278:}
其实甲鱼老师和不二老师我像跟新完再@的

陈尚涵 发表于 2023-7-23 21:34:25

太厉害了{:10_257:}

sfqxx 发表于 2023-7-23 21:36:10

wc

学习编程中的Ben 发表于 2023-7-23 21:40:23

python爱好者. 发表于 2023-7-23 21:11
@不二如是 @isdkz @歌者文明清理员 @sfqxx @Twilight6 @liuzhengyuan @人造人 @学习编程中的Ben @小伤口...

已经支持

歌者文明清理员 发表于 2023-7-23 21:49:22

python爱好者. 发表于 2023-7-23 21:11
@不二如是 @isdkz @歌者文明清理员 @sfqxx @Twilight6 @liuzhengyuan @人造人 @学习编程中的Ben @小伤口...

是的
@小甲鱼 @青出于蓝 @liuhongrun2022 @一点沙 @陈尚涵

Mike_python小 发表于 2023-7-23 22:15:59

太厉害了

cjjJasonchen 发表于 2023-7-23 22:17:39

Mike_python小 发表于 2023-7-23 22:15
太厉害了

{:10_278:}

Mike_python小 发表于 2023-7-23 22:18:22

太厉害了

Mike_python小 发表于 2023-7-23 22:19:07

太厉害了

cjjJasonchen 发表于 2023-7-23 22:22:50

Mike_python小 发表于 2023-7-23 22:19
太厉害了

谢谢谢谢{:10_297:}
页: [1] 2 3 4
查看完整版本: 【pygame】 教程:模拟车辆运动(持续更新)————7/31