Как написать марио на питоне
Перейти к содержимому

Как написать марио на питоне

  • автор:

Python-сообщество

[RSS Feed]

  • Начало
  • » Python для новичков
  • » Mario на Python

#1 Июнь 19, 2018 01:38:32

KrisKreiss Зарегистрирован: 2018-06-19 Сообщения: 1 Репутация: 0 Профиль Отправить e-mail

Mario на Python

Здравствуйте! Подскажите, пожалуйста, как написать Mario на Python.

Пишем платформер на Python, используя pygame

image

Сразу оговорюсь, что здесь написано для самых маленькихначинающих.

Давно хотел попробовать себя в качестве игродела, и недавно выпал случай изучить Python и исполнить давнюю мечту.

Что такое платформер?

Платформер(platformer)— жанр компьютерных игр, в которых основной чертой игрового процесса является прыгание по платформам, лазанье по лестницам, собирание предметов, обычно необходимых для завершения уровня.
Вики

Одними из моих любимых игр данного жанра являются «Super Mario Brothers» и «Super Meat Boy». Давайте попробуем создать нечто среднее между ними.

Самое — самое начало.

Внимание! Используем python ветки 2.х, с 3.х обнаружены проблемы запуска нижеописанных скриптов!

Наверное, не только игры, да и все приложения, использующие pygame начинаются примерно так:

#!/usr/bin/env python # -*- coding: utf-8 -*- # Импортируем библиотеку pygame import pygame from pygame import * #Объявляем переменные WIN_WIDTH = 800 #Ширина создаваемого окна WIN_HEIGHT = 640 # Высота DISPLAY = (WIN_WIDTH, WIN_HEIGHT) # Группируем ширину и высоту в одну переменную BACKGROUND_COLOR = "#004400" def main(): pygame.init() # Инициация PyGame, обязательная строчка screen = pygame.display.set_mode(DISPLAY) # Создаем окошко pygame.display.set_caption("Super Mario Boy") # Пишем в шапку bg = Surface((WIN_WIDTH,WIN_HEIGHT)) # Создание видимой поверхности # будем использовать как фон bg.fill(Color(BACKGROUND_COLOR)) # Заливаем поверхность сплошным цветом while 1: # Основной цикл программы for e in pygame.event.get(): # Обрабатываем события if e.type == QUIT: raise SystemExit, "QUIT" screen.blit(bg, (0,0)) # Каждую итерацию необходимо всё перерисовывать pygame.display.update() # обновление и вывод всех изменений на экран if __name__ == "__main__": main() 

Игра будет «крутиться» в цикле ( while 1), каждую итерацию необходимо перерисовывать всё (фон, платформы, монстров, цифровые сообщения и т.д). Важно заметить, что рисование идет последовательно, т.е. если сперва нарисовать героя, а потом залить фон, то героя видно не будет, учтите это на будущее.

Запустив этот код, мы увидим окно, залитое зелененьким цветом.

(Картинка кликабельна)

Ну что же, начало положено, идём дальше.

Уровень.

А как без него? Под словом «уровень» будем подразумевать ограниченную область виртуального двумерного пространства, заполненную всякой — всячиной, и по которой будет передвигаться наш персонаж.

Для построения уровня создадим двумерный массив m на n. Каждая ячейка (m,n) будет представлять из себя прямоугольник. Прямоугольник может в себе что-то содержать, а может и быть пустым. Мы в прямоугольниках будем рисовать платформы.

Добавим еще константы

PLATFORM_WIDTH = 32 PLATFORM_HEIGHT = 32 PLATFORM_COLOR = "#FF6262" 

Затем добавим объявление уровня в функцию main

level = [ "-------------------------", "- -", "- -", "- -", "- -- -", "- -", "-- -", "- -", "- --- -", "- -", "- -", "- --- -", "- -", "- ----------- -", "- -", "- - -", "- -- -", "- -", "- -", "-------------------------"] 

И в основной цикл добавим следующее:

 x=y=0 # координаты for row in level: # вся строка for col in row: # каждый символ if col == "-": #создаем блок, заливаем его цветом и рисеум его pf = Surface((PLATFORM_WIDTH,PLATFORM_HEIGHT)) pf.fill(Color(PLATFORM_COLOR)) screen.blit(pf,(x,y)) x += PLATFORM_WIDTH #блоки платформы ставятся на ширине блоков y += PLATFORM_HEIGHT #то же самое и с высотой x = 0 #на каждой новой строчке начинаем с нуля 

Т.е. Мы перебираем двумерный массив level, и, если находим символ «-», то по координатам (x * PLATFORM_WIDTH, y * PLATFORM_HEIGHT), где x,y — индекс в массиве level

Запустив, мы увидим следующее:

Персонаж

Просто кубики на фоне — это очень скучно. Нам нужен наш персонаж, который будет бегать и прыгать по платформам.

Создаём класс нашего героя.

Для удобства, будем держать нашего персонажа в отдельном файле player.py

#!/usr/bin/env python # -*- coding: utf-8 -*- from pygame import * MOVE_SPEED = 7 WIDTH = 22 HEIGHT = 32 COLOR = "#888888" class Player(sprite.Sprite): def __init__(self, x, y): sprite.Sprite.__init__(self) self.xvel = 0 #скорость перемещения. 0 - стоять на месте self.startX = x # Начальная позиция Х, пригодится когда будем переигрывать уровень self.startY = y self.image = Surface((WIDTH,HEIGHT)) self.image.fill(Color(COLOR)) self.rect = Rect(x, y, WIDTH, HEIGHT) # прямоугольный объект def update(self, left, right): if left: self.xvel = -MOVE_SPEED # Лево = x- n if right: self.xvel = MOVE_SPEED # Право = x + n if not(left or right): # стоим, когда нет указаний идти self.xvel = 0 self.rect.x += self.xvel # переносим свои положение на xvel def draw(self, screen): # Выводим себя на экран screen.blit(self.image, (self.rect.x,self.rect.y)) 

Что тут интересного?
Начнём с того, что мы создаём новый класс, наследуясь от класса pygame.sprite.Sprite, тем самым наследую все характеристики спрайта.
Cпрайт — это движущееся растровое изображение. Имеет ряд полезных методов и свойств.

self.rect = Rect(x, y, WIDTH, HEIGHT), в этой строчке мы создаем фактические границы нашего персонажа, прямоугольник, по которому мы будем не только перемещать героя, но и проверять его на столкновения. Но об этом чуть ниже.

Метод update(self, left, right)) используется для описания поведения объекта. Переопределяет родительский update(*args) → None. Может вызываться в группах спрайтов.

Метод draw(self, screen) используется для вывода персонажа на экран. Далее мы уберем этот метод и будем использовать более интересный способ отображения героя.

Добавим нашего героя в основную часть программы.

Перед определением уровня добавим определение героя и переменные его перемещения.

hero = Player(55,55) # создаем героя по (x,y) координатам left = right = False # по умолчанию — стоим 

В проверку событий добавим следующее:

if e.type == KEYDOWN and e.key == K_LEFT: left = True if e.type == KEYDOWN and e.key == K_RIGHT: right = True if e.type == KEYUP and e.key == K_RIGHT: right = False if e.type == KEYUP and e.key == K_LEFT: left = False 

Т.е. Если нажали на клавишу «лево», то идём влево. Если отпустили — останавливаемся. Так же с кнопкой «право»

Само передвижение вызывается так: (добавляем после перерисовки фона и платформ)

hero.update(left, right) # передвижение hero.draw(screen) # отображение 

Но, как мы видим, наш серый блок слишком быстро перемещается, добавим ограничение в количестве кадров в секунду. Для этого после определения уровня добавим таймер

timer = pygame.time.Clock() 

И в начало основного цикла добавим следующее:

timer.tick(60) 
Завис в воздухе

Да, наш герой в безвыходном положении, он завис в воздухе.
Добавим гравитации и возможности прыгать.

И так, работаем в файле player.py

Добавим еще констант

JUMP_POWER = 10 GRAVITY = 0.35 # Сила, которая будет тянуть нас вниз 

В метод _init_ добавляем строки:

 self.yvel = 0 # скорость вертикального перемещения self.onGround = False # На земле ли я? 

Добавляем входной аргумент в метод update
def update(self, left, right, up):
И в начало метода добавляем:

if up: if self.onGround: # прыгаем, только когда можем оттолкнуться от земли self.yvel = -JUMP_POWER 

И перед строчкой self.rect.x += self.xvel
Добавляем

if not self.onGround: self.yvel += GRAVITY self.onGround = False; # Мы не знаем, когда мы на земле(( self.rect.y += self.yvel 

И добавим в основную часть программы:
После строчки left = right = False
Добавим переменную up

up = false 

В проверку событий добавим

if e.type == KEYDOWN and e.key == K_UP: up = True if e.type == KEYUP and e.key == K_UP: up = False 

И изменим вызов метода update, добавив новый аргумент up:
hero.update(left, right)
на

hero.update(left, right, up) 

Здесь мы создали силу гравитации, которая будет тянуть нас вниз, постоянно наращивая скорость, если мы не стоим на земле, и прыгать в полете мы не умеем. А мы пока не можем твердо встать на что-то, поэтому на следующей анимации наш герой падает далеко за границы видимости.

Встань обеими ногами на землю свою.

Как узнать, что мы на земле или другой твердой поверхности? Ответ очевиден — использовать проверку на пересечение, но для этого изменим создание платформ.

Создадим еще один файл blocks.py, и перенесем в него описание платформы.

PLATFORM_WIDTH = 32 PLATFORM_HEIGHT = 32 PLATFORM_COLOR = "#FF6262" 

Дальше создадим класс, наследуясь от pygame.sprite.Sprite

class Platform(sprite.Sprite): def __init__(self, x, y): sprite.Sprite.__init__(self) self.image = Surface((PLATFORM_WIDTH, PLATFORM_HEIGHT)) self.image.fill(Color(PLATFORM_COLOR)) self.rect = Rect(x, y, PLATFORM_WIDTH, PLATFORM_HEIGHT) 

Тут нет ни чего нам уже не знакомого, идём дальше.

В основной файле произведем изменения, перед описанием массива level добавим

entities = pygame.sprite.Group() # Все объекты platforms = [] # то, во что мы будем врезаться или опираться entities.add(hero) 

Группа спрайтов entities будем использовать для отображения всех элементов этой группы.
Массив platforms будем использовать для проверки на пересечение с платформой.

if col == "-": #создаем блок, заливаем его цветом и рисеум его pf = Surface((PLATFORM_WIDTH,PLATFORM_HEIGHT)) pf.fill(Color(PLATFORM_COLOR)) screen.blit(pf,(x,y)) 
if col == "-": pf = Platform(x,y) entities.add(pf) platforms.append(pf) 

Т.е. создаём экземплр класса Platform, добавляем его в группу спрайтов entities и массив platforms. В entities, чтобы для каждого блока не писать логику отображения. В platforms добавили, чтобы потом проверить массив блоков на пересечение с игроком.

Дальше, весь код генерации уровня выносим из цикла.

И так же строчку
hero.draw(screen) # отображение
Заменим на

entities.draw(screen) # отображение всего 

Запустив, мы увидим, что ни чего не изменилось. Верно. Ведь мы не проверяем нашего героя на столкновения. Начнём это исправлять.

Работаем в файле player.py

Удаляем метод draw, он нам больше не нужен. И добавляем новый метод collide

def collide(self, xvel, yvel, platforms): for p in platforms: if sprite.collide_rect(self, p): # если есть пересечение платформы с игроком if xvel > 0: # если движется вправо self.rect.right = p.rect.left # то не движется вправо if xvel < 0: # если движется влево self.rect.left = p.rect.right # то не движется влево if yvel >0: # если падает вниз self.rect.bottom = p.rect.top # то не падает вниз self.onGround = True # и становится на что-то твердое self.yvel = 0 # и энергия падения пропадает if yvel < 0: # если движется вверх self.rect.top = p.rect.bottom # то не движется вверх self.yvel = 0 # и энергия прыжка пропадает 

В этом методе происходит проверка на пересечение координат героя и платформ, если таковое имеется, то выше описанной логике происходит действие.

Ну, и для того, что бы это всё происходило, необходимо вызывать этот метод.
Изменим число аргументов для метода update, теперь он выглядит так:

update(self, left, right, up, platforms) 

И не забудьте изменить его вызов в основном файле.

self.rect.y += self.yvel self.rect.x += self.xvel # переносим свои положение на xvel 
self.rect.y += self.yvel self.collide(0, self.yvel, platforms) self.rect.x += self.xvel # переносим свои положение на xvel self.collide(self.xvel, 0, platforms) 

Т.е. передвинули героя вертикально, проверили на пересечение по вертикали, передвинули горизонтально, снова проверили на пересечение по горизонтали.

Вот, что получится, когда запустим.

Фу[у]! Движущийся прямоугольник — не красиво!

Давайте немного приукрасим нашего МариоБоя.

Начнем с платформ. Для этого в файле blocks.py сделаем небольшие изменения.

Заменим заливку цветом на картинку, для этого строчку
self.image.fill(Color(PLATFORM_COLOR))
Заменим на

self.image = image.load("blocks/platform.png") 

Мы загружаем картинку вместо сплошного цвета. Разумеется, файл «platform.png» должен находиться в папке «blocks», которая должна располагаться в каталоге с исходными кодами.

Вот, что получилось

Теперь переходим к нашему герою. Для движущегося объекта нужна не статическая картинка, а анимация. Дабы облегчить себе эту задачу, воспользуемся замечательной библиотекой pyganim . Приступим.

Сперва добавим в блок констант.

ANIMATION_DELAY = 0.1 # скорость смены кадров ANIMATION_RIGHT = [('mario/r1.png'), ('mario/r2.png'), ('mario/r3.png'), ('mario/r4.png'), ('mario/r5.png')] ANIMATION_LEFT = [('mario/l1.png'), ('mario/l2.png'), ('mario/l3.png'), ('mario/l4.png'), ('mario/l5.png')] ANIMATION_JUMP_LEFT = [('mario/jl.png', 0.1)] ANIMATION_JUMP_RIGHT = [('mario/jr.png', 0.1)] ANIMATION_JUMP = [('mario/j.png', 0.1)] ANIMATION_STAY = [('mario/0.png', 0.1)] 

Тут, думаю, понятно, анимация разных действий героя.

Теперь добавим следующее в метод __init__

self.image.set_colorkey(Color(COLOR)) # делаем фон прозрачным # Анимация движения вправо boltAnim = [] for anim in ANIMATION_RIGHT: boltAnim.append((anim, ANIMATION_DELAY)) self.boltAnimRight = pyganim.PygAnimation(boltAnim) self.boltAnimRight.play() # Анимация движения влево boltAnim = [] for anim in ANIMATION_LEFT: boltAnim.append((anim, ANIMATION_DELAY)) self.boltAnimLeft = pyganim.PygAnimation(boltAnim) self.boltAnimLeft.play() self.boltAnimStay = pyganim.PygAnimation(ANIMATION_STAY) self.boltAnimStay.play() self.boltAnimStay.blit(self.image, (0, 0)) # По-умолчанию, стоим self.boltAnimJumpLeft= pyganim.PygAnimation(ANIMATION_JUMP_LEFT) self.boltAnimJumpLeft.play() self.boltAnimJumpRight= pyganim.PygAnimation(ANIMATION_JUMP_RIGHT) self.boltAnimJumpRight.play() self.boltAnimJump= pyganim.PygAnimation(ANIMATION_JUMP) self.boltAnimJump.play() 

Здесь для каждого действия мы создаем набор анимаций, и включаем их(т.е. Включаем смену кадров).

for anim in ANIMATION_LEFT: boltAnim.append((anim, ANIMATION_DELAY 

))
Каждый кадр имеет картинку и время показа.

Осталось в нужный момент показать нужную анимацию.

Добавим смену анимаций в метод update.

if up: if self.onGround: # прыгаем, только когда можем оттолкнуться от земли self.yvel = -JUMP_POWER self.image.fill(Color(COLOR)) self.boltAnimJump.blit(self.image, (0, 0)) if left: self.xvel = -MOVE_SPEED # Лево = x- n self.image.fill(Color(COLOR)) if up: # для прыжка влево есть отдельная анимация self.boltAnimJumpLeft.blit(self.image, (0, 0)) else: self.boltAnimLeft.blit(self.image, (0, 0)) if right: self.xvel = MOVE_SPEED # Право = x + n self.image.fill(Color(COLOR)) if up: self.boltAnimJumpRight.blit(self.image, (0, 0)) else: self.boltAnimRight.blit(self.image, (0, 0)) if not(left or right): # стоим, когда нет указаний идти self.xvel = 0 if not up: self.image.fill(Color(COLOR)) self.boltAnimStay.blit(self.image, (0, 0)) 
Больше, нужно больше места

Ограничение в размере окна мы преодолеем созданием динамической камеры.

Для этого создадим класс Camera

class Camera(object): def __init__(self, camera_func, width, height): self.camera_func = camera_func self.state = Rect(0, 0, width, height) def apply(self, target): return target.rect.move(self.state.topleft) def update(self, target): self.state = self.camera_func(self.state, target.rect) 

Далее, добавим начальное конфигурирование камеры

def camera_configure(camera, target_rect): l, t, _, _ = target_rect _, _, w, h = camera l, t = -l+WIN_WIDTH / 2, -t+WIN_HEIGHT / 2 l = min(0, l) # Не движемся дальше левой границы l = max(-(camera.width-WIN_WIDTH), l) # Не движемся дальше правой границы t = max(-(camera.height-WIN_HEIGHT), t) # Не движемся дальше нижней границы t = min(0, t) # Не движемся дальше верхней границы return Rect(l, t, w, h) 

Создадим экземпляр камеры, добавим перед основным циклом:

total_level_width = len(level[0])*PLATFORM_WIDTH # Высчитываем фактическую ширину уровня total_level_height = len(level)*PLATFORM_HEIGHT # высоту camera = Camera(camera_configure, total_level_width, total_level_height) 

Мы создали внутри большого прямоугольника, размеры которого вычисляются так:

total_level_width = len(level[0])*PLATFORM_WIDTH # Высчитываем фактическую ширину уровня total_level_height = len(level)*PLATFORM_HEIGHT # высоту 

меньший прямоугольник, размером, идентичным размеру окна.

Меньший прямоугольник центрируется относительно главного персонажа(метод update), и все объекты рисуются в меньшем прямоугольнике (метод apply), за счет чего создаётся впечатление движения камеры.

Для работы вышеописанного, нужно изменить рисование объектов.

Заменим строчку
entities.draw(screen) # отображение
На

for e in entities: screen.blit(e.image, camera.apply(e)) 

И перед ней добавим

camera.update(hero) # центризируем камеру относительно персонажа 

Теперь можем изменить уровень.

level = [ "----------------------------------", "- -", "- -- -", "- -", "- -- -", "- -", "-- -", "- -", "- ---- --- -", "- -", "-- -", "- -", "- --- -", "- -", "- -", "- --- -", "- -", "- ------- ---- -", "- -", "- - -", "- -- -", "- -", "- -", "----------------------------------"] 

Вот, собственно, и результат

В следующей части, если будет востребовано сообществом, мы создадим свой генератор уровней с блэкджеком и шлюхами с разными типами платформ, монстрами, телепортами, и конечно же, принцессой.

upd pygame можно скачать отсюда, спасибо, Chris_Griffin за замечание
upd1 Вторая часть

Пишем игру на Python

Прежде чем мы начнём программировать что-то полезное на Python, давайте закодим что-нибудь интересное. Например, свою игру, где нужно не дать шарику упасть, типа Арканоида. Вы, скорее всего, играли в детстве во что-то подобное, поэтому освоиться будет просто.

Логика игры

Есть игровое поле — простой прямоугольник с твёрдыми границами. Когда шарик касается стенки или потолка, он отскакивает в другую сторону. Если он упадёт на пол — вы проиграли. Чтобы этого не случилось, внизу вдоль пола летает платформа, а вы ей управляете с помощью стрелок. Ваша задача — подставлять платформу под шарик как можно дольше. За каждое удачное спасение шарика вы получаете одно очко.

Алгоритм

Чтобы реализовать такую логику игры, нужно предусмотреть такие сценарии поведения:

  • игра начинается;
  • шарик начинает двигаться;
  • если нажаты стрелки влево или вправо — двигаем платформу;
  • если шарик коснулся стенок, потолка или платформы — делаем отскок;
  • если шарик коснулся платформы — увеличиваем счёт на единицу;
  • если шарик упал на пол — выводим сообщение и заканчиваем игру.

Хитрость в том, что всё это происходит параллельно и независимо друг от друга. То есть пока шарик летает, мы вполне можем двигать платформу, а можем и оставить её на месте. И когда шарик отскакивает от стен, это тоже не мешает другим объектам двигаться и взаимодействовать между собой.

Получается, что нам нужно определить три класса — платформу, сам шарик и счёт, и определить, как они реагируют на действия друг друга. Поле нам самим определять не нужно — для этого есть уже готовая библиотека. А потом в этих классах мы пропишем методы — они как раз и будут отвечать за поведение наших объектов.

Весь кайф в том, что мы всё это задаём один раз, а потом объекты сами разбираются, как им реагировать друг на друга и что делать в разных ситуациях. Мы не прописываем жёстко весь алгоритм, а задаём правила игры — а для этого классы подходят просто идеально.

По коням, пишем на Python

Для этого проекта вам потребуется установить и запустить среду Python. Как это сделать — читайте в нашей статье.

Начало программы

Чтобы у нас появилась графика в игре, используем библиотеку Tkinter. Она входит в набор стандартных библиотек Python и позволяет рисовать простейшие объекты — линии, прямоугольники, круги и красить их в разные цвета. Такой простой Paint, только для Python.

Чтобы создать окно, где будет видна графика, используют класс Tk(). Он просто делает окно, но без содержимого. Чтобы появилось содержимое, создают холст — видимую часть окна. Именно на нём мы будем рисовать нашу игру. За холст отвечает класс Canvas(), поэтому нам нужно будет создать свой объект из этого класса и дальше уже работать с этим объектом.

Если мы принудительно не ограничим скорость платформы, то она будет перемещаться мгновенно, ведь компьютер считает очень быстро и моментально передвинет её к другому краю. Поэтому мы будем искусственно ограничивать время движения, а для этого нам понадобится модуль Time — он тоже стандартный.

Последнее, что нам глобально нужно, — задавать случайным образом начальное положение шарика и платформы, чтобы было интереснее играть. За это отвечает модуль Random — он помогает генерировать случайные числа и перемешивать данные.

Запишем всё это в виде кода на Python:

# подключаем графическую библиотеку from tkinter import * # подключаем модули, которые отвечают за время и случайные числа import time import random # создаём новый объект — окно с игровым полем. В нашем случае переменная окна называется tk, и мы его сделали из класса Tk() — он есть в графической библиотеке tk = Tk() # делаем заголовок окна — Games с помощью свойства объекта title tk.title('Game') # запрещаем менять размеры окна, для этого используем свойство resizable tk.resizable(0, 0) # помещаем наше игровое окно выше остальных окон на компьютере, чтобы другие окна не могли его заслонить tk.wm_attributes('-topmost', 1) # создаём новый холст — 400 на 500 пикселей, где и будем рисовать игру canvas = Canvas(tk, width=500, height=400, highlightthickness=0) # говорим холсту, что у каждого видимого элемента будут свои отдельные координаты canvas.pack() # обновляем окно с холстом tk.update()

Мы подключили все нужные библиотеки, сделали и настроили игровое поле. Теперь займёмся классами.

Любишь Python? Зарабатывай на нём!

Любишь Python? Зарабатывай на нём! Любишь Python? Зарабатывай на нём! Любишь Python? Зарабатывай на нём! Любишь Python? Зарабатывай на нём!

Шарик

Сначала проговорим словами, что нам нужно от шарика. Он должен уметь:

  • задавать своё начальное положение и направление движение;
  • понимать, когда он коснулся платформы;
  • рисовать сам себя и понимать, когда нужно отрисовать себя в новом положении (например, после отскока от стены).

Этого достаточно, чтобы шарик жил своей жизнью и умел взаимодействовать с окружающей средой. При этом нужно не забыть о том, что каждый класс должен содержать конструктор — код, который отвечает за создание нового объекта. Без этого сделать шарик не получится. Запишем это на Python:

# Описываем класс Ball, который будет отвечать за шарик class Ball: # конструктор — он вызывается в момент создания нового объекта на основе этого класса def __init__(self, canvas, paddle, score, color): # задаём параметры объекта, которые нам передают в скобках в момент создания self.canvas = canvas self.paddle = paddle self.score = score # цвет нужен был для того, чтобы мы им закрасили весь шарик # здесь появляется новое свойство id, в котором хранится внутреннее название шарика # а ещё командой create_oval мы создаём круг радиусом 15 пикселей и закрашиваем нужным цветом self.id = canvas.create_oval(10,10, 25, 25, fill=color) # помещаем шарик в точку с координатами 245,100 self.canvas.move(self.id, 245, 100) # задаём список возможных направлений для старта starts = [-2, -1, 1, 2] # перемешиваем его random.shuffle(starts) # выбираем первый из перемешанного — это будет вектор движения шарика self.x = starts[0] # в самом начале он всегда падает вниз, поэтому уменьшаем значение по оси y self.y = -2 # шарик узнаёт свою высоту и ширину self.canvas_height = self.canvas.winfo_height() self.canvas_width = self.canvas.winfo_width() # свойство, которое отвечает за то, достиг шарик дна или нет. Пока не достиг, значение будет False self.hit_bottom = False # обрабатываем касание платформы, для этого получаем 4 координаты шарика в переменной pos (левая верхняя и правая нижняя точки) def hit_paddle(self, pos): # получаем кординаты платформы через объект paddle (платформа) paddle_pos = self.canvas.coords(self.paddle.id) # если координаты касания совпадают с координатами платформы if pos[2] >= paddle_pos[0] and pos[0] = paddle_pos[1] and pos[3] = self.canvas_height: # помечаем это в отдельной переменной self.hit_bottom = True # выводим сообщение и количество очков canvas.create_text(250, 120, text='Вы проиграли', font=('Courier', 30), fill='red') # если было касание платформы if self.hit_paddle(pos) == True: # отправляем шарик наверх self.y = -2 # если коснулись левой стенки if pos[0] = self.canvas_width: # движемся влево self.x = -2

Платформа

Сделаем то же самое для платформы — сначала опишем её поведение словами, а потом переведём в код. Итак, вот что должна уметь платформа:

  • двигаться влево или вправо в зависимости от нажатой стрелки;
  • понимать, когда игра началась и можно двигаться.

А вот как это будет в виде кода:

# Описываем класс Paddle, который отвечает за платформы class Paddle: # конструктор def __init__(self, canvas, color): # canvas означает, что платформа будет нарисована на нашем изначальном холсте self.canvas = canvas # создаём прямоугольную платформу 10 на 100 пикселей, закрашиваем выбранным цветом и получаем её внутреннее имя self.id = canvas.create_rectangle(0, 0, 100, 10, fill=color) # задаём список возможных стартовых положений платформы start_1 = [40, 60, 90, 120, 150, 180, 200] # перемешиваем их random.shuffle(start_1) # выбираем первое из перемешанных self.starting_point_x = start_1[0] # перемещаем платформу в стартовое положение self.canvas.move(self.id, self.starting_point_x, 300) # пока платформа никуда не движется, поэтому изменений по оси х нет self.x = 0 # платформа узнаёт свою ширину self.canvas_width = self.canvas.winfo_width() # задаём обработчик нажатий # если нажата стрелка вправо — выполняется метод turn_right() self.canvas.bind_all('', self.turn_right) # если стрелка влево — turn_left() self.canvas.bind_all('', self.turn_left) # пока игра не началась, поэтому ждём self.started = False # как только игрок нажмёт Enter — всё стартует self.canvas.bind_all('', self.start_game) # движемся вправо def turn_right(self, event): # будем смещаться правее на 2 пикселя по оси х self.x = 2 # движемся влево def turn_left(self, event): # будем смещаться левее на 2 пикселя по оси х self.x = -2 # игра начинается def start_game(self, event): # меняем значение переменной, которая отвечает за старт self.started = True # метод, который отвечает за движение платформы def draw(self): # сдвигаем нашу платформу на заданное количество пикселей self.canvas.move(self.id, self.x, 0) # получаем координаты холста pos = self.canvas.coords(self.id) # если мы упёрлись в левую границу if pos[0] = self.canvas_width: # останавливаемся self.x = 0

Счёт

Можно было не выделять счёт в отдельный класс и каждый раз обрабатывать вручную. Но здесь реально проще сделать класс, задать нужные методы, чтобы они сами потом разобрались, что и когда делать.

От счёта нам нужно только одно (кроме конструктора) — чтобы он правильно реагировал на касание платформы, увеличивал число очков и выводил их на экран:

# Описываем класс Score, который отвечает за отображение счетов class Score: # конструктор def __init__(self, canvas, color): # в самом начале счёт равен нулю self.score = 0 # будем использовать наш холст self.canvas = canvas # создаём надпись, которая показывает текущий счёт, делаем его нужно цвета и запоминаем внутреннее имя этой надписи self.id = canvas.create_text(450, 10, text=self.score, font=('Courier', 15), fill=color) # обрабатываем касание платформы def hit(self): # увеличиваем счёт на единицу self.score += 1 # пишем новое значение счёта self.canvas.itemconfig(self.id, text=self.score) 

Игра

У нас всё готово для того, чтобы написать саму игру. Мы уже провели необходимую подготовку всех элементов, и нам остаётся только создать конкретные объекты шарика, платформы и счёта и сказать им, в каком порядке мы будем что делать.

Смысл игры в том, чтобы не уронить шарик. Пока этого не произошло — всё движется, но как только шарик упал — нужно показать сообщение о конце игры и остановить программу.

Посмотрите, как лаконично выглядит код непосредственно самой игры:

# создаём объект — зелёный счёт score = Score(canvas, 'green') # создаём объект — белую платформу paddle = Paddle(canvas, 'White') # создаём объект — красный шарик ball = Ball(canvas, paddle, score, 'red') # пока шарик не коснулся дна while not ball.hit_bottom: # если игра началась и платформа может двигаться if paddle.started == True: # двигаем шарик ball.draw() # двигаем платформу paddle.draw() # обновляем наше игровое поле, чтобы всё, что нужно, закончило рисоваться tk.update_idletasks() # обновляем игровое поле, и смотрим за тем, чтобы всё, что должно было быть сделано — было сделано tk.update() # замираем на одну сотую секунды, чтобы движение элементов выглядело плавно time.sleep(0.01) # если программа дошла досюда, значит, шарик коснулся дна. Ждём 3 секунды, пока игрок прочитает финальную надпись, и завершаем игру time.sleep(3) 

ПОЛНЫЙ КОД ПРОГРАММЫ

# подключаем графическую библиотеку from tkinter import * # подключаем модули, которые отвечают за время и случайные числа import time import random # создаём новый объект — окно с игровым полем. В нашем случае переменная окна называется tk, и мы его сделали из класса Tk() — он есть в графической библиотеке tk = Tk() # делаем заголовок окна — Games с помощью свойства объекта title tk.title('Game') # запрещаем менять размеры окна, для этого используем свойство resizable tk.resizable(0, 0) # помещаем наше игровое окно выше остальных окон на компьютере, чтобы другие окна не могли его заслонить. Попробуйте :) tk.wm_attributes('-topmost', 1) # создаём новый холст — 400 на 500 пикселей, где и будем рисовать игру canvas = Canvas(tk, width=500, height=400, highlightthickness=0) # говорим холсту, что у каждого видимого элемента будут свои отдельные координаты canvas.pack() # обновляем окно с холстом tk.update() # Описываем класс Ball, который будет отвечать за шарик class Ball: # конструктор — он вызывается в момент создания нового объекта на основе этого класса def __init__(self, canvas, paddle, score, color): # задаём параметры объекта, которые нам передают в скобках в момент создания self.canvas = canvas self.paddle = paddle self.score = score # цвет нужен был для того, чтобы мы им закрасили весь шарик # здесь появляется новое свойство id, в котором хранится внутреннее название шарика # а ещё командой create_oval мы создаём круг радиусом 15 пикселей и закрашиваем нужным цветом self.id = canvas.create_oval(10,10, 25, 25, fill=color) # помещаем шарик в точку с координатами 245,100 self.canvas.move(self.id, 245, 100) # задаём список возможных направлений для старта starts = [-2, -1, 1, 2] # перемешиваем его random.shuffle(starts) # выбираем первый из перемешанного — это будет вектор движения шарика self.x = starts[0] # в самом начале он всегда падает вниз, поэтому уменьшаем значение по оси y self.y = -2 # шарик узнаёт свою высоту и ширину self.canvas_height = self.canvas.winfo_height() self.canvas_width = self.canvas.winfo_width() # свойство, которое отвечает за то, достиг шарик дна или нет. Пока не достиг, значение будет False self.hit_bottom = False # обрабатываем касание платформы, для этого получаем 4 координаты шарика в переменной pos (левая верхняя и правая нижняя точки) def hit_paddle(self, pos): # получаем кординаты платформы через объект paddle (платформа) paddle_pos = self.canvas.coords(self.paddle.id) # если координаты касания совпадают с координатами платформы if pos[2] >= paddle_pos[0] and pos[0] = paddle_pos[1] and pos[3] = self.canvas_height: # помечаем это в отдельной переменной self.hit_bottom = True # выводим сообщение и количество очков canvas.create_text(250, 120, text='Вы проиграли', font=('Courier', 30), fill='red') # если было касание платформы if self.hit_paddle(pos) == True: # отправляем шарик наверх self.y = -2 # если коснулись левой стенки if pos[0] = self.canvas_width: # движемся влево self.x = -2 # Описываем класс Paddle, который отвечает за платформы class Paddle: # конструктор def __init__(self, canvas, color): # canvas означает, что платформа будет нарисована на нашем изначальном холсте self.canvas = canvas # создаём прямоугольную платформу 10 на 100 пикселей, закрашиваем выбранным цветом и получаем её внутреннее имя self.id = canvas.create_rectangle(0, 0, 100, 10, fill=color) # задаём список возможных стартовых положений платформы start_1 = [40, 60, 90, 120, 150, 180, 200] # перемешиваем их random.shuffle(start_1) # выбираем первое из перемешанных self.starting_point_x = start_1[0] # перемещаем платформу в стартовое положение self.canvas.move(self.id, self.starting_point_x, 300) # пока платформа никуда не движется, поэтому изменений по оси х нет self.x = 0 # платформа узнаёт свою ширину self.canvas_width = self.canvas.winfo_width() # задаём обработчик нажатий # если нажата стрелка вправо — выполняется метод turn_right() self.canvas.bind_all('', self.turn_right) # если стрелка влево — turn_left() self.canvas.bind_all('', self.turn_left) # пока платформа не двигается, поэтому ждём self.started = False # как только игрок нажмёт Enter — всё стартует self.canvas.bind_all('', self.start_game) # движемся вправо def turn_right(self, event): # будем смещаться правее на 2 пикселя по оси х self.x = 2 # движемся влево def turn_left(self, event): # будем смещаться левее на 2 пикселя по оси х self.x = -2 # игра начинается def start_game(self, event): # меняем значение переменной, которая отвечает за старт движения платформы self.started = True # метод, который отвечает за движение платформы def draw(self): # сдвигаем нашу платформу на заданное количество пикселей self.canvas.move(self.id, self.x, 0) # получаем координаты холста pos = self.canvas.coords(self.id) # если мы упёрлись в левую границу if pos[0] = self.canvas_width: # останавливаемся self.x = 0 # Описываем класс Score, который отвечает за отображение счетов class Score: # конструктор def __init__(self, canvas, color): # в самом начале счёт равен нулю self.score = 0 # будем использовать наш холст self.canvas = canvas # создаём надпись, которая показывает текущий счёт, делаем его нужно цвета и запоминаем внутреннее имя этой надписи self.id = canvas.create_text(450, 10, text=self.score, font=('Courier', 15), fill=color) # обрабатываем касание платформы def hit(self): # увеличиваем счёт на единицу self.score += 1 # пишем новое значение счёта self.canvas.itemconfig(self.id, text=self.score) # создаём объект — зелёный счёт score = Score(canvas, 'green') # создаём объект — белую платформу paddle = Paddle(canvas, 'White') # создаём объект — красный шарик ball = Ball(canvas, paddle, score, 'red') # пока шарик не коснулся дна while not ball.hit_bottom: # если игра началась и платформа может двигаться if paddle.started == True: # двигаем шарик ball.draw() # двигаем платформу paddle.draw() # обновляем наше игровое поле, чтобы всё, что нужно, закончило рисоваться tk.update_idletasks() # обновляем игровое поле и смотрим за тем, чтобы всё, что должно было быть сделано — было сделано tk.update() # замираем на одну сотую секунды, чтобы движение элементов выглядело плавно time.sleep(0.01) # если программа дошла досюда, значит, шарик коснулся дна. Ждём 3 секунды, пока игрок прочитает финальную надпись, и завершаем игру time.sleep(3)

Что дальше

На основе этого кода вы можете сделать свою модификацию игры:

  • добавить второй шарик;
  • раскрасить элементы в другой цвет;
  • поменять размеры шарика; поменять скорость платформы;
  • сделать всё это сразу;
  • поменять логику программы на свою.

Saved searches

Use saved searches to filter your results more quickly

Cancel Create saved search

You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session. You switched accounts on another tab or window. Reload to refresh your session.

super mario in python and pygame

mx0c/super-mario-python

This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Switch branches/tags
Branches Tags
Could not load branches
Nothing to show
Could not load tags
Nothing to show

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?

Cancel Create

  • Local
  • Codespaces

HTTPS GitHub CLI
Use Git or checkout with SVN using the web URL.
Work fast with our official CLI. Learn more about the CLI.

Sign In Required

Please sign in to use Codespaces.

Launching GitHub Desktop

If nothing happens, download GitHub Desktop and try again.

Launching GitHub Desktop

If nothing happens, download GitHub Desktop and try again.

Launching Xcode

If nothing happens, download Xcode and try again.

Launching Visual Studio Code

Your codespace will open once ready.

There was a problem preparing your codespace, please try again.

Latest commit

Git stats

Files

Failed to load latest commit information.

Latest commit message
Commit time
December 26, 2020 03:30
February 18, 2021 00:46
October 24, 2020 13:12
October 29, 2020 22:02
November 2, 2020 16:21
November 15, 2020 18:36
June 1, 2021 12:47
October 22, 2020 21:19
August 12, 2020 14:48
October 28, 2020 10:01
December 26, 2020 03:30
August 12, 2020 10:17

README.md

Super Mario Implementation in Python

This is inspired by Meth-Meth-Method's super mario game

Running

  • $ pip install -r requirements.txt
  • $ python main.py

Standalone windows build

  • $ pip install py2exe
  • $ python compile.py py2exe

Controls

  • Left: Move left
  • Right: Move right
  • Space: Jump
  • Shift: Boost
  • Left/Right Mouseclick: secret

Current state:

Alt text

Dependencies

Contribution

If you have any Improvements/Ideas/Refactors feel free to contact me or make a Pull Request. The code needs still alot of refactoring as it is right now, so I appreciate any kind of Contribution.

About

super mario in python and pygame

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *