Mão na massa
Gerando um céu estrelado aleatório
Publicado por Tarso em 25/04/2020
Uma vez eu estava programando um jogo 2D que seria uma nave viajando no espaço e atirando em inimigos aliens que apareciam em posições aleatórias na tela. O cenário de fundo seria uma representação bem simples do espaço sideral: um fundo preto com pontos brancos pintados aleatoriamente representando as estrelas (claro que o espaço tem bem mais coisas do que isso, mas para o jogo essa abstração já seria suficiente).
O algoritmo
Mas eu realmente queria implementar isso da maneira mais simples possível, mas que ainda provesse algum grau de realismo. Então eu acabei pensando em um algoritmo bem simples e óbvio com os seguintes passos:
- Pinte a tela de preto;
- Divida a tela em N linhas e N colunas invisíveis;
- Gere um ponto aleatório (x, y) dentro de cada célula;
- Pinte cada ponto de branco.
Aqui está um possível resultado para N = 5:
O algoritmo não é nenhuma novidade, claro. Depois que o implementei eu vi algumas pessoas sugerindo o mesmo algoritmo para resolver o problema.
Eu implementei esse algoritmo em Javascript e coloquei nesse repositório aqui. O link para a página pode ser encontrado no arquivo README.
Limitações
Esse algoritmo não evita que estrelas sejam geradas muito próximas umas das outras. Podemos ter por exemplo uma estrela gerada à direita de uma célula e outra gerada à esquerda da célula vizinha.
Além disso, para valores de N já a partir da ordem das centenas, o algoritmo pode ficar bem lento.
O código
O primeiro passo foi criar uma classe que representa uma estrela. Ela tem como atributos apenas suas coordenadas x e y.
1
2
3
4
5
6
7
8
9
10
11
12
13
const TAMANHO = 1;
class Estrela {
constructor(x, y) {
this.x = x;
this.y = y;
}
desenhar(context) {
context.fillStyle = 'white';
context.fillRect(this.x, this.y, TAMANHO, TAMANHO);
}
}
Depois eu criei outra classe para representar o espaço. Essa classe acabou encapsulando toda a lógica que calcula as posições das estrelas.
O construtor da classe percorre N linhas e N colunas para determinar a área de cada célula, e então gera uma posição aleatória no eixo X e outra posição aleatória no eixo Y dentro dessa área. As estrelas geradas são armazenadas numa matriz (um array de arrays).
Finalmente, o método que desenha as estrelas apenas percorre a matriz e ordena que cada estrela se desenhe no canvas.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
const N = 5;
class Espaco {
constructor(largura, altura) {
this.largura = largura;
this.altura = altura;
this.estrelas = [];
this.gerarEstrelas();
}
gerarEstrelas() {
const larguraCelula = Math.floor(this.largura / N);
const alturaCelula = Math.floor(this.altura / N);
for (let linha = 0; linha < N; linha++) {
const estrelasParaLinha = [];
for (let coluna = 0; coluna < N; coluna++) {
const posicaoEsquerdaCelula = coluna * larguraCelula;
const posicaoDireitaCelula = posicaoEsquerdaCelula + larguraCelula - 1;
const posicaoTopoCelula = linha * alturaCelula;
const posicaoFundoCelula = posicaoTopoCelula + alturaCelula - 1;
const posicaoAleatoriaX = Utils.aleatorioEntre(posicaoEsquerdaCelula, posicaoDireitaCelula);
const posicaoAleatoriaY = Utils.aleatorioEntre(posicaoTopoCelula, posicaoFundoCelula);
const estrela = new Estrela(posicaoAleatoriaX, posicaoAleatoriaY);
estrelasParaLinha.push(estrela);
}
this.estrelas.push(estrelasParaLinha);
}
}
desenharEstrelas(context) {
for (let i = 0; i < this.estrelas.length; i++) {
const estrelasParaLinha = this.estrelas[i];
for (let j = 0; j < estrelasParaLinha.length; j++) {
estrelasParaLinha[j].desenhar(context);
}
}
}
desenhar(context) {
context.fillStyle = 'black';
context.fillRect(0, 0, this.largura, this.altura);
this.desenharEstrelas(context);
}
}
Com as classes definidas, só é preciso agora instanciá-las e usá-las:
1
2
3
4
5
const canvas = document.getElementById('estrelas');
const espaco = new Espaco(canvas.width, canvas.height);
const context = canvas.getContext('2d');
espaco.desenhar(context);
E é isso! Em posts futuros vamos dar uma olhada no código do jogo onde eu usei esse algoritmo. Até lá!
Ninguem comentou este post ainda.