#python-3.x #math #tkinter #random #pygame
#python #pygame
Вопрос:
Я хочу реализовать игру со змеями. Змея извивается по игровой площадке. Каждый раз, когда змея ест пищу, длина змеи увеличивается на один элемент. Элементы тела змеи следуют за его головой, как цепочка.
snake_x, snake_y = WIDTH//2, HEIGHT//2
body = []
move_x, move_y = (1, 0)
food_x, food_y = new_food(body)
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT: move_x, move_y = (-1, 0)
elif event.key == pygame.K_RIGHT: move_x, move_y = (1, 0)
elif event.key == pygame.K_UP: move_x, move_y = (0, -1)
elif event.key == pygame.K_DOWN: move_x, move_y = (0, 1)
snake_x = (snake_x move_x) % WIDTH
snake_y = (snake_y move_y) % HEIGHT
if snake_x == food_x and snake_y == food_y:
food_x, food_y = new_food(body)
body.append((snake_x, snake_x))
# [...]
Как мне добиться, чтобы части тела следовали за головой змеи на ее пути, когда голова змеи движется вперед?
Ответ №1:
В общем, вы должны различать 2 разных типа змеи. В первом случае змея движется в сетке, и каждый раз, когда змея движется, она продвигается вперед на одно поле в сетке. В другом типе позиция змеи не находится в растре и не привязана к полям сетки, позиция свободна, и змея плавно скользит по полям.
В первом случае каждый элемент тела привязан к полям сетки, как и голова. Другой — это еще один трюк, потому что положение элемента body зависит от размера элемента и динамических предыдущих положений головы змеи.
Сначала змея, которая привязана к сетке.
Элементы змеи могут храниться в списке кортежей. Каждый кортеж содержит столбец и строку элемента snakes в сетке. Изменения элементов в списке непосредственно следуют за движением змеи. Если змея движется, новая позиция добавляется в начало списка, а хвост списка удаляется.
Например, у нас есть змея со следующими элементами:
body = [(3, 3), (3, 4), (4, 4), (5, 4), (6, 4)]
Когда голова змеи перемещается из формы (3, 3)
в ( 3, 2
), новая позиция головы добавляется в начало списка ( body.insert(0, (3, 2)
):
body = [(3, 2), (3, 3), (3, 4), (4, 4), (5, 4), (6, 4)]
Наконец, хвост ist удаляется ( del body[-1]
):
body = [(3, 2), (3, 3), (3, 4), (4, 4), (5, 4)]
Минимальный пример: repl.it/@Rabbid76/PyGame-SnakeMoveInGrid
import pygame
import random
pygame.init()
COLUMNS, ROWS, SIZE = 10, 10, 20
screen = pygame.display.set_mode((COLUMNS*SIZE, ROWS*SIZE))
clock = pygame.time.Clock()
background = pygame.Surface((COLUMNS*SIZE, ROWS*SIZE))
background.fill((255, 255, 255))
for i in range(1, COLUMNS):
pygame.draw.line(background, (128, 128, 128), (i*SIZE-1, 0), (i*SIZE-1, ROWS*SIZE), 2)
for i in range(1, ROWS):
pygame.draw.line(background, (128, 128, 128), (0, i*SIZE-1), (COLUMNS*SIZE, i*SIZE-1), 2)
def random_pos(body):
while True:
pos = random.randrange(COLUMNS), random.randrange(ROWS)
if pos not in body:
break
return pos
length = 1
body = [(COLUMNS//2, ROWS//2)]
dir = (1, 0)
food = random_pos(body)
run = True
while run:
clock.tick(5)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT: dir = (-1, 0)
elif event.key == pygame.K_RIGHT: dir = (1, 0)
elif event.key == pygame.K_UP: dir = (0, -1)
elif event.key == pygame.K_DOWN: dir = (0, 1)
body.insert(0, body[0][:])
body[0] = (body[0][0] dir[0]) % COLUMNS, (body[0][1] dir[1]) % ROWS
if body[0] == food:
food = random_pos(body)
length = 1
while len(body) > length:
del body[-1]
screen.blit(background, (0, 0))
pygame.draw.rect(screen, (255, 0, 255), (food[0]*SIZE, food[1]*SIZE, SIZE, SIZE))
for i, pos in enumerate(body):
color = (255, 0, 0) if i==0 else (0, 192, 0) if (i%2)==0 else (255, 128, 0)
pygame.draw.rect(screen, color, (pos[0]*SIZE, pos[1]*SIZE, SIZE, SIZE))
pygame.display.flip()
Теперь змея с полностью свободным позиционированием.
Мы должны отслеживать все позиции, которые посетила голова змеи, в списке. Мы должны разместить элементы тела змеи на позициях в списке, как жемчужины цепочки.
Ключ в том, чтобы вычислить евклидово расстояние между последним элементом тела в цепочке и следующими позициями на дорожке. Когда найдена новая точка с достаточно большим расстоянием, в цепочку (тело) добавляется новая жемчужина (элемент).
dx, dy = body[-1][0]-pos[0], body[-1][1]-pos[1]
if math.sqrt(dx*dx dy*dy) >= distance:
body.append(pos)
Следующая функция имеет 3 аргумента. track
это список позиций головы. no_pearls
тогда это количество элементов тела встряхивания и distance
евклидово расстояние между элементами. Функция создает и возвращает список положений тела змеи.
def create_body(track, no_pearls, distance):
body = [(track[0])]
track_i = 1
for i in range(1, no_pearls):
while track_i < len(track):
pos = track[track_i]
track_i = 1
dx, dy = body[-1][0]-pos[0], body[-1][1]-pos[1]
if math.sqrt(dx*dx dy*dy) >= distance:
body.append(pos)
break
while len(body) < no_pearls:
body.append(track[-1])
del track[track_i:]
return body
Минимальный пример: repl.it/@Rabbid76/PyGame-SnakeMoveFree
import pygame
import random
import math
pygame.init()
COLUMNS, ROWS, SIZE = 10, 10, 20
WIDTH, HEIGHT = COLUMNS*SIZE, ROWS*SIZE
screen = pygame.display.set_mode((WIDTH, HEIGHT))
clock = pygame.time.Clock()
background = pygame.Surface((WIDTH, HEIGHT))
background.fill((255, 255, 255))
for i in range(1, COLUMNS):
pygame.draw.line(background, (128, 128, 128), (i*SIZE-1, 0), (i*SIZE-1, ROWS*SIZE), 2)
for i in range(1, ROWS):
pygame.draw.line(background, (128, 128, 128), (0, i*SIZE-1), (COLUMNS*SIZE, i*SIZE-1), 2)
def hit(pos_a, pos_b, distance):
dx, dy = pos_a[0]-pos_b[0], pos_a[1]-pos_b[1]
return math.sqrt(dx*dx dy*dy) < distance
def random_pos(body):
pos = None
while True:
pos = random.randint(SIZE//2, WIDTH-SIZE//2), random.randint(SIZE//2, HEIGHT-SIZE//2)
if not any([hit(pos, bpos, 20) for bpos in body]):
break
return pos
def create_body(track, no_pearls, distance):
body = [(track[0])]
track_i = 1
for i in range(1, no_pearls):
while track_i < len(track):
pos = track[track_i]
track_i = 1
dx, dy = body[-1][0]-pos[0], body[-1][1]-pos[1]
if math.sqrt(dx*dx dy*dy) >= distance:
body.append(pos)
break
while len(body) < no_pearls:
body.append(track[-1])
del track[track_i:]
return body
length = 1
track = [(WIDTH//2, HEIGHT//2)]
dir = (1, 0)
food = random_pos(track)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT: dir = (-1, 0)
elif event.key == pygame.K_RIGHT: dir = (1, 0)
elif event.key == pygame.K_UP: dir = (0, -1)
elif event.key == pygame.K_DOWN: dir = (0, 1)
track.insert(0, track[0][:])
track[0] = (track[0][0] dir[0]) % WIDTH, (track[0][1] dir[1]) % HEIGHT
body = create_body(track, length, 20)
if hit(body[0], food, 20):
food = random_pos(body)
length = 1
screen.blit(background, (0, 0))
pygame.draw.circle(screen, (255, 0, 255), food, SIZE//2)
for i, pos in enumerate(body):
color = (255, 0, 0) if i==0 else (0, 192, 0) if (i%2)==0 else (255, 128, 0)
pygame.draw.circle(screen, color, pos, SIZE//2)
pygame.display.flip()
Комментарии:
1. Отличный ответ! Этот 2-й пример придает змее жутковатое движение, которое отражает то, как я воспринимаю их в реальной жизни.