L's blog

A breakdown of the CSS in my Twine game Capri Toot

This is the inline CSS I used in my Twine game, Capri Toot. Of course, I had to write some other CSS for my special horizontal hacked variant of Jonah, but this below is where almost all of the visual style of Capri Toot comes from.
----------------
#passages {
background-color: #000;
overflow:hidden;
}

----------------
"overflow: hidden" hides the horizontal scrollbar in the passages DIV, which is useful because backtracking isn't possible in Capri Toot.
----------------

@keyframes borderkeyframe
{
0% {box-shadow: 0 0 2.5em 2.5em }
50% {box-shadow: 0 0 0.5em 0.5em }
100% {box-shadow: 0 0 2.5em 2.5em }
}

@-moz-keyframes borderkeyframe
{
0% {box-shadow: 0 0 2.5em 2.5em }

50% {box-shadow: 0 0 0.5em 0.5em }

100% {box-shadow: 0 0 2.5em 2.5em }

}

@-webkit-keyframes borderkeyframe
{
0% {box-shadow: 0 0 2.5em 2.5em }

50% {box-shadow: 0 0 0.5em 0.5em }

100% {box-shadow: 0 0 2.5em 2.5em }

}

@-o-keyframes borderkeyframe
{
0% {box-shadow: 0 0 2.5em 2.5em }

50% {box-shadow: 0 0 0.5em 0.5em }

100% {box-shadow: 0 0 2.5em 2.5em }

}
----------------
CSS Keyframes: These define the animated pulsing glow behind the passages. There are four definitions because each browser has its own syntax (except IE, which doesn't support this). These define some timeline events which the browser tries to smoothly interpolate between. When the animation is 50% complete, the box-shadow is retracted to a size of 0.5em. When the animation starts and finishes, it's at its full size of 2.5em. Defining both a 0% and 100% point ensures that it loops smoothly, if an element uses it as a looping animation.
----------------

.passage:nth-child(8n+7) {
text-shadow: 0 0 0.6em hsl(0,100%,60%);

color: hsl(0,100%,50%);

background-color: hsl(0,100%,5%);

}

.passage:nth-child(8n) {
text-shadow: 0 0 0.6em hsl(45,100%,60%);

color: hsl(45,100%,50%);

background-color: hsl(45,100%,5%);

}

.passage:nth-child(8n+1) {
text-shadow: 0 0 0.6em hsl(90,100%,60%);

color: hsl(90,100%,50%);

background-color: hsl(90,100%,5%);

}

.passage:nth-child(8n+2) {
text-shadow: 0 0 0.6em hsl(135,100%,60%);

color: hsl(135,100%,50%);

background-color: hsl(135,100%,5%);

}

.passage:nth-child(8n+3) {
text-shadow: 0 0 0.6em hsl(180,100%,60%);

color: hsl(180,100%,50%);

background-color: hsl(180,100%,5%);

}

.passage:nth-child(8n+4) {
text-shadow: 0 0 0.6em hsl(225,100%,60%);

color: hsl(225,100%,50%);

background-color: hsl(225,100%,5%);

}

.passage:nth-child(8n+5) {
text-shadow: 0 0 0.6em hsl(270,100%,60%);

color: hsl(270,100%,50%);

background-color: hsl(270,100%,5%);

}

.passage:nth-child(8n+6) {
text-shadow: 0 0 0.6em hsl(315,100%,60%);

color: hsl(315,100%,50%);

background-color: hsl(315,100%,5%);

}

----------------
These are used to colour each of the zone tiers differently. When you click on a link in Jonah (as compared to Sugarcane) a new passage div is added to the end of the #passages div as a new child node. The nth-child() selector allows you to apply specific styles to an element depending on which child it is. nth-child(8n) means that every eighth child should have this style (starting with the first). nth-child(8n+1) means that every eighth child, plus 1 position over, gets this style. So, with these eight definitions, a looping cycle of eight colours is produced. This is kind of overkill for Capri Toot since you can only ever go 8 passages deep, but w/e.

Also, a note: if you don't specify a colour for box-shadow, then it falls back to using the text colour. Hence, we use a colour style here to change the animated shadow's colour without having to set up eight different keyframes (x 4 browsers).
----------------

.passage {
margin-left: 33%;

margin-right: 33%;

min-width: 33%;

margin-top: 3em;

margin-bottom: 3em;

border-radius: 8em;

border-color: white;

border-width: 2px;

box-shadow: 0 0 2.5em 2.5em;

animation: borderkeyframe 3s infinite;

-moz-animation: borderkeyframe 3s infinite;

-webkit-animation: borderkeyframe 3s infinite;

-o-animation: borderkeyframe 3s infinite;

}
----------------
The margin and min-width definitions mean that only one passage is ever visible at one time. The border-radius definition produces the delightful rounded rectangle edges. The animation definitions bind the aforementioned keyframe to this element, give it a speed (3 seconds) and tell it how many times to loop (infinite). Again, 4 definitions because each browser (except IE) has different syntax.
----------------

.passage .body .internalLink {

font-style: italic;

font-size: 1.5em;

color: white;

text-shadow: 0 0 0.6em silver;

}
.passage .body {

color: white;

text-shadow: inherit;

}
.passage .title {

margin-top: 0.2em;

font-style: italic;

font-size: 2em;

color: white;

text-align: center;

}
----------------
Fairly straightforward. Setting .body's text colour to white overrides the previous colour definition for .passage (which as I mentioned is being used for the box shadow).
----------------

.toolbar {

display:none;

}
----------------
This single-handedly hides the Jonah "rewind / bookmark" prompts for each passage. Quite convenient.

Twine: preloading images

Update: embedded images in Twine 1.4 do not need to be preloaded, so this is not necessary if you are using those.

If you use a lot of images in your Twine game, it would be very good of you if you preloaded them at the start of the game - having to wait for images to load during a story, even momentarily, can be distracting.

Now you could bother to convert them all to inline Base64, but there's other, less intrusive ways. You could, rather, put every image in your story in invisible img tags in the Start passage:

<html>
<img src="  [url of an image ] " style="display:none;" >
...
</html>

...but of course, that requires you to manually list every image yourself. Here is my recommendation: use this JavaScript that will do it automatically, when the story starts. Just put this in a passage tagged with "script".

(function(){var r="";var s=Wikifier.formatters;for(var j=0;j<s.length;j++){if(s[j].name=="image"){r=s[j].lookahead;
break;}}var div=document.getElementById("storeArea").firstChild.nextSibling;while(div){if(r){k(new RegExp(r,"mg"),4);
}var b=String.fromCharCode(92);var u=b+"s*['"+'"]?([^"'+"']+(jpe?g|a?png|gif|bmp))['"+'"]?'+b+"s*";
k(new RegExp("url"+b+"("+u+b+")","mig"),1);k(new RegExp("src"+b+"s*="+u,"mig"),1);
div=div.nextSibling;}function k(c,e){do{var d=c.exec(div.innerHTML);if(d){var i=new Image();
i.src=d[e];}}while(d);}}());

That's all.

Update 17/2/13: This now works with images in HTML <img> tags as well.

Update 12/2/13: This will now also preload images used in CSS url( ... ) values. It will search for such values in every passage, include the stylesheet passages, script passages, and inline JavaScript.

Version history:

  1. 17/2/13 - Now works for image files specified in HTML src="..." attributes.
  2. 13/2/13 - CSS preloading now only loads JPEG, JPG, PNG, APNG, GIF and BMP files (that is to say, not font files).
  3. 12/2/13 - Now preloads images specified by CSS URL values too.
  4. 19/1/13 - Initial.
+b+

Leongame plot inventory

Inspired by this.

These are most all of my browser games on my website.

All Against One: Blow up a man.
Assault on Crater Bulb: Blow up aliens.
Trepidatious Peak: Climb a mountain.
Danger Quad: Avoid falling onto spikes.
Prizeturret: Avoid your own missiles.
Pong/Antipong: Bounce a circle with two lines.
Gravity Gunner: Swing stuff away from you and toward aliens.
Disc Gunship: Bisect aliens.
Stork Ride: Avoid approaching obstacles.
Gridball: Keep a circle onscreen.
In The Well: Climb falling debris to escape a pit.
On An Iron Isle: Climb a tower then blow up a robot.
!!!!!!: Go to the right.
Finderseek: Squish aliens.
Fire Triss: Blow up a woman.
Frozen Planetini: Blow up aliens.
Parachute Skiing: Avoid approaching obstacles, then click on people.
Operation Lodestone: Blow up cities.
Brainpan: Prevent ants from dancing in your skull.
Saucer Sandy's Moon Escape: Blow up aliens to escape a prison.
Super Calamity Annie Galaxy: Blow up hombres.
Vertical Impulse: Escape a tower.
Your Mischievous Mouse: Avoid approaching obstacles.
Magnetball: Keep a circle onscreen.
Wee: Climb a hill then urinate on a man.
Return Alive: Kill monsters or get treasure to escape a dungeon.
ABOVE v. BELOW: Blow up things while protecting the bottom of the screen.
The Terrible, Terrible Turret: Avoid your own missiles.
Spirograph Skies: Avoid your own missiles.
Trusty Assistant: Avoid approaching obstacles.
Terra Tam: The World Warrior: Wrestle foes to the ground.
Bind Her! Bind a woman.
Beneath Her Belly: Push a woman/yourself onto player 2.
Rapidly Advancing Laser: Stop a woman from being lasered.
Release Me!: Stop a woman from being headsucked.
SSStop Me: Collect squares while avoiding snakes.

All 1200 games in the 1200-in-1 pirate NES cart

The 1200-in-1 pirate cart is a NES multicart that contains Contra, Super Mario Bros., Tetris II, Chinese Chess, Battle City, Field Combat, Antarctic Adventure, Circus Charlie, Binary Land, Bomberman, Ice Climber, Galaxian, Ninja Jajamaru-kun, and Road Fighter, with various level-skip hacks or other permutations.

Game jam idea: make games using the titles of these entries.

Twine: storing images inside the HTML file

Update: embedded images in Twine 1.4 already use this technology automatically.

One aspect of Twine that I very much like is that the entire game is included in a single HTML file. It provides a great convenience and advantage for maintaining and archiving the game. Some Twine games include images in the text, and in most all cases there are in the form of hyperlinks to external image files, which, being separate from the game file, are vulnerable to being lost or broken.

There is a way to store image files (among other files) inside HTML using Base64 encoding, which converts binary files into streams of ASCII. Simply use an online Base64 encoder to convert your image into a Base64 data URI, and paste that wherever you would paste a URL to an image. This works not only for the HTML img tag, but also for Twine's img markup (although it probably won't be syntax-highlighted correctly in the passage editor).

Here is an example:
[img[data:image/png;base64,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]]

(If you paste the entire data URI between the braces into your browser URL bar, you can see the image.)

Some advantages:
- Stores all the game's resources in one file.
- Images are preloaded when the game starts.

Some disadvantages:
- Base64 encoding increases the size of the images by 33%.
- Can't easily use the same image file for multiple passages - each passage must have a full copy of the file. (You can work around this with some JavaScript preprocessing, if you really need to.)
- Doesn't work with IE versions < 9... go figure.

Tinkering with Twine page transitions

I'm just tinkering with different CSS transitions between pages in Twine games.

* Expand.
(These below force the passages to the fixed width of 60em in order for the out-transition to be congruent with the in-transition.)
* Cross-fade.
* Zoom.

I'll edit more into here if I make any.

(The game that is being used is The Sky in the Room.)

So you want animation trails in Game Maker

screenshot101.png

I believe I know what you yearn for. You wish to forgo the bounds of the typical behaviour of a game engine, and make your game's moving elements leave conspicuous trails as they animate - that is, to prevent the screen from refreshing after each frame, and repeatedly draw the sprites onto the same canvas. Well, Game Maker, at least from version 6 onward, is not set up to easily permit this kind of nonsense.

Let's consider the drawing sequence of Game Maker 6+ :
* First, the screen's buffer still possesses the screen image from the previous frame.
* If viewports are enabled, the buffer is blanked.
* If the background color is visible, it is drawn over the entire buffer.
* If there is a background image, it is drawn over the buffer.
* All instances and tiles are drawn, in descending order of depth.
* The buffer is refreshed to the screen, or the viewports if views are enabled.

This sequence is unalterable. So, here are some ways of allowing the previous frame to persist:

* Don't have any views, and have a static background

What causes the buffer to be obliterated in each frame is the presence of viewports, and the drawing of backgrounds. So if you eliminate these, the buffer's contents will persist.

On the first frame of the room, the background can be drawn. But from then on, it must be invisible. So, include an object with this code:

Create event:
alarm[0] = 1;

Alarm 0 event:
for(i=0; i<8; i+=1) {
background_visible[0]=0;
}
background_showcolor = 0;

The main advantage of this code is that it works in Lite and Pro versions, and, miraculously, even works in the HTML5 engine of GM Studio. But, of course, losing the use of views constrains you to a single-screen room, unless you feel apt to re-implementing scrolling and such on your own terms. So you might want a more technically strenuous solution.

* Use a surface as an alternative screen buffer

If the screen buffer cannot be preserved, devise a replacement.

You might do this by creating an object with a large negative Depth value with this code:

Create event:
s = surface_create(view_wview, view_hview);
surface_set_target(s);
screen_redraw(); // Draw the room's initial state onto the surface
surface_reset_target();

End Step event:
surface_set_target(s);
with all
if (visible && sprite_exists(sprite_index)) {
// This implies that only view 0 is being used
draw_sprite_ext(sprite_index,image_index,x-view_xview,y-view_yview, image_xscale,image_yscale,image_angle,image_blend,image_alpha);
// Replace that with event_perform(ev_draw,0) if it is necessary
}
surface_reset_target();

Draw event:
draw_surface(s,view_xview,view_yview);

What this does is create a surface, draw the first frame of the room on it, and from then on, forcibly draw all of the room's sprites onto the surface before Game Maker runs the drawing sequence, and then, at the end of the drawing sequence, draw the surface over the entire screen, obscuring the screen with your own surface.

This has a number of slight pitfalls. Firstly, there's really no way of accurately replicating GM's instance drawing without prior knowledge of how the objects are implemented. If an object has a Draw event, then that event is performed instead of drawing the sprite. But if it does not (and there is no way for the code to find this out) then it must be drawn using draw_sprite_ext(). In order to ensure that the instances are drawn ideally, you must either give each object a Draw event (one in which it simply draws its own sprite) or forgo Draw events in all of your objects.

Secondly, since this doesn't prevent every object from being drawn normally during the drawing sequence, the Draw events for the objects are still performed. If you call the Draw event for all instances in the End Step event, then they are thus made to happen twice per frame. If you were foolish enough to put game logic inside a Draw event (and you would be very foolish to do so) then you must sort that out yourself. (You could certainly prevent the objects from being drawn normally by forcing visible=0 during the loop in the End Step event, but only if you're comfortable obliterating any values for visible that were already there.)

This has an interesting effect when combined with views. The surface moves with the view, so it often provides the illusion that no motion is occurring. If the view is tracking the player's sprite as it moves to the right, the surface moves to the right at the same speed, so the player's sprite seems to leave no trail at all. This is a counterintuitive but nonetheless logical conclusion, and may well be desirable if you are interested in making your game as "authentically glitchy" as possible.

Now, you might instead want an alternative: a surface which is stationary relative to the view, and covers the span of the entire room, so that the sprites always paint a trail regardless of the view's movements. The biggest problem with this is that, if your room is, say, 12000x480, then you quite probably won't be able to make a surface large enough without running out of video RAM. Nevertheless, if you feel your game is capable of doing this within reason, then feel free to modify that example code to suit this solution.

Attached is an example program that demonstrates these methods, in the objects o_trails_basic and o_trails_surface. The included room only demonstrates the latter object, and contains a view which follows the red sprite's movements.

Source code release

I've uploaded the Game Maker 7 source code for several of my trainwrecks.

http://www.glorioustrainwrecks.com/node/1830 - The Wilframs' Last Stand
http://www.glorioustrainwrecks.com/node/2146 - Rotation Invaders
http://www.glorioustrainwrecks.com/node/1818 - Solar Rain
http://www.glorioustrainwrecks.com/node/1743 - Nipples Vs Clothespegs
http://www.glorioustrainwrecks.com/node/1661 - Mario Power Paintbrush
http://www.glorioustrainwrecks.com/node/1147 - Janesaw
http://www.glorioustrainwrecks.com/node/1205 - The Great Star Fusion
http://www.glorioustrainwrecks.com/node/2263 - Bind Her!
http://www.glorioustrainwrecks.com/node/1489 - Invader Sammy in "One Sam Army"
http://www.glorioustrainwrecks.com/node/1327 - Catatoniac
http://www.glorioustrainwrecks.com/node/1842 - Trepidatious Peak
http://www.glorioustrainwrecks.com/node/1704 - Portal Two-Dimensional
http://www.glorioustrainwrecks.com/node/1363 - Flood the Chamber Again: This Time, It's Lava
http://www.glorioustrainwrecks.com/node/934 - Retro Grade
http://www.glorioustrainwrecks.com/node/1408 - Mid Morning Ride
http://www.glorioustrainwrecks.com/node/1386 - TrailblaZer
http://www.glorioustrainwrecks.com/node/1409 - Getalong Golfhop
http://www.glorioustrainwrecks.com/node/1744 - Renardes
http://www.glorioustrainwrecks.com/node/1448 - Delve Deep
http://www.glorioustrainwrecks.com/node/1378 - Unshrinker
http://www.glorioustrainwrecks.com/node/1773 - Daddy I'm Scared

Note: if you're using these in GameMaker: Studio free version, you may have to delete the "draw_self" script if it's present in one of these codebases. Since draw_self() was added as a standard feature in later GM versions, the script is no longer necessary.

Game Maker: flattening a room's tile layers to a surface at runtime

This is just a proof-of-concept GML code thing I did for rdein. What it does is this: when you start a room, it draw all of the room's tiles to a surface or series of surfaces, then destroys the tiles. Then it draws the surfaces in place of the tiles. Drawing a few large surfaces is theoretically faster than drawing 1000s of individual tiles, each with their own specific top, left, width, and height values.

Dust Prototype

dustprototype.png

This prototype is basically a fangame of James Burton's Stardust. But rather than being a straight puzzle game, this is intended as "a puzzle game, without the thinking". dess once described suteF as a game where the player is in a perpetual state of semi-confusion, where all they can do is stumble forward through a meager set of possible moves, only ever partially understanding what's going on. This is the sort of counterintuitive anti-friendly design principle I am hoping to capture with this game.

Choose a level file when you start up.
You must guide the blue person to the blue goal, and then the red person to the red goal.

Left, right: move.
Space: trigger the block that you are standing on. Each block has a different effect when triggered.
Press R to restart. (You'll have to press it if you fall out of bounds.)

http://www.glorioustrainwrecks.com/files/DustPrototype.7z (Windows)
http://www.glorioustrainwrecks.com/files/DustPrototypeMac.7z (Mac OS X)

Syndicate content