Loading Jam Day 1 thumbnail

Loading Jam Day 1

Published 2015-12-16

It was Friday, and thankfully I didn't have anything important to do that day. So, eager with anticipation, I dove head-first into "hunt.js". It wasn't necessarily bad, but it needed quite the refactor. It wasn't generic enough for my tastes. (That's another recurring theme with me. Things have to be generic and reusable. If they aren't, they can't adapt, and that's bad. I take this a little too seriously.)

Before I started, I decided to download a timelapse program, so I could go ahead and make a Youtube video and slap it on there later. (I never did end up making a YouTube video.) I also decided to keep the code in a git repository, just in case. I knew I'd be doing something like writing this later, so I wanted to make sure I documented as much as I feasibly could. That was probably one of the better decisions I made, as it really helped when making this set of blog posts.

At 11:52 (mostly because I really wanted to sleep in), I dove right in and fixed up that demo's code. And by that, of course, I mean in a vain attempt at improving optimization I switched from drawing on a single canvas to drawing on two canvases stacked on top of each other. That took quite a bit longer than I planned, because I was trying to do really fancy things. I was trying to make it so only objects that were updated that frame got redrawn. Of course, then you get into things like overlapping and canvas transparency and layering and the like.

Render failure, with no bg and the sprites looking like the cards from MS Solitaire

Eventually, after an hour (most of that being me not realizing I put the background on top of the foreground), I got the whole multicanvas thing working. Well, sorta. I threw out the concept of "only redraw what you have to", with the assumption I'd get to that later. Spoiler alert: I never did.


However, while I was working on that (well, much earlier, actually, but I'm shoving time around to tell a story here), I noticed something a bit weird. The game would run fine for a bit, freeze, and I'd jump 50 feet about 3 seconds later. In fact, that applied to every various canvas demo and tutorial I've tried too. Eventually I nailed down the culprit: my web browser. You see, like 99% of the time I use Firefox, almost entirely because of Tree Style Tabs. For whatever reason, I guess Firefox didn't like my graphics card, a 5 year old nVidia Quadro 2800m, capable of DirectX 10 and nothing more, but with a stupidly good framerate. So I was forced to switch browsers.

Pointless tangent time, as if everything else here wasn't. I like MS Edge. It's like what Google Chrome was, before Chrome OS and Chrome Apps and that little button with your name next to the minimize button that I thought was a virus the first time I saw it. It browses the web. If I just want to do that, something like Edge is all I need. Obviously I keep Firefox around because, one, Tree Style Tabs, two, I've been using it for like a decade, and three, addons are useful. Edge is a web browser. 99% of people don't need any more, myself included.

Unfortunately in that other 1% was web developers. Edge's dev tools aren't that great, something I learned rather quickly. So I switched to the last browser I had on my computer: Vivaldi. It's a weird experimental Chrome fork by the guys who made Opera 12. It's nice, and compared to my past experiences with it, stable enough. Only problem is that it eats RAM and CPU like a pig, which sucks if you're on a wimpy computer. (Tangential tangent, I find it concerning that web browsers these days think it's okay to use half your system's resources. It's text and images. It can't be complicated enough to require 2 gigs of RAM & 25% CPU.) But hey, it worked, so I used that.


Anyways, the next thing I had to add were multiple rooms. That was a rather essential part of the game's concept, so it was critical that I didn't just have a big blob of objects. The neat thing about JavaScript is that you can shove anything into a set of curly brackets (an object) and it will work, so I abused that feature to no end. I had a big array named "rooms". In that array was several objects, one for each room. In the room object, there was another array, one for blocks in the room. In that array was the blocks' object. Within that object was (finally) that object's x-y coordinates and stuff. To get to that object, you'd need to type in something like rooms[0].objects[3].x, which was a bit long but also stupidly flexible. For reference, before that the objects were in an array named "blocks", which didn't help with the whole multiple rooms situation. The final room object contained something like this:

var rooms = [
    {
        x: 0, y: 0         // topmost corner
        w: 640, h: 400,    // room size
        background: bgHallMain, // Image displayed in bg layer
        name: "Room 1",
        desc: "...",       // Text displayed in loading screen text adventure
        lsVisited: false,  // Visited in the loading screen before?
        exits: [
            {x: 640, y: 0,
             w: 1, h: 400, // Same collision stuff as objects
             to: 1,        // Room number to link to
             name: "right" // Name of room for text adventure
            },
            ...
        ],
        objects: [
            {
                name: "block", // Name for TA
                adjectives: ["first"], // For TA, to tell objects apart
                desc: "...", // Text for LOOK command
                x:173, y:321, w:32, h:32, // Coords of block for collision
                onCollide: solidCollision, // Function for when two objects touch
                onInit: xxxXXXX // Function for when room first loads
                lsVisible: true, // Can be "LOOK"ed from the text adventure?
                image: blockImage, // Image to display at the given x/y
                animSpeed, 20, maxFrames: 2 // Animation
            },
            ... 
        ]
    },
    ...
]

Complicated? Undeniably. Useful? Undeniably. Did I need to post that? Probably not.


Next thing on the list was changing the movement code. Originally it worked on a system of "you press left, you go 10 pixels left". Seeing as I was converting this into a platformer, that wasn't going to fly, like, at all. I needed physics. Simple physics, at least. Throwing in Box2D or another actual physics engine was undeniably over-the-top. What I needed was a system where if I pressed left, my "left speed" would increase, and when I let go of left, my "left speed" would decrease until I stopped.

Something like this, from my vague fuzzy memories of Multimedia Fusion tutorials, was stupidly simple to accomplish. It really boiled down to "Every frame, when you push left, decrease the horizontal speed by X unless it is greater than maxX. When you push right, increase the horizontal speed by X unless it is less than -maxX. When you're pushing nothing, divide the horizontal speed by Y until you hit 0. To change the position, every frame add the horizontal speed to the current horizontal position." So, that's what I did. (I know I'm overusing that phrase, but this is still the boring "I know exactly what I'm doing" stage. It gets vaguely more interesting later, I swear.)

However, that cropped up a problem. If I touched an object, I couldn't move, and there was no practical way to stop that without walking through walls. So, I came up with a relatively clever idea. Instead of moving the player's ACTUAL position, I'd move the player's FUTURE position, check for collisions, and roll back to the old position if there was a block in the way of the future position. This system actually solved the problem incredibly well, with almost no RAM or CPU overhead, which was nice. (Especially because I still think that actually matters today. I often forget that this is the future, and everyone has 4+ gigabytes of ram and several CPU cores here.) Have an illustration, because I'm dying for one too.

Showing collision with and without the Future system.


After THAT was building upon that system and adding gravity, which was a simple "every frame increase the vertical speed by X until you hit the speed limit." The problem from THAT came when I jumped on the side of the block. You see, I had it set where if I touched a block, I stopped falling. Period. Slight problem with walls and stuff. So I had to figure out a way to say "if I hit the block form the top, stop falling."

To figure out what side it was going to be on, I made two new collision rectangles, one with present X and future Y, and one with future X and present Y. By testing which of these rectangles were hit, I could see which side I was hitting it from.

Fancy directional collision stuff.

Of course, that only got me "horizontal" or "vertical". To get direction, I just took the direction of the horizontal/vertical speed and assumed that if you were going left, you were hitting the object from the right. Simple enough.


The last thing I did was change the collision so instead of every object stopping the player, they could do whatever. One of JavaScript's many weird conventions is the religious use of callbacks. You doing something? Call this function when you're done. When it comes to JavaScript trying to be asynchronous, this is my worst nightmare. (Seriously, try to use the File Picker API to get two files. It's a mess because you have to somehow wait until TWO things are done. I can't wait until ES7 when they add a "yield" keyword.) In this situation, where I didn't actually need a result, this was perfectly fine. I shoved a reference to some subroutine in the object (the onCollide from the object listing above), and every frame I went through each object and called that if it existed. This meant that the ice cream can act differently from the blocks, which was nice. I ended at 22:20 (10:20 PM), which in hindsight feels like a stupidly long time but to be fair it wasn't continuous.


I know, that was long and boring and rambly and bleh. But you know, that's game dev for you. Boring, long, and bleh. But hey, the results are worth it, usually. The next day I worked on the text adventure, so wait for the post on that. (It'll be much shorter, I swear.)