Eleventh post in the series. You can read the other posts here.
Who Am I?
I'm Steve, another newcomer to the
ranks of Frictional Games from the foggy British Isles. I joined
around the same time as Ian give or take a few weeks and, like both
Ian and Patrik, am responsible for level and gameplay scripting.
I'm originally from London, having
moved out five years ago and settled in a small village in
the Midlands. I'm married and have two daughters who I have
to keep out of my room for at least 8 hours a day. So essentially, as an adult about to hit 30, my days involve sitting in a corner of my
bedroom on the PC playing with editors and programming languages
while trying to keep the family out. Life has a funny
habit of coming full circle sometimes!
But before we get on to that, here's
the photo of my workspace:
|
Until recently I had a tiny desk! |
Background
Most of my career up until the past few
years has been spent as a Linux systems administrator both as a
contractor and full time with ISPs. I started out using computers as
a child when my dad gave me a hand-me-down VIC20. All my friends had
games consoles, but I was never allowed one. Instead my dad would set
me code tasks to complete which would
earn me my next upgrade. I went through a C64, Amiga and then on to
various low-end Intel-based PCs until I was old enough to get a job
and buy my own.
My dad's friends were completely
eccentric hacker-types and he'd take me to visit when I was young. I grew up around people who had hacked valve
amplifiers with the cases off and custom-built motion sensors on the
doors to their bedsits wired up to a very loud car alarm sitting on
top of a bookshelf. There was a semi-constructed motorbike
indoors on the first floor of a residential building in Willesden, with lots of computers humming away running
Linux all to the sound of Pink Floyd. Hearing my dad and his friends
talk about computers and electronics was like listening to some sort
of alien language. I didn't understand a word of it, but I wanted to.
At secondary school I met
one guy in particular who I'm still good friends with to this day –
Robert. We'd copy our QBASIC games on to floppies to swap at
school, learning from each other. During this time we also discovered
the editors that came with Duke3D and would share levels we had
made. Because of Robert, I got heavily in to adventure games and
survival horror. Since I didn't have a games console, I had no
access to all the games played by cool kids. I was able to run an
emulator though. Robert let me borrow his Resident Evil
2 Claire disc and I got hooked. It had all the stuff that we liked in
adventure games, obscure puzzles, and it also had this really
interesting mix of action and survival. Robert didn't have a memory card, so he would always play these games through on one run without saving. This got us speculating about horror games where you weren't armed and had to survive using just your wits. Of course since we were young, this was just a dream. We'd have to wait for some smarter people to make that game for us.
After school I ended up going straight into work. I started out in PC repair and went through various support roles before spending about 7 years or so as a Linux Systems Administrator. However, that all came to an end when jobs ended up being cut. The timing was pretty bad: we were expecting a baby, and my fiancée and I were some distance apart - she was finishing up her work contract. I did the sensible thing, moved closer to her, and decided to start down a new path.
I started out writing a shmup using XNA, teaching myself C#. Then I got a job as a game tester at Codemasters. There I met a 3D Artist called Ben, who sold me his PC so I could play more up-to-date games. He wouldn't shut up about one game in
particular - Amnesia.
I downloaded the demo and
that was pretty much it. This was the game I wanted when I was back
in school. It was moody and grim like other first person games I'd
grown up with and it made me use my brain. I bought it and after
gritting my teeth and getting through it, then went back and took a trip through the
halls of Penumbra. I realised that I cared more about story in games than I'd believed. I wasn't averse to a good story in
games, it's just that it really mattered how well it was presented and whether there was synergy between plot and gameplay. It was a bit of an eye-opener.
After leaving Codemasters, Ben and I decided to have a go at writing games like this. We survived for a while on contract work while trying our own thing, but none of our bigger projects came to fruition. I could handle the mechanics, but it all felt very
shallow without solid design. I kept restarting projects trying to get the formula right; but eventually Ben had to
move away, and I had to find full time work. So although it never went anywhere, I've
still got bits and pieces of prototypes lying around
spanning Japanese folk horror, Asimov sci-fi and dark
cyberpunk.
|
Some remnants that I found kicking around.. |
Then I stumbled across a job post for Frictional
Games; the ideal job, but it took me a while to summon up enough courage to apply. I got stressed out a lot during the long
application process and on multiple occasions as the process went on
I gave up and told myself I hadn't got the
job, retiring to the
bathtub for a sulk. But each time I'd get out of the bath to an
email telling me I'd gotten further in the application process. I
guess that was to be expected when applying to an indie studio
renowned for crafting suspense?
As I got through to the last part of
the application process, things took a turn for the worse. My
grandmother who I was very close to passed away. I also had a
wedding very soon, so things were all over the place. I had to ask to
push the interview back which was yet another worry on top as it's
rather unusual to request such a thing during an application process. However as you may guess, the story has
a happy ending. I woke up on August 9th ready to get married, and checked my email – I had a job offer. So that turned out to be a pretty good day.
What do I do?
Most of my work is at an
extremely high level using the in-house editors created by Luis and AngelScript, the scripting language we've embedded. Just like Ian and Patrik, I'm responsible
for setting up the events that you're going to encounter in the game
and ensuring that everything in the environment is hooked up
correctly. At the moment I don't do any C++ stuff, but since I'm
surrounded by a lot of smart and experienced programmers, there's a
lot of opportunity to learn.
Since other bloggers have
already talked about the process of creating the
levels and the events that make up our levels, I thought it would be fun to go a little deeper in to
what it's like to actually work on an event and some of the cool
features in HPL3 that facilitate this high level approach to creating
the game. I sometimes play with the older Amnesia toolset in my spare time, so it's quite easy to spot where
things have improved.
So let's jump right in to some of the
really big differences. One of the most used tools in the scripter's
arsenal is the Area. In Amnesia, you needed to place
down an area with the type set to “Script” and then in order to
hook up a simple player/area collision you'd end up with a section of
your script file that looked a lot like this:
void OnStart()
{
AddEntityCollideCallback("Player", "AreaOne", "CollideAreaOne", true, 1);
AddEntityCollideCallback("Player", "AreaTwo", "CollideAreaTwo", true, 1);
}
Now with HPL3 while you can still add
and remove collision callbacks and connections at runtime the process
is certainly a lot tidier and easier to maintain with regards to
setting these things up at the start.
Your old callbacks such as
PlayerLookAtCallback and PlayerInteractCallback can still be found
under the Basic Callbacks tab but, as you can see, a lot more has been
added in HPL3. To set up a basic collision trigger to use in script, we simply have to specify the entities that we want to take into account
for the collision and the function name that we want to have called in
script. Keep in mind if you have a number of entities that you want to collide with this area, we support wildcards. So if you have a bunch of red cubes you want the player to throw in to a basket, the entities field would have cube_red* and then this works for all matching entities. Lastly, thanks to the handy “Copy” button, we can automatically copy and paste a skeleton method into our script:
bool CollideAreaOne(const tString &in asParent, const tString &in asChild, int alState)
{
return true;
}
The boolean return type lets us control
whether or not the callback will be removed once it has executed, just
like the old callback system. The alState parameter tells us whether the player has entered or left the area - there's no need for
multiple functions. Say we want all the lights to turn off when the
player enters a bathroom and for the player to remark on just how
creepy it was when they exit the area and then disable the callback –
we can now do all this inside a single function using a combination
of the return value and the state parameter.
bool CollideAreaOne(const tString &in asParent, const tString &in asChild, int alState)
{
/////////////////////////////
// Player enters the bathroom
// (When alState==1, we've entered the trigger area.)
if (alState == 1)
{
Lamp_SetLit(“Bathroom_Lamps*”,false,true);
return true; // Allow the callback to run again
}
/////////////////////////////
// Player exits the bathroom and remove callback
// (When alState==-1, we've left the trigger area.)
else // alState will be -1
{
Voice_Play(“CreepyBathroom”);
return false; // Remove the callback
}
}
Obviously while this is one of the most
basic events you can script it should hopefully demonstrate that with
this new approach it's a lot easier to be able to write your scripts
in the order you mostly expect events to happen without having to
move around in the file too much. This, along with many other
improvements that follow the KISS principle, makes HPL3 a lot easier
to work with than its predecessors.
Another thing that's particularly
useful and considerably easier to implement compared to HPL2 is the timed
sequence. Perhaps you remember trying to write an intro sequence and
ending up with something that looked like this? Certainly if you've
ever looked at some of the existing maps you'll recognise this approach
and have probably based some of your own scripts around it:
void TimerIntro(string &in asTimer)
{
string sEvent = asTimer;
AddLocalVarInt(sEvent, 1);
bool bPauseAtStep = false;
float fEventSpeed = 1.0f;
switch(GetLocalVarInt(sEvent))
{
case 0:
PlayGuiSound("scare_baby_cry.snt", 0.3f);
fEventSpeed = 4.0f;
break;
case 1:
FadeIn(5.0f)
FadeImageTrailTo(1.25f,0.1f);
break;
// And many more steps to follow!
default:
bPauseAtStep = true;
break;
}
if(!bPauseAtStep)
AddTimer(sEvent, fEventSpeed, sEvent);
}
Although this worked, this was a
slightly cumbersome way to work with these timed sequences at the
higher level. Now, we have helpers specifically for doing this. How
they actually work is a little different. We have a
separate helper class which holds the current step as an int and then checks to see if the current step should
be run or not. The great thing is that all the inner workings of the
sequence helpers are exposed through script so it is always possible
to add more functionality and reuse this across all maps.
Here's how a sequence now looks:
cSequenceStatesData mSequenceBabyCry;
void SequenceBabyCry(const tString &in asTimer)
{
Sequence_Begin(“SequenceBabyCry”, mSequenceBabyCry);
if (Sequence_DoStepAndWait(4.0f))
{
Sound_PlayGui(“scares/scare_baby_cry”, 0.3f);
}
else if (Sequence_DoStepAndWait(3.0f))
{
Effect_Fade_In(5.0f);
Effect_ImageTrail_Start(1.25f,0.1f,10,10);
}
// More steps!
Sequence_End();
}
This is much easier to work with and
lets you move things around with ease without having to change as
many things if you want to add something later.
Another feature which I'll
praise at least once a month is definitely worth mentioning here. This is where HPL3 really shines for me compared to some other toolsets.
Often it's been the case where I'd have to
stop a game in order to make a change, recompile and then get back to
the point that I was at in order to test. This could get old pretty
fast, especially when you were after a very specific feel that
required some fine tuning.
Almost everything you will ever need is
exposed through scripts with HPL3 and if it isn't, you can easily
write it in script. Most of these with a few exceptions can be reloaded at runtime by simply pressing F5
to reload the level. However, HPL3 will
also reload your level script on task switch if you want it to. So
you can alt tab over to your scripting file to make a few changes,
save the file and when you alt tab back to the game you can just test
your event again. Add a debug key that resets all the conditions and
you'll find yourself able to tweak values to your hearts content with
minimal downtime. However if that still isn't fast enough for you, the game can be left running and can automatically reload changes in your script file - which is particularly efficient if you're running a dual monitor setup.
There's such an extensive list
of new features that HPL3 has it would be impossible to list them all
here, but in time I suspect all will be revealed.. In the meantime
however, if this has gotten you interested in some of the scripting possibilities - you can check out a little more on HPL3 scripting here where Thomas Grip explains some of the other things you can do:
I think I've rambled long enough, so hopefully now you know a little more about me and what I do with the rest of the team. Cheers!