[Devember 2022] a space project | open source space game

Yes! I’d argue that the performance uplift of porting to another language is not nearly as important as knowing your language well. Depending on the situation Java(or most other languages) can them same or better performance than C++.
If you have a performance problem, make sure to measure it, and carefully consider your options. Changing language is one of them, so is reworking your algorithms and data structures.

2 Likes

So much this.

1 Like

Still bug hunting…

  • added braking function to stop ship: [S] on keyboard, [X] on controller
  • added sound effect for collecting drops
  • fix pickup gravity for collecting drops
  • fix collision filter to not do heat damage from stars on gravity sensor
  • fix background tiling to render better on screen resolutions above 1080p
  • fade hit marker trails

Starting to implement more sounds. Trying to figure out best way to handle modulation with control over only volume, pitch and pan.

For looping audio to be perfectly seamless, no gaps, and start and end of waveform must line up so there’s no clipping at the boundary:

A small fade in and fade out at the beginning and end can smooth out noisier clips that don’t line up so nicely.

3 Likes

Interestingly enough, FL studio’s editor has a tool named “Tune loop” which automatically cross-fades an audio clip with itself.
Unfortunately, I just checked Audacity, and it doesn’t seem to have an equivalent.

Maybe there’s a 3rd party plugin that does something similar to this.
I did find that audacity has a “Select At Zero Crossings” function by using Z, so at least there’s that.

EDIT: I just remembered that you use Ableton, so I’m pretty sure you can seamlessly loop/crossfade your samples in there instead. :slight_smile:

1 Like

Oh hey that “select at zero crossings” is handy. thanks!

I was kinda surprised audacity didn’t have a cross-fade. Well it does…looks like it can crossfade multiple clips into eachother, but not one clip into itself. I read a couple forums that said you can do it but process seems confusing and complex.

Yeah abletons nice, but I’d have to hop over to the windows side. I’d rather not hop back and forth between machines for “simple” things. A good fall back tho.

1 Like

Seems I’m not so great at the blog thing, but I have been working on the code pretty consistently. Refactoring old code, fixing bugs, adding new bugs features.

As far as project organization goes, the simple kanban with checklists is working for me.

The physics engine now ticks at 120hz instead of 60hz. This looks smoother on high refresh rate monitors, as well as doubles the maximum velocity for bodies.


Audio

I borke audio…or rather hit an audio limitation.

I am trying to convey the velocity of the ship through the sound of the ships engine to help give the player feedback on how fast they are traveling. Using the saw wave sample as a base, I’ve been playing around with some oscillators to modulate pitch, pan and volume to see what effects I can make.

Sound sawwave = Gdx.audio.newSound(Gdx.files.internal("55hz.wav"));
float soundID == -1;
float accumulator;

public void seamlessLoopTest(float deltaTime) {
    if (soundID == -1) {
        soundID = sawwave.loop();//start looping
    }

    float rate = 300;
    accumulator += rate * deltaTime;
    float oscillator = (float) Math.abs(Math.sin(accumulator));
    sawwave.setPitch(soundID, oscillator);
    sawwave.setPan(soundID, 0, oscillator);
}

It gives some interesting dynamics, but it sounds choppy and borken again. This time its due to Sound.SetPitch() forces the samples to be resampled, which can lead to beginning or ending the sound on a different sample. Thus popping or clipping.

So, it looks like I need to use PCM (Pulse Code Modulation) instead for procedural sound generation/manipulation. libGDX has an AudioDevice, but writing samples to the AudioDevice will block the thread until all samples have been played. Meaning its not fit for real-time manipulation.

This is where I have hit a limitation of libGDX’s Audio interface. So I have added a new library: TuningFork: GitHub - Hangman/TuningFork: Advanced 3D audio features for libGDX desktop users.

This library is essentially a thin-wrapper to expose more OpenAL functions, with some additional goodies. It provides real-time effects such as reverb, chorus, compression, and high-pass/low-pass filtering!

This opens up way more possibilities, so I am in the process of migrating the sound system over.

Also this is a fantastic resource: BasicSynth by Daniel R. Mitchell


Asteroid Mining and Resources

There are 5 resources: Red, Green, Blue, Silver, Gold

Each resource type will have different values and properties. Some materials are lighter than others. Some materials are more dense and difficult to mine.

This is the simplest implementation because colors are easy to render and understand, and I couldn’t yet decide on a resource design.

The player now has a cargo bay / inventory for collecting resources.

Space Stations

I have space stations in the “boxed out” stage. Placeholder texture. Previously the player spawned in the middle of nowhere (0,0). Now the player spawns at a space station as a the starting point.

The space station is the home base. The player can dock and undock by landing at the docking pads. Currently just locks the player relative to the station, but I plan to add IK (Inverse Kinematic) arms to grab the ship so the ships aren’t just floating.

The initial basic gameplay loop is to fly to the asteroids and mine for resources, then return to the space station to sell those resources for space moneys: credits which can be to used to upgrade ship.

UI is certainly a weakness of mine, but here is the basic store idea for buying upgrades:
Screenshot_20231209_141727


LASERS!!!

This is a feature I had been looking forward to implementing for a while. Lasers are dope and of course every space game needs lasers.

The laser is a raycast (or hitscan) tool, which reaches it’s target instantly.

world.rayCast((fixture, point, normal, fraction) -> { 
    //hit! do something
}, p1, p2);

The raycast returns the fixture that was hit. We can retrieve the entity from the fixture’s userData, and apply damage to that entity.

Raycasts, Reflections & Recursion (oh my!)

Also returned from the raycast is the normal for the point of contact. The normal is the vector perpendicular (90 degrees) relative to the face of the surface.

Debug draw normal:
Screenshot_20231209_162213

Since we have the normal and the angle of incidence, we can then calculate the reflection and bounce the beam:

//calculate reflection: reflectedVector = incidentVector - 2 * (incidentVector dot normal) * normal
Vector2 reflectedVector = new Vector2(incidentVector).sub(rayNormal.scl(2 * incidentVector.dot(rayNormal))).setLength(remainingDistance).add(p2);

Then we call our entire function again recursively so it can reflect continuously! With a configurable max refelctions to prevent infite loop.

This was a lot of fun to implement and I like how the beam bounces back and forth between cracks.

Refraction? GLASSTEROIDS!

Since we already have the angle of incidence and the normal, I figured why not play around with refraction too?

So I added a 6th type of asteroid: GLASS. By enabling blending in the asteroid renderer I can draw translucent asteroids with an alpha channel:

//enable transparency
Gdx.gl.glEnable(GL20.GL_BLEND);
Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);

shapeRenderer.begin(ShapeRenderer.ShapeType.Filled);
render();//draw batch of asteroid polygons
shapeRenderer.end();

//disable when finished to not affect subsequent draw calls
Gdx.gl.glDisable(GL20.GL_BLEND);

Screenshot_20231209_163127

Since we have the incidence ray, and normal from the ray cast, we can use that to calculate the refraction just like we calculated the reflection.

Found this awesome in-browser demo for playing with light in prisms and seeing how refraction’s and reflections work:


Source:


Image credit (CCO): List of refractive indices - Wikipedia

So the index of refraction in a vacuum is 1. Since we are in space, n1 will be 1. n2 will be 1.5.
~1.5 is a common index for glass.

In reality the amount of refraction is actually dependent on wavelength of light: (eg 380nm bends more than 700nm because it has a shorter wavelength / higher frequency and will interact more with the medium), but I am using a simplified model for now.

n1 = 1   // vacuum
n2 = 1.5 // glass
/** Refract a ray, converted from 3D to 2D from:
 * https://asawicki.info/news_1301_reflect_and_refract_functions.html
 */
private void refract(Vector2 out, Vector2 incidentVec, Vector2 normal, float n1, float n2) {
    float eta = n2/n1;
    float dot = incidentVec.dot(normal);
    float k = 1.0f - eta * eta * (1.0f - dot * dot);
    if (k < 0.f) {
        //past critical angle
        out.set(0.f, 0.f);
    } else {
        out.set(eta * incidentVec.x - (eta * dot + (float)Math.sqrt(k)) * normal.x,
                eta * incidentVec.y - (eta * dot + (float)Math.sqrt(k)) * normal.y);
    }
}

Source: Reflect and Refract Functions
Refractive index - Wikipedia
Sellmeier equation - Wikipedia

This gives us the angle, but how do we know where the ray should end? A raycast cannot originate from inside a body, so we cannot simply cast a ray from the end point of the previous cast. So how do we get the point of intersection on the opposite side of the polygon?

Solution: We perform the same raycast but with reversed start and end points. If a body is hit and refraction occurs, we then fire an additional ray from the opposite end of the body towards the ray start, and in the callback check that the hit fixture is the same as the fixture hit previously to filter out other bodies that might also be within the cast. Then we can draw a line from the endpoint of the first cast, to the endpoint of the second cast and this will be the internal ray.

I still need to make the refraction’s recursive for proper internal reflection, but this complicates the recursive function as there will be 2 branches: reflection, and depending on critical angle also a refraction branch for each hit. So I will need to refactor this function to keep track of whether the ray is inside or outside of the body. But here is a single refraction per polygon demo:

Polygon Slicing

Since we have both the entry and exit points of the laser, how about polygon slicing? If a ray passes through a polygon, we can split the polygon by creating two new child polygons by creating new vertices at the intersection points.

Luckily, someone has already solved this problem. There’s even a nice in-browser demo:

I have not implemented this yet, but its MIT so I’m going to port PolyK’s Slice function and add a new laser variation that can slice. I think this would be a cool addition to the destructible polygon engine.

Bonus: Tractor beams!

Since we already have the ray’s point of contact on the fixture, how about applying an impulse?

PULL vs PUSH demo:

I’ll probably make the tractor beam be a different non-reflective tool so it only hits the first object it touches. But a neat effect of the recursive nature is that the tractor beam pushes or pulls all reflected bodies, effectively forming a clump. You can build up some angular momentum by applying the beam off center mass of the body.

I really enjoy playing around with the physics engine and different ways to interact with bodies. Once I get everything hooked up the player could slice asteroids and fling bodies around with the tractor beam.


Steam Deck

I have the OG 60hz LCD model, but 90hz OLED models will certainly benefit from the higher tick rate physics.

I haven’t done much optimization yet but so far its performance is great on the deck. The engine is pretty lightweight, so I can actually comfortably develop and compile in docked mode directly on the steam deck natively!

Audio however is not working on deck. Issue #47. I still haven’t found a solution, and it’s driving me nuts.

[ALSOFT] (EE) Failed to connect PipeWire event context (errno: 112)
ALSA lib ../../pulse/pulse.c:242:(pulse_connect) PulseAudio: Unable to connect: Connection refused

Troubleshooting: The small linux problem thread - #5871 by 0xDE57

So here’s a quick demo with no sound:

This mess of a project is starting to form the illusion of a game. Once I get some more gameloops up and running, I’ll throw a build up on itchio for some real feedback.

3 Likes