• Nenhum resultado encontrado

4 METODOLOGIA

4.4 MODELAGEM

A criação da pista foi feita a partir de blocos para garantir uma gama maior de possibilidades, pois a criação da pista inteira como um único objeto tornaria o cenário pesado e a personalização da mesma seria prejudicada. A criação da pista a partir de pequenos blocos e duplicando-os torna o cenário mais leve e permite, também, expandir e alterar a pista com facilidade.

A pista e a sinalização são as partes que interagem com o usuário sendo elas as mais importantes no ambiente, o nível de detalhamento deve ser um pouco mais elevado que dos outros objetos que compõem o cenário. A criação do cenário com edifícios, árvores e montanhas não há necessidade de ser bem elaborados, detalhados e complexos, pois tornaria a renderização do cenário demorado devido à quantidade de vértices que seriam gerados pelos detalhes.

Primeiramente os blocos são criados separadamente através da ferramenta Blender. Após a modelagem, exportam-se os blocos para o Unity 3D em modelos 3Ds que é compatível em ambas às ferramentas (Blender e Unity 3D). Com os blocos modelados e disponíveis no Unity 3D, encaixa-se os blocos uns nos outros de acordo com a pista pretendida. Concluindo a montagem da pista, inserem-se as sinalizações para parecer com as vias reais.

Os blocos foram gerados a partir de um plano com o tamanho de 20 metros, em seguida, este plano foi subdivido em três vezes a fim de ter a noção de posicionamento da calçada e do asfalto. Com isso, obtive um plano com quadrados

com comprimento de aproximadamente 3 metros (FIGURA 15).

FIGURA 15: REPRESENTAÇÃO DE UM PLANO NO BLENDER

Através deste plano, geram-se os blocos removendo os cantos. Para a geração do cruzamento em cruz, por exemplo, removem-se os cantos a partir do terceiro quadrado superior, do sexto quadrado superior, do terceiro quadrado lateral e do sexto quadrado lateral até o inferior na vertical mudando o formato do plano (FIGURA 16). Os retângulos e os quadrados menores nas laterais do plano, vão dar origem à calçada, todo o resto será parte de pista. Para diferenciá-los, foi necessário realizar uma extrusão na calçada de 0,25 centímetros no eixo z para representar o desnível em relação ao asfalto.

FIGURA 16: PLANO EM FORMATO DE CRUZ

Para concluir a origem dos blocos, é necessário inserir as texturas, para isso utiliza-se o sistema de texturas Cycles Render2 (contido na ferramenta Blender)

onde a textura é escolhida e aplicada nas partes desejadas. No caso, utilizar a texturas de asfalto no centro do cruzamento e texturas de calçada nas laterais (FIGURA 17).

FIGURA 17: MODELO 3D DO CRUZAMENTO

Os blocos criados no Blender para a formação da pista do simulador são às partes ilustradas na tabela a seguir (FIGURA 18).

CURVA INTERSEÇÃO EM “T”

RETA COM FAIXA SIMPLES SECCIONADA

INICIO E FIM DA AREA DE ESTACIONAMENTO

FAIXA SIMPLES SECCIONADA COM ESTACIONAMENTO PARA

DEFICIENTES FÍSICO

FAIXA SIMPLES SECCIONADA COM ESTACIONAMENTO

FAIXA DE

PEDESTRE LOMBADA ESTACIONAMENTO

FIGURA 18: MODELOS DE PARTES DA PISTA

As placas e outros objetos foram criados a partir de formas geométricas e texturas que representam a sinalização de trânsito. Ilustradas na tabela a seguir (FIGURA 19).

SINALIZAÇÃO DE REGULAMENTAÇÃO SINALIZAÇÃO DE REGULAMENTAÇÃO

SINALIZAÇÃO DE ADVERTÊNCIA SINALIZAÇÃO DE ADVERTÊNCIA

DISPOSITIVOS DE USOTEMPORÁRIOS SEMÁFORO

FIGURA 19: MODELOS 3D DE SINALIZAÇÃO

Após a modelagem dos blocos e dos objetos, os mesmos foram exportados para o Unity 3D para a montagem do ambiente. Cada bloco foi encaixado em outro

sobre um terreno padrão criado antecipadamente no Unity 3D, dando o formato a pista, mas antes de começar a montar a pista, foi necessário criar um sistema de colisão da pista, fazendo com que o veiculo bata no meio fio caso se aproxime dele por exemplo.

Para a criação do sistema de colisão, foi usado o Mesh Collider3, com isso todo o bloco da pista vai exercer um efeito de colisão para com o veiculo, porém, se for usado no bloco do cruzamento, por exemplo, o bloco todo vai estar com tratamento de colisão, caso possua vários vértices irá se tornar um objeto 3D pesado em termos de renderização do cenário. Para resolver este problema, criam- se cubos no mesmo formato do objeto em que irá aplicar o sistema de colisão. Feito isso, se desmarca a opção Mesh Renderer deixando o cubo transparente sem interferir no cenário (FIGURA 20). Depois disso, torna-se o cubo um objeto com sistema de colisão acionando o componente Box Collider4, obtendo um bom processamento comparado ao Mesh Collider.

FIGURA 20: REPRESENTAÇÃO DO SISTEMA DE COLISÃO

O contorno do cubo formado por linhas verde, que envolve a calçada, só será visível quando estiver selecionado, caso contrário, nem o contorno pode ser

3 - Mesh Collider é um componente do Unity que permite inserir sistema de colisão em todo modelo 3D.

4 - Box Collider é um componente do Unity que define um sistema de colisão com seis faces para modelos 3D.

visto. Agora, quando o veiculo colidir com o cubo, ocorre uma reação.

Depois de feito todo este processo de modelagem e sistema de colisão, foi necessário duplicar cada um dos blocos encaixando-os, montado à pista (FIGURA 21).

FIGURA 21: REPRESENTAÇÃO DA MONTAGEM DA PISTA

Na finalização da construção da pista, foram inseridos os modelos de sinalização com relação aos edifícios presentes no cenário. Os modelos dos edifícios foram exportados do Google Sketchup para o Unity 3D. Com relação às montanhas, árvores e texturas para o terreno isso a própria ferramenta Unity 3D oferece.

O modelo do veículo foi retirado de um tutorial presente na internet com o titulo McLaren Racer_v2_source5. Este modelo está disponível para download sobre licença open source e com o intuito de qualquer pessoa que deseja adquirir conhecimento ou criar um jogo próprio.

Após a inserção de todos os modelos 3D e algumas alterações no terreno obtém a representação de uma cidade (FIGURA 22).

FIGURA 22: ILUSTRAÇÃO DA CIDADE MODELADA

4.4.1 Medições

Cada bloco modelado da pista possui uma medida que varia de um para o outro conforme a necessidade. Os blocos representados na FIGURA 23, exemplificam a trajetória (linhas em vermelho) que o jogador poderá percorrer. O bloco da esquerda (1) mostra uma linha reta de 20 metros, este valor corresponde ao tamanho do bloco. O bloco do meio (2) representa uma curva fechada, esta curva tem aproximadamente 17,5 metros, também é levado em consideração as medidas do plano inicial para o cálculo. O bloco da direita (3) representa uma curva aberta, esta curva tem aproximadamente 22,5 metros, pela distância percorrida ser maior é possível definir as medidas dos outros blocos, como a curva que possui as mesmas medidas das curvas como no caso (2) e (3) e o bloco de interseção, que é o mesmo bloco sem a parte superior do modelo. Porém, o bloco da pista reta possui medidas de 5 metros.

Com estas medidas é possível se obter o tamanho total do percurso do simulador através da soma das medidas de cada um dos blocos do percurso. O resultado deste cálculo resulta em um total de 1 Km (quilometro) e 265 metros.

FIGURA 23: EXEMPLO DE POSSÍVEIS TRAJETÓRIAS DE UM CRUZAMENTO

4.5 DIAGRAMAÇÃO

O diagrama de classe (FIGURA 24) representa as classes presentes no sistema, das quais valem destacar as classes Jogo, Carro, Semaforo e Trigger.

A classe Jogo é responsável pelo controle geral da aplicação e contém os demais componentes que são usados no jogo e realiza a avaliação do jogador através de uma pontuação utilizando as Triggers para verificar se este está seguindo as regras devidamente. Essa classe também é responsável pelos carros controlados pela inteligência artificial e pelos semáforos espalhados pelo percurso.

A classe Carro especifica um carro e seus atributos, este carro pode ou não ser controlado pelo usuário. Caso o carro não seja controlado pelo jogador um comportamento que for atribuído a ele será responsável pelo controle do carro, caso o carro seja controlado pelo jogador, o comportamento atribuído será o comportamento que o jogador deverá seguir. A classe Comportamento também define o percurso que o carro irá seguir.

A classe Trigger possui métodos que verificam quando outro objeto entra na área delas, com isso, é possível determinar se o jogador está desrespeitando alguma regra. Para isso, a Trigger possui um atributo chamado tipo que determina qual será sua função, ou seja, qual será a regra que ela irá verificar como, por exemplo, o objeto Trigger que possui o tipo lombada, verifica a velocidade e a marcha do carro quando este passa pela lombada, penalizando o jogador caso este esteja muito rápido ou na marcha errada.

A classe Semaforo é responsável pelo controle dos sinais definindo, quando estes estão abertos ou fechados e também verifica se o jogador está respeitando o

sinal. Quando um dos quatro sinais é aberto os demais estarão fechados a classe então desabilita os WayPoints correspondentes e habilita as Triggers que irão verificar se o jogador não furou o sinal. Este processo se repete quando um novo sinal é aberto.

FIGURA 24: DIAGRAMA DE CLASSES

O diagrama de estados (FIGURA 25) representa os estados assumidos pelos carros controlados pela inteligência artificial. Eles iniciam estacionados em lugares predeterminados, assim que o jogo começa, eles passam a percorrer o caminho que lhes foi designado. Ao encontrar um sinal fechado, ou algum outro carro parado à

frente deles, eles passam para o estado Parado até que o sinal abra ou o carro saia da frente, voltando para o estado Percorrendo Caminho, isso continua até o final do jogo.

FIGURA 25: DIAGRAMA DE ESTADOS

4.6 IMPLEMENTAÇÃO DO SISTEMA

4.6.1 Triggers

A classe Trigger, como já explicado, define uma área dentro do jogo e verifica quando um determinado objeto entra ou sai de dentro dessa área através

das funções OnTriggerEnter() e OnTriggerExit(). Essas funções verificam se o jogador está se comportando como esperado, e caso não esteja, a variável pontuacao marca quantas infrações ele cometeu. A função OnTriggerEnter() é a responsável pela maior parte das verificações.

if (c.gameObject.name == "Collider2" &&

c.attachedRigidbody.name == "CarroJogador") {

A condição apresentada acima verifica se o objeto que está entrando na Trigger é o carro do jogador, caso seja, então serão feitas as demais verificações, caso contrario nada acontece.

A variável tipoTrigger armazena uma string que determina qual o tipo da Trigger e qual será a verificação realizada.

if (tipoTrigger.Equals("sinal")) { if (habilitada == false) {

pontuacao += 1; }

}

Caso o valor seja “sinal” e a Trigger estiver habilitada (variável controlada pela classe Sinal, ver item 4.5.4) significa que o jogador está ultrapassando um sinal fechado e então é penalizado em 1 ponto.

if (tipoTrigger == "lombada") {

if(car.velocidade > 40 || car.CurrentGear > 2) { pontuacao += 1;

} }

Caso o valor seja “lombada”, é verificado se o carro está a mais de 40 km/h e se está acima da 2º marcha, caso esteja é penalizado em 1 ponto.

time = (int)Time.realtimeSinceStartup; }

}

Caso o valor seja “parada”, significa que o jogador está em uma faixa de pedestres e tem que parar momentaneamente. Ou seja, o carro tem que ficar um determinado tempo dentro da Trigger. Ao entrar, é armazenado em time o horário que o carro entrou na Trigger e através da função OnTriggerExit() é verificado se ele realmente cumpriu a condição.

if (c.gameObject.name == "Collider2" &&

c.attachedRigidbody.name == "CarroJogador") { if (tipoTrigger == "parada") {

int finalTime =(int)Time.realtimeSinceStartup; if (finalTime – time < 2) {

pontuacao += 1; }

} }

Quando o carro sai da Trigger é armazenado o horário. Desta vez, em finalTime, é calculado a diferença entre time e finalTime. Caso o resultado seja menor que 2, significa que ele ficou menos de dois segundos dentro da Trigger, com isso, o jogador será penalizado em 1 ponto.

4.6.2 Pathfinding

Para que os carros controlados pela IA consigam seguir corretamente a pista e realizar as curvas apropriadamente, foram posicionados diversos wayPoints pela pista demarcando-a. Esses wayPoints consistem na marcação de uma posição que identifica uma próxima marcação (um outro wayPoint), para a qual o carro deverá seguir após ter chego próximo o suficiente para ser considerado a sua passagem pelo wayPoint. A inteligência artificial utilizada pelos carros utiliza essas marcações

para direcionar o carro de modo que ele acerte sua direção de movimento conforme sua distância e rotação em relação ao seu destino. Os percursos são definidos por vários wayPoints e descrevem o percurso que o carro irá seguir (FIGURA 26).

FIGURA 26: ILUSTRAÇÃO DE UM PERCURSO COM WAYPOINTS (EM VERMELHO)

O Pathfinding da inteligência artificial é controlado pela classe Comportamento através da função IrParaWayPoint().

Inicialmente transforma-se a posição do wayPoint de coordenadas globais para coordenadas locais referentes a posição do carro.

Vector3 RelativeWaypointPosition = carro.transform.InverseTransformPoint(

new Vector3( waypointAtual.transform.position.x, carro.transform.position.y,

waypointAtual.transform.position.z ));

A classe Vector3, definida pelo Unity, representa um vetor em um espaço tridimensional, e a função InverseTransformPoint() fornece um Vector3 formado pela posição do wayPoint indo em direção á posição do carro. Isso permite calcular a que distância, para a esquerda ou direita, que o wayPoint se encontra. Utilizando essa distância dividida pela distância total que o carro se encontra do wayPoint, é

possível obter uma porcentagem decimal do ângulo que o carro tem que virar.

inputSteer = RelativeWaypointPosition.x / RelativeWaypointPosition.magnitude;

É possível utilizar um cálculo similar para o torque que será aplicado no carro, no entanto, foi observado que isso torna o comportamento do carro demasiadamente variado, sendo preferível definir números fixos ao invés de um cálculo para essa finalidade. O torque varia de acordo com o que o carro está fazendo no momento. Caso ele não esteja virando e o wayPoint esteja para frente dele, é aplicado o valor 1 ao torque, que é equivalente ao pressionamento completo do pedal de aceleração. Caso o wayPoint esteja para trás o torque é invertido.

if(Mathf.Abs(inputSteer) < 0.5 && carro.velocidade < 100) { if(RelativeWaypointPosition.z > 0)

inputTorque = 1F; else

inputTorque = -1F; }

Se o carro fizer uma curva, ele pára de acelerar, no entanto, caso a velocidade já esteja muito baixa, isso faria com que o carro parasse. Para evitar isso, é aplicada uma aceleração, porém reduzida se o carro já estiver indo muito devagar. Else { inputTorque = 0.0F; if (carro.velocidade < 20) { inputTorque = 0.7F; }}

Quando o carro chegar próximo ao seu destino, ou a um wayPoint desabilitado (como em um semáforo), ele deve reduzir a velocidade para que seja possível parar de forma gradual. Primeiro, é necessário verificar se existe um destino

válido, e se o wayPoint para o qual ele está se dirigindo (waypointAtual) é o mesmo que o seu destino (wayPointFinal). Caso o waypointAtual não esteja habilitado, mesmo que não seja igual ao seu destino, o carro também deverá reduzir a velocidade.

if ((wayPointFinal == waypointAtual) && (wayPointFinal != null) || (waypointAtual.habilitado == false)){

if (distanciaDoWayPoint < wayPointAnterior.distan ciaDoProximo * 0.25F) {

inputTorque = inputTorque * 0.5F; }

}

Com o código acima, a aceleração é reduzida pela metade caso o carro esteja a menos de 1/4 da distância do destino, ou de um wayPoint desabilitado em relação ao ultimo wayPoint que ele esteve.

Para que o carro siga o percurso inteiro, não só até o primeiro wayPoint, ao chegar ao waypointAtual, o carro pega o próximo wayPoint que ele tem que seguir.

if (RelativeWaypointPosition.magnitude <

waypointAtual.raioEfetivo) { if (((wayPointFinal == waypointAtual && (wayPointFinal != null)) || (waypointAtual.habilitado == false)) {

stop(); } else { wayPointAnterior = waypointAtual; waypointAtual = waypointAtual.prox[0]; go(); } }

public void stop() {

carro.BackLeftWheel.brakeTorque += 50; carro.BackRightWheel.brakeTorque += 50; }

public void go() {

carro.BackLeftWheel.brakeTorque = 0; carro.BackRightWheel.brakeTorque = 0; }

A função IrParaWayPoint() é chamada pela função aplicarComportamento que recebe um carro como parâmetro. Essa função aplica o inputTorque e o inputSteer, calculados anteriormente no carro que está sendo controlado pela inteligência artificial. O torque do motor que é aplicado nas rodas é calculado utilizando o torque do motor em relação com a marcha sendo utilizada e o valor calculado.

carro.BackLeftWheel.motorTorque = carro.EngineTorque/ carro.GearRatio[carro.CurrentGear] * inputTorque; carro.BackRightWheel.motorTorque = carro.EngineTorque/

carro.GearRatio[carro.CurrentGear] * inputTorque;

O ângulo de rotação aplicado nas rodas é calculado através do inputSteer, calculado anteriormente, e de um valor arbitrário, no caso 30, escolhido por representar mais corretamente o comportamento desejado.

carro.FrontLeftWheel.steerAngle = 30 * inputSteer; carro.FrontRightWheel.steerAngle = 30 * inputSteer;

4.6.3 Prevenção de colisão

Para que os carros controlados pela IA não colidem entre si, nem com o jogador quando o carro parar no semáforo, ou em outras situações, é necessário prevenir esse tipo de colisão. Para isso, foram usadas Triggers posicionadas em frente ao carro permitindo que se detecte se o carro irá ou não bater no carro à frente, e então aplicar as medidas preventivas.

Ao se utilizar uma Trigger é possível detectar quando algo entra em sua área de efeito, posicionando-a em frente ao carro, permite detectar quando o carro chega muito perto do carro à frente. Essa aproximação fará com que o carro a frente entre na área de efeito da Trigger, o que irá ativar o evento OnTriggerEnter que se encarregará de fazer com que o carro freie reduzindo sua velocidade, e evitando a colisão entre os dois.

A classe responsável por gerenciar esses eventos e verificar se o carro irá colidir é a classe AntiColisao, ela possui uma variável booleana chamada colidiu, essa variável é verdadeira caso exista um carro dentro da Trigger.

void OnTriggerEnter(Collider c) { if (c.attachedRigidbody != null) { if (c.attachedRigidbody.name == "CarroJogador" || c.attachedRigidbody.name == "CarroIA") colidiu = true; } }

E quando o carro sair da Trigger a variável colidiu assume o valor de falso.

void OnTriggerExit(Collider c) { if (c.attachedRigidbody != null) { if (c.attachedRigidbody.name == "CarroJogador" || c.attachedRigidbody.name == "CarroIA") colidiu = false; } }

A classe Carro possui um objeto da classe AntiColisao, esse objeto é utilizado para acessar a variável colidiu e passar a informação para a classe Comportamento.

compor.carroAFrente = antiColisao.colidiu;

seguir normalmente caso não haja.

if (carroAFrente == false) go();

else stop();

4.6.4 Semáforo

O semáforo é um controlador que determina quando o jogador ou a inteligência artificial pode ou não atravessar um cruzamento, ele faz isso através de uma temporização de permissões na qual apenas os carros de uma das quatro ruas têm permissão de atravessar de cada vez (FIGURA 27) e após um determinado tempo os carros de nova rua passam a ter permissão de cruzar.

FIGURA 27: FUNCIONAMENTO DO SEMÁFORO ONDE APENAS O WAYPOINT SUPERIOR (MARCADO EM AZUL) ESTÁ HABILITADO.

Ao definir um semáforo é necessário também associar alguns wayPoints e Triggers a ele, isso irá permitir que o semáforo os controle definindo quando eles estarão habilitados de acordo com a temporização da troca do sinal controlado pela

função update() da classe Semaforo mostrada a seguir.

time = (int)Time.realtimeSinceStartup;

A variável time recebe o tempo total desde o inicio do jogo e é usada para fazer a troca dos sinais a cada dez segundos, através da verificação do resto da divisão de time por 10 que se for igual a 9 (primeira condição) irá apenas trocar a cor para o amarelo e caso for igual a 0 (segunda condição) chamará a função trocaSinal() fazendo a troca tanto da cor quanto das Triggers e wayPoints que estão habilitados.

if ((time % 10) == 9 && previousTime != time) { previousTime = time;

sinalAmarelo(); }

if ((time % 10) == 0 && previousTime != time) { previousTime = time;

trocaSinal(); }

A função trocaSinal() realiza a troca dos sinais e habilita e desabilita as Triggers e WayPoints correspondentes para que seja possível realizar as verificações de infrações. A alternação é feita em sentido anti-horário começando no sinal superior, seguido pelo sinal esquerdo, sinal inferior, sinal direito e por fim o sinal superior novamente reiniciando o ciclo.

if (sinalSuperior) { wayPointSuperior.habilitado = false; wayPointEsquerdo.habilitado = true; sinalEsquerdo = true; sinalSuperior = false; objectSinalEsquerdo.setToVerde(); objectSinalSuperior.setToVermelho(); triggerEsquerda.habilitada = true; triggerSuperior.habilitada = false;

} else if (sinalEsquerdo) { wayPointEsquerdo.habilitado = false; wayPointInferior.habilitado = true; sinalEsquerdo = false; sinalInferior = true; objectSinalEsquerdo.setToVermelho(); objectSinalInferior.setToVerde(); triggerEsquerda.habilitada = false; triggerInferior.habilitada = true; }

As condições de verificação e operações realizadas para o sinal inferior e sinal direito seguem o mesmo padrão apresentado acima.

Os wayPoints possuem uma variável que indica se eles se encontram habilitados ou não quando um wayPoint está habilitado ele funciona normalmente porem quando ele está desabilitado um carro controlado pela IA ao chegar nele irá parar completamente desconsiderando o wayPoint seguinte como se chegasse ao seu destino final. Isso é utilizado para que a IA pare caso o semáforo esteja fechado e uma vez que o sinal abra o wayPoint é habilitado novamente e o carro segue o caminho normalmente (como mostrado no item 4.5.2 PathFinding). Uma técnica similar é aplicada as Triggers ais quais são posicionadas na faixa de pedestres e são acionadas caso o carro do jogador entre nelas, esse evento faz com que o jogo verifique se o sinal correspondente aquela faixa está aberto, caso esteja nada irá acontecer, mas caso não esteja aberto o jogador será penalizado de acordo.

A indicação para que o jogador seja capaz de identificar quando um semáforo está ou não aberto é feita através da alteração das texturas que são aplicadas no modelo do semáforo (FIGURA 28). Quando o semáforo está fechado apenas à textura vermelha é visível a amarela e a verde são substituídas por preto, esse processo é o mesmo para quando o semáforo está aberto (quando apenas a

Documentos relacionados