Перейти к содержимому

Kivy что за папка

  • автор:

Куда деть файлы при переводе приложения kivy в apk python?

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

Отслеживать
python_dev
задан 3 июл 2022 в 14:05
python_dev python_dev
1 1 1 бронзовый знак

1 ответ 1

Сортировка: Сброс на вариант по умолчанию

Вам необходимо в buildozer.spec прописать расширения файлов включаемые в APK и папки, пример:

#(list) Source files to include (let empty to include all the files) source.include_exts. = py,png,jpg,kv,atlas,wav #(list) List of inclusions using pattern matching source.include_patterns = audio/guard/*.wav,audio/chisau/*.wav 

Отслеживать
ответ дан 3 июл 2022 в 18:29
videxerion videxerion
1,275 1 1 золотой знак 7 7 серебряных знаков 25 25 бронзовых знаков
можно про папки подробнее?
4 июл 2022 в 6:48
Ну директории которые ты встраиваешь внутрь apk
4 июл 2022 в 9:51

  • python
  • файлы
  • kivy
  • app
    Важное на Мете
Похожие

Подписаться на ленту

Лента вопроса

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

Дизайн сайта / логотип © 2024 Stack Exchange Inc; пользовательские материалы лицензированы в соответствии с CC BY-SA . rev 2024.1.3.2953

Нажимая «Принять все файлы cookie» вы соглашаетесь, что Stack Exchange может хранить файлы cookie на вашем устройстве и раскрывать информацию в соответствии с нашей Политикой в отношении файлов cookie.

Приложение на python kivy для разнообразия рациона питания. От кода и до получения .apk файла для Android

Изучаю python kivy и для себя решил написал маленькое приложение, чтобы разнообразить свое питание. Решил поделиться. Статья рассчитана на новичков в kivy. Приложение занимает около 100 строк кода.

Цель создания велосипеда приложения:

  1. Избежать частых повторений в питании. Чтобы не употреблять одно и то же блюдо слишком часто.
  2. Не забывать блюда, которые ел, потом забыл и годами к ним не возвращался, потому что банально не помнил. У меня такое бывает.

Интро

Можно не читать, в интро всякая лирика.

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

Скриншоты

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

Исходный код и пояснения

main.py

from kivy.app import App from kivy.uix.button import Button from kivy.uix.boxlayout import BoxLayout from kivy.uix.screenmanager import ScreenManager, Screen from kivy.uix.recycleview import RecycleView from kivy.uix.gridlayout import GridLayout from kivy.core.window import Window from kivy.config import ConfigParser from kivy.uix.textinput import TextInput from kivy.uix.label import Label from kivy.metrics import dp from datetime import datetime import os import ast import time class MenuScreen(Screen): def __init__(self, **kw): super(MenuScreen, self).__init__(**kw) box = BoxLayout(orientation='vertical') box.add_widget(Button(text='Дневник питания', on_press=lambda x: set_screen('list_food'))) box.add_widget(Button(text='Добавить блюдо в дневник питания', on_press=lambda x: set_screen('add_food'))) self.add_widget(box) class SortedListFood(Screen): def __init__(self, **kw): super(SortedListFood, self).__init__(**kw) def on_enter(self): # Будет вызвана в момент открытия экрана self.layout = GridLayout(cols=1, spacing=10, size_hint_y=None) self.layout.bind(minimum_height=self.layout.setter('height')) back_button = Button(text='< Назад в главное меню', on_press=lambda x: set_screen('menu'), size_hint_y=None, height=dp(40)) self.layout.add_widget(back_button) root = RecycleView(size_hint=(1, None), size=(Window.width, Window.height)) root.add_widget(self.layout) self.add_widget(root) dic_foods = ast.literal_eval( App.get_running_app().config.get('General', 'user_data')) for f, d in sorted(dic_foods.items(), key=lambda x: x[1]): fd = f.decode('u8') + ' ' + (datetime.fromtimestamp(d).strftime('%Y-%m-%d')) btn = Button(text=fd, size_hint_y=None, height=dp(40)) self.layout.add_widget(btn) def on_leave(self): # Будет вызвана в момент закрытия экрана self.layout.clear_widgets() # очищаем список class AddFood(Screen): def buttonClicked(self, btn1): if not self.txt1.text: return self.app = App.get_running_app() self.app.user_data = ast.literal_eval( self.app.config.get('General', 'user_data')) self.app.user_data[self.txt1.text.encode('u8')] = int(time.time()) self.app.config.set('General', 'user_data', self.app.user_data) self.app.config.write() text = "Последнее добавленное блюдо: " + self.txt1.text self.result.text = text self.txt1.text = '' def __init__(self, **kw): super(AddFood, self).__init__(**kw) box = BoxLayout(orientation='vertical') back_button = Button(text='< Назад в главное меню', on_press=lambda x: set_screen('menu'), size_hint_y=None, height=dp(40)) box.add_widget(back_button) self.txt1 = TextInput(text='', multiline=False, height=dp(40), size_hint_y=None, hint_text="Название блюда") box.add_widget(self.txt1) btn1 = Button(text="Добавить блюдо", size_hint_y=None, height=dp(40)) btn1.bind(on_press=self.buttonClicked) box.add_widget(btn1) self.result = Label(text='') box.add_widget(self.result) self.add_widget(box) def set_screen(name_screen): sm.current = name_screen sm = ScreenManager() sm.add_widget(MenuScreen(name='menu')) sm.add_widget(SortedListFood(name='list_food')) sm.add_widget(AddFood(name='add_food')) class FoodOptionsApp(App): def __init__(self, **kvargs): super(FoodOptionsApp, self).__init__(**kvargs) self.config = ConfigParser() def build_config(self, config): config.adddefaultsection('General') config.setdefault('General', 'user_data', '<>') def set_value_from_config(self): self.config.read(os.path.join(self.directory, '%(appname)s.ini')) self.user_data = ast.literal_eval(self.config.get( 'General', 'user_data')) def get_application_config(self): return super(FoodOptionsApp, self).get_application_config( '<>/%(appname)s.ini'.format(self.directory)) def build(self): return sm if __name__ == '__main__': FoodOptionsApp().run() 

Я сознательно не использовал kv файлы, так как код дан в учебных целях, для людей, которые знакомы с python. Все написано на голом python. В пояснениях я не буду останавливаться на объяснении python кода, а сразу перейду к специфическим фишкам Kivy.

class MenuScreen(Screen):
box = BoxLayout(orientation='vertical')
Button(text='Дневник питания', on_press=lambda x: set_screen('list_food')) 
.add_widget()
self.layout = GridLayout(cols=1, spacing=10, size_hint_y=None)

Grid Layout чем-то напоминает тег table в html, указывается cols — кол-во колонок или rows — кол-во строк.

root = RecycleView(size_hint=(1, None), size=(Window.width, Window.height))
config.get('General', 'user_data')

Запуск на windows & linux & macos

Принцип для всех операционок одинаковый:

  1. Ставим python3
  2. Ставим kivy
  3. Создаем файл main.py и втыкаем в него целиком вышеуказанный код
  4. Запускаем командой

python3 main.py

Сборка apk файла и запуск на телефоне с андроид

Итак, у нас есть файл с кодом программы, написанный на python. Как теперь создать приложение, чтобы его можно было запустить на телефоне с андроидом? Раньше это был достаточно мудреный процесс, требующий навыков и танцев с бубном. Теперь это не проблема.
Вот пошаговая инструкция:

  1. Скачиваем готовую виртуальную машину от разработчиков kivy, в которой уже все настроено. https://github.com/Zen-CODE/kivybits/tree/master/KivyCompleteVM. Пароль: kivy
  2. Запускаем ее в Virtual Box.
  3. Открываем терминал и вводим следующие команды:

# Ставим последнюю версию python-for-android cd /home/kivy/Repos rm -fr python-for-android/ git clone https://github.com/kivy/python-for-android.git cd ~ mkdir Project cd Project git clone https://github.com/Alexmod/FoodOptions.git cd FoodOptions buildozer android debug # Первый раз эта команда будет долго тянуть 100500 всяких библиотек, # но в следующие разы выполняться за секунды. 

Как закинуть apk файл на телефон?

Можно, конечно, сделать это как угодно, отправить себе по почте, куда-нибудь выложить, закинуть в телеграмм и т.д., а потом скачать приложение на телефон.

Но существует специализированный инструмент для этого. Включаем на телефоне режим разработчика, подключаем USB-кабелем. Виртуалка должна увидеть, что вы подключили телефон. Дальше устанавливаем adb:

sudo apt install adb

После установки заходим в папку bin и вводим команду

adb install -r foodoptions-0.1-debug.apk 

И можно примерно через минутку увидеть на телефоне приложение после того, как увидим
Success в консоли.

kivy@kivy-complete:~/Project/FoodOptions/bin$ adb install -r foodoptions-0.1-debug.apk 342 KB/s (10083019 bytes in 28.730s) Success kivy@kivy-complete:~/Project/FoodOptions/bin$ 

Если вдруг приложение падает или ведет себя не так, как ожидалось, то есть вот такая команда для просмотра ошибок

adb logcat| grep python

Русское имя приложения

Если вы захотите, чтобы ваше приложение называлось по-русски, например, «Дневник питания», то надо внести изменения в файл:

.buildozer/android/platform/build/dists/foodoptions/templates/strings.tmpl.xml 

В тег appName прописывается русское название приложения, эта папка создается после первого запуска buildozer android debug. После того как файл отредактируете, вернитесь назад в папку FoodOptions и запустите buildozer android debug повторно. Файл соберется по-новой. После установки на телефон имя программы будет написано на русском.

О файле buildozer.spec

Вот мой файл с гитхаба: buildozer.spec
Именно этот файл указывает buildozer-у, как именно собрать пакет.

Там множество разных вариаций. Кому интересно, то введите внутри виртуалки команду:

cd /tmp buildozer init 

Будет создан дефолтный файл buildozer.spec с кучей комментариев и пояснений. Например, если вы хотите какую-нибудь свою иконку для приложения, то указываете в строке:

icon.filename = %(source.dir)s/data/icon.png

свой файл с иконкой. И приложение соберется уже с вашей иконкой.

Если вам надо подгрузить какой-нибудь специфический модуль, который не входит в официальную библиотеку python, то это делается в строке requirements =. В общем, рассказ о файле buildozer.spec может занять целую статью, а то и две.

Загрузка приложения в Google Play

Надо зарегаться, пройти все процедуры, получить ключи. И дальше запускать:

sudo apt install zipalign buildozer android release jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore /path/keystore bin/apk-unsigned.apk apkname zipalign -v 4 bin/apk-apkname-unsigned.apk bin/apk-apkname-release.apk

Полученный файл apk-apkname-release.apk заливать в Google Play.

Ссылки

  • Видео-уроки по kivy на русском языке. Лично мне понравились
  • Статьи на хабре про kivy от HeaTTheatR (и спасибо ему за помощь!)
  1. Добавить дизайн, чтобы приложение стало красивое
  2. Использовать kv-файлы, чтобы код стал более легким. Я бы привел такую аналогию: те кто знаком с веб-программированием, представьте себе код без html темплейтов и с html темплейтами. Вынос в kv-файлы кнопок, слоев и прочего — это что-то вроде jinja2 для веб-программиста. Логика остается в .py файлах, а фенечки — в kv-файлах.
  3. Добавить подсчет калорий, белка, углеводов, жиров (БЖУ)
  4. Добавить возможность фотографировать блюда

AlexKorablev.ru

AlexKorablev.ru

Александр Кораблев о разработке ПО, ИТ-индустрии и Python.

Проект на Kivy. Часть 1. Настройка окружения

Опубликовано 16 May 2016 в Python

Kivy — интересный проект, который позволяет делать кроссплатформенные GUI приложения, включая мобильные. По слухам. Я решил выяснить можно ли использовать его для создания маленьких приложений на питоне. В этой серии статей я попробую поисследовать Kivy на примере клона «конструктора слов» — одного из упражнений для LingvoLeo.

Эта серия — не учебник, а исследовательский лог моих попыток. В результате я планирую получить собранные приложения для трех платформ минимум: Windows, Mac, Android. Будет здорово, если я соберу еще и iOS версию. В качестве основной платформы для разработки я использую Mac. Так что, все инструкции по установке и настройке окружения будут для этой операционной системы.

Установка

Под мак есть два способа поставить Kivy: скачать бандл или установить зависимости из homebrew, а kivy поставить с помощью pip.

Первый способ (быстрый):

  1. Скачать с http://kivy.org/#download Kivy2.7z (использует системный питон 2.7) или Kivy3.7z (включает в себя Python 3.5)
  2. Распаковываем архив
  3. Копируем в приложения
sudo mv Kivy3.app /Applications/Kivy.app
ln -s /Applications/Kivy.app/Contents/Resources/script /usr/local/bin/kivy

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

Второй способ (правильный):

Подразумеваю, что вы пользуетесь pyenv и pyenv-virtualenv.

Первым делом ставим зависимости.

brew install sdl2 sdl2_image sdl2_ttf sdl2_mixer gstreamer

Ставим свежий питончик.

pyenv install 3.5.1

Иногда установка падает:

zipimport.ZipImportError: can't decompress data; zlib not available

В этом случае поможет:

xcode-select --install

Как только свежий питон стоит, создаем виртуальное окружение для нашего проекта.

pyenv virtualenv 3.5.1 WordConstructor

Активируем созданное окружение

pyenv activate WordConstructor
pip install -I Cython==0.23
USE_OSX_FRAMEWORKS=0 pip install kivy

Проверим, что все работает. Создадим файлик main.py в папке с проектом с таким содержимым:

from kivy.app import App from kivy.uix.widget import Widget class WordConstructorGame(Widget): pass class WordConstructorApp(App): def build(self): return WordConstructorGame() if __name__ == '__main__': WordConstructorApp().run() 
python main.py

Если открылось окно с заголовком WordConstructor, все установилось и работает как надо.

PyCharm

Теперь настроим проект в пайчарме.

В Preferences выбираем Project interpreter соответствующий нашему виртуальному окружению.

Kivy interpreter

Для описания интерфейсов в Kivy используется свой язык Kv Design Language. Неплохо бы добавить для него подсветку синтаксиса и автокомплит.

  1. Для этого качаем https://github.com/Zen-CODE/kivybits/blob/master/IDE/PyCharm_kv_completion.jar?raw=true
  2. В PyCharm в меню File -> Import Settings импортируем этот файл.
  3. Удостоверившись, что стоит галочка File types, нажимаем OK.
  4. Перезагружаем PyCharm и наслаждаемся результатом.

С настройкой все. В следующей статье обсудим более практические вопросы.


Возник вопрос? Мне всегда можно написать в Twitter: avkorablev

Понравилась статья? Поделись с друзьями!

Пишем «Змейку» под Android на Python и Kivy

Обложка поста Пишем «Змейку» под Android на Python и Kivy

В этой статье мы напишем классическую «Змейку» на Python с помощью инструмента для создания GUI Kivy.

Знакомимся с Kivy

Kivy — это популярный инструмент для создания пользовательских интерфейсов, который отлично подходит для разработки приложений и несложных игр. Одним из основных достоинств Kivy является портируемость — возможность безболезненного переноса ваших проектов с одной платформы на другую. Наша «Змейка» будет работать на платформе Android.

Kivy эффективно использует Cython — язык программирования, сочетающий в себе оптимизированность C++ и синтаксис Python — что положительно сказывается на производительности. Также Kivy активно использует GPU для графических процессов, освобождая CPU для других вычислений.

Рекомендуемые ресурсы для начала работы с Kivy:

  • официальная документация;
  • Wiki по Kivy;
  • примеры готовых проектов.

Устанавливаем Kivy

Зависимости

Прим. перев. Код проверен на Ubuntu 16.04, Cython 0.25, Pygame 1.9.4.dev0, Buildozer 0.33, Kyvi 1.10.

Для правильной работы Kivy нам требуется три основных пакета: Cython, pygame и python-dev. Если вы используете Ubuntu, вам также может понадобиться библиотека gstreamer, которая используется для поддержки некоторых видеовозможностей фреймворка.

sudo pip install cython 

Устанавливаем зависимости pygame:

sudo apt-get build-dep python-pygame sudo apt-get install python-dev build-essential 
sudo pip install hg+http://bitbucket.org/pygame/pygame 
sudo apt-get install gstreamer1.0-libav 

Добавляем репозиторий Kivy:

sudo add-apt-repository ppa:kivy-team/kivy sudo apt-get update 
sudo apt-get install python-kivy 

Buildozer

Этот пакет нам понадобится для упрощения процесса установки нашего Android-приложения:

sudo pip install buildozer 

Нам также понадобится Java JDK. И если вы используете 64-битную систему, вам понадобятся 32-битные версии зависимостей.

Устанавливаем Java JDK:

sudo apt-get install openjdk-7-jdk 

Устанавливаем 32-битные зависимости:

sudo dpkg --add-architecture i386 sudo apt-get update sudo apt-get install libncurses5:i386 libstdc++6:i386 zlib1g:i386 

Оно работает?

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

Для проверки напишем старый добрый «Hello, world!».

Приступим к созданию проекта. Нужно перейти в рабочую папку и выполнить команду:

buildozer init 

Теперь откроем файл с расширением .spec в любом текстовом редакторе и изменим следующие строки:

  • имя нашего приложения title = Hello World ;
  • название пакета package.name = helloworldapp ;
  • домен пакета (нужен для android/ios сборки) package.domain = org.helloworldapp ;
  • закомментируйте эти строки, если они ещё не закомментированы:
# version.regex = __version__ = ['"](.*)['"] # version.filename = %(source.dir)s/main.py 
  • строка version = 1.0.0 должна быть раскомментированной.

Создайте файл main.py и добавьте в него следующий код:

import kivy kivy.require('1.8.0') # Ваша версия может отличаться from kivy.app import App from kivy.uix.button import Button class DummyApp(App): def build(self): return Button(text="Hello World") if __name__ == '__main__': DummyApp().run() 

Теперь все готово к сборке. Вернемся к терминалу.

buildozer android debug # Эта команда создает apk файл в папке ./bin buildozer android debug deploy # Если вы хотите установить apk непосредственно на ваше устройство 

Примечание В случае возникновения каких-либо ошибок установите значение log_level = 2 в файле buildozer.spec. Это даст более развернутое описание ошибки. Теперь мы точно готовы приступить к написанию «Змейки».

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

Цели

В этой части урока мы напишем игровой движок нашей «Змейки». И под созданием игрового движка подразумевается:

1. Написание классов, которые станут скелетом нашего приложения.
2. Предоставление им правильных методов и свойств, чтобы мы могли управлять ими по своему усмотрению.
3. Соединение всего в основном цикле приложения.

Классы

Теперь давайте разберем нашу игру на составные элементы: змея и игровое поле. Змея состоит из двух основных элементов: головы и хвоста. И надо не забыть, что змее нужно что-то есть.

Таким образом нам потребуется организовать следующую иерархию виджетов:

Игровое поле (Playground) Фрукты (Fruit) Змея (Snake) Голова (SnakeHead) Хвост (SnakeTail) 

Мы объявим наши классы в файлах main.py и snake.kv , чтобы отделить дизайн от логики:

import kivy kivy.require('1.8.0') # Импортируем элементы Kivy, которые будем использовать в классах from kivy.app import App from kivy.uix.widget import Widget from kivy.properties import ObjectProperty class Playground(Widget): # Привязываем переменным элементы из .kv fruit = ObjectProperty(None) snake = ObjectProperty(None) class Fruit(Widget): pass class Snake(Widget): head = ObjectProperty(None) tail = ObjectProperty(None) class SnakeHead(Widget): pass class SnakeTail(Widget): pass class SnakeApp(App): def build(self): game = Playground() return game if __name__ == '__main__': SnakeApp().run() 
#:kivy 1.8.0 snake: snake_id fruit: fruit_id Snake: id: snake_id Fruit: id: fruit_id head: snake_head_id tail: snake_tail_id SnakeHead: id: snake_head_id SnakeTail: id: snake_tail_id 

Свойства

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

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

И последнее, но не менее важное: нужно реализовать управление вводом, но сделаем мы это в следующем разделе.

class Playground(Widget): fruit = ObjectProperty(None) snake = ObjectProperty(None) # Задаем размер сетки col_number = 16 row_number = 9 # Игровые переменные score = NumericProperty(0) turn_counter = NumericProperty(0) fruit_rythme = NumericProperty(0) # Одработка входных данных touch_start_pos = ListProperty() action_triggered = BooleanProperty(False) 

Змея

Объект змеи не должен содержать ничего, кроме двух ее деталей: головы и хвоста.

Для головы мы должны знать текущее положение и направление движения для правильного графического представления: если змея движется направо — рисуем треугольник повернутый вправо, если змея движется влево — рисуем треугольник повернутый влево.

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

Наконец, нам надо хранить информацию об объекте, нарисованном на холсте, чтобы удалить его позже (например, для перезапуска игры). Мы добавим логическую переменную, указывающую, должен ли объект быть нарисован на холсте:

class SnakeHead(Widget): # Направление головы и ее позиция direction = OptionProperty( "Right", options=["Up", "Down", "Left", "Right"]) x_position = NumericProperty(0) y_position = NumericProperty(0) position = ReferenceListProperty(x_position, y_position) # Представление на холсте points = ListProperty([0]*6) object_on_board = ObjectProperty(None) state = BooleanProperty(False) 

Теперь хвост. Он состоит из блоков (изначально трех), занимающих одну ячейку. Когда «Змейка» будет двигаться, мы будем убирать самый последний блок хвоста и добавлять новый на предыдущую позицию головы:

class SnakeTail(Widget): # Длинна хвоста. Измеряется в количестве блоков size = NumericProperty(3) # Позицию каждого блока хвоста мы будем хранить здесь blocks_positions = ListProperty() # Обьекты (виджеты) хвоста будут находиться в этой переменной tail_blocks_objects = ListProperty() 

Фрукт

Поведение фрукта похоже на поведение змеиной головы. Нам понадобится отслеживать его позицию, состояние и интервал появления:

class Fruit(Widget): # Эти значения будем использовать для определения частоты появления fruit_rhythme duration = NumericProperty(10) # Продолжительность существования interval = NumericProperty(3) # Продолжительность отсутствия # Представление на поле object_on_board = ObjectProperty(None) state = BooleanProperty(False) 

В классе SnakeApp будет происходить запуск нашего приложения:

class SnakeApp(App): game_engine = ObjectProperty(None) def build(self): self.game_engine = Playground() return self.game_engine 

Кое-что еще: нужно задать размеры виджетов. Каждый элемент будет занимать одну ячейку поля. Значит:

  • высота виджета = высота поля / количество строк сетки;
  • ширина виджета = ширина поля / количество колонок сетки.

Также нам нужно добавить виджет отображающий текущий счет.

Теперь snake.kv выглядит так:

#:kivy 1.8.0 snake: snake_id fruit: fruit_id Snake: id: snake_id width: root.width/root.col_number height: root.height/root.row_number Fruit: id: fruit_id width: root.width/root.col_number height: root.height/root.row_number Label: font_size: 70 center_x: root.x + root.width/root.col_number*2 top: root.top - root.height/root.row_number text: str(root.score) head: snake_head_id tail: snake_tail_id SnakeHead: id: snake_head_id width: root.width height: root.height SnakeTail: id: snake_tail_id width: root.width height: root.height 

Методы

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

class Snake(Widget): . def move(self): """ Движение змеи будет происходить в 3 этапа: - сохранить текущее положение головы. - передвинуть голову на одну позицию вперед. - переместить последний блок хвоста на предыдущие координаты головы . """ next_tail_pos = list(self.head.position) self.head.move() self.tail.add_block(next_tail_pos) def remove(self): """ Здесь мы опишем, удаление элементов хвоста и головы """ self.head.remove() self.tail.remove() def set_position(self, position): self.head.position = position def get_position(self): """ Положение змеи равно положению ее головы на поле. """ return self.head.position def get_full_position(self): """ Но иногда нам нужно будет узнавать, какое пространство занимает змея. """ return self.head.position + self.tail.blocks_positions def set_direction(self, direction): self.head.direction = direction def get_direction(self): return self.head.direction 

Мы назвали ряд методов. Теперь давайте их реализуем. Начнем с remove() и add_block() :

class SnakeTail(Widget): . def remove(self): # Сбрасываем счетчик длины self.size = 3 # Удаляем каждый блок хвоста for block in self.tail_blocks_objects: self.canvas.remove(block) # Обнуляем списки с координатами блоков # и их представления на холсте self.blocks_positions = [] self.tail_blocks_objects = [] def add_block(self, pos): """ Здесь действуем в 3 этапа : - Передаем позицию нового блока как аргумент и добавляем блок в список объектов. - Проверяем равенство длины хвоста и количества блоков и изменяем, если требуется. - Рисуем блоки на холсте, до тех пор, пока количество нарисованных блоков не станет равно длине хвоста. """ # Добавляем координаты блоков в список self.blocks_positions.append(pos) # Делаем проверку соответствия количеству блоков змеи на холсте и переменной отражающей длину if len(self.blocks_positions) > self.size: self.blocks_positions.pop(0) with self.canvas: # Рисуем блоки используя координаты из списка for block_pos in self.blocks_positions: x = (block_pos[0] - 1) * self.width y = (block_pos[1] - 1) * self.height coord = (x, y) block = Rectangle(pos=coord, size=(self.width, self.height)) # Добавляем новый блок к списку объектов self.tail_blocks_objects.append(block) # Делаем проверку длины и удаляем лишние блоки с холста, если необходимо if len(self.tail_blocks_objects) > self.size: last_block = self.tail_blocks_objects.pop(0) self.canvas.remove(last_block) 

Теперь работаем с головой. Она будет иметь две функции: move() и remove():

class SnakeHead(Widget): # Представление головы на "сетке" direction = OptionProperty( "Right", options=["Up", "Down", "Left", "Right"]) x_position = NumericProperty(0) y_position = NumericProperty(0) position = ReferenceListProperty(x_position, y_position) # Представление головы на поле points = ListProperty([0] * 6) object_on_board = ObjectProperty(None) state = BooleanProperty(False) def is_on_board(self): return self.state def remove(self): if self.is_on_board(): self.canvas.remove(self.object_on_board) self.object_on_board = ObjectProperty(None) self.state = False def show(self): """ Размещаем голову на холсте. """ with self.canvas: if not self.is_on_board(): self.object_on_board = Triangle(points=self.points) self.state = True else: # Если объект должен быть на поле - удаляем старую голову # и рисуем новую self.canvas.remove(self.object_on_board) self.object_on_board = Triangle(points=self.points) def move(self): """ Не самое элегантное решение, но это работает. Здесь мы описываем изображение треугольника для каждого положения головы. """ if self.direction == "Right": # Обновляем позицию self.position[0] += 1 # Вычисляем положения точек x0 = self.position[0] * self.width y0 = (self.position[1] - 0.5) * self.height x1 = x0 - self.width y1 = y0 + self.height / 2 x2 = x0 - self.width y2 = y0 - self.height / 2 elif self.direction == "Left": self.position[0] -= 1 x0 = (self.position[0] - 1) * self.width y0 = (self.position[1] - 0.5) * self.height x1 = x0 + self.width y1 = y0 - self.height / 2 x2 = x0 + self.width y2 = y0 + self.height / 2 elif self.direction == "Up": self.position[1] += 1 x0 = (self.position[0] - 0.5) * self.width y0 = self.position[1] * self.height x1 = x0 - self.width / 2 y1 = y0 - self.height x2 = x0 + self.width / 2 y2 = y0 - self.height elif self.direction == "Down": self.position[1] -= 1 x0 = (self.position[0] - 0.5) * self.width y0 = (self.position[1] - 1) * self.height x1 = x0 + self.width / 2 y1 = y0 + self.height x2 = x0 - self.width / 2 y2 = y0 + self.height # Записываем положения точек self.points = [x0, y0, x1, y1, x2, y2] # Рисуем голову self.show() 

А что там с фруктами? Мы должны уметь помещать их в заданные координаты и удалять, когда нам это понадобится:

class Fruit(Widget): . def is_on_board(self): return self.state def remove(self, *args): # Удаляем объект с поля и указываем, что он сейчас стерт if self.is_on_board(): self.canvas.remove(self.object_on_board) self.object_on_board = ObjectProperty(None) self.state = False def pop(self, pos): self.pos = pos # объявляем, что фрукт находится на поле # Рисуем фрукт with self.canvas: x = (pos[0] - 1) * self.size[0] y = (pos[1] - 1) * self.size[1] coord = (x, y) # Сохраняем представление и обновляем состояние объекта self.object_on_board = Ellipse(pos=coord, size=self.size) self.state = True 

Почти готово, не сдавайтесь! Теперь нужно организовать весь игровой процесс, который будет происходить в классе Playground. Рассмотрим логику игры: она начинается с того, что змея помещается в случайные координаты. Игра обновляется при каждом перемещении змеи. Во время обновлений мы проверяем направление змеи и ее положение. Если змея сталкивается сама с собой или выходит за пределы поля – мы засчитываем поражение и игра начинается сначала.

Как будет осуществляться управление? Когда игрок касается экрана, мы сохраняем координаты касания. Когда палец будет перемещаться, мы будем сравнивать новое положение с исходным. Если позиция будет изменена на 10 % от размера экрана, мы будем определять это как инструкцию и обрабатывать ее:

class Playground(Widget): . def start(self): # Добавляем змею на поле self.new_snake() # Начинаем основной цикл обновления игры self.update() def reset(self): # Сбрасываем игровые переменные self.turn_counter = 0 self.score = 0 # Удаляем образы змеи и фрукта с поля self.snake.remove() self.fruit.remove() def new_snake(self): # Генерируем случайные координаты start_coord = ( randint(2, self.col_number - 2), randint(2, self.row_number - 2)) # Устанавливаем для змеи новые координаты self.snake.set_position(start_coord) # Генерируем случайное направление rand_index = randint(0, 3) start_direction = ["Up", "Down", "Left", "Right"][rand_index] # Задаем змее случайное направление self.snake.set_direction(start_direction) def pop_fruit(self, *args): # Генерируем случайные координаты для фрукта random_coord = [ randint(1, self.col_number), randint(1, self.row_number)] # получаем координаты всех клеток занимаемых змеей snake_space = self.snake.get_full_position() # Если координаты фрукта совпадают с координатами клеток змеи - генерируем # новые координаты while random_coord in snake_space: random_coord = [ randint(1, self.col_number), randint(1, self.row_number)] # Помещаем образ фрукта на поле self.fruit.pop(random_coord) def is_defeated(self): """ Проверяем, является ли позиция змеи проигрышной. """ snake_position = self.snake.get_position() # Если змея кусает свой хвост - поражение if snake_position in self.snake.tail.blocks_positions: return True # Если вышла за пределы поля - поражение if snake_position[0] > self.col_number \ or snake_position[0] < 1 \ or snake_position[1] >self.row_number \ or snake_position[1] < 1: return True return False def update(self, *args): """ Используется для смены игровых ходов. """ # Перемещаем змею на следующую позицию self.snake.move() # Проверяем на поражение # Если поражение - сбрасываем игру if self.is_defeated(): self.reset() self.start() return # Проверяем, находится ли фрукт на поле if self.fruit.is_on_board(): # Если змея съела фрукт - увеличиваем счет и длину змеи if self.snake.get_position() == self.fruit.pos: self.fruit.remove() self.score += 1 self.snake.tail.size += 1 # Увеличиваем счетчик ходов self.turn_counter += 1 def on_touch_down(self, touch): self.touch_start_pos = touch.spos def on_touch_move(self, touch): # Вычисляем изменение позиции пальца delta = Vector(*touch.spos) - Vector(*self.touch_start_pos) # Проверяем, изменение >10% от размера экрана: if not self.action_triggered \ and (abs(delta[0]) > 0.1 or abs(delta[1]) > 0.1): # Если да, задаем змее подходящее направление if abs(delta[0]) > abs(delta[1]): if delta[0] > 0: self.snake.set_direction("Right") else: self.snake.set_direction("Left") else: if delta[1] > 0: self.snake.set_direction("Up") else: self.snake.set_direction("Down") # Здесь мы регистрируем, что действие закончено, для того, чтобы оно не # происходило более одного раза за ход self.action_triggered = True def on_touch_up(self, touch): # Указываем, что мы готовы принять новые инструкции self.action_triggered = False 

Основной цикл

Здесь происходят процессы, устанавливающие положение фрукта, управляющие движением змеи и определяющие проигрыш:

def update(self, *args): """ Используется для смены игровых ходов. """ # Регистрация последовательности появления фруктов в планировщике событий if self.turn_counter == 0: self.fruit_rythme = self.fruit.interval + self.fruit.duration Clock.schedule_interval( self.fruit.remove, self.fruit_rythme) elif self.turn_counter == self.fruit.interval: self.pop_fruit() Clock.schedule_interval( self.pop_fruit, self.fruit_rythme) . # Каждое обновление будет происходить ежесекундно (1'') Clock.schedule_once(self.update, 1) 

Нужно добавить обработчик для события сброса игры:

def reset(self): . Clock.unschedule(self.pop_fruit) Clock.unschedule(self.fruit.remove) Clock.unschedule(self.update) 

Теперь мы можем протестировать игру.

Одна важная деталь. Чтобы приложение запустилось с правильным разрешением экрана, нужно сделать так:

class SnakeApp(App): game_engine = ObjectProperty(None) def on_start(self): self.game_engine.start() . 

И вуаля! Теперь вы можете запустить приложение. Остается только упаковать его с помощью buildozer и загрузить на устройство.

Пишем «Змейку» под Android на Python и Kivy 1

Создаем экраны

В приложении будет два экрана: приветствия и игровой. Также будет всплывающее меню настроек. Сначала мы сделаем макеты наших виджетов в .kv-файле, а потом напишем соответствующие классы Python.

Внешняя оболочка

PlaygroundScreen содержит только игровое поле:

: game_engine: playground_widget_id Playground: id: playground_widget_id 

Основной экран будет состоять из трех встроенных виджетов: название приложения, кнопки запуска игры и кнопки вызывающая меню настроек:

 AnchorLayout: anchor_x: "center" BoxLayout: orientation: "vertical" size_hint: (0.5, 1) spacing: 10 Label: size_hint_y: .4 text: "Ouroboros" valign: "bottom" bold: True font_size: 50 padding: 0, 0 AnchorLayout: anchor_x: "center" size_hint_y: .6 BoxLayout: size_hint: .5, .5 orientation: "vertical" spacing: 10 Button: halign: "center" valign: "middle" text: "Play" Button: halign: "center" valign: "middle" text: "Options" 

Всплывающее окно будет занимать ¾ экрана приветствия. Оно будет содержать виджеты, необходимые для установки параметров, и кнопку «Сохранить».

 title: "Options" size_hint: .75, .75 BoxLayout: orientation: "vertical" spacing: 20 GridLayout: size_hint_y: .8 cols: 2 AnchorLayout: anchor_x: "center" size_hint: 1, .25 Button: size_hint_x: 0.5 text: "Save changes" on_press: root.dismiss() 

Классы

На экране приветствия требуется только метод show_popup() , который будет вызываться при нажатии кнопки настроек на главном экране. Нам не нужно определять что-либо еще для кнопки включения игры, потому что она будет использовать способность своего родителя обращаться к диспетчеру экрана:

class WelcomeScreen(Screen): options_popup = ObjectProperty(None) def show_popup(self): # Создаем экземпляр всплывающего окна и отображаем на экране self.options_popup = OptionsPopup() self.options_popup.open() 

Теперь нужно сделать так, чтоб экран приветствия показывался при запуске игры, а игра начиналась только тогда, когда будет показано игровое поле:

class PlaygroundScreen(Screen): game_engine = ObjectProperty(None) def on_enter(self): # Показываем экран и начинаем игру self.game_engine.start() 

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

class OptionsPopup(Popup): pass 

Теперь добавим ScreenManager в приложение и зарегистрируем два экрана:

class SnakeApp(App): screen_manager = ObjectProperty(None) def build(self): # Объявление SkreenManager как свойства класса SnakeApp.screen_manager = ScreenManager() # Создание экземплров экранов ws = WelcomeScreen(name="welcome_screen") ps = PlaygroundScreen(name="playground_screen") # Регистрация экранов в SkreenManager self.screen_manager.add_widget(ws) self.screen_manager.add_widget(ps) return self.screen_manager 

Теперь нужно сказать кнопкам, что делать, когда на них нажимают:

 . Button: . on_press: root.manager.current = "playground_screen" Button: . on_press: root.show_popup() . Button: . on_press: root.dismiss() 

После проигрыша нужно возвращаться обратно на экран приветствия:

class Playground(Widget): . def update(self, *args): . # Проверяем проигрыш # Если это произошло, # показываем экран приветствия if self.is_defeated(): self.reset() SnakeApp.screen_manager.current = "welcome_screen" return … 

Добавляем настройки

У нас будет всего два параметра:

  1. Включение/отключение границ. Если границы включены, при выхождении змеи за пределы экрана засчитывается проигрыш. Если границы выключены, змея будет появляться на другой стороне, если выходит за пределы.
  2. Скорость змеи.

Добавляем необходимые виджеты во всплывающее окно:

 border_option_widget: border_option_widget_id speed_option_widget: speed_option_widget_id title: "Options" size_hint: .75, .75 BoxLayout: orientation: "vertical" spacing: 20 GridLayout: size_hint_y: .8 cols: 2 Label: text: "Borders" halign: "center" Switch: id: border_option_widget_id Label: text: "Game speed" halign: "center" Slider: id: speed_option_widget_id max: 10 min: 1 step: 1 value: 1 

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

class Playground(Widget): . # Пользовательские настройки start_speed = NumericProperty(1) border_option = BooleanProperty(False) . #Игровые переменные . start_time_coeff = NumericProperty(1) running_time_coeff = NumericProperty(1) . def start(self): # Если границы включены, рисуем прямоугольник вокруг поля if self.border_option: with self.canvas.before: Line(width=3., rectangle=(self.x, self.y, self.width, self.height)) # Вычисляем коэффициент изменения частоты обновления игры # (по умолчанию 1.1, максимальный 2) self.start_time_coeff += (self.start_speed / 10) self.running_time_coeff = self.start_time_coeff . def reset(self): # Сбрасываем игровые переменные . self.running_time_coeff = self.start_time_coeff . def is_defeated(self): . # Если змея вышла за границы, которые были включены -- поражение if self.border_option: if snake_position[0] > self.col_number \ or snake_position[0] < 1 \ or snake_position[1] >self.row_number \ or snake_position[1] < 1: return True return False def handle_outbound(self): """ Используется для перемещения змеи на противоположную сторону (только если границы выключены) """ position = self.snake.get_position() direction = self.snake.get_direction() if position[0] == 1 and direction == "Left": self.snake.tail.add_block(list(position)) self.snake.set_position([self.col_number + 1, position[1]]) elif position[0] == self.col_number and direction == "Right": self.snake.tail.add_block(list(position)) self.snake.set_position([0, position[1]]) elif position[1] == 1 and direction == "Down": self.snake.tail.add_block(list(position)) self.snake.set_position([position[0], self.row_number + 1]) elif position[1] == self.row_number and direction == "Up": self.snake.tail.add_block(list(position)) self.snake.set_position([position[0], 0]) def update(self, *args): . # Изменяем частоту появления фрукта if self.turn_counter == 0: . Clock.schedule_interval( self.fruit.remove, self.fruit_rythme / self.running_time_coeff) elif self.turn_counter == self.fruit.interval: . Clock.schedule_interval( self.pop_fruit, self.fruit_rythme / self.running_time_coeff) # Проверяем, пересечение змеей границ # если пересекает -- переносим на противоположную сторону if not self.border_option: self.handle_outbound() . # Проверяем готовность фрукта if self.fruit.is_on_board(): if self.snake.get_position() == self.fruit.pos: . self.running_time_coeff *= 1.05 . Clock.schedule_once(self.update, 1 / self.running_time_coeff)

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

class OptionsPopup(Popup): border_option_widget = ObjectProperty(None) speed_option_widget = ObjectProperty(None) def on_dismiss(self): Playground.start_speed = self.speed_option_widget.value Playground.border_option = self.border_option_widget.active 

Пишем «Змейку» под Android на Python и Kivy 2

Готово. Теперь можно упаковать проект и играть:

buildozer android debug # Создает apk-файл в папке ./bin buildozer android debug deploy # Установка на Android-устройство 

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

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