I'm not entirely certain when I was introduced to Doom or Wolfenstein 3D. Long after their respective releases, no doubt. Quake) though, I got to experience when it was new. It was impressive as heck. Then people started making modifications for it. Grappling hooks appeared early on, there were versions that had rudimentary vehicles. What a delight. The people who created some of the initial QuakeC mods ended up being pretty prominent game developers.
At any rate, I found a Quake BSP import plugin for Blender and that unlocked some exciting new possibilities:
“Eternal Dismemberment Complex” by Kevin Shanahan aka Tyrann
Quake levels had a theme alright. Classically the palette of Quake has been described as "brown-ish". Still it beats the stuff I could create at the moment.
There's no lighting implementation at the moment so everything gets the same degree of light.
The next thing I wanted to sort out in my project was a way to have weapons. I figured why not make use of the particle system in SceneKit for this end:
A particle system can perform limited collision detection and resolution with geometries in the scene. If a moving particle intersects a geometry attached to one of the SCNNode objects in this array, SceneKit resolves the collision, either by removing the particle from the scene or allowing it to bounce off or slide along the geometry’s surface.
So I setup a simple scene to work with:
So I want those particles to stop at the green cube.
Now I need to be informed that they have collided with the cube.
Haha. Neat. (The cube “explodes” after getting hit by 10 particles.)
Truly we're cooking with particles here.
Next up, try it with the model I have created:
I had working particle collision and detection, but when you're only dealing with a single object it's very easy to know which object was collided with. In what I was imagining you'd have multiple enemies at any given time. So it was key to figure out which object was getting hit.
I am only informed of where the collision happened, so my first solution was to just iterate through all the objects that could be hit, and see if that position was within their bounds. That worked well enough:
Then I considered how it would work in reverse. I added a target for my enemies to shoot at:
Neat.
Things were working great with just one target, but adding a second target caused some problems:
They should be attacking the closest target, but it's not working. I had to spend some time figuring out how to make sure that the model was correctly rotated to face the target.
Much later, after figuring out the dot product I was able to have a Marten accelerate towards a target, reach a specified range, stop immediately, and start shooting:
Adding things back in, I get a scene with multiple Martens rotating, moving, and firing:
It's a bit chaotic, but I like it.
The way I was trying to organize the behaviour of my enemies was into states, specifically I had
So initially the Marten would be in the RotationState, turn to face the target. Then it would transition into the PursuitState which would cause it to accelerate towards the target, until it reached a specific distance, and then transition into the attack state.
Rotation:
Pursuit:
Attacking:
Adding this back in allows the multiple version to work, and since they have a physics body they collide with each other:
One easy way of re-using assets is to give them different colour schemes. Perhaps the green versions are the standard version, and red would be for an elite or stronger version. So I wanted to see if there was an easy way to add some colours:
This allowed me to try out a version with different coloured versions:
Then I organized them into teams, and had them attempt to engage each other:
I was quite pleased with how this was working. It was time to finally get back to my original project instead of my simple test case:
It worked and it didn't cause everything to immediately explode. Great. Next up a new map design.
Having conquered "doors", I decided to move on to a new problem. Enemies.
Love them, hate them, know that they're always plotting your demise.
The lack of depth in this is a bit tricky, but the yellow portion is the fixed base. The red section can rotate freely allowing left/right directions. The green which would also be the business end of the weapon, can swivel up and down. A 2-axis pan/tilt system.
You can see the small-ish turret in the game scene. I need to fix the normals on the yellow base so they are properly pointing outwards. The blue/teal sphere was part of my initial solution. I was going to apply a constraint to the it that it was always looking at the player's ship. Then I could just take each individual rotation and apply it to either green or red, so that I didn't need do to the math myself.
That didn't work out so great, so I had to figure out the math:
That’s the top down perspective of my Level Zero. I need to at all points figure out what the angle is between the selected object (my turret) and the ship which is located at (1,1,0). This view is directly on top so you do not have any indication about the Z
axis which is the height.
I need to figure out what angle I need to rotate so the red section is facing the ship. Trigonometry to the rescue. Then I do the same thing to figure out the relative height difference for the green section.
α = arctan((turret.y - ship.y ) / (turret.x - ship.x))
Great. Then for my green object I care about the X and Z axis as I need to know how far, and what the height is.
α = arctan((turret.z - ship.z ) / (turret.x - ship.x))
I have to take in to account things like scale and rotation, but there are things designed to do that for me. Let me give this a go.
Left is the turret aimed at a space where the ship was. Right is after it has been told to point at the ship.
I can animate it as well and it looks pretty cool.
I spent a decent amount of time trying to figure out how I wanted the turret to work, maybe I'd give it legs so it could scuttle about. After spending some time looking into how you'd get animations working in SceneKit and exporting those from Blender, I decided to move on.
I returned to what Descent has for enemies:
That’s a model of an enemy from one of the Descents.
It doesn’t have articulated moving animations, it just floats and shoots at you.
I thought the turret route would be easy, but adding legs was probably not the way to go.
I decided that I was going to name all my enemies after Mustelids, your weasels, badgers, otters, ferrets, minks, wolverines, and martens.
Behold the Marten, it’s just a boxy thing with guns, but sometimes the classics are best.
I might have to scale it up, but it shows up. There’s just two on the map right now. I think once I can get them to orient towards me, which might be really easy since I just need to turn the entire model not worry about discrete components. I did leave the “weapon pods” their own nodes so I could in theory tilt them up/down and to the left/right if I thought that would make things look better. Actually the small size will help with that, because they almost always can just fire directly forward if they’re smaller visually.
I wrote at the time:
It’s very cool to see them slowly rotate to face me based on the animation duration and when it updates. I can at this point figure out how I want the firing animation to look like, and because they’re facing the ship I can have them fire “forward” and I get to go home early.
The other thing you need to be an effective enemy, ostensibly on an asteroid mine, is a powerful laser weapon.
SceneKit has particles that I made into an effect:
Which allowed me to add them to the Marten:
Applied to the Marten already in the level:
And I made the laser more additive and added some orange tones:
The one thing I quickly identified was that having four weapon barrels was a tremendous mistake on my part. I should've kept it at two.
I created some new materials for both the door frame and the door segment materials.
I wasn't sure of the best way to add the doors into my level. I could add them individually to the level as part of the level geometry so the level was complete, or I could add nodes that would be spawned/replaced with doors.
I opted to go with door spawning:
Just need to apply some scaling to get it to fit the entire corridor.
The object I was using to represent the door in my level was a 1x1x1 cube, but the door I have created is not 1x1x1 in dimensions. So I adjusted the placeholder object so it would scale properly:
Not exactly what I was hoping for, but fixable.
The other problem I was attempting to solve was how to store Door metadata. I am still treating Blender as a level editor, so why not make it the place where these things are defined.
This allows me to configure everything in Blender, and then it will be read by my game to configure things like the door frame material, if it's locked, requires a key, or how many segments it has.
A door with "3 segments" is just a 2 segment door that opens horizontally, whereas the one with 2 segment is the one that opens vertically. I worked on readjusting the segment materials and updated my game so that it would randomly select a door frame material and a segment material.
I also attempted to indicate that if a door requires a specific key, by adding a colour to the material:
It doesn't look great, but I figured I would solve it later. The other issue I wanted to address was how you opened the door, previously you had to collide with the door for it to open. I added an additional area before/after the door that allows the door to open before you intersect with it.
Just entering the purple zone will open the door.
Having doors that open properly and have random material selection allowed me to create a hallway of doors:
Which looks like this:
My own version of the Get Smart intro.
After my success with the concept of doors, I decided to embark on creating a level in which to have potentially many doors.
Gotta say, the darkness gives it a bit of a vibe.
I also added a way to create/instantiate the Ship from another scene entirely. This lets me attempt to keep things separate, any changes I make to the ship do not require me to update the level, or vice-versa. Previously the Ship object was embedded inside of the level. Unclear as to why the floor is so eye searingly red.
I also added some additional cameras on the ship, including one on the inside.
Floor textured replaced.
Looks like the carpet of a Dentist’s waiting room, but I think we can all agree: not red.
Great, now what I need is to make an elaborate door.
I was quite impressed with how proper textures with depth maps looked in Blender. My plan is to slice the door into 4 segments, so that I can move them independently to create different door effects.
I added a door frame!
That's one way the door can open, but the same idea will be used for hanger and "super market entrance" doors.
You know what you need to separate two areas with? Doors.
I created 4 rectangular boxes to represent the door itself, and a torus to act as the frame. The reason I created 4 of them was so that I could move each independently. Early games would just move the door upwards, like a garage door. I could do that by moving all 4 boxes up. Or I could open from the middle by having the top 2 boxes go up, and the bottom 2 go down.
I was surprised at how quickly this came together, and it works in reverse to close. I added some extra doors, and since my weapon removes things from the scene, I demolished some door segments:
Attempting to navigate this just with keyboard controls was getting a bit tedious, so I added in mouse aiming, which doesn't really look much different from the perspective of a screenshot.
Also, since I had some doors why not add some walls to the place:
Also since I was having to deal with creating a level, why not replace the rather nice default SceneKit model with something that better suited the place:
I had somewhat also figured out how textures worked, so I decided to spruce up everything:
Up next is refining these concepts into more sophisticated versions. I was very excited to see what I could do with the doors.
I'm actually going to skip over a chunk of time I spent trying to figure out controls and physics interactions. Skip to implementing some sort of weapon system.
It's actually pretty easy to add a Particle System in SceneKit:
They didn't actually do anything other than stream forward, but you have to start somewhere. I figured I'd want to make them look more like "Plasma Balls" since that's what I assume you'd fire as a particle.
Excellent. One issue I found is that by adding the Particle System to the model itself, meant that the particles were generated in the centre of the model and therefore had to travel through the front half of the model before becoming visible. Created an invisible node and placed it in front of the "nose" and it works like one would expect.
The next problem was how to handle interactions between the weapon and the things you'd be shooting at. My first solution was to just determine if there was any object between my weapon and a distance x
in front of it.
I added some targets to my testing scene. I was attempting to keep to a colour scene. Anything blue I figured you should be able to shoot.
And after firing the weapon a couple of times:
One thing that caused me some issues here was that as soon as you fired it calculcated what in the path, and removed it from the scene. It worked for the time being, which was enough to move on with.
I spent much time trying to figure out how my controls should work, and how they'd work in my project.
Ultimately this allowed me to take the default SceneKit Jet Model and bring it to an old familiar haunt, Quake's E1M1:
The controls were a bit disjointed, and would sometimes cause the model to spin wildly, but I could still fly around the level. Which was very neat. I've spent a lot of time inside of that level, for one reason or another, so it was exciting to go back and see it. In a fresh new globally illuminated way.
I also marvelled at the ease at which you get real-time reflections:
Elsewhere, I was attempting to get working controls/movement system.
My plan, at the time:
The green cubes would be solid and you’d “bounce” off those, but you collect the blue spheres.
That seems like a reasonable and approachable goal.
I am unsure what the red torus will do, just sit there and look cool I suppose.
I'm always a big fan of setting reasonable and approachable goals.
This was a great opportunity to learn more about the physics system that SceneKit gives you access to.
You can assign a “Physics Body” to a SceneKit node, and then it can be subject to various forces including gravity. There are three properties relevant to collisions/contact.
The category mask represents what type of object it is. Since we’re dealing with bitwise operations, it means that each unique value has to be a power of 2.
Entity | Decimal | Power of Two | Binary |
---|---|---|---|
Level Geometry | 1 | 2^0 | 0001 |
Player / Ship | 2 | 2^1 | 0010 |
Enemies | 4 | 2^2 | 0100 |
“Power Ups” | 8 | 2^3 | 1000 |
So, for example my PlayerShip is setup this way:
Mask | Value (Decimal) |
---|---|
Category | 0x0010 (2) |
Collision | 0x0101 (5) |
Contact | 0x1000 (8) |
So the physics system will have my physics body collide (resulting in the node stopping or bouncing when encountering the body it's colliding with), or generate a contact event indicating that I have run into a collectible power up.
Things like, to be inspired by Descent again:
And so, with a couple of things in place, I was able to test things out:
That's what the scene looks like after I have "collected" the blue spheres. The model bounces off of the green cubes.
From my notes at the time:
I managed to create two adjoining spaces connected by a cylinder. Some parts are inside out because of... reasons. I’ve tried to apply a “Concrete” material to the cylinder, but it mostly just looks grey.
I think I could’ve textured these things, but that’s a complication I don’t want to deal with at the moment.
The inside out nature is due to the normals being in the wrong direction. A constant bane!
Why not just use a Quake map, I say?
In my attempts to make my life easier I found a way to import Quake 1 maps into Blender.
The Slipgate Complex is the first level of the Dimension of the Doomed. The level serves as a relatively gentle introduction to the gameplay of Quake (even on harder skill settings). It consists of two buildings with a small River) between them. The smaller building is where the player starts and it is relatively safer. The larger building is where most of the action takes place - it features several larger rooms, a large Slime) pool, and a series of platforms above it.
The textures are from a project to upscale the original Quake 1 textures. The lighting is also enabled, but it was neat to see.
Eventually I added in the jet model that you get from the SceneKit starter project. I was able to fly through the level, which was pretty exciting.
The first thing I wanted to sort out was going to be some way of handling interactions between my "level geometry" and the player. There's a lot of ways to solve that set of problems, but I endeavoured to make SceneKit's problem so I didn't have to deal with it.
The base object in SceneKit Scene is called a SCNNode
A structural element of a scene graph, representing a position and transform in a 3D coordinate space, to which you can attach geometry, lights, cameras, or other displayable content.
So I've created two new nodes, given them the geometry of a pyramid and a sphere. They also have a physics system which means that when the jet model collides with the pyramid, it causes the wing to lift up. Or when the jet model collides with the sphere, it rotates.
Great. Excellent.
One problem though, is that a game world populated entirely with spheres and pyramids is only so compelling. I need to be able to create arbitrary structures or layouts.
The last time I spent any time with a 3D Modelling application was quite some time ago, and I preferred Modo. However in the intervening span of time, Blender has become the de-facto modelling application for people who do not have some reason to use things like Maya or the other high quality and very expensive applications.
It works though, surprisingly.
The jet model dutifully bounces off the walls. I now have a way of creating things in Blender and making use of them in my project.