I’ve played around with HTML canvas before, and have even made a field of twinkling stars. However, this time, I wanted to recreate an old Windows screensaver where you fly through a star field. In short, something like this.
This proved to be harder than I expected. My first real attempt had stars increasing in size and velocity as they got further from the center.
The problem with that is that no stars don’t seem to move towards us, just off the side of the screen – so, I’ve essentially created a particle emitter. You’re watching stars be created far in the distance, and the sense of depth comes from the changing star size as they get closer to the edge of the screen.
This wasn’t good enough. I wanted each star to actually occupy a position in 3D space, and then have this position be reflected in the view, creating a more realistic simulation. I wanted to create a sense of a star coming right at you. The issue is that stars have to come off the screen eventually no matter what – so, as a star gets closer to you, it needs to (1) increase in size and (2) move toward the edge of the screen
This really tripped me up for a few days. I couldn’t wrap my head around how I can represent 3D space in 2D. I was using the pythagorean theorem in 2D, using each star’s X and Y coordinates. I knew I needed to integrate depth, Z, but I wasn’t sure how. Then, after a few days, it hit me. I watched several working starfields closely to isolate what was happening, and I realized that a star’s distance from the origin point – the center, in my case – impacted two things:
- the size of the star (stars closer to the us appear larger)
- the speed with which the star travels (stars closer to us appear to move faster)
Then came the epiphany. Stars can only move along X & Y axes, but the speed with which they move implies their distance from us.
Using this, I could now create a sense of depth. Two stars can occupy very close X & Y coordinates – sit next to each other – but if one is bigger and moves towards us faster, we perceive it as being much closer. It took me a bit of time to finagle the relationship between the Z coordinate and the star size/speed, but once I did – voila! The starfield looked much more realistic:
So, how does it work? I created a star object with X, Y, and Z coordinates. At each screen refresh, I needed to change each star’s…. (1) x & y coordinates and (2) size, as determined by its Z coordinate.
(1) Each frame, I would get the distance between the star and the center, and would move 2000th of this – times the star’s Z coordinate (which is between 1 and 100+). Thus, stars with higher Z coordinates – which means the star is closer to us – would move off the screen faster.
(2) A star’s size would similarly grow as the star’s Z coordinate increased, and the star got closer.
// each frame... star.size = 0.2 + 0.038*star.z; // a star is always at least 0.2 px in radius + some fraction of 3.8, based on distance - 0.2-4px radius total star.x += (star.x - center.x)/2000 * star.z; star.y += (star.y - center.y)/2000 * star.z; star.z++;
It took me 4-5 days to come up with that relatively simple bit of code – to wrap my head around the relationship between a 2D screen and a 3D space. Once I did, I decided to gamify my new project. More on that soon!