How do I chain the movement of a snake's body?
(1个答案)
上个月关闭。
我是一个相当新手的程序员,这是我第一次开发游戏,我想从一个非常简单的东西开始,所以我选择了蛇游戏。除了吃东西时增加身体部位,我已经编码了所有东西。
import random
import pygame
from pygame import *
import sys
import os
import time
###objects
class snake:
def __init__(self, win):
self.score = 1
self.length = 25
self.width = 25
self.win = win
self.r = random.randint(0,500)
self.vel = 25
self.update = pygame.display.update()
self.right = True
self.left = False
self.up = False
self.down = False
# 0 = right 1 = left 2 = up 3 = down
self.can = [True, False, True, True]
self.keys = pygame.key.get_pressed()
while True:
if self.r % 25 == 0:
break
else:
self.r = random.randint(0,500)
continue
self.x = self.r
self.y = self.r
self.r = random.randint(0,500)
while True:
if self.r % 25 == 0:
break
else:
self.r = random.randint(0,500)
continue
self.a = self.r
self.b = self.r
def move(self, win):
win.fill((0,0,0))
self.keys = pygame.key.get_pressed()
if self.right == True:
self.x += self.vel
if self.left == True:
self.x -= self.vel
if self.up == True:
self.y -= self.vel
if self.down == True:
self.y += self.vel
if self.x > 475:
self.x = 0
if self.x < 0:
self.x = 500
if self.y > 475:
self.y = 0
if self.y < 0:
self.y = 500
if self.keys[pygame.K_RIGHT] and self.can[0] == True:
self.right = True
self.left= False
self.up = False
self.down = False
self.can[1] = False
self.can[0] = True
self.can[2] = True
self.can[3] = True
if self.keys[pygame.K_LEFT] and self.can[1] == True:
self.right = False
self.left = True
self.up = False
self.down = False
self.can[0] = False
self.can[1] = True
self.can[2] = True
self.can[3] = True
if self.keys[pygame.K_UP] and self.can[2] == True:
self.right = False
self.left = False
self.up = True
self.down = False
self.can[3] = False
self.can[0] = True
self.can[1] = True
self.can[2] = True
if self.keys[pygame.K_DOWN] and self.can[3] == True:
self.right = False
self.left = False
self.up = False
self.down = True
self.can[2] = False
self.can[0] = True
self.can[1] = True
self.can[3] = True
self.length = 25 * self.score
self.snake = pygame.draw.rect(self.win, (0,255,0), (self.x, self.y, self.length, self.width))
def food(self, win):
pygame.draw.rect(self.win, (255,0,0), (self.a, self.b,25,25))
if self.a == self.x and self.b == self.y:
self.r = random.randint(0,500)
while True:
if self.r % 25 == 0:
break
else:
self.r = random.randint(0,500)
continue
self.a = self.r
self.b = self.r
self.score += 1
###functions
###main game
##variables
screen = (500,500)
W = 25
L = 25
WHITE = 255,255,255
clock = pygame.time.Clock()
##game
pygame.init()
win = pygame.display.set_mode(screen)
title = pygame.display.set_caption("snake game")
update = pygame.display.update()
snake = snake(win)
run = True
while run:
clock.tick(10)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
snake.move(win)
snake.food(win)
pygame.display.update()
pygame.quit()
我知道代码有点混乱,因为我从未尝试使用OOP,所以想尝试实现OOP。
这也是我第一次使用pygame,所以我做错了事。
到目前为止,我已经做到了,这样蛇和食物就可以在一个不可见的网格中的任意位置生成,并且当蛇的头部具有与食物相同的坐标时,蛇会变得更长(我只是将25个像素添加到蛇的身体,但是当它转弯时,整个矩形蛇都会转弯)。另外,如果蛇到达显示屏的边缘,则从相反的一侧出现。
下面的评论听起来可能很刺耳,我试图以中立的方式写出来,只是指出事实并陈述事实。如果您真的是一个新程序员,那么这是一个值得学习的好项目,并且您可以做得很好。因此,请记住,这些注释并不是故意的,而是客观的,并且总是附带提出的解决方案,以使您成为更好的程序员,而不是使您ash恼。
我也不会在整个list
正文中详细介绍它,其他人已经介绍了它,但是我还将在此代码中使用它。
结果就是这样,打击是代码和一堆指针和技巧。
永远不要重复使用变量
首先,不要重复使用变量名,因为您已经覆盖了snake = snake()
并很幸运地替换了整个snake
类,因此再也无法重用了,从而破坏了OOP和类的全部目的。但是由于您只使用了一次,所以这次意外地解决了。只要为将来的项目牢记这一点。
单字母变量
其次,除非您真的知道自己在做什么,而且经常与数学方程式或其他内容联系在一起,否则我将强烈避免使用单字母变量。我对self.a
和self.b
的整个概念都非常敏感,因为它们没有说出任何有意义的内容,并且在某些迭代中,您可能也不知道它们的作用。这在您快速行动并且目前掌握代码的情况下很普遍-但迟早会给您带来麻烦(会/应该给您学校不好的成绩,或者不会让您梦dream以求的工作正在申请)。
切勿在一个功能中混用逻辑
您还将食物捆绑到播放器对象中,这是一个很大的禁忌。以及运动逻辑中的渲染逻辑。因此,我提出了一种以更大的OOP形式进行的返工,其中食物和玩家是两个单独的实体,并且每个逻辑操作(渲染,移动,进食等)都具有一个功能。
因此,我将其重组为以下逻辑:
在此过程中,我还对运动机制进行了一些重新设计,以使用更少的线条和逻辑来生成相同的东西。我也删除了所有这些逻辑:
self.r = random.randint(0,500)
while True:
if self.r % 25 == 0:
break
else:
self.r = random.randint(0,500)
continue
并用它代替它,它做的完全相同,但是使用内置函数来产生它。希望函数/变量比恶意的
while
循环更具描述性。
self.r = random.choice(range(0, 500, 25))
最终结果将如下所示:
import random
import pygame
from pygame import *
import sys
import os
import time
# Constants (Used for bitwise operations - https://www.tutorialspoint.com/python/bitwise_operators_example.htm)
UP = 0b0001
DOWN = 0b0010
LEFT = 0b0100
RIGHT = 0b1000
###objects
class Food:
def __init__(self, window, x=None, y=None):
self.window = window
self.width = 25
self.height = 25
self.x, self.y = x, y
if not x or not y: self.new_position()
def draw(self):
pygame.draw.rect(self.window, (255,0,0), (self.x, self.y, 25, 25))
def new_position(self):
self.x, self.y = random.choice(range(0, 500, 25)), random.choice(range(0, 500, 25))
class Snake:
def __init__(self, window):
self.width = 25
self.width = 25
self.height = 25
self.window = window
self.vel = 25
self.update = pygame.display.update()
start_position = random.choice(range(0, 500, 25)), random.choice(range(0, 500, 25))
self.body = [start_position]
self.direction = RIGHT
def move(self, window):
self.keys = pygame.key.get_pressed()
# since key-presses are always 1 or 0, we can multiply each key with their respective value from the
# static map above, LEFT = 4 in binary, so if we multiply 4*1|0 we'll get binary 0100 if it's pressed.
# We can always safely combine 1, 2, 4 and 8 as they will never collide and thus always create a truth map of
# which direction in bitwise friendly representation.
if any((self.keys[pygame.K_UP], self.keys[pygame.K_DOWN], self.keys[pygame.K_LEFT], self.keys[pygame.K_RIGHT])):
self.direction = self.keys[pygame.K_UP]*1 + self.keys[pygame.K_DOWN]*2 + self.keys[pygame.K_LEFT]*4 + self.keys[pygame.K_RIGHT]*8
x, y = self.body[0] # Get the head position, which is always the first in the "history" aka body.
self.body.pop() # Remove the last object from history
# Use modolus to "loop around" when you hit 500 (or the max width/height desired)
# as it will wrap around to 0, try for instance 502 % 500 and it should return "2".
if self.direction & UP:
y = (y - self.vel)%500
elif self.direction & DOWN:
y = (y + self.vel)%500
elif self.direction & LEFT:
x = (x - self.vel)%500
elif self.direction & RIGHT:
x = (x + self.vel)%500 # window.width
self.body.insert(0, (x, y))
def eat(self, food):
x, y = self.body[0] # The head
if x >= food.x and x+self.width <= food.x+food.width:
if y >= food.y and y+self.height <= food.y+food.height:
self.body.append(self.body[-1])
return True
return False
def draw(self):
for x, y in self.body:
pygame.draw.rect(self.window, (0,255,0), (x, y, self.width, self.width))
##variables
clock = pygame.time.Clock()
##game
pygame.init()
window = pygame.display.set_mode((500,500))
pygame.display.set_caption("snake game")
snake = Snake(window)
food = Food(window)
food.new_position()
score = 0
run = True
while run:
clock.tick(10)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.fill((0,0,0)) # Move the render logic OUTSIDE of the player object
snake.move(window)
if snake.eat(food):
score += 1
food.new_position()
snake.draw()
food.draw()
pygame.display.update()
pygame.quit()
现在,
draw()
处理对象本身内的所有渲染逻辑,而不是陷入
move()
中。
snake.eat()
现在是根据食物对象内部的蛇头(历史上的第一个位置,又名
True
)返回
False
或
body
的函数。如果成功执行了此功能,那么此函数也会增加内容,也许此代码也应该移到外部,但这只是一行代码,因此我略过了自己的规则以保持代码简单。
food.new_position()
是将食物移动到新位置的功能,例如,当
eat()
成功执行时,或者如果您要以给定的间隔随机移动食物,则调用该功能。
move()
最后是move函数,该函数现在只有一个用途,那就是将蛇移动到某个方向。为此,首先获取当前的头部位置,然后删除最后的历史记录项(尾部随头部移动),然后在身体的前面添加一个等于速度的新位置。
“在内部”逻辑可能看起来像粥,但是它很简单,逻辑是这样的:
如果蛇头
body[0]
的
x
大于或等于食物
x
,则表示蛇头的左下角至少超过或等于食物的左上角。如果头的宽度(x + width)小于或等于食物的宽度,那么我们至少在X轴上。然后我们只重复Y轴,这将告诉您头部是在食物边界之内还是之外。
重新设计了运动逻辑,以使其速度更快,但代码更少,并且一旦您了解了它的工作原理,就希望更容易使用。我切换到了
bitwise operations。基本概念是您可以在“机器级别”(位)上执行快速操作,例如使用
AND
操作确定是否为真。为此,您可以与位序列进行比较,看看在任何时候两个
1
是否相互重叠,如果不重叠,则为
False
。以下是使用的逻辑以及二进制表示形式的
UP
,
DOWN
,
LEFT
和
RIGHT
的所有可能组合的概述:
在某种程度上,
1
就是
0001
,
2
是
0010
,而
4
是
0100
,最后是
8
是
1000
。知道这一点,如果我们按→(向右),我们希望将其转换为静态变量
RIGHT
(二进制的
1000
)的位表示形式。为了实现这一目标,我们只需将pygame在按下键时提供的值乘以
1
即可。我们将其乘以
1000
的十进制版本(右),即
8
。
因此,如果按→,我们将执行
8*1
。这给了我们
1000
。我们只需对所有密钥重复此过程。如果按↑+→,将导致
1001
,因为未按下
8*1
+
1*1
,并且由于未按下←和↓,因此它们将变为
4*0
和
2*0
,从而在二进制位置处产生两个零。
然后,我们可以通过执行上图所示的
AND
运算符来使用这些二进制表示形式来确定是否按下了某个方向,因为
DOWN
仅在<< cc>位置,在这种情况下是从右数第二个数字。其他任何二进制位置编号将导致
True
比较器中的
1
。
这是非常有效的,一旦掌握了它,它对其他事情也非常有用。因此,现在是在一个有希望的受控环境中学习它的好时机。
这里要摘录的主要内容(除了其他人已经指出的以外,将尾巴保留在数组/列表中作为一种位置历史)是游戏对象应该是单个对象,而主要渲染逻辑不应该在播放器对象中,仅播放器渲染细节应在播放器对象中(例如)。
而且,诸如
DOWN
之类的动作应该是一件事情,而不是在处理
False
,
AND
和其他事物的函数中进行检查。
我的建议只是建议。我不是行业游戏开发商,而是尽我所能优化事情。希望这些概念可以使用或激发一个或两个想法。祝你好运。
我是一名优秀的程序员,十分优秀!