{"id":22004462,"url":"https://github.com/lucasctnh/ludact-test","last_synced_at":"2026-04-13T20:03:01.713Z","repository":{"id":106225065,"uuid":"484068289","full_name":"lucasctnh/ludact-test","owner":"lucasctnh","description":"Technical test for Unity Developer role at Ludact","archived":false,"fork":false,"pushed_at":"2022-04-24T17:15:15.000Z","size":11395,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-28T12:46:00.923Z","etag":null,"topics":["job-test","ludact","unity"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lucasctnh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2022-04-21T13:45:04.000Z","updated_at":"2022-04-24T17:18:47.000Z","dependencies_parsed_at":"2023-05-30T17:45:31.781Z","dependency_job_id":null,"html_url":"https://github.com/lucasctnh/ludact-test","commit_stats":null,"previous_names":["lucasctnh/ludact-test"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lucasctnh%2Fludact-test","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lucasctnh%2Fludact-test/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lucasctnh%2Fludact-test/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lucasctnh%2Fludact-test/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lucasctnh","download_url":"https://codeload.github.com/lucasctnh/ludact-test/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245062806,"owners_count":20554802,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["job-test","ludact","unity"],"created_at":"2024-11-30T00:16:02.399Z","updated_at":"2026-04-13T20:03:01.707Z","avatar_url":"https://github.com/lucasctnh.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Teste Desenvolvedor Unity - Ludact\n\n## Instruções\n\n1 - Fazer naves aparecendo na sequência de fibonacci sem utilizar laços(foreach, for, while, do/while) e utilizando object pooling;\n\n2 - As naves devem possuir velocidades diferentes, aumentando de maneira linear de acordo com o tempo passado;\n\n3 - Fazer 1 nave ser destruída a cada 1 segundo;\n\n4 - Deve haver uma UI demonstrando quantas naves já foram criadas e outras informações que julgar pertinente;\n\n5 - A UI deve ser responsiva com diferentes resoluções;\n\n## Implementação das Instruções\n\n1 - Ship/Spawner/ShipSpawner.cs:28; A minha interpretação dessa instrução foi de fazer a quantidade de naves que aparem na tela serem referentes à sequência de Fibonacci. Outra interpretação que tive, mas descartei foi: o tempo para uma quantidade X qualquer de naves aparecer ser referente à Fibonacci. Achei que do primeiro jeito seria mais interessante. A forma que de fato implementei essa instrução foi usando do próprio Update da Unity e fiz as naves aparecerem em ‘rounds’. A cada round é spawnada uma quantidade de naves Fibonacci(n), utilizando um algoritmo recursivo, onde n é o número de rounds. Como uma nave é destruída a cada segundo, o round muda quando as o número de naves ativas do round chega a zero.\n\n2 - Ship/Spawner/ShipSpawner.cs:49, e Ship/Ship.cs:88; Cada nave é criada com uma velocidade randômica cuja variação é configurável, por default essa variação é de 1 à 10. Além disso sua velocidade é aumentada linearmente por um valor arbitrário também configurável, o padrão sendo 0.05. Por causa do aumento linear, a velocidade chega a valores altos muito rápido e para conseguir mostrar na tela esse aumento fiz com que a naves ficassem confinadas à area da camera.\n\n3 - Destroyer/ShipDestroyer.cs:31; Eu queria que a destruição das naves pudesse ser visualizada, assim, coloquei uma nave estática central cuja função é destruir as outras. A cada segundo a ‘destroyer’ atira em uma nave, quando esta é destruída ela mira em um novo alvo, outra nave, e repete o ciclo.\n\n4 - Além de naves criadas, também adicionei a informação de naves spawnadas (diferente de criadas por causa do reciclamento do object pooling), naves destruídas, naves ativas e informações dos rounds. E também botões que controlam alguns estados do jogo.\n\n5 - A UI é responsiva usando a ancoragem de elementos da Unity.\n\n## Sistemas\n\nDá pra dividir os sistemas em três áreas maiores: elementos que compõe o jogo, elementos de UI e gerenciamento. Tirando o AudioManager.cs, que é Singleton, os scripts se conversam por eventos, seguindo majoritariamente o Observer design pattern.\n\n### Gameplay\n\nO ‘core loop’ da gameplay é o sistema de rounds, gerenciado pelo próprio spawner das naves ShipSpawner.cs. O spawner possui três variáveis que permitem esse core loop sem utilizar laços, _shipsCounter, _rounds e _fibonacci. A primeira é referente à quantidade de naves spawnadas, isso pra garantir que a cada round a quantidade de naves seja a mesma que a sequência de Fibonacci atual. A segunda conta os rounds, pra ser utilizado como o termo n de Fibonnaci(n), cujo resultado é guardado em _fibonacci.\n\nO spawner monitora a quantidade de objetos do tipo Ship ativas atualmente na pool, e como uma nave é destruída a cada segundo pela nave central (Destroyer), quando a quantidade de naves ativas chega a zero é chamado o método NewRound(). O método reinicia a contagem de _shipsCounter e aumenta em 1 a senquência de Fibonacci.\n\n### Spawners\n\nAlém do spawner de naves, também tem o spawner dos tiros e das explosões. Todos funcionam essencialmente da mesma forma, por object pooling. No Start() a pool de objetos é criada e é associada à ela os métodos de evento que serão chamados ao criar um novo objeto (caso não tenha um objeto disponível), ao pegar (Get) um objeto da pool, ao liberá-lo (Release) de volta à ela e ao destruí-lo. A criação é simplesmente uma instanciação, os mais importantes são os métodos de Get e Release que, respectivamente, ativam e desativam o objeto, mantendo sua referência.\n\n### Naves\n\nO funcionamento da nave é de se mover para frente, e aumentar linearmente sua velocidade. A velocidade inicial é definida pelo spawner. Existem dois elementos estáticos que demarcam os limites da área jogável. O Camera Bounds é delimitado pela área da câmera, e o World Bounds é ligeiramente maior que o Camera Bounds. Dito isso, as naves monitoram o estado do GameMode, caso o modo seja ‘Confined’ as naves irão colidir com o Camera Bounds e refletir seu vetor frontal e vetor velocidade dando a impressão de quicar. Caso o modo seja ‘Free’, o GameManager.cs muda a forma de colisão com Camera Bounds para trigger, assim, no último instante de colisão, a nave passa a ser teleportada para trás do limite da câmera porém no lado oposto, dando impressão de que a nave está livre de qualquer limite. Independente do modo de jogo, as naves sempre irão rebater no Destroyer.\n\n### Explosões\n\nO ExplosionSpawner.cs monitora os eventos disparados de quando uma nave é destruída para spawnar uma explosão na posição que aquela nave se encontrava. A explosão em si é simplesmente um sistema de partículas que roda logo ao ser instanciado e retorna para a pool quando acaba a duração.\n\nTanto as naves quanto as explosões possuem AudioSources embutidos que não são gerenciados pelo AudioManager, por isso, eles, individualmente, monitoram o estado de som e mutam/desmutam quando necessário.\n\n### Destroyer\n\nO ShipDestroyer.cs cumpre a função de destruir uma nave a cada segundo. Seu ciclo de vida é: achar um alvo (quando não existir um ou uma nave for destruída), mirar, atirar (caso um segundo tenha se passado)...\n\n### Tiros\n\nO BulletSpawner.cs é um elemento filho do Destroyer, dessa forma, quando o Destroyer chama o método de atirar, uma Bullet.cs é spawnada e à ela é atribuída o alvo do Destroyer. O funcionamento da Bullet é de simplesmente manter seu vetor frontal alinhado com o alvo e seguir muito rápido para frente.\n\n### Utils\n\nSão uma coleção de métodos úteis que posso reusar em qualquer lugar da aplicação. Além do método que calcula a sequência de Fibonacci recursivamente, têm dois métodos para manter os objetos somente nos eixos X e Y, em KeepParentZAxisOf o objeto mantém o eixo Z do parent e em RemoveZAxisOf o eixo Z do objeto é mantido em 0. Esses métodos são necessários as vezes pois a interpolação esférica retorna uma posição nos três eixos. Além disso também existe o método GetRandomPositionInSpawnableArea, que calcula uma posição aleatória dentro da área da câmera.\n\n## UI\n\nA UI é composta de duas áreas principais, o lado esquerdo possui informações, de cima para baixo: a quantidade de naves únicas criadas, a quantidade de naves spawnadas, a quantidade total de naves destruídas, a quantidade de naves que foram spawnadas no round atual, a quantidade de naves atualmente ativas/vivas e em qual round o jogo se encontra; já o lado direito possui botões interativos que mudam estados do jogo, de cima para baixo: pausa e despausa o jogo, muda o modo de jogo (confinar as naves ou deixá-las livres), pula um round, altera o estado de som do jogo (pode-se colocar no mudo apenas os efeitos sonoros, todos os sons ou tirar tudo do mudo) e reinicia o jogo.\n\n### Background\n\nO background possui um efeito parallax, são três imagens em camadas. A camada mais a frente se move mais rápido enquanto a mais atrás se move mais lentamente. A cada uma taxa de segundos configurável (2.5 por default), uma nova posição aleatória é definida para as três camadas seguirem. A velocidade é definida por um interpolação esférica da posição atual da camada e da posição alvo.\n\n## Gerenciamento\n\n### Do Audio\n\nO AudioManager.cs possui uma lista de componentes AudioSource, _tracks, podendo comportar múltiplas faixas ao mesmo tempo e uma lista de sons _sounds. A primeira faixa é separada para música de fundo e a segunda para efeitos sonoros. Os métodos são acessados pela instância estática do AudioManager, o método PlaySoundOneShot toca um som uma vez somente recebendo como parâmetros o número da faixa e do som. Além disso também têm os métodos para colocar ou retirar os sons do mudo.\n\n### Do Jogo\n\nO GameManager.cs possui variáveis estáticas para os estados importantes de jogo, IsGamePaused, se o jogo está atualmente pausado; GameMode, que rege o estado da naves, entre ficarem confinadas à area da câmera (Confined) e ficarem “livres” (Free) e para tal, ele altera a forma de colisão com Camera Bounds para ‘trigger’ quando no modo Free; e SoundState que rege o estado do som, entre deixar mudo somente os SFX (MuteSFX), deixar tudo no mudo (MuteAll), ou tirar tudo do mudo (UnmuteAll). O método que pausa o jogo o faz ao colocar ao mudar o Time.timeScale em 0 (ou 1 para despausar) e, ao pausar, o estado de som também é alterado para MuteAll. Outro método importante é o de SkipRound, ele funciona ao achar todas a naves ativas e destruí-las, assim, o próprio ShipSpawner reconhecerá que um novo round deve ser feito. Além disso, outra função importante do manager é manter o frame rate em 60 fps.\n\n### Da UI\n\nO UIManager.cs mantém a referência dos elementos da interface e os modifica de acordo com os eventos em que é inscrito. Possui variáveis internas para auxiliar principalmente nas contagens, por exemplo, na quantidade de naves criadas ou destruídas, isso para evitar a confusão de parâmetros passados pelos eventos. Um função importante do manager é desabilitar os botões, tirando o de despausar e reinicar, sempre que o jogo é pausado.\n\n## Testes Unitários\n\nFiz apenas um teste que julguei realmente necessário, que foi verificar se o algoritmo recursivo para o cálculo da sequência de Fibonacci estava correto.\n\n## Créditos\n\nTentei usar somente os assets que foram providenciados ou que eu mesmo fiz (os sons, o backgrounde e os icons), pra ficar mais autêntico. Porém, a música eu peguei de um\nbundle free na Asset Store: [Free Music Bundle by neocrey](https://assetstore.unity.com/packages/audio/music/electronic/free-music-bundle-by-neocrey-92835).\nAlém disso, o Readme eu vi em um projeto oficial da Unity e achei interessante aplicar aqui, sendo ele o [3D Game Kit](https://assetstore.unity.com/packages/templates/tutorials/3d-game-kit-115747).","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flucasctnh%2Fludact-test","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flucasctnh%2Fludact-test","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flucasctnh%2Fludact-test/lists"}