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

I love the sound idea. I would say that I like the dissonant sound better than the dirty sound ratio.

2 Likes

I agree the true random pitch sounds better.

I think the amethyst block sounds from minecraft are quite pleasant so maybe I’ll aim in that more “sparkily gem” feeling. Or I could sample some sounds of rocks or big boulders smashing, idk yet.


I have some ideas for what I want to do with the resources that asteroids drop. I could do the standard:

  • iron
  • gold
  • silver
  • copper
  • etc, just grab some metals off the periodic table
  • gems: ruby,sapphire,emerald,etc…
  • maybe there’s icy asteroids and we need water?

Perhaps the sound of the asteroid can depend on its type or what resources it drops.

I’m open to ideas.

1 Like

I realized I haven’t talked much about the gameplay itself yet. Which is probably important, given that it is a game. But first a little history.


I like to start on paper before touching code. Here are some of my old design notes:

Looks like at the time I was debating between either libGDX, slick2D, or LWJGL (which I always have fun trying to pronounce). I wonder if I would have chosen Godot if it existed back then…?

I had also wanted at the time some sort of good vs evil system with npcs, which was pretty much cut entirely due to complexity.

Why my own engine?
I started this project in ~2013. My previous project was a juiced up breakout clone in gamemaker 8, and by this time I was working almost purely in gml (game maker’s scripting language) because I liked having more control through code than the drag n’ drop actions/events.

On the side I also had started a simple tile-based engine based of TheCherno’s old engine series: https://www.youtube.com/@TheCherno because I was getting more interested in the lower level underlying technology.

When I had the idea for this game, I knew that some of the features I wanted to implement would probably be much harder to do in gamemaker. I didn’t want to limit my design or ideas based on the the constraints of someone else’s engine…

Then I stumbled upon some articles about the “magic of ECS” and the “evils of OOP”, and I was drawn by the flexibility. I ̶w̶a̶s̶t̶e̶d̶ spent a lot of time reading about and stressing over different design patterns and clean code and code quality. It’s easy to have ideas for features, but how to add them all without making a mess? How do I structure my program as it grows in scope and complexity? It certainly doesn’t help if you like to over-engineer things.

I have found that properly planning what I want to build first before touching code is really helpful.



(edit: added a few more)





It also allows me to identify certain problems before running into them during implementation or runtime.

For example: these pages are from a time when I was trying to sort out the some architecture issues. I mapped it out the class hierarchy / project structure to wrap my head around around the dependencies and coupling:

This helped me figure out where/how/why I was using certain objects and simplify the project a little.


A couple re-writes later and here we are… Now I have a more pragmatic approach of just keep it simple and follow best practices where applicable.

It is interesting to see what I have been able to implement so far compared to what I had originally planned. Some things have changed overtime, features cut because they were too complex, or didn’t make sense. Sometimes there is a technical limitation in the hardware or the software. Or even dealing with limitations in myself and my own abilities. Other times I am more excited by an idea itself than the execution.

Progress is definitely slow compared to if had decided to use a full engine like Unity or Godot. But ultimately I am glad I took the time to DIY as much as I could because I’ve learned a lot from it.

Thankfully I also have the freedom to walk away from this project to focus on other things when I feel burnt out or overwhelmed. And that I’m stubborn enough to keep coming back to it from time to time.


Right, so back to gameplay. Right now I have a problem of large empty universe, kinda boring and lonely.

Next up is getting the mining gameplay loop:

  • asteroids drop loot when destroyed
  • player collects loot
  • ship can carry X amount of loot in cargo until full.
  • player sells loot space station

Space Station:

At the space station the player can:

  • sell loot
  • repair ship
  • upgrade components on ship:
    • more health
    • better engine
    • etc
  • buy new components:
    • shield
    • hyperdrive for faster travel
    • different weapons
    • maybe a solar panel so you can recharge/heal when near stars or in direct light path of star (ray cast)
    • etc
  • buy/sell different ships (out of scope for now: I have some ship classes and types on paper)

Once you have loot and items, you need some flavor of UI/inventory. I’ll draft some stuff on paper but I need to sort out some more details. Again, I’d prefer to keep UI simple and minimal for now. I don’t like complex inventory management systems.

AI is partially implemented but disabled spawning to sort out some other stuff. The idea is AI ships will fly from planet to planet.

AI:
- fly between planets or stars
- can dock at space stations
- can mine asteroids
- passive by default, but will return fire when attacked

Some AI miners flying between asteroids mining locations and space stations. In theory you could be a space pirate, attack another miner to take their loot.

In my original design doc I had planned some sort of economy and trading system. Where resource X would be rarer in system A, than in system B. And with some travel you can profit. This has always felt out of scope till recently it’s coming into view, but I am not sure if that’s even what I want to do anymore or how I want to implement it. I don’t want a really complex UI / inventory system.

Planned Content:

  • black hole at the center of the galaxy
  • worm holes or some sort of warp gate teleport type thing
  • AI battles
  • space creatures / bosses
  • scripted events / quests?
  • landing on planets (probably out of scope, will break down in future post)

While far from complete, I’m forcing myself to make a demo build at the end of the January here so at least I can get some feedback on the controls and feel and people can poke at it. I know I said that last year, but it was really too incomplete and broken then. I’ll see what I can get done in this final week (I’ll be gone this weekend but I’ll do my best)

I’m in this awkward stage where I want to show certain things, and other things simply aren’t ready. But I can’t develop in a vacuum anymore and I’ve already refined some controls for the ships based on some feedback people have given me. Which is very useful.

Down the road once I have a more complete gameloop and a little more polish I’ll make an itchio page and start putting builds there.

I feel like I’m finally hitting a turning point where I can start working less on the engine, and more on the actual game itself!

3 Likes

Hey, this is part of the creative process. It is better to get feedback early versus send out a viable product that no one wants, right?!

That must be a great feeling.

2 Likes

It’s an interesting transition. Adding new mechanics and building out the world is usually a lot more fun than some of the other stuff.

For example, sometimes you spend a long time working on something internally in the engine that’s important, but not very interesting work on. And often those under the hood tasks don’t have any obvious impact on gameplay or visually, so it can look and feel like your not making much progress. Those periods can feel slow and be difficult to get though. Especially without a clear goal or plan.

That’s certainly one way to do this, but it’s probably not the best.
For another a better approximation of actual luminosity values, try something like this:

float grey = 0.21 * sample.r + 0.71 * sample.g + 0.07 * sample.b;

As you can see, this is a weighted sum(You method is as well, but all the weight are 0.33. I hope this makes sense to you). And intuitively the green should contribute more to the total luminosity, since our eyes are more sensitive to green light.
(I know the weight add up to .99. I think this is intentional since it maps better to 0-255 without clamping)

Other than that, your game looks very cool. It’s certainly ambitious, but you seem to make great progress. Keep it up! :stuck_out_tongue:

1 Like

Yep, makes perfect sense. I can see how green being more visible to our eyes than red is reflected in your weights.

How did you arrive on these specific values?


Also here is what the change looks like in engine:

The previous .33 weight:

With the weights you provided:

It’s certainly darker and blacker overall results. Here is less zoom, less dark more mix:

I didn’t though of these values myself.
It’s a well-known formula, but you can search google for it(keywords: luminosity greyscale shader).
Here’s an article from 2009 comparing some methods:

2 Likes

Found those weights in the YUV YUV - Wikipedia

More color space stuff. Thanks!

1 Like

Item pick up magnetic/gravity effect prototype.


need to tune the forces a little based on distance.

Using collision filtering to differentiate between inner and outer circles:
image



Bug while implementing where i used the opposite angle and they were pushed away instead of sucked in:

3 Likes

Welp, here we are. I didn’t quite get everything I wanted in for this demo, but I am pretty happy with progress made the last 2 sprints.

I will keep working on this obviously, but as far as Devanuary goes I’ll cut it off here. This is my official submission:

spaceproject_devember_enginedemo_20230131

There is a requirement to install Java (sorry). I will look into packaging a jvm the with build: Deploying your application - libGDX

If you don’t feel like running unsigned jars from strangers on the internet, source code here: GitHub - 0XDE57/SpaceProject at spaceproject_devember_enginedemo_20230131

Will sort out some better devops stuff later for future builds.


I put a lot of energy into fixing bugs and crashes instead of pushing more features in for this build.

Things that have not made it into this demo that I hoped for:

  • space station and docking
  • basic lighting / post-processing
  • laser and asteroid slicing
  • better destruction rules for asteroids
  • AI (AI spawning is currently disabled)

Known issues:

  • lack of gameplay: this is a basic engine demo
  • asteroids make extra pieces sometimes or spawn funny: #2
  • drops do not spawn correctly, this is a direct side effect broken origin caused by #2
  • magnet/gravity effect for sucking in drops needs tuning as I implemented this only just last night (they just get stuck in orbit around player or whip out passed the player when higher velocity)
  • there’s nothing to do with collected drops yet (you will find a count below the health bar of how many you picked up. eventually to be sold at a space station)
  • missing sounds
  • camera needs work (disabled lerping due to running into some stuttering issues and framing, need to lead target, etc)
  • options UI for changing keybindings is really bad and broken
  • no in-game tutorial or explanation for controls
  • many more: see issues first
  • many other things in todo not in issues yet

I currently master everything in a plain text todo file, but its about ~1000 items long and only somewhat organized. So I’m going to experiment with github’s project organizing workspace thingy, and I’m migrating items from my todo into that. Hopefully this tool gives me better organization and planning.


Anywho, here are the controls:


Notes and tips:

  • Some controls disabled; features not ready. Will break down in future post.
  • frame of reference and sense of scale can be confusing, zoom in and out to get your bearings
  • If you are going too fast to stop your ship before ramming into an asteroid, you can activate the shield to safely bounce off.
  • If you fly into a star, you can use the shield to protect your ship from burning up.
  • Minimap is off by default, M to toggle between [off, corner, full] and supports its own zoom level when mouse over.
  • all debug controls and rendering is left in because I think that stuff is interesting so feel free to poke around the debug menu options and have fun.
  • NOT LISTED IN CONTROLS: I left a very specific debug control in intentionally: Right-Click will spawn a new asteroid at mouse position. This is for you to play with / abuse / test and wont be part of regular gameplay.

Let me know what you think!

4 Likes

Java gets a lot of undeserved hate. Yes, I am biased.

All of the cool kidz do it though… thanks for providing the source code.

2 Likes

Java was one of the first languages I learned after gml and visual basic. I liked that I never had to to care about what operating system the user is running. Which seems to be true of more languages and engines these days but not so much 10 years ago.

I have been tempted to port to c++ “because performance”, but I’m not going to worry about ‘java is slow’ until I actually hit that wall (if I even do for a simple 2D game like this).

Different tools for different jobs. I’d like to try Rust or Go Jai at some point.

3 Likes

Fixed some bugs. Been pretty busy with work, will try to release update this weekend.

2 Likes

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