18-2 Grupo 6

## **Autores**

* Wladimir Roberto Barbosa

* Hadston Nunes

* Ricardo Moura Cunha

* Julia Caroline

## **Projeto**

Joystick Shield V1a para Battle Balls Arena na Unity 2018

## **Resumo**

Projeto para construção e programação de controle baseado no arduino para um jogo feito na Unity 2018.

## **Software/materiais utilizados**

**Materiais:**

* Esfera de isopor de 15cm de diâmetro
* Joystick shield V1a
* Massa de biscuit incolor de 500g
* Led RGB
* Estilete
* Fita crepe

**Softwares:**

* Arduino IDE
* Unity 2018.2.7f
* Blender 2.79
* Biblioteca Ardity de integração do arduino com a Unity

## **Metodologia**

O jogo foi baseado na brincadeira de bolinhas de gude, mas com acréscimos de alguns recursos de batalha, sendo duas esferas de cor diferente (verde e vermelha) - uma pra cada jogador - que duelam entre si em uma arena estilo Sci-Fi, respeitando uma mecânica de turnos com contagem regressiva para efetuar ações. Cada uma possui inicialmente 100 de vida e 5 de ataque, valores que podem sofrer alterações durante o jogo através dos danos efetuados - que são calculados conforme a força escolhida do movimento que também é baseada no atributo ataque - ou da obtenção de power ups (benefícios).

O objetivo do jogo pode ser dois:

* Reduzir a vida do adversário a zero
* Ou, em vantagem de vida, entrar no buraco situado no centro da arena.

A partir disso, foi confeccionado um controle esférico de massa de biscuit no molde de uma esféra de isopor de 15cm de diâmetro em torno de um Joystick Shield V1a do arduino para controlar todo o jogo.

## **Desenvolvimento**

É possível dividir o projeto em quatro partes que foram muito bem distribuídas entre os autores:

* Jogo Battle Balls Arena
* A programação da integração
* O controle esférico
* Documentação

### **Jogo Battle Balls Arena**

Como a ideia inicial do projeto era fazer um controle de integração com a Unity, foi conveniente usarmos um jogo que já tínhamos pronto e que foi entregue na disciplina de Física Aplicada a Jogos Digitais.

O jogo estava pronto, mas funcionando com mouse e teclado da seguinte forma:

* Eixo X do mouse: girar a câmera em torno da bolinha (player)
* Botão esquerdo: efetuar ação de movimento (soltar) e habilitar barra de força de movimento (segurar)
* Roda de scroll: controle de zoom da câmera
* Botão ESC: pausar

### **A programação da integração**

Essa provavelmente foi a parte mais difícil do projeto. Houve muita pesquisa para encontrar os recursos ideais para a integração. Inicialmente, encontramos uma biblioteca de código aberto chamada WRMHL que parecia ótima por ter uma latência bem reduzida comparada a outras, pois utiliza threads específicas para lidarem com mensagens vindas do Arduino. Após alguns dias, ao desconectar o cabo sem querer do Arduino, percebemos que a conexão com a porta era perdida e, por algum motivo que não conseguimos descobrir, só era restabelecida com um novo envio do código para o Arduino.

Ficamos incomodados com a situação e resolvemos procurar por outra solução que resolvesse isso. Foi aí que encontramos a Ardity, uma outra biblioteca também de código aberto, porém mais recentemente atualizada e que integrava a Unity com o Arduino de uma forma semelhante à WRMHL, através de threads para reduzir a latência.

Foi feita a troca, realizados alguns testes e pronto, o problema estava resolvido. Após desconectar o cabo USB do Arduino a conexão era perdida, mas logo que reconectava a conexão era restabelecida de forma imperceptível.

#### **Código na Unity 2018**

A programação na Unity foi feita em C#, orientada a objetos e versionada com o Collab, solução própria da Unity pra projetos feitos nela. Basicamente, o que a Ardity faz é criar uma thread que fica lendo uma porta serial (COM) e registrando todas as informações lidas eu uma fila thread-safe, ou seja, que tem sincronização perfeita com a thread.

Dentre todos os scripts usados no jogo somente alguns foram necessários para a integração, em geral, todos os que tinha leitura de entradas, como teclado e mouse. Segue alguns deles:

##### Script responsável por estabelecer a comunicação do jogo com a biblioteca Ardity

```
using System.Collections;
using System.Collections.Generic;
using System;
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Globalization;

public class ArduinoListener: MonoBehaviour {
public SerialController serial;
[HideInInspector]
public bool checkButtonsState = false;
private bool justAwaked = true;
private string[] stateBeforeLoadScene = {"0", "0", "0", "0", "0", "0", "0", "0", "0"};
[HideInInspector]
public bool transitionBlock = false;
public int[] buttonsBlockedOnTransitions; // Indice dos botões a serem verificados no início dos caregamentos de cena para evitar ações indesejadas
[HideInInspector]

// X Y A B C D E F Z
public string[] commands = new string[9] {"0", "0", "0", "0", "0", "0", "0", "0", "0"};

void Awake() {
DontDestroyOnLoad(gameObject);
SceneManager.sceneLoaded += OnSceneLoaded;
}

void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
checkButtonsState = !justAwaked;
justAwaked = false;
}

void Update ()
{
string message = serial.ReadSerialMessage();

if (message == null) {
return;
}

// Check if the message is plain data or a connect/disconnect event.
if (ReferenceEquals(message, SerialController.SERIAL_DEVICE_CONNECTED))
Debug.Log("Connection established");
else if (ReferenceEquals(message, SerialController.SERIAL_DEVICE_DISCONNECTED))
Debug.Log("Connection attempt failed or disconnection detected");
else {
commands = message.Split(' ');
TransitionBlocked();
}
System.Array.Copy(commands, stateBeforeLoadScene, commands.Length);
}

public void TransitionBlocked() {
if (checkButtonsState) {
float afterValue = 0f;
float beforeValue = 0f;
foreach (int v in buttonsBlockedOnTransitions) {
afterValue += float.Parse(commands[v], CultureInfo.InvariantCulture);
beforeValue += float.Parse(stateBeforeLoadScene[v], CultureInfo.InvariantCulture);
}

transitionBlock = beforeValue >= 1f && afterValue >= 1f;
checkButtonsState = transitionBlock;
}

return;
}

}

```

##### Script responsável por gerenciar a mecânica da barra de força, que inclui receber sinais de botões do Arduino

```
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class PowerBar : MonoBehaviour {
    public Camera MyCamera;
    public Rigidbody m_Rigidbody;
public float m_MaxForce;
[Header("Force UI")]
public float m_TimeToMaxForce;
public Slider m_ForceSlider;
public Image m_ForceFillImage;
public Color m_MinForceColor;
public Color m_MaxForceColor;
public AnimationCurve m_Curve;
public enum ShooterState
{
None, Charging, Moving
}
private bool m_Shoot;
public ShooterState m_CurrentState = ShooterState.None;
private float m_ForceDirection = 1;
public Mechanics mech;
public bool paused = true, sleeping = false;
private ArduinoListener arduino;
public bool timesUp = false;

private void Awake() {
arduino = GameObject.Find("ArduinoListener").GetComponent<ArduinoListener>();
}

private void Update()
{
if (!paused) {
if (timesUp) {
if (arduino.commands[4] == "0")
timesUp = false;
}
else {
if (!timesUp && m_CurrentState == ShooterState.None && arduino.commands[4] == "1" && !arduino.transitionBlock)
{
m_ForceSlider.gameObject.SetActive(true);
m_ForceSlider.value = 0.0f;
m_ForceDirection = 1;
m_CurrentState = ShooterState.Charging;
}

if (m_CurrentState == ShooterState.Charging && arduino.commands[4] == "1" && !arduino.transitionBlock)
{
m_ForceSlider.value += (Time.deltaTime / m_TimeToMaxForce) * m_ForceDirection;
m_ForceFillImage.color = Color.Lerp(m_MinForceColor, m_MaxForceColor, m_Curve.Evaluate(m_ForceSlider.value));

if (m_ForceSlider.value >= 1.0f || m_ForceSlider.value <= 0.0f)
{
m_ForceDirection *= -1;
}

}

if (m_CurrentState == ShooterState.Charging && arduino.commands[4] == "0")
{
mech.DesabilitaTempo();
m_CurrentState = ShooterState.Moving;
float force = m_ForceSlider.value * m_MaxForce;
m_ForceSlider.gameObject.SetActive(false);
m_Rigidbody.AddForce(Vector3.Scale(MyCamera.transform.forward, Vector3.forward + Vector3.right) * force);
}
}
}
}

private void LateUpdate ()
{
     if (m_CurrentState == ShooterState.Moving) {
            if (sleeping)
            {
                m_CurrentState = ShooterState.None;
mech.ChangeTurn(false);
            }
}
}
}

```

##### Script responsável por gerenciar o controle das câmeras, que também recebe sinais do Arduino

```
using UnityEngine;
using UnityStandardAssets.CrossPlatformInput;
using UnityEngine.SceneManagement;

namespace UnityStandardAssets.Cameras
{
public class FreeLookCamBall : PivotBasedCameraRig
{
// This script is designed to be placed on the root object of a camera rig,
// comprising 3 gameobjects, each parented to the next:
//  Camera Rig
//      Pivot
//          Camera

[SerializeField] public float m_MoveSpeed = 1f; // How fast the rig will move to keep up with the target's position.
[Range(0f, 10f)] [SerializeField] public float m_TurnSpeed = 1.5f; // How fast the rig will rotate from user input.
[SerializeField] public float m_TurnSmoothing = 0.0f; // How much smoothing to apply to the turn input, to reduce mouse-turn jerkiness
[SerializeField] public float m_TiltMax = 75f; // The maximum value of the x axis rotation of the pivot.
[SerializeField] public float m_TiltMin = 45f; // The minimum value of the x axis rotation of the pivot.
[SerializeField] private bool m_LockCursor = false; // Whether the cursor should be hidden and locked.
[SerializeField] private bool m_AutoRotate = false;
[SerializeField] public bool m_VerticalAutoReturn = false; // set wether or not the vertical axis should auto return
[SerializeField] private float m_ZoomSensitivity = 10f;
public float m_LookAngle; // The rig's y axis rotation.
public float m_TiltAngle; // The pivot's x axis rotation.
private const float k_LookDistance = 100f; // How far in front of the pivot the character's look target is.
public Vector3 m_PivotEulers;
public Quaternion m_PivotTargetRot;
public Quaternion m_TransformTargetRot;
private Vector3 m_CamLimitIn = new Vector3(0f, 0.45f, -0.21f), m_CamLimitOut = new Vector3(10f, 3.86f, -10.21f);
public Mechanics mech;
private ArduinoListener arduino;
private bool startPressed = false;

protected override void Awake()
{
base.Awake();
// Lock or unlock the cursor.
arduino = GameObject.Find("ArduinoListener").GetComponent<ArduinoListener>();
Cursor.lockState = m_LockCursor ? CursorLockMode.Locked : CursorLockMode.None;
Cursor.visible = !m_LockCursor;
m_PivotEulers = m_Pivot.rotation.eulerAngles;
m_PivotTargetRot = m_Pivot.transform.localRotation;
m_TransformTargetRot = transform.localRotation;
}

protected void Update()
{
if (!m_AutoRotate) {
HandleRotationMovement(arduino.commands);
HandleZoomInOut(arduino.commands);
}
else {
HandleRotationMovement(null);
HandleZoomInOut(null);
}

if (arduino != null) {
(!mech.gameEndP.activeSelf && !mech.PauseP.activeSelf)) {
if (arduino.commands[7] == "1")
startPressed = true;
if (arduino.commands[7] == "0" && startPressed) {
startPressed = false;
mech.PausarJogo();
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
}
}
(!mech.gameEndP.activeSelf && mech.PauseP.activeSelf)) {
if (arduino.commands[7] == "1")
startPressed = true;
if (arduino.commands[7] == "0" && startPressed) {
startPressed = false;
mech.OkClick();
}
}
}
}

protected override void FollowTarget(float deltaTime)
{
if (m_Target == null) return;
// Move the rig towards target position.
transform.position = Vector3.Lerp(transform.position, m_Target.position, deltaTime*m_MoveSpeed);
}

private void HandleZoomInOut(string[] commands) {
if (!m_AutoRotate) {
float scroll_wheel = float.Parse(commands[1]) * 0.002f;
if (scroll_wheel != 0f && commands[2] == "1") {
if (m_Cam.localPosition.x >= 0f && scroll_wheel < 0f)
m_Cam.Translate(Vector3.forward * scroll_wheel * m_ZoomSensitivity);
if (m_Cam.localPosition.x <= 10f && scroll_wheel > 0f)
m_Cam.Translate(Vector3.forward * scroll_wheel * m_ZoomSensitivity);
if (m_Cam.localPosition.x < 0f)
m_Cam.localPosition = m_CamLimitIn;
else if (m_Cam.localPosition.x > 10f)
m_Cam.localPosition = m_CamLimitOut;
}
}
}

private void HandleRotationMovement(string[] commands)
{
if(Time.timeScale < float.Epsilon)
return;
var x = 0f;
var y = 0f;
// Read the user input
if (m_AutoRotate) {
x = m_TurnSpeed * Time.deltaTime;
}
else {
x = float.Parse(commands[0]) * 0.003f;
y = float.Parse(commands[1]) * 0.003f;
}

// Adjust the look angle by an amount proportional to the turn speed and horizontal input.
m_LookAngle += x*m_TurnSpeed;
// Rotate the rig (the root object) around Y axis only:
m_TransformTargetRot = Quaternion.Euler(0f, m_LookAngle, 0f);

if (m_VerticalAutoReturn)
{
// For tilt input, we need to behave differently depending on whether we're using mouse or touch input:
// on mobile, vertical input is directly mapped to tilt value, so it springs back automatically when the look input is released
// we have to test whether above or below zero because we want to auto-return to zero even if min and max are not symmetrical.
m_TiltAngle = y > 0 ? Mathf.Lerp(0, -m_TiltMin, y) : Mathf.Lerp(0, m_TiltMax, -y);
}
else
{
// on platforms with a mouse, we adjust the current angle based on Y mouse input and turn speed
m_TiltAngle -= y*m_TurnSpeed;
// and make sure the new value is within the tilt range
m_TiltAngle = Mathf.Clamp(m_TiltAngle, -m_TiltMin, m_TiltMax);
}

// Tilt input around X is applied to the pivot (the child of this object)
m_PivotTargetRot = Quaternion.Euler(m_TiltAngle, m_PivotEulers.y , m_PivotEulers.z);
if (m_TurnSmoothing > 0)
{
m_Pivot.localRotation = Quaternion.Slerp(m_Pivot.localRotation, m_PivotTargetRot, m_TurnSmoothing * Time.deltaTime);
transform.localRotation = Quaternion.Slerp(transform.localRotation, m_TransformTargetRot, m_TurnSmoothing * Time.deltaTime);
}
else
{
m_Pivot.localRotation = m_PivotTargetRot;
transform.localRotation = m_TransformTargetRot;
}
}
}
}

```

##### Script responsável por gerenciar o controle das interfaces, dentro e fora do gameplay

```
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Globalization;

public class UIController : MonoBehaviour {
public Button selected;
private ArduinoListener arduino;
private string lastState = "0";
private string lastClickState = "0";
private PointerEventData pointer;

void OnEnable() {
arduino = GameObject.Find("ArduinoListener").GetComponent<ArduinoListener>();
pointer = new PointerEventData(EventSystem.current);
selected.Select();
selected.OnSelect(null);
}

// Update is called once per frame
void Update () {
if (float.Parse(arduino.commands[1], CultureInfo.InvariantCulture) <= -1f && lastState == "0") {
lastState = "-1";
Down();
} else if (float.Parse(arduino.commands[1], CultureInfo.InvariantCulture) >= 1f && lastState == "0") {
lastState = "1";
Up();
} else if (float.Parse(arduino.commands[1], CultureInfo.InvariantCulture) < 1f && float.Parse(arduino.commands[1], CultureInfo.InvariantCulture) > -1f && lastState != "0") {
lastState = "0";
}

arduino.TransitionBlocked();

if (arduino.commands[4] == "1" && lastClickState == "0" && !arduino.transitionBlock) {
lastClickState = "1";
arduino.checkButtonsState = true;
ExecuteEvents.Execute(selected.gameObject, pointer, ExecuteEvents.submitHandler);
} else if (arduino.commands[4] == "0" && lastClickState != "0") {
lastClickState = "0";
}
}

void Up() {
selected = (Button)selected.FindSelectableOnUp();
EventSystem.current.SetSelectedGameObject(selected.gameObject, null);
}

void Down() {
selected = (Button)selected.FindSelectableOnDown();
EventSystem.current.SetSelectedGameObject(selected.gameObject, null);
}
}

```

#### **Código na Arduino IDE**

Além do código em C#, não podemos esquecer do código em C que é executado dentro do Arduino. No início, tentamos utilizar a classe que o professor Perrotti disponibilizou para utilização do Joystick Shield V1a, porém precisávamos de uma solução que enviasse o estado de todos os botões ininterruptamente à Ardity, ou seja, à porta serial. A classe disponibilizada pelo professor só emitia sinais quando se tinha uma alteração de estados. Era uma outra proposta.

Então, baseados na classe dele, escrevemos um código que atendia as nossas necessidades e ele ficou assim:

##### **BBA\_JoystickController.ino**

```
// Button's pins
#define BT_A_PIN 2
#define BT_B_PIN 3
#define BT_C_PIN 4
#define BT_D_PIN 5
#define BT_E_PIN 6
#define BT_F_PIN 7
#define BT_Z_PIN 8

// JoyStick's pins
#define X_AXIS_PIN A0
#define Y_AXIS_PIN A1

#define NORMALIZE_VAL 1
#define TOLERANCE     10

int xAxisRef, yAxisRef, z;
boolean zIgnoreXY = true;
byte tolerance = 1;
float scaleX = 1, scaleY = 1;

void axisConfig(int normalizeTo, byte pTolerance, boolean pZIgnoreXY)
{
  if(tolerance > 0) tolerance = pTolerance;
  zIgnoreXY = pZIgnoreXY;

  if(normalizeTo)
  {
    scaleX = (float)normalizeTo/xAxisRef;
    scaleY = (float)normalizeTo/yAxisRef;
  }
}

void setup()
{
  Serial.begin(250000);
  xAxisRef = analogRead(X_AXIS_PIN);
  yAxisRef = analogRead(Y_AXIS_PIN);
  axisConfig(NORMALIZE_VAL, TOLERANCE, true);
}

void loop()
{
  z = digitalRead(BT_Z_PIN);

  if(!zIgnoreXY || z) {
    Serial.print((analogRead(X_AXIS_PIN) - xAxisRef) * scaleX);
    Serial.print(" ");
    Serial.print((analogRead(Y_AXIS_PIN) - yAxisRef) * scaleY);
    Serial.print(" ");
  }
  else {
    Serial.print("0 ");
    Serial.print("0 ");
  }
  Serial.print(!digitalRead(BT_A_PIN));
  Serial.print(" ");
  Serial.print(!digitalRead(BT_B_PIN));
  Serial.print(" ");
  Serial.print(!digitalRead(BT_C_PIN));
  Serial.print(" ");
  Serial.print(!digitalRead(BT_D_PIN));
  Serial.print(" ");
  Serial.print(!digitalRead(BT_E_PIN));
  Serial.print(" ");
  Serial.print(!digitalRead(BT_F_PIN));
  Serial.print(" ");
  Serial.println(!z);
  delay(1);
}

```

### **O controle esférico**

O Joystick Shield não tem um acabamento, o que não torna a experiência do gameplay tão agradável. Dessa forma, como o jogo tem como protagonistas bolinhas de gude, pensamos em um controle com um acabamento esférico, pra facilitar a imersão. A partir dessa ideia, fizemos um modelo.

O controle foi confeccionado usando uma esfera de isopor como molde preenchida com massa de biscuit incolor que foi pintada em seguida.

### **Documentação**

A documentação teve contribuição desde o versionamento do código às fotos e screenshots tiradas durante o desenvolvimento. Com isso, é possível avaliar melhor o todo o processo e aperfeiçoar no que se refere a ferramentas e técnicas de gestão de projetos.

## Resultados

Durante os testes foram vericados vários problemas e alguns deles já foram mencionados, mas nesta seção iremos tratar com mais detalhes e dizer como foram resolvidos.

**1. Arduino perde a conexão com a biblioteca de integração -** Inicialmente, ao desconectar e reconectar o arduino do computador, a conexão não era restavbelecida automaticamente e precisávamos reenviar o código do arduino para conseguir continuar a jogar. Não conseguimos descobrir exatamente o por que, mas a troca da biblioteca (WRMHL para Ardity) resolveu essa questão. Provavelmente o problema se dava pelo controle de fila não ser totalmente thread-safe.

​​​**2. Ao fazer carregamento de cena na Unity, a conexão com o controle tinha uma latência de alguns segundos para ser restabelecida -** O jogo é composto por duas cenas: uma para o menu principal e a outra para o jogo em si. Ambas precisavam do script chamado SerialController, da Ardity, responsavel pela configuração da porta serial, baud rate entre outras, além de iniciar a thread de escuta da porta. Porém, por padrão, a Unity destrói todos os objetos da cena na troca delas, fazendo com que seja necessário um esforço pra reconexão.

Isso foi resolvido, tornando o objeto que matinha o SerialController indestrutível pelas trocas de cena, como mostra o código abaixo.

```
// Trecho dentro do ArduinoListener.cs
void Awake() {
DontDestroyOnLoad(gameObject);
SceneManager.sceneLoaded += OnSceneLoaded;
}

```

**3. Ao fazer a troca entre algumas telas no jogo segurando o botão de confirmação eram provocadas confirmações indesejadas -** Após a correção do problema anterior, foram notadas várias confirmações indesejadas, pois sem a latência de conexão, o jogo não respeitava o respiro entre a cenas pelo jogador. Devido ao rápido processamento, se você apertasse para confirmar o ok do menu de pause o jogo já iniciava com  a barra de força liberada e esperando a escolha da força do movimento, o que prejudicava a experiência do jogo. Então, para resolver, em vez de fazer a ligação entre as classes da integração com o jogo pelo editor da unity, fazia-se uma busca pelo nome do obteto que continha o script no evento Awake ou no OnEnable, dependendo da situação, como a seguir:

```
// Trecho do código dentro de UIController.cs.
//Aqui, a solução mencionada acima impedia que fosse liberada a PowerBar logo após dar ok no menu de pause

void OnEnable() {

arduino = GameObject.Find("ArduinoListener").GetComponent<ArduinoListener>();

pointer = new PointerEventData(EventSystem.current);

selected.Select();

selected.OnSelect(null);

}

```

## Conclusão

O projeto foi executado com sucesso, com os objetivos propostos atingidos. Os problemas detectados durante a execução e testes foram solucionados a contento.Também foi possível identificar aperfeiçoamentos e upgrades que podem vir a ser implementados no futuro, por exemplo, um led RGB para indicar a cor do jogador que deve usar o controle, caso esteja utilizando apenas um controle para jogar; colocar leds onde cada um representasse uma porcentagem da vida das bolinhas do jogador e até mesmo quantas bolinhas o jogador ainda tem em jogo. Trocar a placa do Arduino por uma com wifi ou bluetooth para o controle nao precisar de fios.

O jogo para o qual este controle foi desenvolvido, ainda está em desenvolvimente e pode ter muitas melhorias, assim como o próprio controle.

O principal destaque foi o aprendizado da interação da Unity com o Arduino. Foi um grande desafio que ensinou a forma de como os controles "especiais" são feitos para um video game, como as antigas pistolas, volantes, tapetes de danças, etc. Existe opções de um controle que interage com jogo ou até um jogo que interaja com um arduino, podendo ser um drone que responde a interações dentro de um jogo.

Alguns alunos e professores da FATEC de Americana já testaram o jogo com o primeiro controle de Arduino e foi bem sucedido. Objetivo de ser apresentado uma versão definitiva do controle até o evento da "FATEC Portas Abertas" de 2019.

Para fazer seus próprios testes, basta utilizar o link para download do jogo Battle Balls Arena.

## Download

Segue o link para download do jogo:

https://drive.google.com/drive/folders/1ppiSvpJoZRYPi2X1hNSZmV2lR0C1i98P?usp=sharing

## Instalando o Arduino

Depois de efetuar o download, dentro da pasta "Windows\_x64", você encontrará o arquivo "BBA\_JoystickController.ino" para instalar em seu Arduino Joystick Shield V1a.

Em breve, um tutorial em video estará disponível nessa página para auxiliar na instalação.