Designing the Top Gear

Asked

Viewed 616 times

17

I’m trying to make a game like Top Gear (SNES) to acquire some knowledge about 2D games, only I came to a question about the tracks: they are straight, curved or images?

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

How do I do that?

1 answer

26


These 2D racing games as far as I know render each line of the screen regardless of the others. If you observe a screenshot of the game (I’m assuming you refer to the first game in the series) you will see that there is no use of curves or straights, but an irregular pattern, when you look vertically. When you look horizontal, on the other hand, all the lines are straight!

inserir a descrição da imagem aqui

If you look at other games of the same genre - like Sega’s Outrun - you will see that the same technique is employed, with varying levels of sophistication. The result is a perspective projection with a single vanishing point, where horizontal lines (x) and vertical lines (y) remain parallel according to the real world, only depth (z) does not retain parallelism (and by the way, if the track is rendered line by line, the most complex objects are formed by images, via Parallax).

As the resolution of the games at that time was low (i.e. few pixels per unit of measurement) so this can be done relatively efficiently[1]. I don’t know Javafx, but I’ll give you an example using canvas in Javascript (in general 2D drawing libraries have very similar API, I think you will have no difficulty in 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>

Putting curves to the left and right is easy: just move the line drawn according to the position of the last line (how to better represent the track, I can’t tell you... In this example, I will use an array with the lateral variations of the angle from a certain distance travelled).

// 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>

Similarly, one can simulate climbs 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 quite "raw" (I did in a few minutes[2], kind of in trial and error) and some distortions are quite apparent. But it’s a good example of what you can do with only about 50 lines of code, and it’s consistent with what was used in practice in this type of game.

This technique (simulate 3D using 2D) is sometimes called 2.5D (or 2½D, or perspective ¾; not to be confused with the mathematical concept of fractal). I don’t know details of the specific technique used in racing games, but I could notice - because of my difficulty in making this small example minimally presentable - that a knowledge and application of geometry is essential to successfully implement it. This is what will determine the distance that each line represents, what relationship between the angle of the curve and the lateral displacement of the track, how to correct the fact that the larger angles "stretch" the lateral lines (making it look like the car went backwards), etc.

But at the end of the day, that’s the way to draw the clue given the landmarks. At least in the old games, of course - nothing prevents you from rendering the scene in other ways, for example using this QuadCurve (or even a bézier curve more generally, if Java supports) using reference points calculated by the same method.


Notes:

[1]: Nowadays we can also do this efficiently, through the programming of Fragment shaders direct at the GPU, but this somewhat extrapolates the scope of the question.

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

Browser other questions tagged

You are not signed in. Login or sign up in order to post.