Drawing Top Gear

17

I'm trying to make a game like Top Gear (SNES) to get a little bit of knowledge about 2D games, but I had a question about the clues: are they straight lines, curves or images?

I'm using JavaFX. If I try to use curves ( QuadCurve ) it is difficult to make the lines on the track where the color changes to a lighter or darker color. If you use straight lines it will get "strange", and if you use images it will "flood" the screen because of few changes.

How do I do this?

    
asked by anonymous 21.09.2015 / 01:10

1 answer

26

These 2D racing games as far as I know render each line of the screen apart from the rest. If you observe a screenshot of the game (I'm assuming you mean the first game in the series) you'll see that there is no use of curves or straight lines, but an irregular pattern when you look upright. When you look horizontally, on the other hand, all the lines are straight!

Ifyoulookatothergamesofthesamegenre-suchasOutrunfromSega-youwillseethatthesametechniqueisemployed,withvaryinglevelsofsophistication.Theresultisaperspectiveprojectionwithasingle vanishing point , where horizontal (x) and vertical (y) lines become (z) does not retain parallelism (and by the way, if the lane is rendered line by line, the more complex objects are formed by images, via parallax ).

As the resolution of the games of that time was low (i.e. few pixels per unit of measure) then this can be done relatively efficiently [1]. I do not know JavaFX, but I'll give you an example using canvas in JavaScript (generally 2D drawing libraries have very similar APIs, I think you will have no difficulty adapting):

var ctx = document.querySelector("canvas").getContext("2d");
var delta = 0;

var g = ["#00AA00", "#00FF00"]; // verde escuro / verde claro
var rw = ["#FF0000", "#FFFFFF"]; // vermelho / branco
var lg = ["#AAAAAA", "#777777"]; // cinza claro / cinza escuro

function render() {
  // Limpa o cenário com a cor do céu
  ctx.fillStyle = "#3333FF";
  ctx.fillRect (0, 0, 300, 150);
  
  // Para cada linha de pixels na tela
  for ( var i = 0 ; i < 100 ; i++ ) {
    // Quanto mais longe, mais finas as linhas
    var n = i * Math.log((i+20)/20)
    
    // Limpa a linha com a grama (alterna verde claro e escuro a cada 20 pixels)
    ctx.fillStyle = g[Math.floor((n+delta)%40/20)];
    ctx.fillRect(0, 150-i, 300, 1);
    
    // Coloca a lateral da pista (alterna vermelho e branco a cada 10 pixels)
    ctx.fillStyle = rw[Math.floor((n+delta)%20/10)];
    ctx.fillRect(10+i, 150-i, 300-2*(10+i), 1);
    
    // Coloca o centro da pista (alterna cinza claro e escuro a cada 20 pixels)
    ctx.fillStyle = lg[Math.floor((n+delta)%40/20)];
    ctx.fillRect(20+i, 150-i, 300-2*(20+i), 1);
  }
  
  // Avança pela pista (nesse exemplo, na velocidade do render; na prática, usar o tempo)
  delta ++;
  requestAnimationFrame(render);
}
render();
<canvas width="300" height="150"></canvas>
Placing curves left and right is easy: just move the line drawn according to the position of the last line (how best to represent the lane, I do not know how to tell you ... In this example, I'll use an array with the lateral variations of the angle from a certain distance traveled).

// Representando uma pista
var pista = [[300,0],[50,1],[200,0],[50,-1],[500,0],[50,3]];

function lateral(pos) {
  // Acha o tracho da pista em que estamos
  for ( var i = 0 ; pos > pista[i%pista.length][0] ; i++ )
    pos -= pista[i%pista.length][0];
  
  // Tenta dar uma atenuada no ângulo, no começo e final da curva
  var ret = pista[i%pista.length];
  return ret[1] * Math.min(20, pos, ret[0]-pos)/20;
}
             
var ctx = document.querySelector("canvas").getContext("2d");
var delta = 0;

var g = ["#00AA00", "#00FF00"]; // verde escuro / verde claro
var rw = ["#FF0000", "#FFFFFF"]; // vermelho / branco
var lg = ["#AAAAAA", "#777777"]; // cinza claro / cinza escuro

function render() {
  // Limpa o cenário com a cor do céu
  ctx.fillStyle = "#3333FF";
  ctx.fillRect (0, 0, 300, 150);
  
  // Para cada linha de pixels na tela
  var lat = 0;    // Deslocamento lateral em relação à última linha
  var angulo = 0; // Variável auxiliar pra calcular lat
  for ( var i = 0 ; i < 100 ; i++ ) {
    // Quanto mais longe, mais finas as linhas
    var n = i * Math.log((i+20)/20)
    
    // Posição da linha na pista em relação ao carro
    var pos = Math.floor(n + delta);
    angulo += lateral(pos);
    lat += Math.floor(10 * Math.sin(angulo*Math.PI/180));
    
    // Limpa a linha com a grama (alterna verde claro e escuro a cada 20 pixels)
    ctx.fillStyle = g[Math.floor((n+delta)%40/20)];
    ctx.fillRect(0, 150-i, 300, 1);
    
    // Coloca a lateral da pista (alterna vermelho e branco a cada 10 pixels)
    ctx.fillStyle = rw[Math.floor((n+delta)%20/10)];
    ctx.fillRect(10+i+lat, 150-i, 300-2*(10+i), 1);
    
    // Coloca o centro da pista (alterna cinza claro e escuro a cada 20 pixels)
    ctx.fillStyle = lg[Math.floor((n+delta)%40/20)];
    ctx.fillRect(20+i+lat, 150-i, 300-2*(20+i), 1);
  }
  
  // Avança pela pista (nesse exemplo, na velocidade do render; na prática, usar o tempo)
  delta ++;
  requestAnimationFrame(render);
}
render();
<canvas width="300" height="150"></canvas>

Likewise, you can simulate ascents and descents by varying the "height" of the line. Or use a small random value to create the "slots" in the tracks. Etc. Note that both examples are pretty "raw" (I did in a few minutes [2], rather than in trial and error) and some distortions are quite apparent. But it is a good example of what can be done with only 50 lines of code, and is consistent with what was used in practice in this type of game.

This technique (simulating 3D using 2D) is sometimes called

But ultimately, the way of drawing the lane given the landmarks is that. At least in old games, of course - nothing prevents you from rendering the scene in other ways, for example using that QuadCurve (or even a # more general, if Java supports) using the reference points calculated by the same method.

Notes:

[1]: Nowadays you can do this efficiently by programming GPU shreders directly, but that goes beyond the scope of the question.

[2]: Ok, the basic example came out in a few minutes, but when I tried to make the curve I ended up "grabbing" for a bit longer ...: P

    
21.09.2015 / 20:22