In this case, ‘raycasting’ refers to an old-school rendering algorithm used in early ‘pseudo-3D’ games such as Wolfenstein 3D. This tutorial provides a good overview of how it’s normally implemented in its simplest form: for each column of pixels on the screen. The world is represented as a 2D grid of integers in which the number 0 is empty space and anything else is a wall. Each ray iterates over the grid until it intersects a wall, whereupon you use the distance from the camera to the wall to draw a vertical line of pixels on the screen. Simple! Then you can add more on top, like texturing the walls and floor, ‘sprites’ which aren’t on the grid and always face the camera, etc.
(Reminder that I tend to add mouse-over text to my images.)
What if you want the freedom to have walls that aren’t grid-aligned, though? It’s easy enough to write functions which test for intersection between line segments (rays) and arbitrary polygons and circles, but when the number of objects in your world grows large, unless you want to raycast against every object in your scene hundreds of times each frame, you’re going to want to sort those objects into some kind of spatial partition. This will take a fair bit of your time, energy and sanity.
The good news is that Box2D already sorts every fixture (shape) in its world into a spatial partition. This is great because it saves us the effort of doing so, and gives us access to all the other cool stuff Box2D does. The b2World::RayCast function is fast and easy to use - so why not try using it to render the Box2D world from a first-person perspective?
Download the demo program (Windows only) from here. Controls are in the readme that should be included in the .zip or on the readme on the front page of the GitHub repo where you can view all the source code.
Next I’m going to add sprites. I think I’ve figured out how to do it in my head and just need to put my thoughts into code. The nice thing is that I should be able to render sprites in the same pass as I render the walls – in my old grid-based raycaster the walls are rendered and the sprites are handled afterwards. On the other hand, to handle partially-transparent sprites (and walls) I’ll have to continue rays beyond their first intersection-point, which complicates things somewhat.
Texturing the walls is going to take more time to solve. On a grid it’s easy to figure out how far along the wall the ray intersection-point is, here not so much. I’m going to be doing a lot of head-scratching, and I fear the solution I come up with in the end will be sub-optimal. Won’t know until I try, though!
The images below demonstrate the difference made by calculating the ray direction and intersection-point distance in different ways. If I were going to explain this here I’d want to use some diagrams. Maybe later, huh?
Further reading:
The 108th issue of Computer Gaming World from July 1993, featuring this quote about the 7th Game Developer’s Conference, is pretty cool to read:
Ever since we moved in together you’ve been passive-aggressively eating my groceries, leaving dishes in the sink, not taking the rubbish out, and more. You know I can’t stand mess! You must be doing it on purpose.
SUPERMAN:
I’m too busy to do chores! I have to be out there, all the time, using my superpowers to save people. If you had superpowers, maybe you’d understand!
BATMAN:
This time you’ve crossed the line! How dare you. Maybe if you didn’t have superpowers you’d have a little bit of perspective. I had to become a superhero. You just had it all handed to you on a plate!
SUPERMAN:
Oh, unlike your family’s seemingly-infinite wealth?
BATMAN:
My parents died!
SUPERMAN:
My parents died, too!
BATMAN:
…I guess we’re not so different after all.
SUPERMAN:
Batman. Let’s make up and put this behind us.
BATMAN:
Agreed. Best of friends, forever.
THE JOKER:
Hello boys!
BATMAN:
Joker! What happened to your face?
JOKER:
Do you like it? Hmm? I thought you would, Batsy. But what about you, “Super Man”? Do I look… a little familiar?
SUPERMAN:
Lex Luthor! You look like Lex Luther! How is this possible?
JOKER:
Instead of leaving you to ponder this mystery I’m going to immediately give it all away! I injected Lexy’s DNA into my face and BLAM! New face, new me, newly-found villainous outlook on life, and a wonderfully eeevil scheme to take over the world and destroy you both!
SUPERMAN:
Look out, Batman, he’s got a weapon that exploits your only weakness that the writer doesn’t read enough comics to know about!
BATMAN:
…Kryptonite?
JOKER:
Time to die, boys!
SUPERMAN:
Laser eyes attack! PEW!
JOKER:
Oh ho ho, you think that will work on me now, Supes? Don’t you remember that Lex Luthor’s DNA is immune to lasers?
SUPERMAN:
Quick, Batman, use your large bank account!
BATMAN:
The Joker can’t be bribed. He just burns all the money I send him.
SUPERMAN:
There’s no way to stop him, then!
BOTH:
Oh no!
JOKER:
Ha ha ha!
BATMAN:
Superman…
SUPERMAN:
Batman…
BOTH:
I love you.
WONDER WOMAN:
Hello, I am here now as you saw in the trailers. Power pose!
JOKER:
What?! A female? Is this some kind of SJW plot?
WONDER WOMAN:
I use my special move, ‘Pop-Feminism’!
JOKER:
I’m melting for some reason!
WONDER WOMAN:
Goodbye, Joker. I guess you could say that this time it is you who has been joked.
JOKER:
Argh!
BATMAN & SUPERMAN:
Thanks, Wonder Woman! We thought that by combining our powers of Super and Bats we could take on any challenge, but in the end it turned out we were missing the most important power of all: Wonder!
WONDER WOMAN:
And how!
Wonder Woman turns to the camera with her mouth open. Hundreds of DC-universe characters pour out.
Beneath the Great Oak of Chorrol I approach member of the town watch. Apart from his Chorrol uniform, he is an exact copy of every other guardsman in the game. The gruff stare. The podgy face. The stubble. He tells me he is on duty but doesn’t stop me from launching into conversation - I suppose he’s aware that while I’m talking to him, everything else in the world, including lawbreakers, is frozen.
The conversation goes like this: I boast about some feat I’ve accomplished, then I admire his few positive features, then I crack a stinking joke, then I wrap up by attempting to coerce him into liking me more. Then I do it all again. I am speaking at such breakneck speed that he is barely able to respond before the next phrase is blasted from my mouth. From moment to moment his disposition towards me varies wildly, as I alternately offend and impress him.
At the back of my mind I wonder whether this counts as NPC torture. At the front of my mind I continue clicking away at the conversation wheel. You see, this is how I get better at talking to people.
This weekend I took part in Global Game Jam for the first time.
Global Game Jam is an annual happening where people gather at specific locations all over the world and make games. It’s not like Ludum Dare where you can work from home - instead the emphasis is very much on being in a shared space, meeting and working with new people. Abertay University was host to a throng of jammers, a crowd consisting of students, teachers, local industry and many other sorts of people.
This news is about a month old by now, but: that’s right, I’m ‘done’ with Flappy Word.
You can play a web build here or on the itch.io page, where you can also download versions for Windows, Mac or Linux1.
Things I learned
1) I think Unity’s WebGL build target isn’t quite there yet. They’ve dropped the ‘preview’ label but all this really means is that it’s now at a point where the Unity folks are willing to cover support tickets about it – if you’re a Premium or Enterprise user. It’s still kind of laggy and outputs enormous files which the user has to download. I don’t want to be too down on it but at least for the immediate future if I want to target HTML5/WebGL I’ll probably just do it the ‘hard’ way and write some JavaScript2. I’m sure it’ll be good eventually.
2) Implementing the word-typing (you know, the main mechanic) taught me a fair bit.
a) Use System.Enum.Parse to convert between letters (chars) and key codes (enums) so you know which key the player should type next. I don’t understand what magic this function is performing but it’s very useful.
b) I used Resources for the dictionary file. I’ve no idea if this is the optimal way to handle big text-based data assets in Unity, but it was simple and I’ve found no reason to switch to any other method.
c) This big huge string is parsed and broken up into an array of strings, which is then sorted for length. Sorting is nice and simple thanks to an anonymous delegate:
Delegates are kind of analogous to modern C++ lambdas (wee local function objects), with differences. They can’t capture variables from their scope, but other methods can be ‘assigned’ to them (I don’t think I understand what that actually means yet).
3) Screen shake made the game feel better, but it introduced audio problems. I was using AudioSource.PlayClipAtPoint, which had worked fine up until then as a quick throwaway audio clip solution, but now that the camera (and therefore listener) was jiggling about the 3D spatialisation was noticeable, and horrible. Detaching the Listener from the camera worked, but it still bothered me that I didn’t have a method for just playing non-spatialised temporary audio clips.
So I had to write my own function which does EXACTLY what PlayClipAtPoint does but without the position and zeroing out the spatialBlend variable. Because the Unity scripting API doesn’t have one for some reason.
4) Unity’s UI stuff is cool. My UIController script sure as hell doesn’t interface with it as gracefully as I’d like. That’s something to improve upon next time.
5) Avoid having one big monolithic script that controls everything by factoring parts of it out into separate scripts as early as possible, even if you end up attaching them all to one ‘controller’ GameObject. It’s just better that way. The mono-Monobehaviour I created was horrifying to work with until I refactored most of it out into new scripts.
Anyway, if I keep going I’ll be writing for ages. This was a surprisingly educational project.
Repository
I’ve decided to make the Bitbucket repository public. By NO means should any of the code within be imitated. It is all bad. Horrible and bad, but if you end up taking a look hopefully you can learn how not to do things.