Files for Part 2.
I introduce SpriteList, collideOneWithMany, and begin to isolate functionality as properties of different objects.
I introduce SpriteList, collideOneWithMany, and begin to isolate functionality as properties of different objects.
I meant to get this video of Canvasser up yesterday, but had trouble with my setup and then ran out of time. So, it got pushed to today and, with my summer semester now in session, I had to wait till this afternoon to write the post. Also, as I’m trying to do more often, I turned the video into a GIF too.
Here are the files from the first lesson.
(Note: I highly recommend watching the video in at least 480p, if not 720p. Many of the words are hard to make out otherwise.)
As was promised last month, here is the first video on JawsJS. I cover creating Sprites, basic one-with-one collision, and the three main properties of a game state object within JawsJS: the setup, update, and draw functions.
“[T]he game industry is content to keep throwing the immersive fallacy around. It’s the idea that you’ll buy into a game world so completely that it will immerse you like millions of gallons of water and you will forget you are playing a video game.”
I’m glad that I came across Robert Yang’s “On focalization, and against convenient understandings of immersion / flow“ today as it gave me some hope that I might not be alone in the thought that immersion might actually be bad and that maybe we should give it up. That in trying to engulf the player into their world, the game (as a system) might be overwriting the player herself. That the loss of self, currently thought of as a good reaction to the game, might actually be the loss of voice.
It’s an idea that has haunted me for about a week now. As part of doing research for an essay on object-oriented feminism (yes, it exists) for a class, I stumbled onto a collection of thoughts by Levi R. Bryant titled “Feminist Metaphysics as Object-Oriented Ontology– OOO/OOP Round-Up“. Buried deep within the post was a sentence that has utterly changed how I think about immersion and how Csíkszentmihalyian flow, while it might be useful for describing religious experiences or even high-level play, might not actually be an ideal state for players.
“The forgetting of the real is always a masculine gesture.”
It’s rather simple, right? To forget the real is to give up the ‘body’. It’s giving up everything that feminism has taught us: that the ‘body’ has a materialism performed into reality. That ‘body’ is conceived through discourse and within a cultural position. Every ‘body’ is unique because its materialism as experienced cannot be replicated.
Yet, here is immersion, at least through this lens, standing opposite to that. It is positioning the system as superior and of overwriting the ‘body’ of the player. They have no voice, no agency. They are yet another gear in the tyranny of the system. It’s perhaps the extreme of the overdetermined-ness I have dreaded in considering these interactions.
This, of course, brings me back to Yang and the intriguing usage of “focalization” as perhaps the return of inter-subjectivity and interpretation. It’s thinking about the focusing action, of what might be shown in connection to what was previous shown. (And maybe even narrativizing the juxtaposition of the two as well.) Yang has two great examples of this. First, in connection to Iron Man:
“But what about when the camera lingers on Gwyneth Paltrow for a bit, then cuts to a shot of Robert Downey Jr. staring at her? In that instance, we’re staring at Ms. Paltrow with Robert Downey Jr’s character, who is quite taken with her for some reason.”
And then from Thirty Flights of Loving:
“So you focalize Anita and remember her various quirks — you’re always staring at her, looking at her as she peels oranges or drinks hooch or stumbles drunkenly or angrily points a gun at your face. I think all players, even if confused by most of the game, understand that their character loves and cares about Anita — because you’re always focalizing her to make her seem compelling, and focalizing through this player character who loves her, often in sympathetic ways.”
It’s a suture moment. From Iron Man, it’s the shot-reverse-shot moment that folds the viewer into the semiotic order of its world. It’s a common technique in film that will have a shot of an object of desire and then the object looking at it. The viewer is passed between their points of views in order to link the two within the created narrative of the one watching.
Even though Thirty Flights of Loving, with its fantastic jump-cuts and time compression, pushes this to the forefront of understanding its world, it’s not uncommon for most games to enact frequently invisible suture moments on the player without them realizing the purpose of such an action too. The introduction cutscenes in most videogames act in this way, for example.
The character is shown doing what they do and acting in a way coherent to the story being presented. Their motives are established. And then the very next shot is from the player’s perspective with instructions about how to act in the same way. The player is sutured into the system through being shown who they should be and then given instructions about how to be that way. The player, as a ‘body’, is neglected in this way. It is forced to conform to this new shape.
It’s what makes giving up immersion as a useful term something I am interested in exploring more. I am also increasingly convinced, given this outlook on what immersion should do, that it might be why game criticism might be so lacking at times. With game reviews overemphasizing the system over the player, the ‘body’ is being ignored. It’s often not what was felt or interpreted, but how ‘lost’ the person was in the simulation or engrossed in the system. It’s so immersive! I hardly remembered I was living at all!
I am especially in agreement with Yang then on “The game industry argues that games “immerse”, but immersion theory confuses realism for presence, and production value for realism”. That somehow, if they exponentially increasing spending on a game for more ‘realistic’ graphics and audio mechanics, this will mean better games. As if “better” means faster immersion and a quicker lose of self to its system.
If that is the case, then feminism is desperately needed. We must remember our ‘body’. The player must always examine how they think about the game — and what the game might trying to make them think. Reflection on the game as experienced is vitally important. If immersion is the ‘drowning of the body,’ then players need to surface from time to time. They need to remember who they are and that stopping a game is not the end of an experience, but the beginning of a greater conversation between ourselves and the machines we play with.
(If you have the access, I also recommend checking out “‘Girls Welcome!!!’ Speculative Realism, Object Oriented Ontology and Queer Theory“. O’Rourke does a good job of surveying many of these ideas and the sources especially are a rich vein I have been spending some time looking through recently.)
Okay, I admit it: I thought it would be scarier. From all of what I had heard and read, I thought I would be really scared. I mean, really scared. The game had been built up to me as one of the best horror games many people had ever played. And at least in the first 35 minutes, it didn’t seem that way to me.
Now, the Penumbra series freaks me out. I’ve played about an hour of each and I don’t like those games. Even with slightly aged graphics, they still get to me with little things like simple scares and creepy noises. I have jumped back in my chair more than once playing those games, which is why I stopped playing them each time. When it stops being fun, I often stop playing.
Here, I don’t know, I was laughing at times while playing. Pulling books from the shelves and then dropping them on the floor was a funny thing to me. It was, in the middle of this deary Poe-esque tale, something not unlike a toddler would do. Try to tell me where to go, huh? Why, I’ll just throw all the books on the floor then! How do you like that!?
Even the narration, which I liked, brought me out of the game though. I’m already named Daniel. Having someone address me and himself as Daniel made me respond back in my usually absurd manner. And many times, I was talking back to the game — which is also why I decided not to attempt any more commentaries. I often devolved into being silly and for a game this serious, that’s not very helpful.
Still, I think I may invest more time into it to see if I want to finish it or not. I’ve heard that there are later parts which are frustrating, so I’m not looking forward to those, but I thought it was interesting if not unexpectedly funny so far. I admit to being curious about how the sanity meter works later on and what more the game might throw at me.
Update (7 hours later):
Okay. I figured it out. I need to use a canvas object.
TL;DR version: I will start producing the JawsJS video series tomorrow, 16 May.
The new code looks like this:
jaws.Sprite.prototype.setColor = function(value) {
this.color = value;
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
canvas.width = this.width;
canvas.height = this.height;
context.fillStyle = this.color;
context.fillRect(0, 0, this.width, this.height);
this.image = canvas;
}
It’s a separate function entirely that also allows for changing colors during runtime. (For example, as a result of object collision — as I’ll talk about in the first video.) It also makes sure that the resulting canvas is of the necessary size too.
Original post:
It all started with a simple problem. Because many browsers enforce a fierce interpretation of the same-origin policy on the canvas element in HTML5, trying to test the creation and loading of images can often become a nightmare. The browser, detecting that an image might be loaded from a “file://” protocol, assumes that the canvas is being polluted from sources coming from both HTTP and “file://” at the same time.
That’s a security risk and thus the browser shuts it down. It tries to prevent that from happening. Errors are thrown and, if you are trying to playtest a game, it gets shut down too.
The solution then is to just test using a web server. All the resources will be sent using HTTP and everything can be moved later from a testing to a production environment. However, that introduces another problem: not everyone can setup a web server. Especially in a Windows environment, it can require installing extra software, perhaps even opening ports in firewalls, and having administrative access. It’s too many steps to learn to program games.
Because that’s what I have been wanting to do for weeks now. I promised last month that I would have a video series out on JawsJS, the same game library I’ve been using to make games on nearly a weekly basis, and I haven’t been able to do that yet. I initially thought it would be pretty easy to get all the tools together and then create a series covering making basic art assets, programming in JavaScript, and then moving to publishing — and minifying the source — out for others to play.
However, I ran into that image loading problem a couple weeks ago. After thinking through it some, I came up with what I thought was a good solution: just use colored squares!
I wouldn’t need to package image assets in any of my lesson files and anyone reading the posts later could download and play with the code without having to worry about the image issue. It seemed to solve all of the issues and was also a way to decrease the slope of the learning curve as I could cover image loading in a later lesson as I went over creating and colliding objects early on before getting into animation and more advance topics.
So, after many frustrating attempts and a little bit of a back-and-forth with Ippa (the maintainer of JawsJS), we finally came to a consensus of how I could include code that would, when passed a string with a color name or hexadecimal value, generate a Sprite in the place of what would normally be a loaded image. Problem solved!
Except… no, it turns out the underlying issue was far worse that I imagined. The difference between an Image and other image-like objects is very nuanced and, of course, implemented ever so slightly different per rendering engine.
Let’s start with what I wanted the declaration to be like using its object literal notation.
var player = new jaws.Sprite({color: "red", x: 20, y: 20, width: 20, height: 20});
Nice and simple. The player object is a Sprite of the color red, at position 20, 20 and is a square of 20 pixels. Easy to understand and very human-readable.
And here is the internal code which is supposed to do exactly what I wrote in the declaration code. It should generate a colored square and assign it to the this.image property of Sprite that will later be drawn.
if(!this.image && this.color && this.width && this.height) {
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
context.fillStyle = this.color;
context.fillRect(0, 0, this.width, this.height);
this.image = context.getImageData(0, 0, this.width, this.height);
}
Here’s the thing: it doesn’t work. Well, it works, but not as I expected to work.
Instead of creating an Image, it assigns this.image what is known as an ImageData object. And Image and ImageData, as you might imagine, are two different things. One is a DOM object and the other is a specially created object that exists solely as the values used in getImageData and putImageData as part of the HTML5 canvas spec. It is produced by the “get” and then drawn by the “put” functions. No other functionality uses it and it’s not a valid type for drawImage (which is used as part of Sprite’s own draw() function).
I had to come up with a different solution then. What if, I thought, I had the canvas generate an Image for me? It has the very useful toDataURL function that will convert the canvas into a base64 encoded string that can be passed to a new Image as its source property.
if(!this.image && this.color && this.width && this.height) {
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
context.fillStyle = this.color;
context.fillRect(0, 0, this.width, this.height);
this.image = canvas.getDataURL("image/png");
}
Using that code solved the problem… sometimes. It turns out I rediscovered a bug that has been around in Firefox since 2010. (That’s what my tweets were about last night.) In fact, using toDataURL introduces a little-documented bug in all current browsers where, at run-time, if the image has been processed by not loaded by the time the requested renderer calls for it, an error will get thrown. Well, an error will get thrown in Firefox (“NS_ERROR_NOT_AVAILABLE”) and IE10 (“Unspecified error”). In Chrome, it just fails completely and doesn’t give any feedback about it.
It’s a very strange race condition between the canvas event queue and browser-specific implementation of requestAnimationFrame that only occurs at the boundary between the JaveScript engine and its underlying language. If toDataURL completes first, everything works as it should. If it doesn’t, well, the whole thing will crash. What’s even worse is that I’m not sure how to catch the error within the code. It doesn’t seem to have a fixed label or even level of error. And, as I wrote, Chrome will just fail without doing anything about it.
I also can’t figure out a way to block to code, not that I would want to anyway, to allow for toDataURL to finish before moving on to the rest. The timer associated with the requestAnimationFrame looping is either running or its isn’t. And it’s already been started by the time the the initial setup code has run. By that point, it’s too late. The race condition has already played out and the results will only show up at the first usage of any drawing functionality.
It’s the problem that has consumed all my time for the last several nights. I spent a small part of Sunday night setting up the files and writing out what I wanted to talk about in the first video I was going to produce. Coming back to it Monday night, I began to test all the code to make sure it did what I was going to say it did and discovered this error and race condition. And here I am late Tuesday night and I am no closer to figuring this out.