Случайны выбор дневника Раскрыть/свернуть полный список возможностей


Найдено 3568 сообщений
Cообщения с меткой

tutorial - Самое интересное в блогах

Следующие 30  »
rss_rss_hh_new

Использование ImGui с SFML для создания инструментов для разработки игр

Воскресенье, 13 Августа 2017 г. 09:52 (ссылка)

Привет, Хабр!

Данная статья — вольный перевод моей статьи на русский с некоторыми небольшими изменениями и улучшениями. Хотелось бы показать как просто и полезно использовать ImGui с SFML. Приступим.





Введение



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



Вот какие инструменты я создал с помощью ImGui для своей игры:





Редактор уровней





Консоль Lua





Редактор анимаций



Как можно видеть, в ImGui есть достаточно разных виджетов, чтобы создавать полезные и удобные интерфейсы.



ImGui и концепция immediate GUI



Immediate mode GUI немного отличается от классической методики программирования интерфейсов, которая называется retained mode GUI. ImGui виджеты создаются и рисуются в каждом кадре игрового цикла. Сами виджеты не хранят внутри себя своё состояние, либо хранят абсолютно минимальный необходимый минимум, который обычно скрыт от программиста.



В отличие от того же Qt, где для создания кнопки нужно создавать объект QPushButton, а затем связывать с ней какую-нибудь функцию-callback, вызываемую при нажатии, в ImGui всё делается гораздо проще. В коде достаточно написать:



if (ImGui::Button("Some Button")) {
... // код, вызываемый при нажатии кнопки
}


Данный код должен вызываться в каждой итерации игрового цикла, в которой эта кнопка должна быть доступна пользователю.



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



Советую посмотреть вот это видео Кейси Муратори про ImGui, если вы хотите узнать чуть больше о данной методике.



Итак, каковы же достоинства ImGui?




  • MIT-лицензия

  • Очень быстрая и занимает мало памяти

  • Постоянно обновляется и расширяется

  • Почти не производит динамическую аллокацию/деалокаццию (и это можно контролировать, устанавливая каким образом ImGui будет получать необходимую память)

  • Очень портабельна: есть множество биндингов для различных библиотек и платформ

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



Настройка



Итак, начнём




  1. Создайте простую программу на SFML, которая показывает пустое окно. Если вы раньше этим не занимались, то можете воспользоваться туториалом.

  2. Скачайте ImGui.

  3. Скачайте ImGui SFML биндинг и положите его в папку, в которую скачали ImGui.

    Важно: добавьте содержимое imconfig-SFML.h в imconfig.h

  4. Добавьте папку ImGui в include директории вашего проекта

  5. Добавьте следующие файлы в билд вашего проекта:




    • imgui.cpp

    • imgui_draw.cpp

    • imgui-SFML.cpp

    • imgui_demo.cpp




  6. Если вы будете получать ошибки линковки, то залинкуйте OpenGL к своему проекту.



Вот небольшой пример кода, который создаёт окошко ImGui и позволяет менять цвет заднего фона и заголовок окна. Объяснения того, что происходит, будут далее.



#include "imgui.h"
#include "imgui-sfml.h"

#include Graphics/RenderWindow.hpp>
#include System/Clock.hpp>
#include Window/Event.hpp>

int main()
{
sf::RenderWindow window(sf::VideoMode(640, 480), "");
window.setVerticalSyncEnabled(true);
ImGui::SFML::Init(window);

sf::Color bgColor;
float color[3] = { 0.f, 0.f, 0.f };

// здесь мы будем использовать массив char. Чтобы использовать
// std::string нужно сделать действия, описанные во второй части
char windowTitle[255] = "ImGui + SFML = <3";
window.setTitle(windowTitle);

sf::Clock deltaClock;
while (window.isOpen()) {
sf::Event event;
while (window.pollEvent(event)) {
ImGui::SFML::ProcessEvent(event);

if (event.type == sf::Event::Closed) {
window.close();
}
}

ImGui::SFML::Update(window, deltaClock.restart());

ImGui::Begin("Sample window"); // создаём окно

// Инструмент выбора цвета
if (ImGui::ColorEdit3("Background color", color)) {
// код вызывается при изменении значения, поэтому всё
// обновляется автоматически
bgColor.r = static_cast(color[0] * 255.f);
bgColor.g = static_cast(color[1] * 255.f);
bgColor.b = static_cast(color[2] * 255.f);
}

ImGui::InputText("Window title", windowTitle, 255);

if (ImGui::Button("Update window title")) {
// этот код выполняется, когда юзер жмёт на кнопку
// здесь можно было бы написать
// if(ImGui::InputText(...))
window.setTitle(windowTitle);
}
ImGui::End(); // end window

window.clear(bgColor); // заполняем окно заданным цветом
ImGui::SFML::Render(target);
window.display();
}

ImGui::SFML::Shutdown();
}


Вы должны увидеть что-то вроде этого:





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





Отлично, теперь разберёмся как всё работает.



ImGui инициализируется вызовом ImGui::SFML::Init, при вызове в функцию передаётся ссылка на окно sf::RenderWindow. В этот момент также создаётся стандартный шрифт, который будет использоваться в дальнейшем. (см. раздел Fonts how-to в описании imgui-sfml, чтобы увидеть как использовать другие шрифты).



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



В игровом цикле ImGui имеет две фазы: обновление и рендеринг.



Обновление состоит из обработки событий, обновления состояния ImGui и обновления/создания виджетов. Обработка событий происходит через вызов ImGui::SFML::ProcessEvent. ImGui обрабатывает события клавиатуры, мыши, изменения фокуса и размера окна. Обновление состояния ImGui производится в ImGui::SFML::Update, в неё передаётся delta time (время между двумя обновлениями), который ImGui использует для обновления состояния виджетов (например, для анимации). Также в данной функции вызывается ImGui::NewFrame, после вызова которой уже можно создавать новые виджеты.



Рендеринг ImGui осуществляется вызовом ImGui::SFML::Render. Очень важно создавать/обновлять виджеты между вызовами ImGui::SFML::Update и ImGui::SFML::Render, иначе ImGui будет ругаться на нарушение состояния.



Если вы рендерите реже, чем обновляете ввод и игру, то в конце каждой итерации вашего update необходимо также вызывать ImGui::EndFrame:



while (gameIsRunning) {
while (updateIsNeeded()) {
updateGame(dt);
ImGui::SFML::Update(window, dt);
ImGui::EndFrame();
}
renderGame();
}


Виджеты создаются путём вызова соответствующих функций (например, ImGui::InputInt или ImGui::Button). Если вызвать ImGui::ShowTestWindow, то можно увидеть много примеров использования ImGui, весь код можно найти в imgui_demo.cpp.



Полезные перегрузки функций для SFML



Для SFML в биндинге были созданы некоторые перегрузки функций, например в ImGui::Image и ImGui::ImageButton можно кидать sf::Sprite и sf::Texture, также можно легко рисовать линии и прямоугольники вызовом DrawLine, DrawRect и DrawRectFilled.



Заключение



Вот такая библиотека: проста в использовании и настройке, и очень полезна для создания инструментов и дебагинга. Приятного использования!



P.S. Если возникнет интерес, то могу перевести и вторую часть туториала, которая рассказывает про использование ImGui с современным C++ и стандартной библиотекой. Советую обратить на статью внимание тем, кто решит использовать (или уже использует) ImGui: она показывает как просто решать основные проблемы ImGui и делать всё проще и безопаснее, чем это делается в C++03.


Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/335512/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

[Из песочницы] Истинная реализация нейросети с нуля на языке программирования C#

Понедельник, 07 Августа 2017 г. 11:58 (ссылка)

image



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



Недавно мне пришлось сделать нейросеть для распознавания рукописных цифр(сегодня будет не совсем её код) в рамках школьного проекта, и, естественно, я начал разбираться в этой белиберде теме. Посмотрев приблизительно достаточно об этом в интернете, я понял чуть более, чем ничего. Но неожиданно(как это обычно бывает) получилось наткнуться на книгу Саймона Хайкина(не знаю почему раньше не загуглил). И тогда началось потное вкуривание матчасти нейросетей, состоящее из одного матана.



На самом деле, несмотря на обилие математики, она не такая уж и запредельно сложная. Понять сатанистские каракули и письмена этого пособия сможет среднестатистический 11-классник товарищ-физмат или 1~2-курсник технарьской шараги. Помимо этого, пусть книга достаточно объёмная и трудная для восприятия, но вещи, написанные в ней, реально объясняют, что "твориться у тачки под капотом". Как вы поняли я крайне рекомендую(ни в коем случае не рекламирую) "Нейронные сети. Полный курс" Саймона Хайкина к прочтению в том случае, если вам придётся столкнуться с применением/написанием/разработкой нейросетей и прочего подобного stuff'а. Хотя в ней нет материала про новомодные свёрточные сети, никто не мешает загуглить лекции от какого-нибудь харизматичного работника Yandex/Mail.ru/etc. никто не мешает.



Конечно, осознав устройство сеток, я не мог просто остановиться, так как впереди предстояло написание кода. В связи со своим параллельным занятием, заключающемся в создани игр на Unity, языком реализации оказался ламповый и няшный шарпей 7 версии(ибо она последняя актуальная). Именно в этот момент, оказавшись на просторах интернета, я понял, что число внятных туториалов по написанию нейросетей с нуля(без ваших фреймворков) на шарпе бесконечно мало. Ладно. Я мог использовать, всякие Theano и Tensor Flow, НО под капотом моей смерть-машины в моём ноутбуке стоит "красная" видеокарта без особой поддержки API, через которые обращаются к мощи GPU(ведь именно их и используют Theano/Tensor Flow/etc.).



Помогите школоте прошариться:

Моя видеокарта называется ATI Radeon HD Mobility 4570. И если кто знает, как обратиться к её мощностям для параллелизации нейросетевых вычислений, пожалуйста, напишите в комментарии. Тогда вы поможете мне, и возможно у этой статьи появится продолжение. Не осуждается предложение других ЯП.



Просто, как я понял, она настолько старая, что нифига не поддерживает. Может быть я не прав.



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



Здесь я не буду рассматривать код сети для распознования цифр(как упоминалось ранее), ибо я оставил его на флэшке, удалив с ноута, а искать сей носитель информации мне лень, и в связи с этим я помогу вам сконструировать многослойный полносвязный персептрон для решения задачи XOR и XAND(XNOR, хз как ещё).



Прежде чем начать программировать это, можнонужно нарисовать на бумаге, дабы облегчить представление структуры и работы нейронки. Моё воображение вылилось в следующую картинку. И да, кстати, это консольное приложение в Visual Studio 2017, с .NET Framework версии 4.7.



Краткая инфа по сетке(для тех, кому это хоть о чём-то говорит)

Многослойный полносвязный персептрон.

Один скрытый слой.

4 нейрона в скрытом слое(на этом количестве персептрон сошёлся).

Алгоритм обучения — backpropagation.

Критерий останова — преодоление порогового значения среднеквадратичной ошибки по эпохе.(0.001)

Скорость обучения — 0.1.

Функция активации — логистическая сигмоидальная.



image

Потом надо осознать, что нам нужно куда-то записывать веса, проводить вычисления, немного дебажить, ну и кортежи поюзать(но для них юзинг мне не нужен). Соответственно, using'и у нас такие.



Also

В папке release||debug этого прожекта располагаются файлы(на каждый слой по одному) по имени типа (fieldname)_memory.xml сами знаете для чего. Они создаются заранее с учётом общего количества весов каждого слоя. Знаю, что XML — это не лучший выбор для парсинга, просто времени было немного на это дело.



using System.Xml;
using static System.Math;
using static System.Console;


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



enum MemoryMode { GET, SET }
enum NeuronType { Hidden, Output }


Всё остальное будет происходить внутри пространства имён, которое я назову просто: Neural Network.



namespace NeuralNetwork
{
//всё, что будет описано ниже, располагается здесь
}


Прежде всего, важно понимать, почему нейроны входного слоя я изобразил квадратами. Ответ прост. Они ничего не вычисляют, а лишь улавливают информацию из внешнего мира, то есть получают сигнал, который будет пропущен через сеть. Вследствие этого, входной слой имеет мало общего с остальными слоями. Вот почему стоит вопрос: делать для него отдельный класс или нет? На самом деле, при обработке изображений, видео, звука стоит его сделать, лишь для размещения логики по преобразованию и нормализации этих данных к виду, подаваемому на вход сети. Вот почему я всё-таки напишу класс InputLayer. В нём находиться обучающая выборка организованная необычной структурой. Первый массив в кортеже — это сигналы-комбинации 1 и 0, а второй массив — это пара результатов этих сигналов после проведения операций XOR и XAND(сначала XOR, потом XAND).



class InputLayer
{
private (double[], double[])[] _trainset = new(double[], double[])[]//да-да, массив кортежей из 2 массивов
{
(new double[]{ 0, 0 }, new double[]{ 0, 1 }),
(new double[]{ 0, 1 }, new double[]{ 1, 0 }),
(new double[]{ 1, 0 }, new double[]{ 1, 0 }),
(new double[]{ 1, 1 }, new double[]{ 0, 1 })
};
//инкапсуляция едрид-мадрид
public (double[], double[])[] Trainset { get => _trainset; }//такие няшные свойства нынче в C# 7
}


Теперь реализуем самое важное, то без чего ни одна нейронная сеть не станет терминатором, а именно — нейрон. Я не буду использовать смещения, потому что просто не хочу. Нейрон будет напоминать модель МакКаллока-Питтса, но иметь другую функцию активации(не пороговую), методы для вычисления градиентов и производных, свой тип и совмещенные линейные и нелинейные преобразователи. Естественно без конструктора уже не обойтись.



class Neuron
{
public Neuron(double[] inputs, double[] weights, NeuronType type)
{
_type = type;
_weights = weights;
_inputs = inputs;
}
private NeuronType _type;
private double[] _weights;
private double[] _inputs;
public double[] Weights { get => _weights; set => _weights = value; }
public double[] Inputs { get => _inputs; set => _inputs = value; }
public double Output { get => Activator(_inputs, _weights); }
private double Activator(double[] i, double[] w)//преобразования
{
double sum = 0;
for (int l = 0; l < i.Length; ++l)
sum += i[l] * w[l];//линейные
return Pow(1 + Exp(-sum), -1);//нелинейные
}
public double Derivativator(double outsignal) => outsignal * (1 - outsignal);//формула производной для текущей функции активации уже выведена в ранее упомянутой книге
public double Gradientor(double error, double dif, double g_sum) => (_type == NeuronType.Output) ? error * dif : g_sum * dif;//g_sum - это сумма градиентов следующего слоя
}


Ладно у нас есть нейроны, но их необходимо объединить в слои для вычислений. Возвращаясь к моей схеме выше, хочу объяснить наличие чёрного пунктира. Он разделяет слои так, чтобы показать, что они содержат. То есть один вычислительный слой содержит нейроны и веса для связи с нейронами предыдущего слоя. Нейроны объединяются массивом, а не списком, так как это менее ресурсоёмко. Веса организованы матрицей(двумерным массивом) размера(нетрудно догадаться) [число нейронов текущего слоя X число нейронов предыдущего слоя]. Естественно, слой инициализирует нейроны, иначе словим null reference. При этом эти слои очень похожи друг на друга, но имеют различия в логике, поэтому скрытые и выходной слои должны быть реализованы наследниками одного базового класса, который кстати оказывается абстрактным.



abstract class Layer//модификаторы protected стоят для внутрииерархического использования членов класса
{//type используется для связи с одноимённым полю слоя файлом памяти
protected Layer(int non, int nopn, NeuronType nt, string type)
{//увидите это в WeightInitialize
numofneurons = non;
numofprevneurons = nopn;
Neurons = new Neuron[non];
double[,] Weights = WeightInitialize(MemoryMode.GET, type);
for (int i = 0; i < non; ++i)
{
double[] temp_weights = new double[nopn];
for (int j = 0; j < nopn; ++j)
temp_weights[j] = Weights[i, j];
Neurons[i] = new Neuron(null, temp_weights, nt);//про подачу null на входы ниже
}
}
protected int numofneurons;//число нейронов текущего слоя
protected int numofprevneurons;//число нейронов предыдущего слоя
protected const double learningrate = 0.1d;//скорость обучения
Neuron[] _neurons;
public Neuron[] Neurons { get => _neurons; set => _neurons = value; }
public double[] Data//я подал null на входы нейронов, так как
{//сначала нужно будет преобразовать информацию
set//(видео, изображения, etc.)
{//а загружать input'ы нейронов слоя надо не сразу,
for (int i = 0; i < Neurons.Length; ++i)
Neurons[i].Inputs = value;
}//а только после вычисления выходов предыдущего слоя
}
public double[,] WeightInitialize(MemoryMode mm, string type)
{
double[,] _weights = new double[numofneurons, numofprevneurons];
WriteLine($"{type} weights are being initialized...");
XmlDocument memory_doc = new XmlDocument();
memory_doc.Load($"{type}_memory.xml");
XmlElement memory_el = memory_doc.DocumentElement;
switch (mm)
{
case MemoryMode.GET:
for (int l = 0; l < _weights.GetLength(0); ++l)
for (int k = 0; k < _weights.GetLength(1); ++k)
_weights[l, k] = double.Parse(memory_el.ChildNodes.Item(k + _weights.GetLength(1) * l).InnerText.Replace(',', '.'), System.Globalization.CultureInfo.InvariantCulture);//parsing stuff
break;
case MemoryMode.SET:
for (int l = 0; l < Neurons.Length; ++l)
for (int k = 0; k < numofprevneurons; ++k)
memory_el.ChildNodes.Item(k + numofprevneurons * l).InnerText = Neurons[l].Weights[k].ToString();
break;
}
memory_doc.Save($"{type}_memory.xml");
WriteLine($"{type} weights have been initialized...");
return _weights;
}
abstract public void Recognize(Network net, Layer nextLayer);//для прямых проходов
abstract public double[] BackwardPass(double[] stuff);//и обратных
}


Соль абстрактных классов

Класс Layer — это абстрактный класс, поэтому нельзя создавать его экземпляры. Это значит, что наше желание сохранить свойства "слоя" выполняется путём наследования родительского конструктора через ключевое слово base и пустой конструктор наследника в одну строчку(ибо вся логика конструктора определена в базовом классе, и её не надо переписывать).



Теперь непосредственно классы-наследники: Hidden и Output. Сразу два класса в цельном куске кода.



class HiddenLayer : Layer
{
public HiddenLayer(int non, int nopn, NeuronType nt, string type) : base(non, nopn, nt, type){}
public override void Recognize(Network net, Layer nextLayer)
{
double[] hidden_out = new double[Neurons.Length];
for (int i = 0; i < Neurons.Length; ++i)
hidden_out[i] = Neurons[i].Output;
nextLayer.Data = hidden_out;
}
public override double[] BackwardPass(double[] gr_sums)
{
double[] gr_sum = null;
//сюда можно всунуть вычисление градиентных сумм для других скрытых слоёв
//но градиенты будут вычисляться по-другому, то есть
//через градиентные суммы следующего слоя и производные
for (int i = 0; i < numofneurons; ++i)
for (int n = 0; n < numofprevneurons; ++n)
Neurons[i].Weights[n] += learningrate * Neurons[i].Inputs[n] * Neurons[i].Gradientor(0, Neurons[i].Derivativator(Neurons[i].Output), gr_sums[i]);//коррекция весов
return gr_sum;
}
}
class OutputLayer : Layer
{
public OutputLayer(int non, int nopn, NeuronType nt, string type) : base(non, nopn, nt, type){}
public override void Recognize(Network net, Layer nextLayer)
{
for (int i = 0; i < Neurons.Length; ++i)
net.fact[i] = Neurons[i].Output;
}
public override double[] BackwardPass(double[] errors)
{
double[] gr_sum = new double[numofprevneurons];
for (int j = 0; j < gr_sum.Length; ++j)//вычисление градиентных сумм выходного слоя
{
double sum = 0;
for (int k = 0; k < Neurons.Length; ++k)
sum += Neurons[k].Weights[j] * Neurons[k].Gradientor(errors[k], Neurons[k].Derivativator(Neurons[k].Output), 0);//через ошибку и производную
gr_sum[j] = sum;
}
for (int i = 0; i < numofneurons; ++i)
for (int n = 0; n < numofprevneurons; ++n)
Neurons[i].Weights[n] += learningrate * Neurons[i].Inputs[n] * Neurons[i].Gradientor(errors[i], Neurons[i].Derivativator(Neurons[i].Output), 0);//коррекция весов
return gr_sum;
}
}


В принципе, всё самое важное я описал в комментариях. У нас есть все компоненты: обучающие и тестовые данные, вычислительные элементы, их "конгламераты". Теперь настало время всё связать обучением. Алгоритм обучения — backpropagation, следовательно критерий останова выбираю я, и выбор мой — есть преодоление порогового значения среднеквадратичной ошибки по эпохе, которое я выбрал равным 0.001. Для поставленной цели я написал класс Network, описывающий состояние сети, которое принимается в качестве параметра многих методов, как вы могли заметить.



class Network
{
//все слои сети
InputLayer input_layer = new InputLayer();
public HiddenLayer hidden_layer = new HiddenLayer(4, 2, NeuronType.Hidden, nameof(hidden_layer));
public OutputLayer output_layer = new OutputLayer(2, 4, NeuronType.Output, nameof(output_layer));
//массив для хранения выхода сети
public double[] fact = new double[2];//не ругайте за 2 пожалуйста
//ошибка одной итерации обучения
double GetMSE(double[] errors)
{
double sum = 0;
for (int i = 0; i < errors.Length; ++i)
sum += Pow(errors[i], 2);
return 0.5d * sum;
}
//ошибка эпохи
double GetCost(double[] mses)
{
double sum = 0;
for (int i = 0; i < mses.Length; ++i)
sum += mses[i];
return (sum / mses.Length);
}
//непосредственно обучение
static void Train(Network net)//backpropagation method
{
const double threshold = 0.001d;//порог ошибки
double[] temp_mses = new double[4];//массив для хранения ошибок итераций
double temp_cost = 0;//текущее значение ошибки по эпохе
do
{
for (int i = 0; i < net.input_layer.Trainset.Length; ++i)
{
//прямой проход
net.hidden_layer.Data = net.input_layer.Trainset[i].Item1;
net.hidden_layer.Recognize(null, net.output_layer);
net.output_layer.Recognize(net, null);
//вычисление ошибки по итерации
double[] errors = new double[net.input_layer.Trainset[i].Item2.Length];
for (int x = 0; x < errors.Length; ++x)
errors[x] = net.input_layer.Trainset[i].Item2[x] - net.fact[x];
temp_mses[i] = net.GetMSE(errors);
//обратный проход и коррекция весов
double[] temp_gsums = net.output_layer.BackwardPass(errors);
net.hidden_layer.BackwardPass(temp_gsums);
}
temp_cost = net.GetCost(temp_mses);//вычисление ошибки по эпохе
//debugging
WriteLine($"{temp_cost}");
} while (temp_cost > threshold);
//загрузка скорректированных весов в "память"
net.hidden_layer.WeightInitialize(MemoryMode.SET, nameof(hidden_layer));
net.output_layer.WeightInitialize(MemoryMode.SET, nameof(output_layer));
}
//тестирование сети
static void Test(Network net)
{
for (int i = 0; i < net.input_layer.Trainset.Length; ++i)
{
net.hidden_layer.Data = net.input_layer.Trainset[i].Item1;
net.hidden_layer.Recognize(null, net.output_layer);
net.output_layer.Recognize(net, null);
for (int j = 0; j < net.fact.Length; ++j)
WriteLine($"{net.fact[j]}");
WriteLine();
}
}
//запуск сети
static void Main(string[] args)
{
Network net = new Network();
Train(net);
Test(net);
ReadKey();//чтоб консоль не закрывалась :)
}
}


Результат обучения.

image



Итого, путём насилования мозга несложных манипуляций, мы получили основу работающей нейронной сети. Для того, чтобы заставить её делать что-либо другое, достаточно поменять класс InputLayer и подобрать параметры сети для новой задачи. Через время(какое конкретно не знаю) напишу продолжение этой статьи с руководством по созданию с нуля свёрточной нейронной сети на C# и здесь сделаю апдейт этой с ссылками на MLP-рекогнитор картинок MNIST(но это не точно) и код статьи на Python(точно, но дольше ждать).



За сим всё, буду рад ответить на вопросы в комментариях, а пока извольте, новые дела ждут.

P.S.: Для желающих помацать код клацать.

P.P.S.: Сеть по ссылке выше — потненькая необученная няша-стесняша.


Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/335052/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

[Перевод] SwiftyBeaver руководство для iOS: платформа логирования для Swift

Четверг, 03 Августа 2017 г. 12:20 (ссылка)

Ошибки бывают разных форм и размеров. Некоторые ошибки милые и пушистые и их легко исправить, потому что вы можете запустить ваше приложение и пойти прямо к тому месту где происходит ошибка. Если вы счастливчик, Xcode покажет вам какая строка привела к падению вашего приложения.



Другие ошибки более зловещие. Они таятся в фоне и случаются когда вы менее всего этого ожидаете. Они ужасы ночных кошмаров и рекламных роликов Listerine.



В этом уроке вы обнаружите секретное оружие, с которым сможете разобраться с этим зловещими ошибками, которые притаились в фоне. При должном использовании это оружие похоже на свет фонарика в самых глубоких и темных расщелинах вашего приложения. У этих жутких ошибок не будет ни единого шанса.



Это оружие — SwiftyBeaver, основанная на Swift система логирования для iOS и macOS.



В этом руководстве вы узнаете как отлаживать ваше приложения используя SwiftyBeaver и получите следующие полезные умения на этом пути:


  • When SwiftyBeaver logging is better than print() and NSLog()

  • Как получить доступ к журналам SwiftyBeaver в Crypto Cloud

  • Как использовать уровни логирования для различных типов сообщений журнала

  • Как фильтровать записи, чтобы облегчить поиск ошибок





Примечание: перед началом урока вы должны быть знакомы с Cocoapods и фреймворком CoreLocation. Сначала ознакомьтесь с этими руководствами чтобы получить необходимые умения.



Начало



Загрузите начальный проект. Этот архив содержит проект и все необходимые файлы для завершения урока.



Вы собираетесь создать приложение для отслеживания ваших детей. Если они отходят слишком далеко от дома, приложение будет предупреждать их.



Это приложение названо Ten PM (Вы знаете где ваши дети?).

Откройте TenPM.xcworkspace и посмотрите проект. Теперь откройте Main.storyboard и вы увидете два view controller'а внутри navigation controller'а:







View controller слева — это тот, который родители могут использовать, чтобы сказать приложению где находится «дом» а также задать максимальное расстояние, на которое дети могут отходить. Класс — TrackingSetupViewController, который запрашивает доступ к службам геолокации и сохраняет введенное расстояние для ограничения расстояния.



View controller справа — это тот, который будут использовать дети. Он отображает карту с зеленым кругом представляющим безопасную зону. Он также содержит блок текста ниже, чтобы сообщим им, находятся ли они в настоящее время в безопасной зоне или нет. Класс — TrackingViewController, в котором используется MKMapView отображающий допустимую для детей территорию.



И здесь еще несколько строк на заметку:


  • LocationTracker.swift: выступает в роли делегата CLLocationManager. Здесь обрабатываются обновления местоположения и изменения разрешений для служб геолокации.

  • ParentProfile.swift: сохраняет важные данные пользователя в NSUserDefaults. Эти данные включают в себя местоположение дома и расстояние, на которое их дети могут отходить от дома.





Используем начальное приложение



Запустите предложение на iOS симуляторе. Вы увидете view controller который представлен классом TrackingSetupViewController:







Перед тем как настроить отслеживание, вы должны быть уверены, что симулятор предоставляет данные о начальном местоположении. Чтобы сделать это выберете симулятор, затем на панели выберите Debug\Location\Apple.







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



Сейчас введите 1 в текстовое поле ниже как безопасное расстояние от дома. Это означает, что вы не хотите в дальнейшем быть дальше чем 1 километр от симулированной локации Apple.







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



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



Теперь когда все настроено, самое время притвориться, что сейчас 2011 год, вы получили прототип iPhone 4 и отправляетесь на прогулку. Давайте симулируем новое местоположение, вне безопасной зоны, в том месте где вы назначили встречу с репортером Gawker.



В меню iOS Simulator снова пройдите в Debug\Location, но в этот раз выберите Custom Location. Введите 37.3393 как широту и -122.0426 как долготу после чего нажмите OK.







Вы должны видеть, как синяя точка движется к новому местоположению и сообщение внизу экрана меняется чтобы сообщить вам, что вы на опасном расстоянии от дома.



Это краткое описание функциональности приложения Ten PM. Это довольно простое приложение, которое может быть легко расширено. Вы могли бы начать отправку уведомлений родителям когда их ребенок выходит за пределы безопасной зоны и т.д.



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



Установка SwiftyBeaver



Для начала установим SwiftyBeaver используя Cocoapods. Найдите корневую директорию проекта и найдите там Podfile. Откройте его в редакторе, который для вас наиболее удобен и добавьте следующую строку под строчкой # Pods for TenPM:

pod 'SwiftyBeaver'


Откройте Terminal, перейдите в директорию с эти Podfile и введите следующую команду:

pod install



После минуты или может чуть больше установка SwiftyBeaver в ваш проект будет завершена. Теперь откройте AppDelegate.swift и добавьте следующую строку в подходящем месте в верхней части файла:

import SwiftyBeaver




Скомпилируйте этот проект и вы увидете, что SwiftyBeaver теперь доступен в проекте потому что он был успешно скомилирован.






Примечание: это руководство по установке работает с Cocoapods и Swift 3. Более подробную инструкцию по установке вы можете найти на странице репозитория проекта SwiftyBeaver на GitHub.






Написание ваших первых логов со SwiftyBeaver



Фух. Вы готовы написать немного кода?



Загляните в AppDelegate.swift снова. Это место где вы сейчас настроите логирование для SwiftyBeaver. Вы заметили заготовку метод в стартовом проекте?

setupSwiftyBeaverLogging() вызывается каждый раз когда ваше приложение запускается, и как следует из названия вы будете его использовать для подготовки SwiftyBeaver к использованию. Перейдите к этому методу и добавьте следующее:

let console = ConsoleDestination()
SwiftyBeaver.addDestination(console)


SwiftyBeaver это необычный инструмент логирования. Он имеет несколько входов названых Destinations, которые вы можете конфигурировать в соответствии с вашими нуждами.

Destination определяет где появляются ваши логи.

ConsoleDestination() создает Console Destination, который вы добавили как активный destination для SwiftyBeaver.

В метод application(_:didFinishLaunchingWithOptions:) добавьте следущую строку кода, после вызова метода setupSwiftyBeaverLogging():

SwiftyBeaver.info("Hello SwiftyBeaver Logging!")


Этот код отображает лог уровня info в консоли когда приложение запустится. Вы узнаете больше об уровнях логов чуть позже.

Запустите Ten PM снова. Проверьте консоль когда приложение запустится и вы увидете запись:

12:06:05.402 INFO AppDelegate.application():36 — Hello SwiftyBeaver Logging!



Круто! Вы ведете логирование в консоли с помощью SwiftyBeaver. Изобращение сердца относится к уровню логов, которые разъяснены ниже.



Краткое объяснение уровней логов



Я уверен, что на этом этапе вам интересно почему SwiftyBeaver заставляет использовать метод с именем info() вместо более понятных и логичных имен таких как log() или print().

Это связано с тем, что называется Log Levels.

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

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

SwiftyBeaver придерживается договоренностей и использует эти 5 уровней логов:


  1. Verbose: наименьший уровень приоритета. Используйте их для контекстной информации.

  2. Debug: используйте этот уровень для отображения переменных или иных результатов, которые помогут вам исправить ошибку или решить проблему.

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

  4. Warning: используйте этот уровень когда достигаете состояния, которое не обязательно станет причиной проблемы, но решительно ведет приложение в этом направлении.

  5. Error: самый сериезный и высокий приоритет логов. Используйте его только тогда, когда ваше приложение вызывало серьезную ошибку.





Как записывать разные уровни логов со SwiftyBeaver



Также как вы использовали метод info() для логирования на уровне info, вы можете использовать четыре других метода: verbose(), debug(), warning(), и error() для логирования на остальных четырех уровнях.



Попробуйте. Вернитесь назад к методу application(_:didFinishLaunchingWithOptions:) и замените вызов info() на следующим кодом:

SwiftyBeaver.debug("Look ma! I am logging to the DEBUG level.")




Теперь запустите ваше приложение. Вы должны увидеть другой цвет иконки сердца и другое сообщение на уровне debug.

14:48:14.155 DEBUG AppDelegate.application():36 — Look ma! I am logging to the DEBUG level.



Обратите внимание, что цвет иконки сменился на зеленый, чтобы обозначить debug уровень логов. Это одна из причин, почему SwiftyBeaver лучше, чем print() и NSLog(). Вы можете быстро просмотреть логи и найти сообщения на уровне, который вас интересует.



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



Настройка SwiftyBeaver Crypto Cloud



Одна из крутейших особенностей SwiftyBeaver это возможность ведения логов напрямую в облаке. SwiftyBeaver имеется приложение для macOS, которое позволяет просматривать логи в реальном времени. Если вы когда-нибудь интересовались что же происходит в вашем приложении установленном на тысячи устройств, то теперь вы можете это знать.

Загрузите приложение SwiftyBeaver для Mac. Откройте его и заполните форму, чтобы создать аккаунт.





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



Назовите этот файл TenPMLogs, выберите любую локацию по своему усмотрению и нажмите Save.





Файл, которые в создали хранит логи для одного приложения, по этой причине вы и назвали его TenPMLogs. Когда вы открываете этот файл с помощью приложения SwiftyBeaver Mac App вы можете увидеть логи, связанные с приложением Ten PM.

После сохранения файла вам будет предоставлен выбор. Вы можете зарегистрировать новое приложение или просмотреть логи из ранее зарегистрированного приложения. Вы продолжите на вкладке New App.





Кликните по кнопке Generate New App Credentials. Вы должны увидеть следующий экран, показывающий сгенерированные идентификатор и ключи для вашего приложения:



Теперь пора добавить еще один destination для логов используя только что созданные учетные данные безопасности Crypto Cloud. Оставьте это окно открытым и вернитесь к методу setupSwiftyBeaverLogging() в AppDelegate.swift.



Добавьте эти строки в низ этого метода, заменяя строки на соответствующие значение из приложения SwiftyBeaver Mac App:

let platform = SBPlatformDestination(appID: "Enter App ID Here",
appSecret: "Enter App Secret Here",
encryptionKey: "Enter Encryption Key Here")

SwiftyBeaver.addDestination(platform)


Вернитесь в приложение SwiftyBeaver Mac App и кликнките по кнопке Connect. Теперь запустите приложение Ten PM.



Примечание: если вы сглупили как и я и нажали кнопку Connect до того как скопировали учетные данные в ваше приложение, вы можете кликнуть по кнопке настроек (шестеренка) в приложении SwiftyBeaver Mac App, чтобы посмотреть их после подключения.







Теперь проверьте это! Ваши логи появляются в приложении SwiftyBeaver Mac App. Если вы не видите записи сразу же, то не волнуйтесь. Иногда это занимает несколько минут прежде чем записи в журнале попадут в облако. В конечном итоге они все равно появятся.

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



Фильтрация логов по уровню логирования, избранным записям и минимальным уровням логирования



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

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

Также вы можете пометить запись как избранную. Вы можете видеть все избранные записи по клику на звезду в левом меню.



Фильтрация по минимальному уровню логирования



Это еще одна особенность, которую вы действительно полюбите. SwiftyBeaver позволяет вам установить минимальный уровень для заданного destination. Если вы хотите использовать ваш Crypto Cloud аккаунт для серьезных предупреждений и ошибок вы можеет это сделать.



Для начала замените текущий код логирования в applicationDidFinishLaunching() на следующий:

SwiftyBeaver.verbose("Watch me bloviate. I'm definitely not important enough for the cloud.")

SwiftyBeaver.debug("I am more important, but the cloud still doesn't care.")

SwiftyBeaver.info("Why doesn't the crypto cloud love me?")

SwiftyBeaver.warning("I am serious enough for you to see!")

SwiftyBeaver.error("Of course I'm going to show up in your console. You need to handle this.")


Сейчас вы логируете сообщению на каждом уровне. Запустите приложение и вы должны видеть как все эти записи попадают в Crypto Cloud.







В методе setupSwiftyBeaverLogging() добавьте следующее перед тем как добавляете эту платформу в destination:

platform.minLevel = .warning




Запустите приложение снова. Посмотрите на новый вид вашей консоли Crypto Cloud.





Вы должны видеть только предупреждения и ошибки за последнее время. Никаких других записей не попадет в Crypto Cloud. Вы по прежнему видите все в консоли Xcode!



Примечание: Вы можете задать минимальный уровень для любого типа логирования (destination) в SwiftyBeaver. Вы можете создать несколько Crypto Cloud журналов для различных уровней логирования. SwiftyBeaver имеет большой простор для разных способов логирования.



Исправление труднодоступных ошибок



Было весело поговорить о логировани в Crypto Cloud, но у вас есть некоторые зловещие ошибки, которые вам нужно исправить. Ну или как минимум у вас есть зловещие ошибки, которые вы намерено смоделируете и затем исправите их.

Начните с очистки всех ранних записей. Удалите все логирование из application(_:didFinishLaunchingWithOptions:). Также удалите установку значения для platform.minLevel чтобы по умолчанию отображались все записи.

Для этого теста вам нужно будет видеть все записи в логах.



Моделирование ошибки



Теперь вы готовы к использованию облачного логирования и это самое время чтобы смоделировать неприятный фоновый баг.



Откройте LocationTracker.swift и найдите там метод locationManager(_:didUpdateLocations:). Вставьте туда следующий код после объявления двух guard значений в верхней части метода:

let bug = true

if bug == true {
return
}


Это довольно глупо, но здесь выпритворяетесь что где-то в LocationTracker есть ошибка, которая препятствует отслеживанию местоположения пользователя. Этот код здесь предотвращает уведомления о том, что пользователь вошел или покинул безопасную зону. Когда «ошибка» «отключена» этот код будет работать нормально.

Запустите приложение, чтобы убедиться в том, что «ошибка» есть.





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



Отслеживание ошибки с помощью SwiftyBeaver



Как теперь мы могли бы отследить эту ошибку с помощью SwiftyBeaver?

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

Для начала импортируйте SwiftyBeaver в верхней части LocationTracker:

import SwiftyBeaver


Далее добавьте запись вверху метода locationManager(_:didUpdateLocations:):

SwiftyBeaver.info("Got to the didUpdateLocations() method")


Теперь добавьте несколько строк внизу и вставьте эту строку прямо после объявления константы bug:

SwiftyBeaver.debug("The value of bug is: \(bug)")


Далее добавьте информацию когда мы проверяем значение этой константы, но перед return:

SwiftyBeaver.error("There's definitely a bug... Aborting.")


И наконец, добавьте запись в конце метода locationManager(_:didUpdateLocations:):

SwiftyBeaver.info("Got to the end the didUpdateLocations method")




Этой информации должно быть достаточно, чтобы начать вычислиение что же происходит в вашем коде. Вот как должно выглядеть все содержимое метода locationManager(_:didUpdateLocations:):

SwiftyBeaver.info("Got to the didUpdateLocations() method")

guard let homeLocation = ParentProfile.homeLocation else {
ParentProfile.homeLocation = locations.first
return
}

guard let safeDistanceFromHome = ParentProfile.safeDistanceFromHome else {
return
}

let bug = true
SwiftyBeaver.debug("The value of bug is: \(bug)")

if bug == true {
SwiftyBeaver.error("There's definitely a bug... Aborting.")
return
}

for location in locations {
let distanceFromHome = location.distance(from: homeLocation)

if distanceFromHome > safeDistanceFromHome {
NotificationCenter.default.post(name: TenPMNotifications.UnsafeDistanceNotification, object: nil)
} else {
NotificationCenter.default.post(name: TenPMNotifications.SafeDistanceNotification, object: nil)
}
}
SwiftyBeaver.info("Got to the end the didUpdateLocations method")


В симуляторе установите местположение в Apple также как делали это ранее. Теперь запустите приложение. Не смотря на то, что сейчас вам доступны логи в консоли Xcode проигнорируйте их и представьте, что вы отслеживаете записи в Crypto Cloud от удаленного пользователя.

Введите безопасное расстояние в 1 километр и нажмите NEXT. После загрузки карты смените ваше местоположение на широту 37.3397 и долготу -122.0426 через особое местоположение.

И снова вы вышли за пределы вашей безопасной зоны без обновления текста.





Вы должны заметить следующие записи, повторяющиеся в SwiftyBeaver Crypto Cloud после того, как установите фильтр в ALL:





Воу, это действительно полезно! Если вы вернетесь к своему коду в классе LocationTracker то сможете сопоставить это с логами и вы увидете как далеко проходит выполнение вашего кода прежде чем оно остановится. Здесь это явно в if bug == true где была запись об ошибке была выведена.

Чтобы «исправить» эту «ошибку» просто установите константу bug в значение false где она объявляется в методе locationManager(_:didUpdateLocations:):

let bug = false




Запустите приложение. Смоделируйте начальную позицию в Apple со смещением за пределы безопасной зоны. В этот раз вы увидите предупреждение о безопасной зоне, которое сработало правильно.

Также вы должны видеть следующие записи в вашей Crypto Cloud консоли.





Это выглядит так как будто приложение миновало ошибку и снова успешно реагирует на изменения местоположения. Вы успешно исправили эту ошибку!



Куда дальше?



Вы можете найти завершенный проект к этому уроку здесь.

Также ознакомьтесь с файловым логированием с помощью SwiftyBeaver. Если у вас есть iPhone и вы хотите быть на 100% увереным, что получите все свои записи (даже если нет Интернет-подключения).

Для более интересных случаев ознакомьтесь с форматированием записей.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/333852/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
Erina50

TUTOSERINA

Воскресенье, 23 Июля 2017 г. 15:20 (ссылка)


Aziza



 



6118043_Aziza (700x428, 114Kb)



6118043_untitled_1_ (71x76, 30Kb)


Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
Juliana-Juliana

Шкатулка «Лион».

Вторник, 18 Июля 2017 г. 19:01 (ссылка)

Это цитата сообщения MonaLina Оригинальное сообщение


Автор Наталья Кушнир



21 (700x420, 342Kb)



22 (700x420, 341Kb)



В работе были использованы следующие материалы: 1. Заготовка МДФ 2. Шпатлевка по дереву 3. Фоновая распечатка 4. Клей ПВА 5. Краска акриловая- синяя, охра, белая, бирюза. 6. Краска масляная черная, умбра жженая 7. Лак акриловый матовый 8. Воск жидкий

Читать далее...
Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
Juliana-Juliana

Рисуем пион по шаблону в технике one stroke. Урок Кристины Агилера. Учебные карты.

Воскресенье, 16 Июля 2017 г. 15:07 (ссылка)


Великолепный урок Кристины Агилера



https://youtu.be/6IiF_5f9WMs

 











Шаблоны для росписи пионов найдете в моем посте

http://www.liveinternet.ru/users/juliana-juliana/post418070222/



тренировочные карты под катом
Метки:   Комментарии (1)КомментироватьВ цитатник или сообщество
rss_rss_boingboing

Watch this artist repaint a Barbie doll to look like Wonder Woman

Пятница, 14 Июля 2017 г. 22:08 (ссылка)

https://www.youtube.com/watch?v=9GBVyVdd_kM


Watch as an off-the-shelf Barbie gets a superhero makeover in this delightful tutorial. Includes a list of materials used and a very relaxing voiceover. (more…)

http://feeds.boingboing.net/~r/boingboing/iBag/~3/Q_S8rONBlm4/watch-this-artist-repaint-a-ba.html

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
Juliana-Juliana

Уроки китайских мастеров . Как нарисовать пион.

Вторник, 11 Июля 2017 г. 13:07 (ссылка)

Видео уроки от мастеров китайской традиционной живописи.
Чен Yixuan "Как нарисовать пион"

https://youtu.be/ELkwJWj2Ngk





Мастер Чжао Ксиао пион
Peony Demo Part 1 of 3

https://youtu.be/eNtjnbFnllk



Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
tatyana_marf

Декорируем старые пластиковые горшки. Мастер-класс | Домохозяйка

Понедельник, 10 Июля 2017 г. 09:11 (ссылка)
domohozyajka.com/2015/02/de...ter-klass/

Старые пластиковые горшки иногда очень быстро теряют свой внешний вид, особенно, когда эксплуатируются летом во дворе и на даче. Они выгорают, покрываются
Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

Умный замок на Android Things и Raspberry Pi3

Четверг, 29 Июня 2017 г. 10:02 (ссылка)

В декабре 2016 года Google анонсировал выход первой Developer Preview версии Android Things. С тех пор проект сильно изменился. Все еще доступна только preview-версия, но с каждым шагом у платформы появляются новые возможности и растет число поддерживаемых устройств.



С каждым днем появляются новые примеры использования IoT устройств в реальном мире, а сама платформа становится все более привлекательной. Мы в Live Typing решили тоже погрузиться в интереснейший мир Интернет Вещей и рассказать о своем опыте. Эта статья для тех, кто слышал об Android Things, но боялся попробовать. А также о том, как мы реализовали свой «умный замок» и пользуемся им в собственном офисе.



Описание идеи



Проблема №1: Наша компания снимает офис с системой электронных пропусков и стеклянными дверями. Часто сотрудники забывают свои карточки дома или просто выходят на улицу без них, а потом стучатся или звонят коллегам, чтобы попасть обратно. Карточку нужно прикладывать к магнитному замку и внутри, и снаружи офиса. Если внутри мы просто привязали карточку на веревочку, то попасть в офис без ключа снаружи — это проблема, которую мы бы хотели решить.



Проблема №2: По выходным в нашем офисе проводятся разного рода митапы. Основная часть присутствующих не является нашими коллегами. Их количество варьируется, но сколько бы их ни было, дать ключ постороннему человеку мы не можем, как и держать дверь все время открытой — это небезопасно для нашего имущества. Поэтому сейчас приходится назначать специального «человека-швейцара» или подпирать дверь чем придётся.



Отключить, убрать или модернизировать проходную систему мы не имеем права, но на то, чтобы подключить что-то снаружи, ограничения не распространяются. Мы решили оборудовать дверь сервоприводом, который будет поворачивать прикреплённую карточку к считывающему датчику при успешном распознавании лица. Фотографию лица предоставляет камера. Таким образом получаем этакий умный дверной замок.



С таким замком у нас появляется своя идентификационная система с блэкджеком и широкими возможностями. (Например смешные фотографий сотрудников, из которых можно делать стикеры для внутреннего шутливого использования). Если говорить о второй проблеме, то замок позволяет сделать регистрацию и пропуск по фотографиям участников встречи. Awesome, не правда ли?



Дальше идея обрастала сопутствующими нюансами и вопросами. Когда начинать и заканчивать фотографировать? Как часто и долго делать фотографии? Стоит отключать систему в нерабочие часы и темное время суток? Как визуализировать работу системы? Но обо всем этом далее и по отдельности. За основу проекта мы взяли пример Doorbell с официальной страницы Android Things. Оригинальный пример называется «звонок», однако мы хотели, чтобы пользователь системы отпирал дверь с минимальными усилиями, а посторонние люди внутрь не попали. Поэтому мы посчитали более правильным назвать его «умный замок».



Благодарности



Сначала у нас не было ничего. Ни самого Raspberry, ни комплектующих, ни опыта работы с ними — только теоретические знания, полученные из статей и документации. Первый раз попробовать поиграться c Android Things удалось на CodeLab проводимом в нашей IT-столице Сибири, городе Омск, ребятами из Mobilatorium. Мы быстро завели проект, где вместо Google Cloud Vision имплементировали свою реализацию FindFace на Tensor Flow. Если вам интересно, как устроен back-end, то можете ознакомиться с отличной статьей «Ищем знакомые лица», где автор очень подробно описал принципы и алгоритмы работы с распознаванием лиц. Если нет, то можете воспользоваться связкой Google Cloud Vision + Firebase Realtime Database, как это сделано в приведённом выше CodeLab.



Когда мы вернулись в офис, оказалось что у нашего сотрудника Миши есть все необходимые компоненты и даже сам Raspberry Pi3, которые он недавно приобрёл, желая побаловаться чем-то эдаким. Также остались компоненты от ребят, проводивших [летнюю школу] (https://vk.com/mobilatorium?w=wall-130802553_81%2Fall) с изучением Arduino. Огромное им спасибо за предоставленные железяки.



Комплектующие



Для реализации умного замка нам понадобились:




  • Raspberry Pi 3 — 1шт;

  • MicroSD 8Gb — 1шт;

  • NoIR Camera V2 — 1шт;

  • Breadboard — 1шт;

  • Infrared PIR Motion Sensor Module — 1шт;

  • SG90 Servo Motor — 1шт;

  • Photoresistor (Light Sensor) — 1шт;

  • Push Button — 1шт;

  • LED — 3 шт;

  • Resistors (1k) — 3шт;

  • Resistors (10k) — 1 шт;

  • Pin Jumper Wires — много штук;

  • Изолента — 1шт.



Все комплектующие легко найти и недорого заказать на китайских сайтах, и мы специально оставили их названия на английском для удобства поиска. Стоит лишь упомянуть, что комплект обойдётся вам примерно в 100-125$. Самыми дорогими компонентами являются камера и сам Raspberry Pi3.



Реализация



Для лучшего понимания мы разобьём описание реализации на отдельные шаги. Соединяя схему по частям, удобней восстановить картину на любом шаге. Кода мало, и если вы написали хотя бы одно приложение под Android, то проблем у вас не возникнет, на мой взгляд. Для разработки будем использовать привычную Android Studio. Вы даже сможете использовать свои любимые библиотеки и фреймворки, такие как Dagger, RxJava, Retrofit, OkHttp, Timber и т.п.



Перед началом работ стоит ознакомиться с кратким введением в Android Things, а также с пинами на Raspberry Pi3. А эта цветная картинка с распиновкой является отличным наглядным гайдом и пригодится вам ещё не один раз.



Raspberry Pi поддерживает разный набор интерфейсов оборудования. Но нас главным образом интересуют GPIO (General-purpose input/output) и PWM (Pulse Width Modulation). Они будут основными способами взаимодействия между платой и датчиками при реализации нашего проекта.



Библиотеки для различных периферийных устройств уже написаны до нас, а многие из них доступны даже сразу из коробки. Поэтому, когда начнёте интегрировать новый датчик, сначала ознакомтесь с этим и этим репозиториями. Здесь собрано множество драйверов. Скорее всего, вы найдете нужный. Если нет, то Google предоставили специальный концепт User Drivers, который расширяет возможности Android Framework Services. Он перенаправляет происходящие в железе во фреймворк и позволяет обработать их стандартными средствами Android API и таким образом создать свой драйвер. Коротко работу с любым драйвером можно разбить на следующие этапы:




  • создать объект драйвера;

  • зарегистрировать драйвер;

  • подписаться на события;

  • отписаться и отменить регистрацию;

    Можете ознакомиться с примером реализации собственного драйвера в этой статье.



Шаг 1: Установка Android Things



Устанавливаем самый свежий образ Android Things на Raspberry Pi3. Там же приведены ссылки на инструкции по установке образа для различных операционных систем.



Убедиться, что всё успешно установлено, можно, подключив к Raspberry какой-нибудь дисплей через HDMI-кабель. Если все окей, то вы увидите на экране анимацию загрузки Android Things.



img



Для более комфортного взаимодействия с устройством в документации советуют настроить подключение по WiFi. После этого внизу экрана под заставкой Android Things появится IP-адрес девайса в вашей WiFi сети.



img



Если в вашей сети только одно такое устройство, то можно не запоминать адрес и не проверять его всякий раз при изменении, а воспользоваться зарезервированным Raspberry именем хоста и подключаться через adb командой.



$ adb connect Android.local


Шаг 2: Создание приложения



Создаём новое приложение через Android Studio. Можно визуализировать работу своей программы стандартными Android виджетами, разместив их экране, хотя это не обязательно. Ознакомьтесь с полной инструкцией создания первого Android Things приложения на официальном сайте, а мы разберём только основные моменты.



Минимальные требования:




  • Android Studio 2.2 и выше;

  • SDK Tools 25.0.3 и выше;

  • Min SDK 24 и выше;

  • Target SDK 24 и выше.



Добавим в app/build.gradle зависимость Android Things support library, которая даст нам доступ к нужному API, не являющемся частью стандартного Android SDK.



dependencies {
...
provided 'com.google.android.things:androidthings:0.4-devpreview'
...
}


Каждое приложение связывается с используемой по умолчанию библиотекой Android, в которой имеются базовые пакеты для построения приложений (со стандартными классами, например Activity, Service, Intent, View, Button, Application, ContentProvider и так далее).

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




...

...


Android Things позволяет одновременно устанавливать только одно приложение, а больше нам и не надо. Благодаря этому ограничению появляется возможность декларировать для Activity, как IOT_LAUCHER в AndroidManifest приложения, что позволяет запускать это Activity по-умолчанию сразу же при старте девайса. Также оставим стандартный чтобы Android Studio смогла запустить наше приложение после сборки и деплоя.




...












...


Шаг 3: Кнопка



Начнём с подключения тактовой кнопки, при нажатии на которую камера сделает один снимок. Это простой механизм: толкатель нажимается — цепь замыкается. Кнопка с четырьмя контактами представляет собой две пары соединительных рельс. При замыкании и размыкании между пластинами кнопки возникают микроискры, провоцирующие многократные переключения за крайне малый промежуток времени. Такое явление называется дребезгом. Подробней о кнопке.



raspberry_step#1



Кнопка подключается через макетную плату с использованием резистора на 1 кОм. Чтобы не запутаться в резисторах обратите, внимание на их цветовую кодировку. Не будем подробно расписывать процесс подключения. Просто сопоставьте представленную схему с распиновкой Raspberry, данной чуть выше.



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



dependencies {
...
compile 'com.google.android.things.contrib:driver-button:0.3'
...
}


Напишем класс-обёртку для работы с кнопкой. Возможно, реализация через обёртку покажется немного излишней, но таких образом мы сможем инкапсулировать работу с кодом драйвера кнопки и создать собственный интерфейс взаимодействия.



ButtonWrapper.java
import com.google.android.things.contrib.driver.button.Button;

public class ButtonWrapper {

private @Nullable Button mButton;

private @Nullable OnButtonClickListener mOnButtonClickListener;

public ButtonWrapper(final String gpioPin) {
try {
mButton = new Button(gpioPin, Button.LogicState.PRESSED_WHEN_HIGH);
mButton.setOnButtonEventListener(new Button.OnButtonEventListener() {
@Override
public void onButtonEvent(Button button, boolean pressed) {
if (pressed && mOnButtonClickListener != null) {
mOnButtonClickListener.onClick();
}
}
});

} catch (IOException e) {
e.printStackTrace();
}
}

public void setOnButtonClickListener(@Nullable final OnButtonClickListener listener) {
mOnButtonClickListener = listener;
}

public void onDestroy() {
if (mButton == null) {
return;
}
try {
mButton.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
mButton = null;
}
}

public interface OnButtonClickListener {
public void onClick();
}
}


Воспользуемся этой обёрткой в нашем Activity. Просто передадим в конструктор объекта кнопки название GPIO порта ("BCM4") на Raspberry Pi3, к которому она подключена на схеме.



MainActivity.java
public class MainActivity extends Activity {

private static final String GPIO_PIN_BUTTON = "BCM4";

private ButtonWrapper mButtonWrapper;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
mButtonWrapper = new ButtonWrapper(GPIO_PIN_BUTTON);
mButtonWrapper.setOnButtonClickListener(new ButtonWrapper.OnButtonClickListener() {
@Override
public void onClick() {
Timber.d("BUTTON WAS CLICKED");
startTakingImage();
}
});
...
}

@Override
protected void onDestroy() {
super.onDestroy();
...
mButtonWrapper.onDestroy();
...
}

private void startTakingImage() {
// TODO take photo
...
}
}


Шаг 4: Камера



Мы использовали камеру NoIR Camera V2. Примерно за 45$ вы получите характеристики, достаточные для нашего проекта:




  • максимальное разрешение: 8 Мп (3280x2464);

  • поддерживаемые видеоформаты: 1080p (30fps), 720p (60fps), 640x480p (90fps);

  • энергопотребление: 250 мА



Камера подключается к управляющей плате шлейфом через видеовход CSI (Camera Serial Interface). Такой способ снижает нагрузку на центральный процессор по сравнению с подключением аналогичных камер по USB.



Добавляем в манифест разрешение на использование камеры и требования, что устройство должно ею обладать. Добавление разрешений обязательно, однако согласие на все Permissions, в том числе и на Dangerous Permissions, получается автоматически при установке приложения (существует известная проблема, что при добавлении нового пермишена после переустановки приложения требуется полностью перезагрузить устройство).



    




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



Шаг 5: Светодиод



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



Мы будем использовать три светодиода разных цветов:




  • жёлтый (BCM20) — индикатор начала работы, которая продолжается указанный разработчиком интервал времени;

  • зелёный (BCM21) — программа успешно распознала лицо из базы данных;

  • красный (BCM16) — программа не распознала лицо в течении указанного интервала времени



Можно использовать один трехцветный светодиод вместо трёх одноцветных. Мы пользовались тем, что было под рукой. Используя резисторы в 1 кОм, поочередно подключаем наши светодиоды через макетную плату в соответствии с приведенной схемой.



raspberry_step#2



При реализации обёртки для работы со светодиодом воспользуемся PeripheralManagerService, сервисом, который дает доступ к GPIO интерфейсу. Открываем соединение и конфигурируем его для передачи сигнала. К сожалению, если заглянуть в реализацию абстрактного класса com.google.android.things.pio.Gpio, то можно увидеть, что вызов почти каждого метода способен генерировать java.io.IOException. Для простоты скроем все try-catch выражения в нашей обертке.



LedWrapper.java
public class LedWrapper {

private @Nullable Gpio mGpio;

public LedWrapper(String gpioPin) {
try {
PeripheralManagerService service = new PeripheralManagerService();
mGpio = service.openGpio(gpioPin);
mGpio.setDirection(Gpio.DIRECTION_OUT_INITIALLY_LOW);
} catch (IOException e) {
e.printStackTrace();
}
}

public void turnOn() {
if (mGpio == null) {
return;
}
try {
mGpio.setValue(true);
} catch (IOException e) {
e.printStackTrace();
}
}

public void turnOff() {
if (mGpio == null) {
return;
}
try {
mGpio.setValue(false);
} catch (IOException e) {
e.printStackTrace();
}
}

public void onDestroy() {
try {
mGpio.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
mGpio = null;
}
}
}


Имплементируем его в наше Activity для каждого светодиода по отдельности.



MainActivity.java
public class MainActivity extends Activity {

private final static String GPIO_PIN_LED_GREEN = “BCM21”;
private LedWrapper mLedWrapper;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
mLedWrapper = new LedWrapper(GPIO_PIN_LED_GREEN);
mLedWrapper.turnOff();
...
}

private void turnOn() {
mLedWrapper.turnOn();
}

@Override
protected void onDestroy() {
super.onDestroy();
...
mLedWrapper.onDestroy();
...
}
}


Шаг 6: Датчик движения



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



Подключаем датчик движения через BCM6 пин по схеме, приведенной ниже.



img_motion



MotionWrapper.java
public class MotionWrapper {

private @Nullable Gpio mGpio;
private @Nullable MotionEventListener mMotionEventListener;

public MotionWrapper(String gpioPin) {
try {
mGpio = new PeripheralManagerService().openGpio(gpioPin);
} catch (IOException e) {
e.printStackTrace();
}
}

public void setMotionEventListener(@Nullable final MotionEventListener listener) {
mMotionEventListener = listener;
}

public void startup() {
try {
mGpio.setDirection(Gpio.DIRECTION_IN);
mGpio.setActiveType(Gpio.ACTIVE_HIGH);
mGpio.setEdgeTriggerType(Gpio.EDGE_RISING);
mGpio.registerGpioCallback(mCallback);
} catch (IOException e) {
e.printStackTrace();
}
}

public void shutdown() {
if (mGpio == null) {
return;
}
try {
mGpio.unregisterGpioCallback(mCallback);
mGpio.close();
} catch (IOException e) {
e.printStackTrace();
}
}

public void onDestroy() {
try {
mGpio.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
mGpio = null;
}
}

private final GpioCallback mCallback = new GpioCallback() {
@Override
public boolean onGpioEdge(Gpio gpio) {
if (mMotionEventListener != null) {
mMotionEventListener.onMovement();
}
return true;
}
};

public interface MotionEventListener {
void onMovement();
}
}


MainActivity.java

public class MainActivity extends Activity {



private static final String GPIO_PIN_MOTION_SENSOR = "BCM6";

private MotionWrapper mMotionWrapper;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
mMotionWrapper = new MotionWrapper(GPIO_PIN_MOTION_SENSOR);
mMotionWrapper.setMotionEventListener(new MotionWrapper.MotionEventListener() {
@Override
public void onMovement() {
startTakingPhotos();
}
});
mMotionWrapper.startup();
...


}



@Override
protected void onDestroy() {
super.onDestroy();
...
mMotionWrapper.shutdown();
mMotionWrapper.onDestroy();
...
}

private void startTakingPhotos() {
...
}


}



Шаг 7: Сервопривод



Сервопривод совершает основную механическую работу в нашем девайсе. Именно он поворотом выходного вала на 180 градусов подносит карточку к считывающему устройству. Подробней про сервоприводы.



Мы снова используем уже существующий драйвер, над которым напишем свою обёртку. Добавляем в app/build.gradle зависимость.



dependencies {
...
compile 'com.google.android.things.contrib:driver-pwmservo:0.2'
...
}


Подключим привод через интерфейс широтно-импульсной модуляции PWM1 в соответствии со схемой приведенной ниже. Использование PWMинтерфейса обусловлено тем, что, в отличии от предыдущих случаев, требуется передать конкретное значение через управляющий сигнал, а не просто бинарный импульс. Управляющий сигнал — импульсы постоянной частоты и переменной ширины. Сервопривод использует широтно-импульсный входящий PWM-сигнал, преобразуя его в конкретный угол поворота выходного вала.



img_servo



ServoWrapper.java
public class ServoWrapper {

private static final float ANGLE_CLOSE = 0f;
private static final float ANGLE_OPEN = 180f;

private Servo mServo;
private Handler mHandler = new Handler();

public ServoWrapper(final String gpioPin) {
try {
mServo = new Servo(gpioPin);
mServo.setAngleRange(ANGLE_CLOSE, ANGLE_OPEN);
mServo.setEnabled(true);
} catch (IOException e) {
e.printStackTrace();
}
}

public void open(final long delayMillis) {
try {
mServo.setAngle(ANGLE_OPEN);
} catch (IOException e) {
e.printStackTrace();
}

mHandler.removeCallbacks(mMoveServoRunnable);
if (delayMillis > 0) {
mHandler.postDelayed(mMoveServoRunnable, delayMillis);
}
}

public void close() {
if (mServo == null) {
return;
}

try {
mServo.setAngle(ANGLE_CLOSE);
} catch (IOException e) {
e.printStackTrace();
}
}

public void onDestroy() {
mHandler.removeCallbacks(mMoveServoRunnable);
mMoveServoRunnable = null;

if (mServo != null) {
try {
mServo.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
mServo = null;
}
}
}

private Runnable mMoveServoRunnable = new Runnable() {
@Override
public void run() {
mHandler.removeCallbacks(this);
close();
}
};
}


MainActivity.java

public class MainActivity extends Activity {



private static final String GPIO_PIN_SERVO = "PWM1";

private ServoWrapper mServoWrapper;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
mServoWrapper = new ServoWrapper(GPIO_PIN_SERVO);
...


}



private void openDoor() {
...
mServoWrapper.open(DELAY_SERVO_MS);
...
}

@Override
protected void onDestroy() {
super.onDestroy();
...
mServoWrapper.onDestroy();
...
}


}



Шаг 8: Фоторезистор



В тёмное время суток работа замка бессмысленна, потому что по полученным фотографиям сложнее распознать лица. Значит, систему можно временно отключать. Для этого в качестве датчика света используем фоторезистор — Light Dependent Resistors (LDR). Подробней о фоторезисторе.



Для работы с фоторезистором подходит драйвер кнопки, описанный ранее. Это логично, ведь суть и механика работы действительно совпадают. В app/build.gradle уже должна быть подключена библиотека.



dependencies {
...
compile 'com.google.android.things.contrib:driver-button:0.3'
...
}


Схема подключения к макетной плате аналогична схеме подключения кнопки. Отличие лишь в использовании резистор в 10 кОм. Используем порт BCM25.



doorbell_full_scheme_version



Несмотря на всю похожесть, напишем для него отдельную обёртку.



BrightrWrapper.java
public class BrightrWrapper {

private @Nullable Button mLightDetector;

private @Nullable OnLightStateChangeListener mOnLightStateChangeListener;

public BrightrWrapper(final String gpioPin) {
try {
mLightDetector = new Button(gpioPin, Button.LogicState.PRESSED_WHEN_HIGH);
mLightDetector.setOnButtonEventListener(new Button.OnButtonEventListener() {
@Override
public void onButtonEvent(Button button, boolean isLighted) {
if (mOnLightStateChangeListener != null) {
mOnLightStateChangeListener.onLightStateChange(isLighted);
}
}
});
} catch (IOException e) {
e.printStackTrace();
}
}

public void setOnLightStateListener(@Nullable final OnLightStateChangeListener listener) {
mOnLightStateChangeListener = listener;
}

public void onDestroy() {
if (mLightDetector == null) {
return;
}
try {
mLightDetector.close();
} catch (IOException e) {
e.printStackTrace();
}
}

public interface OnLightStateChangeListener {
public void onLightStateChange(boolean isLighted);
}
}


MainActivity.java
public class MainActivity extends Activity {

private static final String GPIO_PIN_LIGHT_DETECTOR = "BCM25";

private BrightrWrapper mBrightrWrapper;

private boolean mIsTakePhotoAllowed = true;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
mBrightrWrapper = new BrightrWrapper(GPIO_PIN_LIGHT_DETECTOR);
mBrightrWrapper.setOnLightStateListener(new BrightrWrapper.OnLightStateChangeListener() {
@Override
public void onLightStateChange(final boolean isLighted) {
mIsTakePhotoAllowed = isLighted;
handleLightState();
}
});
...
}

private void handleLightState() {
if (mIsTakePhotoAllowed) {
...
} else {
...
}
}

@Override
protected void onDestroy() {
super.onDestroy();
...
mBrightrWrapper.onDestroy();
...
}
}


Демонстрация реализации



Пересмотрев все выпуски телепередачи «Очумелые ручки», мы искусно упаковали нашего «монстра» в коробочку из-под старого телефона. В разобранном виде это выглядит так



img



Видео-демонстрация







Проблемы



Плохое качество датчиков и сенсоров. Срабатывают или не срабатывают, когда надо и когда не надо. Сервопривод потрескивает в режиме ожидания.

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

Разместив нашу конструкцию изнутри на тройной стеклопакет все фотографии оказались сильно засвечены. Быстро сообразив, что проблема заключается в отражении света от белой поверхности коробочки и его многократном преломлении через стеклопакет, мы просто наклеили чёрный лист бумаги вокруг камеры, чтобы он поглощал часть света.

Нет защиты от распечатанных фотографий.



Итоги



Пример кода на Github.



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



Какие варианты монетизации разработок под IoT знаете вы? Делитесь своим опытом в комментариях.



Не на правах рекламы хочу поделиться отличной статьей, где автор придумал и реализовал самодельную читалку новостей шрифтом Брайля BrailleBox для слабо видящих на Android Things. Выглядит и реализовано так же круто, как и звучит. Отличный, воодушевляющий проект.



Полезные ссылки




Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/331888/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

[recovery mode] Как пожизненный пользователь Windows переключился на Linux по-плохому

Пятница, 23 Июня 2017 г. 12:18 (ссылка)

Позвольте мне перефразировать этот заголовок сказав таким образом: я всегда интересовался тем, как устроены вещи, а не лишь тем, что они делают. Если что-либо что-то делает, то я бы хотел знать зачем, как и могу-ли я это кастомизировать.



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



К счастью для меня и других, подобных мне, есть альтернатива. Ну, существует несколько (очень много) дистрибутивов Linux, и большинство из них интуитивно понятны и полностью пригодны для использования сразу после установки. Возможно, одним из самых известных «Windows-подобных» дистрибутивов является Ubuntu. У этого есть рабочий стол, похожий на Windows 10, и он очень «графический» и ориентирован в основном на то, что являются новыми для Linux.

Я, в свою очередь, пришёл к Debian, дистрибутив, на котором и был основан Ubuntu. Он не держит вас за ручку так крепко как другие многофункциональные дистрибутивы, но от того он и более настраиваемый. Я начал с установочного образа, найденного здесь, который вы можете скачать и использовать для создания загрузочного USB.



Это самая простая установка Debian и исключает те вещи, которые я, как предыдущий пользователь Windows, воспринимаю как должное. Помимо прочего, мне пришлось найти и установить (или, по крайней мере, настроить):




  • Менеджер окон (i3)

  • Приложение, которое позволит мне подключиться к Интернету (NetworkManager / nm-applet)

  • Программа, которая позволяет мне контролировать яркость экрана (xrandr)

  • Программа, которая обрабатывает звук, подобные программы ещё называют звуковым сервером (pulseaudio)

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

  • Дисплей для базовой информации о состояниях (i3bar)

  • Уведомления рабочего стола (dunst) (об этом ещё обязательно будет отдельная статья)

  • Основные программы, такие как графический файловый менеджер (PCmanFM), просмотрщик PDF (Okular) и редактор изображений (Darktable)



Если, в отличие от меня, вы предпочитаете использовать более полнофункциональную среду рабочего стола, то есть лёгкий способ: Net Install по умолчанию предоставляет стандартную систему с рабочей средой GNOME. Для Debian есть полное руководство по установке, которое шаг за шагом проведет вас через этот процесс.

Однако, делая это сложным путём («по-плохому») я многому научился. После начала работы с базовой системой я получил куда лучшее представление в отношении того, что происходит “под капотом”. Если в будущем у меня возникнет проблема с одним компонентом, у меня куда больше шансов получить представление о том, как же это исправить, поскольку я “это” сам собрал. На этом пути я набил не мало шишек, но также нашёл некоторые решения, которые работаю куда лучше, чем даже предлагаемые Net Install по-умолчанию.



Но, сперва, для мотивации, скриншот моего рабочего стола (Linux-не Linux, а я люблю, когда всё красивенько):

image

Разрешение на распространение этих ну очень красивых конфигурационных файлов были любезно предоставлены мне Vicky Lai для написания этой статьи и доступны в её GitHub, так что милости прошу.



Что вообще такое файлы конфигурации?



Если вы новичок в Linux или вовсе в программировании, то это в основном техническая версия простого и знакомого «Файл> Настройки» в программах GUI. В зависимости от приложения, которое вы настраиваете, есть несколько разных форматов и языков этих самых файлов конфигурации. К счастью, есть достаточно примеров конфигурационных файлов, доступных с помощью простого веб-поиска.



Куда же засунуть все эти файлы конфигурации?



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

systemd event handlers:   /etc/systemd/logind.conf
Для URxvt: ~/.Xdefaults
Для i3wm: ~/.config/i3/config
Для i3bar: ~/.config/i3status/config/i3status.conf
Для dunst: ~/.config/dunst/dunstrc
Для Compton: ~/.config/compton.conf




Я выбрал глупое имя пользователя, как его изменить?



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




  • Dropbox

  • Anaconda and pip

  • Файловые пути, написанные полностью в файлах конфигурации



Чтобы избежать проблем, с которыми я столкнулся, измените свое имя пользователя, прежде чем настраивать все свои программ. Если для этого уже слишком поздно, просто имейте в виду, что затронуты многие программы, включая все файловые пути содержащие: /home /oldusername /…

Чтобы изменить имя пользователя через терминал, войдите в систему с правами root, а затем:

$ killall -u oldusername
$ id oldusername
>>> uid=1000(oldusername) gid=1000(oldusername) groups=1000(oldusername),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),108(netdev)
# изменение логина
$ usermod -l newusername oldusername
# изменение имени группы
$ groupmod -n newusername oldusername
# изменение директории home
$ usermod -d /home/newusername -m newusername
# добавление комментария с полным именем
$ usermod -c "New Full Name" newusername
# проверяем что "newusername" заменил "oldusername" во всех полях
$ id newusername
>>> uid=1000(newusername) gid=1000(newusername) groups=1000(newusername),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),108(netdev)


Если вы предварительно установили фоновое изображение в i3, и оно исчезнет, не забудьте проверить путь к файлу в файле конфигурации.



Как заставить NetworkManager и Dropbox запускаться автоматически?



Dropbox для Linux предположительно имеет настройку автозапуска, где вы можете ввести dropbox autostart y прямо в терминал и таким образом создать обработку события. Но я, естественно, решил что это не для меня. Вместо этого я добавил все приложения, которые я хотел бы запустить автоматически при входе в мой конфигурационный файл i3 с помощью этого синтаксиса:

# автозапуск приложений
exec --no-startup-id /usr/bin/nm-applet
exec --no-startup-id dropbox start


В выше приведённом коде exec является командой выполнения (от “execute”), --no-startup-id в основном избавляет вас от того чтобы смотреть как ваш курсор делает эту причудливую вращающуюся загрузку. И последним компонентом команды является путь к файлу программы или синтаксис для его запуска, так, как если бы вы писали в терминале.



Как настроить OpenVPN с помощью NetworkManager?



Для начала убедитесь, что вы выполнили

apt-get install network-manager-openvpn


чтобы получить плагин.

Вам понадобится файл client.ovpn. В моем случае я настроил свой собственный VPN с помощью Amazon EC2 и загрузил файл client.ovpn со своей страницы консоли OpenVPN.

Откройте client.ovpn с помощью текстового редактора, например vim (уверен ниже из-за этого появится ссылка на статью о его закрытии), и измените любые экземпляры “удалённый openvpn порт xxxx” (так как я пошёл по пути англоязычного ПО здесь и далее в скобках тот вариант который лицезрел я в оригинале “remote openvpn port xxxx”) вместо “удалённый <ваш ip-адрес> порт xxxx” (“remote port xxxx”).

Используйте nm-applet для настройки нового VPN-соединения. Если вы установили плагин OpenVPN, в раскрывающемся списке вы увидите OpenVPN. Выберите вариант “Импортировать сохраненную конфигурацию VPN”.

image

Как только вы загрузите файл client.ovpn и нажмите “Создать”, все настройки будут заполнены за вас, кроме имени пользователя и пароля. Заполняете их и всё, готово.



Как получить статус VPN в i3bar?



Найдите файл конфигурации i3status. Если у вас его еще нет, вы можете использовать такой потрясающий шаблон, как мой (на базе Vicky Lai, огромное ей спасибо).



Отредактируйте путь к файлу в следующем разделе кода:

run_watch VPN {
pidfile = "sys/class/net/yoursetting"
}


Где «yoursetting» – это один из tap/tun/tun tap в зависимости от ваших настроек VPN. Если вы не знаете, что это такое вы можете узнать в настройках конфигурации VPN в разделе VPN> Дополнительно>«Установить тип виртуального устройства» (VPN > Advanced > «Set virtual device type»).



Как вновь начать использовать клавиши Print Screen/управления подсветкой и звуком?



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

# настройки яркости (brightness adjustment)
bindsym $mod+Shift+F6 exec xrandr --output eDP-1 --brightness 1
bindsym $mod+F6 exec xrandr --output eDP-1 --brightness 0.8
bindsym $mod+F5 exec xrandr --output eDP-1 --brightness 0.5
bindsym $mod+F7 exec xrandr --output eDP-1 --brightness 0.1

# настройки звука (volume control)
bindsym $mod+F12 exec amixer -q sset Master 3%+
bindsym $mod+F11 exec amixer -q sset Master 3%-
bindsym $mod+F10 exec amixer -q sset Master toggle


Как вы можете видеть, в случае xrandr я смог найти способ установить определенные уровни. Для управления громкостью, amixer не предоставляет возможности задать уровень, вместо этого он предлагает приращения к уже имеющемуся значению, от того пришлось немного помучаться с документацию для того чтобы определить, как сделать mute.

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



Я отредактировал мой файл конфигурации, но я не вижу никаких изменений. Что за дела?

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



Переключение на Linux, безусловно, стоило того. Хотя некоторые из этапов обучения и открытий были до безумного долгими и гнетущими, я честно наслаждался каждой минутой. Ничто не сравнится с ощущением того, что вы что-то понимаете и заставляете это работать!




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

Ссылки:


Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/331492/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

[Из песочницы] Туториал: Создание простейшей 2D игры на андроид

Воскресенье, 11 Июня 2017 г. 19:46 (ссылка)

Этот туториал предназначен в первую очередь для новичков в разработке под андроид, но может быть будет полезен и более опытным разработчикам. Тут рассказано как создать простейшую 2D игру на анроиде без использования каких-либо игровых движков. Для этого я использовал Android Studio, но можно использовать любую другую соответствующее настроенную среду разработки.



Шаг 1. Придумываем идею игры

Для примера возьмём довольно простую идею:



Внизу экрана — космический корабль. Он может двигаться влево и вправо по нажатию соответствующих кнопок. Сверху вертикально вниз движутся астероиды. Они появляются по всей ширине экрана и двигаются с разной скоростью. Корабль должен уворачиваться от метеоритов как можно дольше. Если метеорит попадает в него — игра окончена.







Шаг 2. Создаём проект

В Android Studio в верхнем меню выбираем File -> New -> New Project.







Тут вводим название приложения, домен и путь. Нажимаем Next.







Тут можно ввести версию андроид. Также можно выбрать андроид часы и телевизор. Но я не уверен что наше приложение на всём этом будет работать. Так что лучше введите всё как на скриншоте. Нажимаем Next.







Тут обязательно выбираем Empty Activity. И жмём Next.







Тут оставляем всё как есть и жмём Finish. Итак проект создан. Переходим ко третьему шагу.



Шаг 3. Добавляем картинки



Скачиваем архив с картинками и распаковываем его.



Находим папку drawable и копируем туда картинки.







Позже они нам понадобятся.



Шаг 4. Создаём layout



Находим activity_main.xml, открываем вкладку Text и вставляем туда это:












На вкладке Design видно как наш layout будет выглядеть.







Сверху поле в котором будет сама игра, а снизу кнопки управления Left и Right. Про layout можно написать отдельную статью, и не одну. Я не буду на этом подробно останавливаться. Про это можно почитать тут.



Шаг 5. Редактируем MainActivity класс



В первую очередь в определение класса добавляем implements View.OnTouchListener. Определение класса теперь будет таким:



public class MainActivity extends AppCompatActivity implements View.OnTouchListener {


Добавим в класс нужные нам статические переменные (переменные класса):



public static boolean isLeftPressed = false; // нажата левая кнопка
public static boolean isRightPressed = false; // нажата правая кнопка


В процедуру protected void onCreate(Bundle savedInstanceState) {

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



GameView gameView = new GameView(this); // создаём gameView

LinearLayout gameLayout = (LinearLayout) findViewById(R.id.gameLayout); // находим gameLayout
gameLayout.addView(gameView); // и добавляем в него gameView

Button leftButton = (Button) findViewById(R.id.leftButton); // находим кнопки
Button rightButton = (Button) findViewById(R.id.rightButton);

leftButton.setOnTouchListener(this); // и добавляем этот класс как слушателя (при нажатии сработает onTouch)
rightButton.setOnTouchListener(this);


Классы LinearLayout, Button и т.д. подсвечены красным потому что ещё не добавлены в Import.

Чтобы добавить в Import и убрать красную подсветку нужно для каждого нажать Alt+Enter.

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



Теперь добавляем процедуру:



public boolean onTouch(View button, MotionEvent motion) {
switch(button.getId()) { // определяем какая кнопка
case R.id.leftButton:
switch (motion.getAction()) { // определяем нажата или отпущена
case MotionEvent.ACTION_DOWN:
isLeftPressed = true;
break;
case MotionEvent.ACTION_UP:
isLeftPressed = false;
break;
}
break;
case R.id.rightButton:
switch (motion.getAction()) { // определяем нажата или отпущена
case MotionEvent.ACTION_DOWN:
isRightPressed = true;
break;
case MotionEvent.ACTION_UP:
isRightPressed = false;
break;
}
break;
}
return true;
}


Если кто-то запутался - вот так в результате должен выглядеть MainActivity класс:



package com.spaceavoider.spaceavoider;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
public class MainActivity extends AppCompatActivity implements View.OnTouchListener {
public static boolean isLeftPressed = false; // нажата левая кнопка
public static boolean isRightPressed = false; // нажата правая кнопка
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
GameView gameView = new GameView(this); // создаём gameView
LinearLayout gameLayout = (LinearLayout) findViewById(R.id.gameLayout); // находим gameLayout
gameLayout.addView(gameView); // и добавляем в него gameView
Button leftButton = (Button) findViewById(R.id.leftButton); // находим кнопки
Button rightButton = (Button) findViewById(R.id.rightButton);
leftButton.setOnTouchListener(this); // и добавляем этот класс как слушателя (при нажатии сработает onTouch)
rightButton.setOnTouchListener(this);
}
public boolean onTouch(View button, MotionEvent motion) {
switch(button.getId()) { // определяем какая кнопка
case R.id.leftButton:
switch (motion.getAction()) { // определяем нажата или отпущена
case MotionEvent.ACTION_DOWN:
isLeftPressed = true;
break;
case MotionEvent.ACTION_UP:
isLeftPressed = false;
break;
}
break;
case R.id.rightButton:
switch (motion.getAction()) { // определяем нажата или отпущена
case MotionEvent.ACTION_DOWN:
isRightPressed = true;
break;
case MotionEvent.ACTION_UP:
isRightPressed = false;
break;
}
break;
}
return true;
}
}


Итак, класс MainActivity готов! В нём инициирован ещё не созданный класс GameView. И когда нажата левая кнопка — статическая переменная isLeftPressed = true, а когда правая — isRightPressed = true. Это в общем то и всё что он делает.



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



Шаг 6. Создаём класс GameView



Теперь наконец-то создадим тот самый недостающий класс GameView. Итак приступим. В определение класса добавим extends SurfaceView implements Runnable. Мобильные устройства имею разные разрешения экрана. Это может быть старенький маленький телефон с разрешением 480x800, или большой планшет 1800x2560. Для того чтобы игра выглядела на всех устройствах одинаково я поделил экран на 20 частей по горизонтали и 28 по вертикали. Полученную единицу измерения я назвал юнит. Можно выбрать и другие числа. Главное чтобы отношение между ними примерно сохранялось, иначе изображение будет вытянутым или сжатым.



public static int maxX = 20; // размер по горизонтали
public static int maxY = 28; // размер по вертикали
public static float unitW = 0; // пикселей в юните по горизонтали
public static float unitH = 0; // пикселей в юните по вертикали


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



private boolean firstTime = true;
private boolean gameRunning = true;
private Ship ship;
private Thread gameThread = null;
private Paint paint;
private Canvas canvas;
private SurfaceHolder surfaceHolder;


Конструктор будет таким:



public GameView(Context context) {
super(context);
//инициализируем обьекты для рисования
surfaceHolder = getHolder();
paint = new Paint();

// инициализируем поток
gameThread = new Thread(this);
gameThread.start();
}


Метод run() будет содержать бесконечный цикл. В начале цикла выполняется метод update()

который будет вычислять новые координаты корабля. Потом метод draw() рисует корабль на экране. И в конце метод control() сделает паузу на 17 миллисекунд. Через 17 миллисекунд run() запустится снова. И так до пока переменная gameRunning == true. Вот эти методы:



@Override
public void run() {
while (gameRunning) {
update();
draw();
control();
}
}

private void update() {
if(!firstTime) {
ship.update();
}
}

private void draw() {
if (surfaceHolder.getSurface().isValid()) { //проверяем валидный ли surface

if(firstTime){ // инициализация при первом запуске
firstTime = false;
unitW = surfaceHolder.getSurfaceFrame().width()/maxX; // вычисляем число пикселей в юните
unitH = surfaceHolder.getSurfaceFrame().height()/maxY;

ship = new Ship(getContext()); // добавляем корабль
}

canvas = surfaceHolder.lockCanvas(); // закрываем canvas
canvas.drawColor(Color.BLACK); // заполняем фон чёрным

ship.drow(paint, canvas); // рисуем корабль

surfaceHolder.unlockCanvasAndPost(canvas); // открываем canvas
}
}

private void control() { // пауза на 17 миллисекунд
try {
gameThread.sleep(17);
} catch (InterruptedException e) {
e.printStackTrace();
}
}


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



Шаг 7. Создаём класс SpaceBody



Он будет родительским для класса Ship (космический корабль) и Asteroid (астероид). В нём будут содержаться все переменные и методы общие для этих двух классов. Добавляем переменные:



protected float x; // координаты
protected float y;
protected float size; // размер
protected float speed; // скорость
protected int bitmapId; // id картинки
protected Bitmap bitmap; // картинка


и методы



void init(Context context) { // сжимаем картинку до нужных размеров
Bitmap cBitmap = BitmapFactory.decodeResource(context.getResources(), bitmapId);
bitmap = Bitmap.createScaledBitmap(
cBitmap, (int)(size * GameView.unitW), (int)(size * GameView.unitH), false);
cBitmap.recycle();
}

void update(){ // тут будут вычисляться новые координаты
}

void drow(Paint paint, Canvas canvas){ // рисуем картинку
canvas.drawBitmap(bitmap, x*GameView.unitW, y*GameView.unitH, paint);
}


Шаг 8. Создаём класс Ship



Теперь создадим класс Ship (космический корабль). Он наследует класс SpaceBody поэтому в определение класа добавим extends SpaceBody.



Напишем конструктор:



public Ship(Context context) {
bitmapId = R.drawable.ship; // определяем начальные параметры
size = 5;
x=7;
y=GameView.maxY - size - 1;
speed = (float) 0.2;

init(context); // инициализируем корабль
}


и переопределим метод update()



@Override
public void update() { // перемещаем корабль в зависимости от нажатой кнопки
if(MainActivity.isLeftPressed && x >= 0){
x -= speed;
}
if(MainActivity.isRightPressed && x <= GameView.maxX - 5){
x += speed;
}
}


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



Шаг 9. Создаём класс Asteroid



Добавим класс Asteroid (астероид). Он тоже наследует класс SpaceBody поэтому в определение класса добавим extends SpaceBody.



Добавим нужные нам переменные:



private int radius = 2; // радиус
private float minSpeed = (float) 0.1; // минимальная скорость
private float maxSpeed = (float) 0.5; // максимальная скорость


Астероид должен появляться в случайной точке вверху экрана и лететь вниз с случайной скоростью. Для этого x и speed задаются при помощи генератора случайных чисел в его конструкторе.



public Asteroid(Context context) {
Random random = new Random();

bitmapId = R.drawable.asteroid;
y=0;
x = random.nextInt(GameView.maxX) - radius;
size = radius*2;
speed = minSpeed + (maxSpeed - minSpeed) * random.nextFloat();

init(context);
}


Астероид должен двигаться с определённой скорость вертикально вниз. Поэтому в методе update() прибавляем к координате x скорость.



@Override
public void update() {
y += speed;
}


Так же нам нужен будет метод определяющий столкнулся ли астероид с кораблём.



public boolean isCollision(float shipX, float shipY, float shipSize) {
return !(((x+size) < shipX)||(x > (shipX+shipSize))||((y+size) < shipY)||(y > (shipY+shipSize)));
}


Рассмотрим его поподробнее. Для простоты считаем корабль и астероид квадратами. Тут я пошёл от противного. То есть определяю когда квадраты НЕ пересекаются.



((x+size) < shipX) — корабль слева от астероида.

(x > (shipX+shipSize)) — корабль справа от астероида.

((y+size) < shipY) — корабль сверху астероида.

(y > (shipY+shipSize)) — корабль снизу астероида.



Между этими четырьмя выражениями стоит || (или). То есть если хоть одно выражение правдиво (а это значит что квадраты НЕ пересекаются) — результирующие тоже правдиво.



Всё это выражение я инвертирую знаком!. В результате метод возвращает true когда квадраты пересекаются. Что нам и надо.



Про определение пересечения более сложных фигур можно почитать тут.



Шаг 10. Добавляем астероиды в GameView



В GameView добавляем переменные:



private ArrayList asteroids = new ArrayList<>(); // тут будут харанится астероиды
private final int ASTEROID_INTERVAL = 50; // время через которое появляются астероиды (в итерациях)
private int currentTime = 0;


также добавляем 2 метода:



private void checkCollision(){ // перебираем все астероиды и проверяем не касается ли один из них корабля
for (Asteroid asteroid : asteroids) {
if(asteroid.isCollision(ship.x, ship.y, ship.size)){
// игрок проиграл
gameRunning = false; // останавливаем игру
// TODO добавить анимацию взрыва
}
}
}

private void checkIfNewAsteroid(){ // каждые 50 итераций добавляем новый астероид
if(currentTime >= ASTEROID_INTERVAL){
Asteroid asteroid = new Asteroid(getContext());
asteroids.add(asteroid);
currentTime = 0;
}else{
currentTime ++;
}
}


И в методе run() добавляем вызовы этих методов перед вызовоом control().



@Override
public void run() {
while (gameRunning) {
update();
draw();
checkCollision();
checkIfNewAsteroid();
control();
}
}


Далее в методе update() добавляем цикл который перебирает все астероиды и вызывает у них метод update().



private void update() {
if(!firstTime) {
ship.update();
for (Asteroid asteroid : asteroids) {
asteroid.update();
}
}
}


Такой же цикл добавляем и в метод draw().



private void draw() {
if (surfaceHolder.getSurface().isValid()) { //проверяем валидный ли surface

if(firstTime){ // инициализация при первом запуске
firstTime = false;
unitW = surfaceHolder.getSurfaceFrame().width()/maxX; // вычисляем число пикселей в юните
unitH = surfaceHolder.getSurfaceFrame().height()/maxY;

ship = new Ship(getContext()); // добавляем корабль
}

canvas = surfaceHolder.lockCanvas(); // закрываем canvas
canvas.drawColor(Color.BLACK); // заполняем фон чёрным

ship.drow(paint, canvas); // рисуем корабль

for(Asteroid asteroid: asteroids){ // рисуем астероиды
asteroid.drow(paint, canvas);
}

surfaceHolder.unlockCanvasAndPost(canvas); // открываем canvas
}
}


Вот и всё! Простейшая 2D игра готова. Компилируем, запускаем и смотрим что получилось!

Если кто-то запутался или что-то не работает можно скачать исходник.



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



На этом всё. Пишите отзывы, вопросы, интересующие вас темы для продолжения.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/330686/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
svetik_bor

Серьги кисточки фриволите иглой, анкарс. МК для начинающих

Суббота, 10 Июня 2017 г. 17:42 (ссылка)


Серьги кисточки фриволите иглой, анкарс. МК для начинающих.



DIY Earrings tassels to frivolite needle



https://www.youtube.com/watch?v=4GLEt4bRPjI&amp;t=25s



Серьги кисточки фриволите иглой, анкарс. МК для начинающих. DIY Earrings tassels to frivolite needle/5017731_ (700x393, 305Kb)



серьги кисточки,earrings frivolite,серьги фриволите,кружевные серьги,мастер класс,knitting needles,how to knit,Анкарс,Diy,tutorial,frivolite needle,Серьги вязанные иглой,вязание иглой,orecchini chiacchierino,Кружево фриволите иглой,фриволите иглой,Кружево фриволите,фриволите иглой для начинающих,Lace with a needle,lace floral frivolite,frivolite,frivolite earrings with a needle,фриволите,frivolite knitting,Вязанные Серьги,кружево иглой,chiacchierino

Комментарии (0)КомментироватьВ цитатник или сообщество
svetik_bor

Серьги Яблочки фриволите иглой, анкарс

Вторник, 06 Июня 2017 г. 11:11 (ссылка)


Серьги Яблочки фриволите иглой, анкарс. Видео урок для начинающих



https://www.youtube.com/watch?v=33TG2d2SnFA



5017731_IMG_62951 (700x443, 47Kb)

Комментарии (0)КомментироватьВ цитатник или сообщество
maksimumv

MaxVBar

Среда, 24 Мая 2017 г. 05:32 (ссылка)


Приглашаю на свой канал на youtube!!!





https://www.youtube.com/channel/UCTnTPICgh8H12Ouw1lTxNwA


Комментарии (1)КомментироватьВ цитатник или сообщество

Следующие 30  »

<tutorial - Самое интересное в блогах

Страницы: [1] 2 3 ..
.. 10

LiveInternet.Ru Ссылки: на главную|почта|знакомства|одноклассники|фото|открытки|тесты|чат
О проекте: помощь|контакты|разместить рекламу|версия для pda