Painkiller is a little-known gem in the first-person shooter (FPS) genre. I’ve played through it many times, but my most recent playthrough had something different: I did it entirely on Linux (Kubuntu 20.04 LTS).
All along, I’ve been using the Painkiller Black Edition from GOG.com, which has a Windows-only installer of close to 4GB that you can download after purchasing the game.
Once downloaded, we can install the game by opening a terminal, navigating to the directory where it downloaded, and running the installer with WINE, as I’ve shown many times before in previous articles (note that that’s a single filename… the spaces should actually be underscores):
cd Downloads
wine setup_painkiller_black_1.64_lang_update_\(24538\).exe
This brings up the GOG installer for Painkiller, so you select the installation language, tick the box accepting the EULA, and optionally choose the folder to install to.
Click the Install button to begin the installation, and the installer runs into a snag towards the end:
This problem has happened to me with several GOG games before, and each time, I thought all was lost. So it was to my great surprise that, when I clicked the Launch button after closing the error, the game ran perfectly fine anyway.
I was able to play through the entire game without problems, so if you get this error while installing Painkiller (or another GOG game) with WINE, just ignore it and try playing anyway. Chances are it will be fine.
Playing the Game
Monster at the Cathedral
City on Water
The Palace
You can run Painkiller by double-clicking the handy desktop icon it sets up for you. At that point, all that’s left is to enjoy the action and stunning visuals.
Before 3D took the gaming world by storm, many RPGs used a pseudo-3D engine in which you could move in discrete steps within a grid, with a first person view. Although this was popularised by “blobbers” such as Dungeon Master and Eye of the Beholder, it goes at least as far back as 1979 with Akalabeth.
“Blobber: A slang term for party-based games with first-person view, such as Wizardry, Dungeon Master and Legend of Grimrock, where the entire party moves as one, as if it was an amorphous blob.”
Unity3D gives us everything we need to easily set up this first-person grid-based movement.
This article is based on Unity3D 2020.3.13f1 (LTS), and the source code is available in the Unity3dFirstPersonGridMovement folder of the Gigi Labs Bitbucket Repository.
Setting the Scene
First, create a new 3D project. Add a few Cubes to the scene so that we’ll have something to see as we move around. You can do this via GameObject menu -> 3D Object -> Cube, although after creating the first one, you can select it and press Ctrl+D to duplicate it. Set their X and Z positions to integer values, leaving their Y position set to zero. Since the Main Camera faces down the Z-axis, it’s also nice to not set X=0 for any cubes, so that we have a corridor that we can immediately walk down.
Added a few cubes to the scene.
Optionally, add a few different-coloured materials and apply them to the cubes. This will later help make the movement more obvious, rather than just having a long, solid white wall. Refer to “Simple Brick Wall with Unity3D” if you don’t know how to do this.
A bit of colour makes all the difference.
Finally, adjust the camera so that its Y-position is zero, aligning it with the cubes.
Create a script called “Movement”, and drag it onto your Main Camera. Double-click the script to open it in an editor.
In the Update() method, add the following to enable the typical WASD keyboard movement (‘W’ to go forwards, ‘S’ to go backwards, ‘A’ to move left, and ‘D’ to move right, all while facing down the Z-axis):
void Update()
{
if (Input.GetKeyDown(KeyCode.W))
this.transform.position += Vector3.forward;
else if (Input.GetKeyDown(KeyCode.S))
this.transform.position += Vector3.back;
else if (Input.GetKeyDown(KeyCode.A))
this.transform.position += Vector3.left;
else if (Input.GetKeyDown(KeyCode.D))
this.transform.position += Vector3.right;
}
If you press Play, you can use the WASD keys to move around:
Moving forwards into the corridor.
Implementing Rotation
That was easy enough! The fact that the camera is conveniently aligned with the Z-axis allows us to use predefined vectors to move in specific directions. However, this is no longer the case once we allow rotation. Any movement must be done with respect to whatever direction the camera is currently facing.
Fortunately, we already know what direction the camera is facing. That’s its transform.rotation. We can change direction by multiplying it by a Quaternion, which we can conveniently create based on an angle in degrees using Quaternion.Euler(). Let’s see this in practice by allowing the ‘Q’ and ‘E’ keys to rotate the camera 90 degrees left and right, respectively:
void Update()
{
if (Input.GetKeyDown(KeyCode.W))
this.transform.position += Vector3.forward;
else if (Input.GetKeyDown(KeyCode.S))
this.transform.position += Vector3.back;
else if (Input.GetKeyDown(KeyCode.A))
this.transform.position += Vector3.left;
else if (Input.GetKeyDown(KeyCode.D))
this.transform.position += Vector3.right;
else if (Input.GetKeyDown(KeyCode.Q))
this.transform.rotation *= Quaternion.Euler(0, -90, 0);
else if (Input.GetKeyDown(KeyCode.E))
this.transform.rotation *= Quaternion.Euler(0, 90, 0);
}
The camera can now turn left and right:
I walked down the corridor and turned left.
However, this messes things up because the WASD keys still move with respect to the Z-axis, rather than in the direction that the camera is facing. We can easily fix this by multiplying the movement vectors by the camera’s rotation:
void Update()
{
if (Input.GetKeyDown(KeyCode.W))
this.transform.position += this.transform.rotation * Vector3.forward;
else if (Input.GetKeyDown(KeyCode.S))
this.transform.position += this.transform.rotation * Vector3.back;
else if (Input.GetKeyDown(KeyCode.A))
this.transform.position += this.transform.rotation * Vector3.left;
else if (Input.GetKeyDown(KeyCode.D))
this.transform.position += this.transform.rotation * Vector3.right;
else if (Input.GetKeyDown(KeyCode.Q))
this.transform.rotation *= Quaternion.Euler(0, -90, 0);
else if (Input.GetKeyDown(KeyCode.E))
this.transform.rotation *= Quaternion.Euler(0, 90, 0);
}
And this works really nicely.
I walked down the corridor, turned right, and moved a step backwards. I’m actually inside the red cube, because we have no collision detection!
Watch the demo video on YouTube to see this in action!
Wrapping Up
As we’ve seen, Unity3D makes it really easy to set up this simple game mechanic where you have a first person view, move in steps, and turn in 90-degree angles. It does help to be comfortable with vectors. If you’re not, check out my “A Concise Introduction to Vectors” [PDF] at Swords and Software.
If you’d like a little extra exercise, try to make the movement and rotations as smooth transitions. You can use Vector3.MoveTowards() and Quaternion.RotateTowards() for this. You’ll also need to change your camera’s near clipping plane to zero to avoid weirdness during rotations.
In Unity3D, we can use prefabs to create a template for an object that we’ll create many times, such as bullets, planets, or – as we’ll see in this article – bricks. Prefabs are handy because each instance will carry the same scripts, materials, transforms, etc. Let’s see how this works in practice by creating a wall made up of many bricks.
To start off, create a new 3D project with Unity3D. I’m using Unity3D 2020.3.13f1 (LTS), although any version is probably fine for this beginner exercise.
Creating a Brick Prefab
We’ll start off by creating a simple brick, which will be the building block for our wall.
Create a Cube, via the menu GameObject -> 3D Object -> Cube. Rename it to “Brick”. In the Transform portion of the Inspector, set the X component of its Scale to 2, so that it looks elongated like… a brick.
After creating a Cube GameObject, name it “Brick” and set its X-scale to 2.
Next, we’ll create a material for the brick. In your Project window, right click on Assets and then go Create -> Material. (You could create an appropriate folder structure to separate things like Materials, Scripts etc, but since we’ll have very few of these in this case, I won’t bother.) Name it “Brick Material”, and then set the colour of its Albedo property to something that looks like brick red (in my case I went with RGB(183, 50, 57)).
Name the material “Brick Material”, and set its Albedo to an appropriate colour.
Drag the Brick Material onto the Brick (in either the Hierarchy or Scene windows) to apply the colour.
Next, drag the Brick GameObject from the Hierarchy window to the Assets folder in the Project window to create a prefab out of it. Note how the Brick is now blue in the Hierarchy window, and it also gets an “Open Prefab” button in the Inspector.
Drag the Brick GameObject from the Hierarchy window to the Assets folder in the Project window. The Brick GameObject is now an instance of a prefab.
Instantiating a Single Brick
We now have two concepts of “Brick” in this project: the original Brick GameObject (as seen in the Hierarchy and Scene windows), and the Brick prefab (in the Assets folder of the Project window). The Brick GameObject is an instance of the Brick prefab. We can create more instances of the Brick prefab, and they will have the same components (such as materials and transforms) unless we specifically change them.
This makes it very easy to write a script to create lots of bricks using the Brick prefab as a template. In fact, we don’t need the Brick GameObject any more. Go ahead and delete it.
Right click on the Assets folder of the Project window, select Create -> C# Script, and call it WallGenerator. Double-click the script to open it in your script editor.
Add a serializable field for the Brick prefab at the beginning of the class:
public class WallGenerator : MonoBehaviour
{
[SerializeField]
GameObject brickPrefab;
Save the script, then go back to the Unity3D editor. Then:
Drag the WallGenerator script onto your Main Camera.
Notice the Brick Prefab property for the script in the Inspector.
Drag the Brick prefab from the Project window into that slot in the Inspector.
Drag the WallGenerator script onto the Main Camera, and then drag the Brick prefab into the script’s relevant slot in the Inspector.
With this set up, we can start creating instances of the prefab from the script. Go back to the script editor, and add a call to Instantiate() in the Start() method as follows:
// Start is called before the first frame update
void Start()
{
Instantiate(brickPrefab);
}
Back in the Unity3D editor, press Play to run the game. You’ll see that a brick is actually generated at runtime, even though we don’t have any in the Scene window in the editor:
A single brick, created by the WallGenerator script, is rendered in the scene at runtime.
Building a Wall
Now that we know how to create a single brick from a script, we can use simple loops to create more. Let’s create a 5×5 wall by using a different overload of Instantiate() that also takes a position (which we’ll provide) and rotation (which we’ll ignore):
void Start()
{
for (int y = 0; y < 5; y++)
{
for (int x = 0; x < 5; x++)
{
Vector3 position = new Vector3(x * 2, y, 0);
Instantiate(brickPrefab, position, Quaternion.identity);
}
}
}
If you press Play, you’ll see the wall, although it’s a little hard to distinguish the bricks because they’re touching each other:
A 5×5 grid of bricks with no spacing in between, so it looks like one solid material.
You can tweak the scale of the Brick prefab to get some space between the bricks. For instance, this is how it looks with a scale of (X, Y, Z) = (1.95, 0.95, 1):
The brick wall with each brick having a scale of (X, Y, Z) = (1.95, 0.95, 1).
Finally, just to give it a bit more style, let’s change the script so that it generates alternating patterns of bricks:
void Start()
{
for (int y = 0; y < 5; y++)
{
int xOffset = (y % 2 == 0) ? 1 : 0;
for (int x = 0; x < 5; x++)
{
Vector3 position = new Vector3(x * 2 + xOffset, y, 0);
Instantiate(brickPrefab, position, Quaternion.identity);
}
}
}
All we did here was use the modulus operator (%) to check whether the row is an even-numbered one, and if so, add an extra 1 to the X position of each brick. This results in the following wall, better than any that Donald himself could ever hope to build:
A brick wall with alternating, slightly spaced bricks.
Summary
A prefab is a template of a GameObject used to create instances of it that share the same or similar components. As we have seen, we can easily call the Instantiate() method to create lots of instances of a GameObject from a script, making this one of the cornerstones of dynamic behaviour in Unity3D.
"You don't learn to walk by following rules. You learn by doing, and by falling over." — Richard Branson