HayBox é um firmware modular e multiplataforma para controladores digitais ou mistos analógicos/digitais, voltado principalmente para controladores estilo B0XX.
Os recursos incluem:
Se você quiser simplesmente usar um firmware pré-construído com mapeamentos e configuração de pinos padrão, consulte a seção de binários pré-construídos. Se você quiser fazer alguma alteração no código, consulte a seção construindo a partir do código-fonte.
.uf2
), basta colocá-lo no modo bootsel enquanto o conecta ao seu PC e arrastar e soltar o arquivo .uf2
na unidade RPI-RP2 que aparece.hex
), poderá usar um programa como o QMK Toolbox para atualizar o arquivo .hex
para eleExistem atualmente três maneiras principais de construir HayBox:
Tanto GitHub Actions quanto GitHub Codespaces exigem que você crie uma conta GitHub, mas não exigem que você instale nenhuma dependência em sua máquina local.
As seguintes dependências são necessárias ao construir localmente:
Depois de instalar todos os requisitos, baixe e extraia a versão mais recente do HayBox ou clone o repositório se você tiver o git instalado (o que torna mais fácil obter atualizações).
Depois disso:
git config --global core.longpaths true
em qualquer terminal (dentro do VS Code ou cmd/PowerShell normal, tudo bem)config/<environment>/config.cpp
). Quaisquer botões que seu controlador não possua podem simplesmente ser excluídos da lista.HayBox/.pio/build/<environment>/firmware.uf2
no RPI -RP2 unidade que aparece.Esta é provavelmente a maneira mais conveniente de modificar e reconstruir o HayBox, mas lembre-se de que o nível gratuito do GitHub impõe algumas limitações sobre quanto você pode usar Codespaces por mês. Por causa disso, você deve desligar seus Codespaces quando não os estiver usando, para maximizar o que pode obter com sua cota.
Primeiro, crie uma conta GitHub ou apenas faça login se você já tiver uma, então bifurque este repositório e abra seu fork em um novo Codespace clicando no botão verde Código -> Codespaces -> Criar codespace no master. Isso deve abrir o VS Code em seu navegador com todas as extensões e dependências necessárias pré-instaladas. A partir daqui, o processo é praticamente o mesmo de construir localmente, exceto que você não pode usar o botão Upload para atualizar o firmware. Em vez disso, você terá que baixar o binário compilado de HayBox/.pio/build/<environment>/
e atualizá-lo manualmente (veja aqui para mais informações sobre isso).
Este repositório contém uma definição de fluxo de trabalho do GitHub Actions que cria cada ambiente PlatformIO especificado na matriz em cada push e carrega binários de firmware como artefatos que você pode baixar clicando em um fluxo de trabalho específico executado no histórico. Você pode criar uma bifurcação deste repositório e ativar Ações clicando em Configurações -> Ações -> Geral -> Selecione "Permitir todas as ações e fluxos de trabalho reutilizáveis" -> Salvar.
A maneira mais rápida de fazer alterações se você deseja construir apenas por meio do GitHub Actions é usar github.dev. Você pode fazer isso simplesmente pressionando .
no teclado enquanto você mantém o fork deste repositório aberto, e ele abrirá um editor de código VS no seu navegador. Isso não oferece os mesmos recursos de desenvolvimento que você obteria em um Codespace, mas permite fazer alterações e confirmá-las diretamente do seu navegador. Altere o que desejar e use a guia Controle de origem à esquerda para adicionar, confirmar e enviar suas alterações. Por fim, volte ao repositório e clique na guia Ações, clique na execução do seu fluxo de trabalho e aguarde a construção do artefato.
Se você estiver adicionando um novo ambiente de configuração de dispositivo/PlatformIO, você terá que adicionar o ambiente à matriz para que ele seja criado pelo fluxo de trabalho do GitHub Actions. Você também pode remover quaisquer ambientes da matriz com os quais não se importa, a fim de reduzir o uso de recursos e potencialmente acelerar suas compilações.
Para reinicializar os controladores baseados no Pico no modo bootsel, segure Iniciar no plugin.
Para mudar para o modo de placa Brook em GCCPCB2, GCCMX, B0XX R2 ou LBX, segure B no plugin.
Os back-ends de comunicação são selecionados de maneira ligeiramente diferente dependendo do tipo de microcontrolador usado no controlador.
No Pico/RP2040, USB vs GameCube vs Nintendo 64 é detectado automaticamente. Se não estiver conectado a um console, o padrão é XInput , que deve funcionar plug-and-play com a maioria dos jogos de PC. Outros backends são selecionados segurando um dos seguintes botões no plugin:
No Arduino/AVR, o backend DInput é selecionado se uma conexão USB for detectada. Caso contrário, o padrão é o backend do GameCube, a menos que outro backend seja selecionado manualmente segurando um dos seguintes botões no plugin:
Ao contrário de outros firmwares semelhantes, o HayBox, por padrão, permite que você alterne os modos rapidamente, sem desconectar o controlador. Isso é útil principalmente no PC, ao contrário do console, onde normalmente você precisa reiniciar o console para trocar de jogo de qualquer maneira. Ele também serve para reduzir o número de botões que você precisa segurar com uma mão enquanto conecta.
As combinações padrão dos botões do modo do controlador são:
Combinações padrão de botões do modo de teclado (disponíveis apenas ao usar o backend DInput, não com XInput):
HayBox precisa de um perfil de controlador Dolphin diferente do firmware oficial B0XX, pois usa diferentes mapeamentos DInput que fazem mais sentido para uso em vários jogos. Eles podem ser encontrados na pasta dolphin
no repositório HayBox. Os arquivos de perfil são nomeados para indicar a qual back-end de comunicação e sistema operacional eles se destinam:
Para instalar o perfil:
dolphin
dentro do HayBox para a pasta <YourDolphinInstallation>UserConfigProfilesGCPad
(crie-o se não existir)%appdata%Slippi LaunchernetplayUserConfigProfilesGCPad
~/.config/SlippiOnline/Config/Profiles/GCPad/
Cmd + Shift + G
e digite o caminho /Users/<USER>/Library/Application Support/Slippi Launcher/netplay/Slippi Dolphin.app/Contents/Resources/Sys/Config/Profiles/GCPad
%userprofile%DocumentsDolphin EmulatorConfigProfilesGCPad
~/.config/dolphin-emu/Profiles/GCPad/
* O macOS suporta apenas DInput (e não muito bem), então se estiver usando um controlador baseado em Pico/RP2040 você terá que forçar o modo DInput segurando Z no plugin, e mesmo assim pode não funcionar. Eu realmente não posso fazer nada sobre o fraco suporte ao controlador da Apple (que eles parecem quebrar com todas as outras atualizações) e não possuo nenhum dispositivo Apple, então isso também será considerado uso não suportado do HayBox.
O backend de comunicação (por exemplo, DInput, GameCube ou N64) é selecionado parcialmente por meio de detecção automática e parcialmente baseado nos botões mantidos no plugin. Isso é tratado em config/<environment>/config.cpp
, na função setup()
. A lógica é bastante simples e mesmo que você não tenha experiência em programação não deve ser muito difícil ver o que está acontecendo e mudar as coisas, se desejar.
As pastas de configuração correspondentes aos ambientes Arduino são:
config/arduino_nativeusb/
para Arduino com suporte USB nativo (por exemplo, Leonardo, Micro)config/arduino/
para Arduino sem suporte USB nativo (por exemplo, Uno, Nano, Mega 2560) Para configurações de dispositivos Arduino, você pode notar que o número 125 é passado para GamecubeBackend()
. Isso permite que você altere a taxa de polling, por exemplo, se o seu DIY não suportar USB nativo e você quiser usá-lo com um adaptador de controlador GameCube com overclock. Nesse exemplo, você poderia passar 1000 para sincronizar até a taxa de pesquisa de 1000 Hz ou 0 para desativar completamente essa correção de atraso. A taxa de votação pode ser passada para o construtor N64Backend da mesma maneira que esta.
Você pode notar que a taxa de polling de 1000 Hz também funciona no console. Esteja ciente de que, embora isso funcione, resultará em mais atraso de entrada. O objetivo de definir a taxa de pesquisa aqui é para que o backend do GameCube possa atrasar até um pouco antes da próxima pesquisa antes de ler as entradas, para que as entradas sejam atualizadas e não desatualizadas.
Para o Pico/RP2040, não é necessário passar uma taxa de polling do console, pois o Pico tem poder de processamento suficiente para ler/processar entradas após receber a polling do console, portanto não há necessidade de prever quando a poll chegará e preparar as coisas antecipadamente.
Para configurar os pressionamentos de botão para modos de entrada (modos de controlador/teclado), edite a função select_mode()
em config/mode_selection.hpp
. Cada instrução if
é uma combinação de botões para selecionar um modo de entrada.
A maioria dos modos de entrada suporta a passagem em um modo de limpeza SOCD, por exemplo, socd::2IP_NO_REAC
. Veja aqui os outros modos disponíveis.
Para criar novos modos de entrada, ajuda se você conhece um pouco de C++ ou pelo menos tem alguma experiência em programação. Dito isto, você poderá sobreviver mesmo sem experiência anterior se apenas basear seu novo modo nos existentes e se referir a eles como exemplos.
Existem dois tipos de modos de entrada: ControllerMode e KeyboardMode
Os modos de teclado são um pouco mais simples, então vamos começar por aí.
Um KeyboardMode se comporta como um teclado padrão e deve funcionar com qualquer dispositivo que suporte teclados.
Você é livre para usar quaisquer truques de lógica e programação que desejar na função UpdateKeys()
para decidir as saídas com base no estado de entrada. Você pode criar camadas de entrada (como a camada D-Pad no modo Melee que é ativada ao segurar Mod X e Mod Y) ou outros tipos de entradas condicionais.
A lista de códigos de acesso disponíveis pode ser encontrada aqui.
Lembre-se de que os modos de teclado só podem ser ativados ao usar o backend de comunicação DInput ( não XInput).
Um ControllerMode pega um estado de entrada de botão digital e o transforma em um estado de saída correspondente a um gamepad padrão. Qualquer ControllerMode funcionará com qualquer CommunicationBackend. Um CommunicationBackend simplesmente lê entradas de uma ou mais fontes de entrada, usa o ControllerMode atual para atualizar as saídas com base nessas entradas e controla o envio das saídas para o console ou PC.
Para criar um ControllerMode, basta implementar as funções UpdateDigitalOutputs()
e UpdateAnalogOutputs()
.
UpdateDigitalOutputs()
é muito semelhante à função UpdateKeys()
nos modos de teclado, com a diferença de que, em vez de chamar uma função Press()
para enviar entradas imediatamente, estamos simplesmente definindo o estado de saída para esta iteração. Como o nome indica, nesta função trataremos apenas das saídas digitais.
UpdateAnalogOutputs()
é um pouco mais complicado. Primeiramente, é necessário chamar UpdateDirections()
antes de fazer qualquer outra coisa. Esta função recebe valores que indicam se os controles esquerdo e direito estão apontando para a esquerda/direita/para cima/para baixo. Você também passa os valores analógicos mínimo, neutro (centro) e máximo, para que possa configurá-los por modo. Todas essas informações são usadas para definir automaticamente os valores analógicos do stick com base nas entradas que você passou. Isso é tudo que você precisa fazer, a menos que queira implementar modificadores.
UpdateDirections()
também preenche as directions
variáveis com valores que indicam a direção atual do stick, que pode ser 1, 0 ou -1 para os eixos X e Y de ambos os sticks. Esses valores tornam muito mais fácil escrever lógica modificadora.
Depois de chamar UpdateDirections()
, adicione qualquer lógica de manipulação de modificador desejada. Lembre-se de que UpdateDirections()
já definiu os valores padrão do stick analógico, portanto, ao manipular modificadores, você só precisa definir manualmente os valores dos eixos que estão realmente sendo modificados. Fora isso, não posso ensinar como escrever sua lógica modificadora, então basta olhar os exemplos e brincar.
Por fim, defina quaisquer valores de disparo analógico necessários.
Nota: As saídas de gatilho analógicas também poderiam ser tratadas em UpdateDigitalOutputs()
, mas acho que geralmente parece mais limpo mantê-las junto com as outras saídas analógicas.
Observe também: você não precisa se preocupar em redefinir o estado de saída ou limpar nada dele. Isso é feito automaticamente no início de cada iteração.
No construtor de cada modo (para modos de controlador e modos de teclado), você pode configurar pares de entradas de direções opostas para aplicar a limpeza SOCD.
Por exemplo, em src/modes/Melee20Button.cpp
:
_socd_pair_count = 4;
_socd_pairs = new socd::SocdPair[_socd_pair_count]{
socd::SocdPair{&InputState::left, &InputState::right, socd_type},
socd::SocdPair{ &InputState::down, &InputState::up, socd_type},
socd::SocdPair{ &InputState::c_left, &InputState::c_right, socd_type},
socd::SocdPair{ &InputState::c_down, &InputState::c_up, socd_type},
};
Isso configura esquerda/direita, baixo/cima, C-Esquerda/C-Direita e C-Baixo/C-Cima como pares de direções cardeais opostas para as quais a limpeza SOCD será aplicada. A limpeza do SOCD é feita automaticamente antes de UpdateDigitalOutputs()
e UpdateAnalogOutputs()
, e você não precisa se preocupar mais com isso.
Para cada SocdPair
você pode passar um SocdType
de sua escolha. Por padrão, para a maioria dos modos, isso é passado como um único parâmetro do construtor, mas é possível passar vários parâmetros ou simplesmente usar um valor codificado. Ambas as abordagens são exemplificadas em src/modes/FgcMode.cpp
.
SocdType | Descrição |
---|---|
SOCD_NEUTRAL | Esquerda + direita = neutro - o padrão se nenhum SocdType for especificado no SocdPair |
SOCD_2IP | Segunda prioridade de entrada - esquerda -> esquerda + direita = direita e vice-versa. Liberar a segunda direção fornece a direção original |
SOCD_2IP_NO_REAC | Segunda prioridade de entrada sem reativação - igual ao acima, exceto que a liberação da segunda direção resulta em neutro. A direção original deve ser reativada fisicamente. |
SOCD_DIR1_PRIORITY | O primeiro botão do SocdPair sempre tem prioridade sobre o segundo |
SOCD_DIR2_PRIORITY | O segundo botão do SocdPair sempre tem prioridade sobre o primeiro |
SOCD_NONE | Sem resolução SOCD – o jogo decide |
Observe que você não precisa implementar uma função HandleSocd()
como nos modos Melee20Button e Melee18Button. Ele só é substituído nesses modos para que possamos verificar se a esquerda e a direita estão pressionadas antes da limpeza do SOCD, porque quando ambos são mantidos pressionados (sem uma direção vertical mantida), precisamos substituir todos os modificadores.
Se o seu controlador não tiver botões de proteção contra luz, você pode usar o Mod X para proteção contra luz e colocar a inclinação do escudo em R. Você pode fazer isso usando o modo Melee18Button em vez de Melee20Button.
Os modos Melee20Button e Melee18Button oferecem a escolha de quais coordenadas usar ao pressionar para baixo + direita. Por padrão, manter pressionado + voltar permitirá que você cancele automaticamente o jab, o que é uma técnica útil para alguns personagens.
Outra técnica popular que usa a diagonal para baixo + direita é a chamada seleção de opções de agachar/caminhar. Esta técnica envolve segurar para baixo + para frente em um determinado ângulo enquanto está agachado, de modo que, após cancelar um ataque agachado, você automaticamente começará a caminhar em direção ao seu oponente em vez de voltar a agachar-se. Isso pode ser muito útil para perseguir tecnologia, mas as coordenadas usadas para esta técnica não permitem o cancelamento automático do jab.
Isso pode ser configurado conforme visto em config/mode_selection.hpp
definindo a opção crouch_walk_os
como true:
new Melee20Button(socd::SOCD_2IP_NO_REAC, { .crouch_walk_os = false })
Você também terá que alterar isso em seu config/<environment>/config.cpp
para que seja aplicado no plugin, pois mode_selection.hpp
controla apenas o que acontece quando você alterna o modo.
O modo ProjectM possui algumas opções extras para configurar determinados comportamentos. Como visto em config/mode_selection.hpp
:
new ProjectM(
socd::SOCD_2IP_NO_REAC,
{ .true_z_press = false, .ledgedash_max_jump_traj = true }
)
Em primeiro lugar, a opção ledgedash_max_jump_traj
permite ativar ou desativar o comportamento emprestado do modo corpo a corpo, onde segurar para a esquerda e para a direita (e sem direções verticais) fornecerá um cardeal de 1,0, independentemente dos modificadores mantidos.
Se você alterar o modo SOCD para 2IP (com reativação), você também deve alterar esta opção para falso se quiser uma experiência de jogo tranquila.
Em segundo lugar, a opção true_z_press
existe porque o Projeto M/Project+ não lida com pressionamentos Z da mesma forma que o Melee. Corpo a corpo interpreta pressionar Z como escudo de luz + A e, portanto, pode ser usado para cancelar L sem bloquear você dos técnicos. Em PM/P+, pressionar Z acionará uma tecnologia e, portanto, causará bloqueios de tecnologia indesejados se usado para cancelar L. Por padrão no HayBox, o modo ProjectM é configurado para usar uma macro lightshield + A para preservar o comportamento esperado do Melee. No entanto, esta macro não permite que você use ataques de amarrar/agarrar ou agarrar itens. Para contornar isso, você pode pressionar Mod X + Z para enviar uma entrada Z verdadeira.
Se isso incomoda você e você deseja apenas enviar uma entrada Z verdadeira por padrão ao pressionar Z, você pode definir a opção true_z_press
como true.
HayBox suporta várias fontes de entrada que podem ser lidas para atualizar o estado de entrada:
GpioButtonInput
- O mais comumente usado, para leitura de interruptores/botões conectados diretamente aos pinos GPIO. Os mapeamentos de entrada são definidos por um array de GpioButtonMapping
como pode ser visto em quase todas as configurações existentes.SwitchMatrixInput
- semelhante ao acima, mas verifica uma matriz de comutadores de estilo de teclado em vez de comutadores individuais. Uma configuração para o Modelo C<=53 do Crane está incluída em config/c53/config.cpp
que serve como um exemplo de como definir e usar uma fonte de entrada de matriz de comutação.NunchukInput
- Lê entradas de um Wii Nunchuk usando i2c. Isto pode ser usado para controladores de entrada mista (por exemplo, a mão esquerda usa um Nunchuk para movimento e a mão direita usa botões para outros controles)GamecubeControllerInput
– Semelhante ao acima, mas lê de um controlador GameCube. Pode ser instanciado de forma semelhante ao GamecubeBackend. Atualmente implementado apenas para Pico, e você deve executá-lo em uma instância pio diferente (pio0 ou pio1) de qualquer instância do GamecubeBackend ou certificar-se de que ambos usem o mesmo deslocamento de memória de instrução PIO. Cada fonte de entrada possui um valor de "velocidade de varredura" que indica aproximadamente quanto tempo leva para ler as entradas. Fontes de entrada rápida são sempre lidas no último momento possível (pelo menos no Pico), resultando em latência muito baixa. Por outro lado, fontes de entrada lentas são normalmente lidas muito antes de serem necessárias, pois são muito lentas para serem lidas em resposta à pesquisa. Por causa disso, é mais ideal ler constantemente essas entradas em um núcleo separado. Isso não é possível em MCUs AVR porque são todos de núcleo único, mas é possível (e fácil) no Pico/RP2040. A parte inferior da configuração padrão do Pico config config/pico/config.cpp
ilustra isso usando core1 para ler as entradas do Nunchuk enquanto core0 cuida de todo o resto. Consulte a próxima seção para obter mais informações sobre como usar core1.
Na função setup()
de cada configuração, construímos um array de fontes de entrada e depois o passamos para um backend de comunicação. O back-end de comunicação decide quando ler quais fontes de entrada, porque as entradas precisam ser lidas em diferentes momentos para diferentes back-ends. Também construímos uma série de back-ends de comunicação, permitindo que mais de um back-end seja usado ao mesmo tempo. Por exemplo, na maioria das configurações, o backend do visualizador de entrada B0XX é usado como backend secundário sempre que o backend DInput é usado. Em cada iteração, o loop principal informa a cada um dos backends para enviar seus respectivos relatórios. No futuro, poderá haver mais back-ends para coisas como gravar informações em um display OLED.
Em cada configuração, existem as funções setup()
e loop()
, onde setup()
é executado primeiro e, em seguida, loop()
é executado repetidamente até que o dispositivo seja desligado.
No Pico/RP2040, as funções setup()
e loop()
são executadas no core0, e você pode adicionar as funções setup1()
e loop1()
para executar tarefas no core1.
Por exemplo, para ler as entradas do controlador GameCube no core1:
GamecubeControllerInput *gcc = nullptr;
void setup1() {
while (backends == nullptr) {
tight_loop_contents();
}
gcc = new GamecubeControllerInput(gcc_pin, 2500, pio1);
}
void loop1() {
if (backends != nullptr) {
gcc->UpdateInputs(backends[0]->GetInputs());
}
}
O loop while
garante que esperemos até que setup()
no core0 termine de configurar os back-ends de comunicação. Em seguida, criamos uma fonte de entrada do controlador GameCube com uma taxa de polling de 2500Hz. Também o executamos no pio1
como uma maneira fácil de evitar interferir em qualquer back-end do GameCube/N64, que usa pio0
a menos que especificado de outra forma. Em loop1()
assumimos que o backend primário é o primeiro elemento do array backends
(que está configurado no mesmo arquivo de qualquer maneira, então não estamos realmente assumindo nada que não sabemos) e verificamos diretamente o controlador do GameCube entradas no estado de entrada do back-end.
Como um exemplo hipotético um pouco mais maluco, seria possível até mesmo alimentar todos os controles de um gabinete de arcade para duas pessoas usando um único Pico, criando duas fontes de entrada de matriz de comutação usando, digamos, 10 pinos cada, e dois back-ends de GameCube, ambos em núcleos separados. As possibilidades são infinitas.
Se você estiver usando um adaptador oficial com um controlador baseado em Arduino, provavelmente terá que segurar A no plugin que desativa a otimização da latência de polling, passando uma taxa de polling de 0 para o construtor GamecubeBackend.
Se você estiver usando um controlador baseado em Arduino sem circuito de reforço, você precisará de energia de 5 V, portanto, para o adaptador Mayflash, você precisará de ambos os cabos USB conectados e, no console, a linha de vibração precisa estar intacta. O Pico funciona nativamente com alimentação de 3,3 V, então isso não é um problema.
Aceito contribuições e se você fizer um modo de entrada que deseja compartilhar, sinta-se à vontade para fazer uma solicitação pull. Instale o plugin clang-format para VS Code e use-o para formatar qualquer código que você deseja adicionar.
Usamos SemVer para versionamento. Para as versões disponíveis, consulte as tags neste repositório.
Veja também a lista de colaboradores que participaram deste projeto.
Este projeto está licenciado sob a GNU GPL Versão 3 - consulte o arquivo LICENSE para obter detalhes