当前位置:   article > 正文

python/pygame 挑战魂斗罗 笔记(二)

python/pygame 挑战魂斗罗 笔记(二)

一、建立地面碰撞体:

现在主角Bill能够站立在游戏地图的地面,是因为我们初始化的时候把Bill的位置固定了self.rect.y = 250。而不是真正的站在地图的地面上。

背景地图是一个完整的地图,没有地面、台阶的概念,就无法通过碰撞检测来实现玩家角色在各台阶地面上的移动跳跃,可以考虑在PS中把地面、台阶给提取出来,让角色可以通过碰撞检测来实现,但这需要重新PS中修改地图,并在代码中加载好多个图片。

这里采用的是在所有地面、台阶的位置画一条线,暂时就叫地面碰撞体吧。然后实现主角Bill和这些地面碰撞体发生碰撞,从而让主角Bill能够站在这个碰撞体上面。

1、先写出地面碰撞体的类:

在ContraMap.py中增加CollideGround类:

  1. class CollideGround(pygame.sprite.Sprite):
  2. def __init__(self, length, x, y):
  3. pygame.sprite.Sprite.__init__(self)
  4. self.image = pygame.Surface((length * Constant.MAP_SCALE, 3))
  5. self.image.fill((255, 0, 0))
  6. self.rect = self.image.get_rect()
  7. self.rect.x = x * Constant.MAP_SCALE - Constant.WIDTH * 2
  8. self.rect.y = y * Constant.MAP_SCALE

这个类很简单,就是一个3像素高的红色矩形。因为地图放大了3倍,同时我们在主角出现,也就是地图走到- Constant.WIDTH * 2的时候再把碰撞体画出来,把这些因素考虑进去,可以使在PS中测量碰撞体长度、坐标位置更方便一点。

2、测量并定义地面碰撞体:

在Config.py中增加一些存放碰撞体的组,测量可以在PS或其它画图软件中进行:

  1. collider = pygame.sprite.Group()
  2. collider83 = pygame.sprite.Group()
  3. collider115 = pygame.sprite.Group()
  4. collider146 = pygame.sprite.Group()
  5. collider163 = pygame.sprite.Group()
  6. collider178 = pygame.sprite.Group()
  7. collider211 = pygame.sprite.Group()
  8. collider231 = pygame.sprite.Group()

这里先测量并定义出前面的部分,其实有collider一个组就行,这里按照y坐标的位置分了很多组只是为了测量、加载的时候更清晰一点,不容易乱,最后统一加入collider组。

  1. Variable.collider115.add(
  2. CollideGround(736, 832, 115)
  3. )
  4. Variable.collider146.add(
  5. CollideGround(97, 959, 146),
  6. CollideGround(66, 1215, 146)
  7. )
  8. Variable.collider163.add(
  9. CollideGround(95, 1440, 163)
  10. )
  11. Variable.collider178.add(
  12. CollideGround(32, 1055, 178),
  13. CollideGround(32, 1151, 178)
  14. )
  15. Variable.collider211.add(
  16. CollideGround(63, 1088, 211),
  17. CollideGround(63, 1407, 211)
  18. )
  19. Variable.collider.add(Variable.collider83, Variable.collider115, Variable.collider146, Variable.collider163,
  20. Variable.collider178, Variable.collider211, Variable.collider231)
3、修改StateMap类的update方法,在Varibale.step==2时,加入all_sprites组进行绘制。
  1. def update(self):
  2. if self.order == 2:
  3. print('纵向地图')
  4. else:
  5. if Variable.step == 0 and self.rect.x >= -Constant.WIDTH:
  6. self.rect.x -= 10
  7. if Variable.step == 1 and self.rect.x > -Constant.WIDTH * 2:
  8. self.rect.x -= 10
  9. if self.rect.x == -Constant.WIDTH * 2:
  10. Variable.step = 2
  11. Variable.all_sprites.add(Variable.collider)

这样就得到了这张带红色地面线的图片。 

二、主角Bill移动+跳跃:

1、给主角Bill定义移动、跳跃、下落三种状态。

初始状态为下落,也就是主角从画面外降落,碰撞到地面碰撞体后把碰撞体的top值赋值给Bill的bottom,实现停止下落。

  1. self.falling = True
  2. self.jumping = False
  3. self.moving = False
2、常量中增加x、y两个方向的移动速度,以及模拟重力。
  1. SPEED_X = 3
  2. SPEED_Y = -10
  3. GRAVITY = 0.5
 3、先定义移动、跳跃、下落、动作图片序列四个方法:

移动需要考虑三种情况:

第一是开始直到人物走到屏幕中间,这部分是主角Bill正常移动;

第二是Bill移动到游戏窗口中间时,Bill需要一直停留在中间,而Bill的向前移动实际是地图向后移动,这里设立了一个x = self.rect.centerx - Constant.WIDTH / 2,也就是Bill移动超过游戏窗口中间的距离,然后让all_sprites中的所有精灵,包括Bill都向后移动这个距离。反过来也一样。

第三是地图移动到边界时就不能继续移动了,这时需要Bill继续移动,并保证不能走出游戏窗口。这里为了控制地图,需要获取地图的rect属性。网上搜了很多一直没找到怎么从all_sprites精灵组中获取指定精灵的方法,搞了好久,后来终于想到给地图单独一个组Varible.map_storage,通过遍历这个组来获取地图以及地图的rect属性。

  1. def move(self):
  2. if self.direction == 'right' and self.rect.right <= Constant.WIDTH - 80 * Constant.MAP_SCALE:
  3. self.rect.x += Constant.SPEED_X
  4. if self.rect.centerx >= Constant.WIDTH / 2:
  5. x = self.rect.centerx - Constant.WIDTH / 2
  6. for j in Variable.map_storage:
  7. if j.rect.right >= Constant.WIDTH:
  8. for i in Variable.all_sprites:
  9. i.rect.x -= x
  10. elif self.direction == 'left' and self.rect.left >= 40 * Constant.MAP_SCALE:
  11. self.rect.x -= Constant.SPEED_X
  12. if self.rect.centerx <= Constant.WIDTH / 2:
  13. x = Constant.WIDTH / 2 - self.rect.centerx
  14. for j in Variable.map_storage:
  15. if j.rect.left <= -Constant.WIDTH * 2:
  16. for i in Variable.all_sprites:
  17. i.rect.x += x

图片序列:就是正常设定时间钟,然后image_order在0-5中间循环。

跳跃:先设定SPEED_Y为-10,GRAVITY为0.5,这样向上移动SPPED_Y的值逐渐减小,直到SPPED_Y==0,调整jumping和falling状态,并将SPEED_Y恢复为-10,完成一次跳跃。

下落:简单的写了一个10倍Constant.GRAVITY 下落。

  1. def order_loop(self):
  2. self.now_time = pygame.time.get_ticks()
  3. if self.now_time - self.last_time >= 100:
  4. self.image_order += 1
  5. if self.image_order > 5:
  6. self.image_order = 0
  7. self.last_time = self.now_time
  8. def jump(self):
  9. Constant.SPEED_Y += Constant.GRAVITY
  10. self.rect.y += Constant.SPEED_Y
  11. if Constant.SPEED_Y == 0:
  12. self.jumping = False
  13. self.falling = True
  14. Constant.SPEED_Y = -10
  15. def fall(self):
  16. self.rect.y += Constant.GRAVITY * 10
4、修改update方法,实现跳下功能:

前后移动以及跳跃都可以根据设定的按键来修改状态以及图片类型。

重点是向下跳跃,向下跳跃实现的思路,是将Bill的状态设定为一直处于下落状态(跳跃时除外),让他与地面碰撞体一直碰撞,并将碰撞体的top值赋值给self.floor变量,然后向下跳的时候(s和j键同时按下),将该碰撞体从collider中删除并用sprite_storage组暂时寄存,实现Bill往下落:

  1. if key_pressed[pygame.K_s] and key_pressed[pygame.K_j]:
  2. self.image_type = 'oblique_down'
  3. Variable.collider.remove(l[0])
  4. Variable.sprite_storage.add(l[0])

等到Bill的rect.top值大于self.floor时,也就是完全超过哪条地面线的位置,再把该碰撞体加载回来,否则就会出现跳下一半就检测到碰撞,被拉回原来地面的情况。加回碰撞体后,就可以实现跳回来的动作。

  1. if self.rect.top >= self.floor:
  2. for i in Variable.sprite_storage:
  3. Variable.collider.add(i)
  4. Variable.sprite_storage.remove(i)

其它的部分,就是按照按键以及按键组合,调整各自的状态参数就可以了。

把按键监测写到碰撞后的for循环里面,是想控制主角Bill必须是与碰撞体接触后才能进行移动及跳跃等操作,否则在刚开始下落的时候就可以移动了,也会出现半空中继续跳跃的情况。 

  1. def update(self):
  2. self.image = self.image_dict[self.image_type][self.image_order]
  3. if self.direction == 'left':
  4. self.image = pygame.transform.flip(self.image, True, False)
  5. if self.rect.top >= self.floor:
  6. for i in Variable.sprite_storage:
  7. Variable.collider.add(i)
  8. Variable.sprite_storage.remove(i)
  9. key_pressed = pygame.key.get_pressed()
  10. if self.falling:
  11. self.fall()
  12. if self.jumping:
  13. self.jump()
  14. if self.moving:
  15. self.move()
  16. self.order_loop()
  17. collider_ground = pygame.sprite.groupcollide(Variable.player_sprites, Variable.collider, False, False)
  18. for p, l in collider_ground.items():
  19. self.floor = l[0].rect.top
  20. p.rect.bottom = self.floor
  21. if key_pressed[pygame.K_d]:
  22. self.direction = 'right'
  23. self.moving = True
  24. if key_pressed[pygame.K_w]:
  25. self.image_type = 'oblique_up'
  26. elif key_pressed[pygame.K_s]:
  27. self.image_type = 'oblique_down'
  28. elif key_pressed[pygame.K_j]:
  29. self.image_type = 'jump'
  30. self.jumping = True
  31. self.falling = False
  32. else:
  33. self.image_type = 'run'
  34. elif key_pressed[pygame.K_a]:
  35. self.direction = 'left'
  36. self.moving = True
  37. if key_pressed[pygame.K_w]:
  38. self.image_type = 'oblique_up'
  39. elif key_pressed[pygame.K_s]:
  40. self.image_type = 'oblique_down'
  41. elif key_pressed[pygame.K_j]:
  42. self.image_type = 'jump'
  43. self.jumping = True
  44. self.falling = False
  45. else:
  46. self.image_type = 'run'
  47. elif key_pressed[pygame.K_w]:
  48. self.image_type = 'up'
  49. self.moving = False
  50. elif key_pressed[pygame.K_s]:
  51. self.image_type = 'down'
  52. self.moving = False
  53. else:
  54. self.image_type = 'stand'
  55. self.moving = False
  56. if key_pressed[pygame.K_s] and key_pressed[pygame.K_j]:
  57. self.image_type = 'oblique_down'
  58. Variable.collider.remove(l[0])
  59. Variable.sprite_storage.add(l[0])

这一部分功能的实现费了挺长时间,前前后后改了很多次才终于完成,虽然总觉得跳的有点别扭,但总算是实现了跳下跳回的功能,就这样吧。

三、现阶段各文件完整代码:

1、Contra.py
  1. # Contra.py
  2. import sys
  3. import pygame
  4. import ContraBill
  5. import ContraMap
  6. from Config import Constant, Variable
  7. def control():
  8. for event in pygame.event.get():
  9. if event.type == pygame.QUIT:
  10. Variable.game_start = False
  11. if event.type == pygame.KEYDOWN:
  12. if event.key == pygame.K_RETURN:
  13. Variable.step = 1
  14. class Main:
  15. def __init__(self):
  16. pygame.init()
  17. self.game_window = pygame.display.set_mode((Constant.WIDTH, Constant.HEIGHT))
  18. self.clock = pygame.time.Clock()
  19. def game_loop(self):
  20. while Variable.game_start:
  21. control()
  22. if Variable.stage == 1:
  23. ContraMap.new_stage()
  24. ContraBill.new_player()
  25. if Variable.stage == 2:
  26. pass
  27. if Variable.stage == 3:
  28. pass
  29. Variable.all_sprites.draw(self.game_window)
  30. Variable.all_sprites.update()
  31. self.clock.tick(Constant.FPS)
  32. pygame.display.set_caption(f'魂斗罗 1.0 {self.clock.get_fps():.2f}')
  33. pygame.display.update()
  34. pygame.quit()
  35. sys.exit()
  36. if __name__ == '__main__':
  37. main = Main()
  38. main.game_loop()
2、ContraMap.py

这里把第一关的全部地面碰撞体都测量并加载出来。

  1. # ContraMap.py
  2. import os
  3. import pygame
  4. from Config import Constant, Variable
  5. class StageMap(pygame.sprite.Sprite):
  6. def __init__(self, order):
  7. pygame.sprite.Sprite.__init__(self)
  8. self.image_list = []
  9. self.order = order
  10. for i in range(1, 9):
  11. image = pygame.image.load(os.path.join('image', 'map', 'stage' + str(i) + '.png'))
  12. rect = image.get_rect()
  13. image = pygame.transform.scale(image, (rect.width * Constant.MAP_SCALE, rect.height * Constant.MAP_SCALE))
  14. self.image_list.append(image)
  15. self.image = self.image_list[self.order]
  16. self.rect = self.image.get_rect()
  17. self.rect.x = 0
  18. self.rect.y = 0
  19. self.speed = 0
  20. def update(self):
  21. if self.order == 2:
  22. print('纵向地图')
  23. else:
  24. if Variable.step == 0 and self.rect.x >= -Constant.WIDTH:
  25. self.rect.x -= 10
  26. if Variable.step == 1 and self.rect.x > -Constant.WIDTH * 2:
  27. self.rect.x -= 10
  28. if self.rect.x == -Constant.WIDTH * 2:
  29. Variable.step = 2
  30. Variable.all_sprites.add(Variable.collider)
  31. def new_stage():
  32. if Variable.map_switch:
  33. stage_map = StageMap(Variable.stage - 1)
  34. Variable.all_sprites.add(stage_map)
  35. Variable.map_storage.add(stage_map)
  36. Variable.map_switch = False
  37. class CollideGround(pygame.sprite.Sprite):
  38. def __init__(self, length, x, y):
  39. pygame.sprite.Sprite.__init__(self)
  40. self.image = pygame.Surface((length * Constant.MAP_SCALE, 3))
  41. self.image.fill((255, 0, 0))
  42. self.rect = self.image.get_rect()
  43. self.rect.x = x * Constant.MAP_SCALE - Constant.WIDTH * 2
  44. self.rect.y = y * Constant.MAP_SCALE
  45. Variable.collider83.add(
  46. CollideGround(511, 2176, 83),
  47. CollideGround(161, 2847, 83),
  48. CollideGround(64, 3296, 83)
  49. )
  50. Variable.collider115.add(
  51. CollideGround(736, 832, 115),
  52. CollideGround(128, 1568, 115),
  53. CollideGround(160, 1695, 115),
  54. CollideGround(128, 1856, 115),
  55. CollideGround(256, 1984, 115),
  56. CollideGround(224, 2656, 115),
  57. CollideGround(65, 3040, 115),
  58. CollideGround(64, 3264, 115),
  59. CollideGround(64, 3392, 115),
  60. CollideGround(128, 3808, 115)
  61. )
  62. Variable.collider146.add(
  63. CollideGround(97, 959, 146),
  64. CollideGround(66, 1215, 146),
  65. CollideGround(225, 2400, 146),
  66. CollideGround(96, 2976, 146),
  67. CollideGround(64, 3136, 146),
  68. CollideGround(160, 3424, 146),
  69. CollideGround(64, 3744, 146),
  70. CollideGround(32, 3936, 146)
  71. )
  72. Variable.collider163.add(
  73. CollideGround(95, 1440, 163),
  74. CollideGround(64, 2304, 163),
  75. CollideGround(32, 2912, 163),
  76. CollideGround(32, 2328, 163),
  77. CollideGround(32, 3328, 163),
  78. CollideGround(97, 3840, 163)
  79. )
  80. Variable.collider178.add(
  81. CollideGround(32, 1055, 178),
  82. CollideGround(32, 1151, 178),
  83. CollideGround(65, 2720, 178),
  84. CollideGround(64, 2816, 178),
  85. CollideGround(96, 3168, 178),
  86. CollideGround(63, 3648, 178),
  87. CollideGround(32, 3969, 178)
  88. )
  89. Variable.collider211.add(
  90. CollideGround(63, 1088, 211),
  91. CollideGround(63, 1407, 211),
  92. CollideGround(97, 2208, 211),
  93. CollideGround(192, 2528, 211),
  94. CollideGround(33, 3135, 211),
  95. CollideGround(33, 3295, 211),
  96. CollideGround(97, 3520, 211),
  97. CollideGround(242, 3807, 211)
  98. )
  99. Variable.collider.add(Variable.collider83, Variable.collider115, Variable.collider146, Variable.collider163,
  100. Variable.collider178, Variable.collider211, Variable.collider231)
3、ContraBill.py
  1. # ContraBill.py
  2. import os
  3. import pygame
  4. from Config import Constant, Variable
  5. class Bill(pygame.sprite.Sprite):
  6. def __init__(self):
  7. pygame.sprite.Sprite.__init__(self)
  8. self.image_dict = {
  9. 'be_hit': [],
  10. 'down': [],
  11. 'jump': [],
  12. 'oblique_down': [],
  13. 'oblique_up': [],
  14. 'run': [],
  15. 'shoot': [],
  16. 'stand': [],
  17. 'up': []
  18. }
  19. for i in range(6):
  20. for key in self.image_dict:
  21. image = pygame.image.load(os.path.join('image', 'bill', str(key), str(key) + str(i + 1) + '.png'))
  22. rect = image.get_rect()
  23. image_scale = pygame.transform.scale(
  24. image, (rect.width * Constant.PLAYER_SCALE, rect.height * Constant.PLAYER_SCALE))
  25. self.image_dict[key].append(image_scale)
  26. self.image_order = 0
  27. self.image_type = 'stand'
  28. self.image = self.image_dict[self.image_type][self.image_order]
  29. self.rect = self.image.get_rect()
  30. self.rect.x = 100
  31. self.rect.y = 100
  32. self.direction = 'right'
  33. self.now_time = 0
  34. self.last_time = 0
  35. self.falling = True
  36. self.jumping = False
  37. self.moving = False
  38. self.floor = 0
  39. def update(self):
  40. self.image = self.image_dict[self.image_type][self.image_order]
  41. if self.direction == 'left':
  42. self.image = pygame.transform.flip(self.image, True, False)
  43. if self.rect.top >= self.floor:
  44. for i in Variable.sprite_storage:
  45. Variable.collider.add(i)
  46. Variable.sprite_storage.remove(i)
  47. key_pressed = pygame.key.get_pressed()
  48. if self.falling:
  49. self.fall()
  50. if self.jumping:
  51. self.jump()
  52. if self.moving:
  53. self.move()
  54. self.order_loop()
  55. collider_ground = pygame.sprite.groupcollide(Variable.player_sprites, Variable.collider, False, False)
  56. for p, l in collider_ground.items():
  57. self.floor = l[0].rect.top
  58. p.rect.bottom = self.floor
  59. if key_pressed[pygame.K_d]:
  60. self.direction = 'right'
  61. self.moving = True
  62. if key_pressed[pygame.K_w]:
  63. self.image_type = 'oblique_up'
  64. elif key_pressed[pygame.K_s]:
  65. self.image_type = 'oblique_down'
  66. elif key_pressed[pygame.K_j]:
  67. self.image_type = 'jump'
  68. self.jumping = True
  69. self.falling = False
  70. else:
  71. self.image_type = 'run'
  72. elif key_pressed[pygame.K_a]:
  73. self.direction = 'left'
  74. self.moving = True
  75. if key_pressed[pygame.K_w]:
  76. self.image_type = 'oblique_up'
  77. elif key_pressed[pygame.K_s]:
  78. self.image_type = 'oblique_down'
  79. elif key_pressed[pygame.K_j]:
  80. self.image_type = 'jump'
  81. self.jumping = True
  82. self.falling = False
  83. else:
  84. self.image_type = 'run'
  85. elif key_pressed[pygame.K_w]:
  86. self.image_type = 'up'
  87. self.moving = False
  88. elif key_pressed[pygame.K_s]:
  89. self.image_type = 'down'
  90. self.moving = False
  91. else:
  92. self.image_type = 'stand'
  93. self.moving = False
  94. if key_pressed[pygame.K_s] and key_pressed[pygame.K_j]:
  95. self.image_type = 'oblique_down'
  96. Variable.collider.remove(l[0])
  97. Variable.sprite_storage.add(l[0])
  98. def order_loop(self):
  99. self.now_time = pygame.time.get_ticks()
  100. if self.now_time - self.last_time >= 100:
  101. self.image_order += 1
  102. if self.image_order > 5:
  103. self.image_order = 0
  104. self.last_time = self.now_time
  105. def move(self):
  106. if self.direction == 'right' and self.rect.right <= Constant.WIDTH - 80 * Constant.MAP_SCALE:
  107. self.rect.x += Constant.SPEED_X
  108. if self.rect.centerx >= Constant.WIDTH / 2:
  109. x = self.rect.centerx - Constant.WIDTH / 2
  110. for j in Variable.map_storage:
  111. if j.rect.right >= Constant.WIDTH:
  112. for i in Variable.all_sprites:
  113. i.rect.x -= x
  114. elif self.direction == 'left' and self.rect.left >= 40 * Constant.MAP_SCALE:
  115. self.rect.x -= Constant.SPEED_X
  116. if self.rect.centerx <= Constant.WIDTH / 2:
  117. x = Constant.WIDTH / 2 - self.rect.centerx
  118. for j in Variable.map_storage:
  119. if j.rect.left <= -Constant.WIDTH * 2:
  120. for i in Variable.all_sprites:
  121. i.rect.x += x
  122. def jump(self):
  123. Constant.SPEED_Y += Constant.GRAVITY
  124. self.rect.y += Constant.SPEED_Y
  125. if Constant.SPEED_Y == 0:
  126. self.jumping = False
  127. self.falling = True
  128. Constant.SPEED_Y = -10
  129. def fall(self):
  130. self.rect.y += Constant.GRAVITY * 10
  131. def new_player():
  132. if Variable.step == 2 and Variable.player_switch:
  133. bill = Bill()
  134. Variable.all_sprites.add(bill)
  135. Variable.player_sprites.add(bill)
  136. Variable.player_switch = False
4、Config.py
  1. # Config.py
  2. import pygame
  3. class Constant:
  4. WIDTH = 1200
  5. HEIGHT = 720
  6. FPS = 60
  7. MAP_SCALE = 3
  8. PLAYER_SCALE = 2.5
  9. SPEED_X = 3
  10. SPEED_Y = -10
  11. GRAVITY = 0.5
  12. class Variable:
  13. all_sprites = pygame.sprite.Group()
  14. player_sprites = pygame.sprite.Group()
  15. collider = pygame.sprite.Group()
  16. collider83 = pygame.sprite.Group()
  17. collider115 = pygame.sprite.Group()
  18. collider146 = pygame.sprite.Group()
  19. collider163 = pygame.sprite.Group()
  20. collider178 = pygame.sprite.Group()
  21. collider211 = pygame.sprite.Group()
  22. collider231 = pygame.sprite.Group()
  23. sprite_storage = pygame.sprite.Group()
  24. map_storage = pygame.sprite.Group()
  25. game_start = True
  26. map_switch = True
  27. player_switch = True
  28. stage = 1
  29. step = 0

以上代码完成,我们的主角Bill终于可以连跑带跳游览整个地图,并终于站在了第一关的关头。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/447919
推荐阅读
相关标签
  

闽ICP备14008679号