The Quest to Make Cake Quest_part8.p8

Devising a Double Jump (Days 17 and 18)

When I first implemented Satan’s ability to jump, I dismissed the idea of implementing a double jump. I thought that it wouldn’t be interesting enough, that as the final ability in the game, the conclusion it would help provide would be unsatisfying to players. In other words, it wasn’t ideal. However, I mentioned in the last post that losing the project was an opportunity to reevaluate previous decisions I had made.

My choice to use PICO-8 had forced me to think pragmatically from the outset. I gained an awareness of just how little space I had after finishing the initial jumping challenge, and the spike challenge had been an exercise in making the most of that space. Limitations like this were what had appealed to me and had given me hope that I could finish this project.

With that in mind, my opposition to a double jump was unjustifiable. It was a self-imposed limitation that impeded any sort of progress because I kept looking for alternatives. I needed to change my mindset, so to facilitate and force that change, I moved all of the jump-related code into its own function:

FUNCTION HANDLE_JUMPING()

IF((BTNP(2) OR BTNP(4)) AND P.HAS_WINGS) THEN

IF IS_GROUNDED() THEN

IF(((SOLID_TILE(P.X,P.Y-1)==TRUE) OR (SOLID_TILE(P.X+15,P.Y-1)==TRUE)) OR (SOLID_TILE(P.X+7,P.Y-1)==TRUE)) THEN
P.DY=0
ELSE
P.DY-=10
END

END

END

END

Next, I thought about the best approach I could take to implement a functional double jump. In this case, the “best approach” meant something that I understood and that worked. I created two variables: p.double_jump_enabled and p.jump_count. I set the latter to zero, and for testing purposes, I set the former to true. My plan was to increment p.double_jump_enabled by one when the jump key was pressed. When its value was equal to two, jumping would be disabled until Satan touched the ground, at which point the value would reset to zero.

Since the game already had an area dedicated to jumping, I decided to use it as a testing environment for the double jump. I don’t have the code associated with my initial implementation, unfortunately. However, I had the foresight to make a GIF to use as a visual aid. The main takeaways from it are that I counted jumps improperly and that I didn’t specify the conditions under which Satan could and could not double jump, which is why he can jump through platforms:

cakequest_8

Beyond those, my main problem was trying to rely entirely on the handle_jumping() function. As is the case with handle_physics(), the function “handles” core functionality, while restrictions on the values used by the function are entrusted to other functions. In terms of the double jump, this core functionality refers to its height and to incrementing p.jump_count:

FUNCTION HANDLE_JUMPING()

IF((BTNP(2) OR BTNP(4)) AND P.HAS_WINGS) THEN

IF IS_GROUNDED() THEN

IF(((SOLID_TILE(P.X,P.Y-1)==TRUE) OR (SOLID_TILE(P.X+15,P.Y-1)==TRUE)) OR (SOLID_TILE(P.X+7,P.Y-1)==TRUE)) THEN
P.DY=0
ELSE
P.DY-=10
P.JUMP_COUNT+=1
END

ELSE

IF(P.DOUBLE_JUMP_ENABLED AND P.CAN_DOUBLE_JUMP) THEN

IF(P.JUMP_COUNT==1) THEN
P.DY=0
P.DY-=8
P.JUMP_COUNT+=1
END

END

END

END

END

The variable p.can_double_jump starts as false and is toggled in handle_physics() depending on whether Satan is stuck in the ceiling. If he is, then it is false, and if he isn’t, then it is true. The height of the jump (the value of p.dy) is slightly less than that of the regular jump. Satan has a fair amount of girth, and I didn’t want the double jump to look effortless.

Setting p.dy to zero just before the actual jump occurs is important because Satan is constantly affected by gravity. Without this step, the height of the second jump changes depending on Satan’s location in the air. In another game, that might be acceptable, but this one isn’t designed around that level of precision. Double jumps (as I understand them) defy gravity, so this allows Satan’s jump to do so.

Of course, this code only permits Satan to perform a single double jump because the value of p.jump_count never becomes less than or equal to one after the jump is performed. I solved the problem by making an addition to update_player() above the code that enables lives:

FUNCTION UPDATE_PLAYER()

IF(P.BOLT_COUNT<0) THEN
P.BOLT_COUNT=0
END
IF COLLIDE(P.X,P.Y,P.HITBOX,T.X,T.Y,T.HITBOX) THEN
P.HAS_TRIDENT=TRUE
END
IF COLLIDE(P.X,P.Y,P.HITBOX,CHC.X,CHC.Y,CHC.HITBOX) THEN
P.HAS_WINGS=TRUE
END
IF P.DOUBLE_JUMP_ENABLED THEN

IF(IS_GROUNDED()==TRUE) THEN
P.JUMP_COUNT=0
ELSE

IF(P.JUMP_COUNT>2) THEN
P.JUMP_COUNT=2
END

END

END

IF COLLIDE(P.X,P.Y,P.HITBOX,GVC.X,GVC.Y,GVC.HITBOX) THEN
P.LIVES_ENABLED=TRUE
P.LIVES=3
END

END

Checking whether p.double_jump_enabled is true might seem superfluous because of its perpetual true status, but it’s simply a preemptive time-saving measure. I’d have had to write the condition when I made the double jump a cake-given ability, and now it’s one less thing to worry about when I get to that point. The rest of the code fulfills my earlier objectives. The value of p.jump_count is reset once Satan touches the ground, and restricting the maximum value to two ensures that he can’t jump more than twice in the air (what matters is that it’s higher than one; two just makes sense because it’s a “double” jump).

Beyond determining whether a double jump was actually occurring, my goal for the second test was to get a visual indication of the height Satan gained when jumping. An increase in height increased the likelihood of coming in contact with (and getting stuck in) a ceiling. Mastery is the product of experimentation, so when players experimented with the double jump, I wanted to make sure that they didn’t end up in situations that halted their progress or punished them unnecessarily. I couldn’t guarantee it’d never happen, but I could attempt to minimize the risk.

My next post will discuss the how the double jump influenced the design of the final area of the game.

cakequest_9

Advertisements

The Quest to Make Cake Quest_part7.5.p8

Crisis Averted (Days 15 and 16)

A[nother] Second Chance

I’m not sure if I’ve explicitly mentioned it before, but part of what motivates me to make games is documenting the process through these posts. They help keep projects alive as records of my progress at specific points in time. Since this site partially serves as a repository, that isn’t necessarily surprising, but I never thought I’d end up using it as a resource. Without the screenshots, GIFs, and code snippets I’ve previously shared and discussed, recreating Cake Quest would have taken a lot longer than it ultimately did, especially since I’m not sure if I’d have made the attempt.

Losing the project was more of an opportunity than a setback. I could reevaluate decisions I had made and “refactor” code I had written. Some portions of the map received minor spacing adjustments to make traversing it easier:

cakequest_2
This is from the old build.
cakequest_4
This is from the new build.

With a few exceptions, changes to the code were organizational. The first of these exceptions was an addition to the draw_trident() function:

FUNCTION DRAW_TRIDENT()

IF NOT P.HAS_TRIDENT THEN
T.SPRITE=SPR(5,T.X,T.Y,2,2)
ELSE
T.HITBOX={X=0,Y=0,W=0,H=0}

END

END

Because the collision between the trident’s hitbox and Satan’s is what determines whether the trident has been collected, setting the hitbox’s dimensions to zero ensures the two hitboxes can’t collide again once p.has_trident is true. The next addition was to update_player(), an if statement that checks whether p.bolt_count is less than zero and sets it to zero if it is.

cakequest_5

This fixes a bug related to p.bolt_count. In order for a bolt to be fired, p.bolt_count must be zero (checked in can_fire()), ensuring that only one bolt is fired at a time. Its value increases by one whenever a bolt is fired and decreases by one whenever a bolt hits a wall or rock. However, it also decreases when a bolt is a certain distance away from Satan in either direction. Consequently, if a bolt collides with a wall or rock at that distance, then the value of p.bolt_count becomes -1, disabling the ability to fire bolts and preventing players from progressing beyond the jumping challenge.

The last significant change to the code was the removal of the update_flags() function. In part 6, I said that despite no longer being drawn, doors were treated as solid objects because of the flag assigned to their sprites. While recreating the map, I realized that statement was slightly inaccurate. Each door originally replaced a section of the wall. The wall’s sprites are also flagged as solid objects, and these sections were intact in the map data, only getting replaced at runtime. All I needed to do was open the map editor and remove the wall sprites from where I wanted doors to be. Satan could then progress immediately after the door sprites were replaced with empty ones (and now he can).

The Nature of Life and Death

Creating Objects

Implementing the behavior of spikes started by once again duplicating the solid_tile() function and changing the flag:

FUNCTION SPIKE_TILE(X,Y)
LOCAL TILEX=FLR(X/8)
LOCAL TILEY=FLR(Y/8)

IF(FGET(MGET(TILEX,TILEY),4) THEN
RETURN TRUE
ELSE
RETURN FALSE
END

END

In the previous post, I mentioned that Satan couldn’t collide with spikes until he obtained lives. The table in make_table() needed two additional parameters: p.lives and p.lives_enabled. The former has a nil value by default, and the latter is set to false. Lives are enabled when Satan eats the green velvet cake. It’s identical to the devil’s food/chocolate cake aside from location:

FUNCTION MAKE_CAKE()
CHC={
X=224,
Y=384,
SPRITE=NIL,
HITBOX={X=4,Y=4,W=7,H=14}
}
GVC={
X=608,
Y=64,
SPRITE=NIL,
HITBOX=CHC.HITBOX
}

END

Its corresponding rock is also identical to the chocolate rock. It consists of four segments that are initially set to nil values and has a hit count of zero. While its hit count is less than two, it is drawn. Once its hit count is equal to two, then its segments are set to empty sprites (in update_grn_vlvt_rock()), and the cake is drawn in its place:

FUNCTION MAKE_ROCKS()
CR={
TOP_LEFT=NIL,
TOP_RIGHT=NIL,
BOTTOM_LEFT=NIL,
BOTTOM_RIGHT=NIL,
HIT_COUNT=0
}
GVR={
TOP_LEFT=NIL,
TOP_RIGHT=NIL,
BOTTOM_LEFT=NIL,
BOTTOM_RIGHT=NIL,
HIT_COUNT=0
}

END

FUNCTION DRAW_GRN_VLVT_ROCK()

IF(GVR.HIT_COUNT<2) THEN
GVR.TOP_LEFT=MSET(76,8,70)
GVR.TOP_RIGHT=MSET(77,8,71)
GVR.BOTTOM_LEFT=MSET(76,9,86)
GVR.BOTTOM_RIGHT=MSET(77,9,87)
ELSE
UPDATE_GRN_VLVT_ROCK()
DRAW_CAKE()
END

END

FUNCTION DRAW_CAKE()

IF NOT P.HAS_WINGS THEN
CHC.SPRITE=SPR(41,CHC.X,CHC.Y,2,2)
END
IF NOT P.LIVES_ENABLED THEN

IF(GVR.HIT_COUNT==2) THEN
GVC.SPRITE=SPR(9,GVC.X,GVC.Y,2,2)
END

END

END

As a reminder, the last two parameters in this implementation of spr() refer to width and height in sprite dimensions. Sprites are eight pixels high and eight pixels wide. A value of two sets the width and height to 16 pixels. Four sprites are drawn in total, with the sprite specified by number (i.e. the first parameter) comprising the top-left portion of the larger sprite.

There is currently only one extra life in the game. If that were to change, it would happen in make_extra_lives(). An extra life has a position, a sprite, a hitbox, and a collected status. It is drawn as long as that status is false:

FUNCTION MAKE_EXTRA_LIVES()
LIFE={
X=992,
Y=48,
SPRITE=NIL,
HITBOX={X=0,Y=0,W=8,H=8},
COLLECTED=FALSE
}
END

FUNCTION DRAW_EXTRA_LIVES()

IF NOT LIFE.COLLECTED THEN
LIFE.SPRITE=SPR(102,LIFE.X,LIFE.Y)
ELSE
LIFE.HITBOX={X=0,Y=0,W=0,H=0}
END

END

Collision-based Functionality

Everything associated with spikes and lives is handled in two main functions: update_player() and move_player(). In update_player(), once Satan’s hitbox collides with that of the green velvet cake, p.lives_enabled is set to true, and p.lives is set to three:

FUNCTION UPDATE_PLAYER()

IF(P.BOLT_COUNT<0) THEN
P.BOLT_COUNT=0
END
IF COLLIDE(P.X,P.Y,P.HITBOX,T.X,T.Y,T.HITBOX) THEN
P.HAS_TRIDENT=TRUE
END
IF COLLIDE(P.X,P.Y,P.HITBOX,CHC.X,CHC.Y,CHC.HITBOX) THEN
P.HAS_WINGS=TRUE
END
IF COLLIDE(P.X,P.Y,P.HITBOX,GVC.X,GVC.Y,GVC.HITBOX) THEN
P.LIVES_ENABLED=TRUE
P.LIVES=3
END

END

An addition to move_player() uses the status of p.lives_enabled to limit Satan’s movement without lives. Once lives are enabled, then a function named manage_lives() is called:

IF P.LIVES_ENABLED THEN
MANAGE_LIVES()
ELSE

IF(P.X>664) THEN
P.X=664
END

END

The manage_lives() function decreases the value of p.lives by one every time Satan collides with a spike. The set_position() function is called immediately afterward. It sets p.x and p.y to specific values depending on where Satan is when a collision occurs. If the total number of lives is equal to or below zero, then run() is called, resetting the game. Spike collisions don’t include a one-pixel buffer between sprites; this gives Satan more maneuverability (and players more path choices) during the spike challenge.

In order of appearance, the following checks refer to Satan’s top-left, top-center, top-right, center-left, bottom-left, bottom-center, bottom right, and center-right.

FUNCTION MANAGE_LIVES()

IF((SPIKE_TILE(P.X,P.Y)==TRUE) OR (SPIKE_TILE(P.X+7,P.Y)==TRUE) OR (SPIKE_TILE(P.X+15,P.Y)==TRUE) OR (SPIKE_TILE(P.X,P.Y+7)==TRUE) OR (SPIKE_TILE(P.X,P.Y+15)==TRUE) OR (SPIKE_TILE(P.X+7,P.Y+15)==TRUE) OR (SPIKE_TILE(P.X+15,P.Y+15)==TRUE) OR (SPIKE_TILE(P.X+15,P.Y+7)==TRUE) THEN

IF(P.LIVES>0) THEN
P.LIVES-=1
SET_POSITION()
ELSE
RUN()
END

END

END

FUNCTION SET_POSITION()

IF(P.Y<=200) THEN
--ON PLATFORM BEFORE FIRST SPIKES
P.X=656
P.Y=88

ELSEIF(P.Y>200 AND P.Y<350) THEN
--RIGHT OF GREEN DOOR
P.X=708
P.Y=344
END

END

cakequest_7

Since obtaining an extra life is the result of a hitbox-based collision, it gets added to the total in update_player():

IF(P.LIVES_ENABLED==TRUE) THEN

IF COLLIDE(P.X,P.Y,P.HITBOX,LIFE.X,LIFE.Y,LIFE.HITBOX) THEN
P.LIVES+=1
LIFE.COLLECTED=TRUE
END

END

Displaying Inventory

I figured that the simplest way to inform players of their total lives was to display that total on the screen. I wrote a function named draw_inventory() to accomplish this. In the original build, it only drew lives as they were gained or lost, but during one of many playthroughs, I decided to have the keys drawn as Satan collected them. The only other indication that a key has been collected is the disappearance of its corresponding door, but that’s not immediately apparent like a change to Satan’s sprite is, for example.

FUNCTION DRAW_INVENTORY()
CAMERA()

The call to camera() resets the camera offset to its default value (zero), ensuring that whatever is drawn afterward remains in a specific location on the screen regardless of the camera’s position.

IF P.LIVES_ENABLED THEN

IF(P.LIVES>=1) THEN
SPR(102,88,0)
END
IF(P.LIVES>=2) THEN
SPR(102,100,0)
END
IF(P.LIVES>=3) THEN
SPR(102,112,0)
END
IF(P.LIVES>=4) THEN
SPR(102,76,0)
END

END

Each of the four sprites is visible unless the value of p.lives falls below the specified number in each statement. For example, if Satan has four lives and then loses one, the fourth life is no longer visible, but the other three remain visible.

IF P.HAS_CHOC_KEY THEN
SPR(40,88,12)
END

IF P.HAS_GV_KEY THEN
SPR(55,100,12)
END

END

The “gv” in p.has_gv_key stands for “green velvet.” That key is created in make_keys(), drawn in draw_keys(), and is collected via hitbox collision in update_player(). Remember that the coordinates in make_keys() refer to the locations of both keys on the map, while the previous coordinates refer to where on the screen their sprites are drawn once they’ve been collected:

FUNCTION MAKE_KEYS()
CHOC={
X=248,
Y=64,
SPRITE=NIL,
HITBOX={X=0,Y=0,W=6,H=8}
GREEN={
X=944,
Y=200,
SPRITE=NIL,
HITBOX=CHOC.HITBOX
}

END

FUNCTION DRAW_KEYS()

IF NOT P.HAS_CHOC_KEY THEN
CHOC.SPRITE=SPR(40,CHOC.X,CHOC.Y)
END
IF NOT P.HAS_GV_KEY THEN
GREEN.SPRITE=SPR(55,GREEN.X,GREEN.Y)
END

END

IF COLLIDE(P.X,P.Y,P.HITBOX,CHOC.X,CHOC.Y,CHOC.HITBOX) THEN
P.HAS_CHOC_KEY=TRUE
END
IF COLLIDE(P.X,P.Y,P.HITBOX,GREEN.X,GREEN.Y,GREEN.HITBOX) THEN
P.HAS_GV_KEY=TRUE
END

Future Plans

Most of the code in the current build has been covered in this post. The only omissions are additions to the door-related functions, specifically relating to the green velvet door. I’ve decided that because I’ll be adding code related to the third door type to these functions, it’d be more appropriate to show and discuss both at the same time rather than in separate posts.

The Quest to Make Cake Quest_part7.p8

The Key to Progression: Spike Challenge (Days 12-14)

Creating Life in Hell

In many games, players are given health or lives as soon as a level loads. With them comes the expectation of potential loss upon contact with an enemy or object, but they’re otherwise taken for granted. Their impact on how players approach situations consequently tends to be minimal unless and until they’re almost gone. I wanted them to be meaningful as soon as they were introduced.

cakequest_010

Conveying that through appearance was simple enough. Shaded similarly to the trident and keys, the sprite is green because I decided Satan would earn lives by eating green velvet cake. The actual design was inspired by the miniature cakes and cupcakes sold under Hostess and other brands and is meant to be a non-specific devil or demon. I always thought characters collecting their own faces was a little weird, so I intentionally avoided basing it on Satan himself.

Pain without Suffering

It didn’t take long for spikes to become the designated hazard in this game. They were simple to draw, versatile (I could place them on floors, walls, ceilings, and platforms), and most importantly, they didn’t take up much space in the sprite sheet. The jumping challenge took up a significant portion of PICO-8’s map, and I quickly discovered that what looked like available space in the sprite sheet wasn’t. When I experimented with designs larger than 8×8, portions of those designs would appear in various places, mainly in walls and floors. This taught me the importance of making the most of limited space. To that end, I created two segments in addition to the four directional spike segments, allowing me to quickly and easily create spikes of any length.

Their challenge begins as an extension of the jumping challenge, but it doesn’t officially begin until Satan obtains lives by transforming the green velvet rock, which is placed in a recess above and to the left of a platform. To the right of that platform is a line of smaller platforms, and beneath these are several spikes. Without lives, Satan’s horizontal movement range is restricted to the end of the initial platform. Players can thus see the spikes but are unable to interact with them. Bolts can’t reach the green velvet rock unless Satan is in the air, forcing players to learn and master how to fire them while jumping. The movement restriction prevents players from accidentally falling into the spikes, and the platform’s longer length compared to the others helps players adjust to the new mechanic. More specifically, it reinforces the need for Satan to be moving before he can fire.

cakequest_008

As soon as Satan obtains lives, the movement restriction disappears. If he falls onto the spikes, his position is set to the initial platform. I wanted players to be penalized for losing lives, but I also wanted them to be able to start playing again immediately. If all lives are lost, then the game restarts:

cakequest_0

Players are offered a choice when they reach the fourth platform. They can either move down to the ground or head upward and attempt to get an extra life. There isn’t much to say about that upper section except that there were originally four spikes above the platform. I changed it when I realized that the reward wasn’t worth the risk. Getting on the platform without touching the spikes required pixel-perfect precision that players wouldn’t be prepared for, and even if they were, it was difficult to do reliably.* Reducing the number of spikes to three made it significantly easier to do, but it was still challenging (the below demonstration took three or four attempts):

cakequest_1

*Every time I make an addition or change, I play the game from the beginning up to the point where that addition or change is implemented. Not only does it help me experience the level as players will, but it also helps me make sure that the difficulty isn’t increasing too quickly from section to section. 

What Lies Beneath

Whether they get the extra life or not, players can only progress by jumping or falling down the spike-lined hole in the floor. If they head right, they see the green velvet cake key. At this point in the game, players would know that they needed to obtain it, so I used the key as a starting point for this section’s design. Its placement at the top of the section meant that players would need to work their way upward to reach it. Taking into account the limited space available (this was the second of three challenges, after all), I thought the best approach would be to create a maze or puzzle of sorts.

cakequest_009

I started by wondering how closely together platforms could be positioned with players still able to jump or move between them. The closer the platforms were, the more likely it was that players would get stuck in the bottoms of platforms above them. It then occurred to me that players could be encouraged to use that “ability” where they otherwise wouldn’t through spike placement. Furthermore, access to certain platforms could be restricted or prevented entirely, forcing players to make calculated decisions about which platforms will help them get to the key.

The spike placement in the above image was based on an arbitrary path I took involving the central platforms in each row. I intended it to be the only path, but after I happened to find another one, I realized that it was better if players had more than the illusion of choice. The prospect of them discovering paths I hadn’t planned or wasn’t aware of was exciting rather than a problem I needed to fix, with one exception. I designed the corridor at the top of the room mainly so that when players fell, the first thing they would see is the door leading to the next section of the level (this spot is also where they respawn upon losing a life). Its secondary purpose is to prevent players from falling on or near the top row of platforms and essentially skipping the challenge.

Code Catch-up: A Note

This isn’t how I intended to finish this post, but I’m committed to documenting everything that happens during the development of this game. A few days ago, I encountered an issue in which I wasn’t able to boot into my computer’s operating system. After trying every possible solution I could think of and find online, I erased the disk and reinstalled the operating system. I lost everything except for the screenshots and GIFs used here because I never created a backup (PICO-8’s automatic backup was deleted along with PICO-8).

As of this writing, I have recreated all of the sprites and redrawn the map. Publication of the next post will be delayed until I’ve rewritten the code. In that post, I’ll discuss the code relating to this post as well as any changes made to the game’s functionality or design (I’ve yet to test the redrawn map). If you are reading this, thank you for your patience and understanding.

The Quest to Make Cake Quest_part6.p8

Correction: In the previous post, I said that the parameters of mset() referred to pixel coordinates, but as I’ll discuss in this one, they actually refer to tile coordinates. 

The Key to Progression: Jumping Challenge (Days 10 and 11)

Design Decisions and Changes

Satan earning his wings meant that I had exhausted my list of definite mechanics. I didn’t have a clear idea of what to work on next, so I referred to the shorter list of potential mechanics. The first two were cake-focused (and therefore ability/attribute-focused), but the third, obtaining a “cake key”* to reach the level’s exit, required me to focus on level design. I’ve mentioned previously that I had been using the map solely as a testing environment up to this point. That would have to change sooner or later, so implementing this mechanic was my opportunity to enact that change.

As I thought about what Satan might have to do to obtain this key and how long it might take players to acquire, I toyed with the idea of having three keys instead of one, and the floodgates opened. Since I had three cake varieties, and each enhanced Satan in some way, I could design level sections based on those enhancements. The goal in each section could be to reach and obtain a key in order to unlock a door leading to the next section.

I tried to emulate the trident’s faux 3D appearance when designing the devil’s food/chocolate cake key. The key proper was the color of the cake’s frosting, and the shaded portions that created the 3D effect were the color of the cake. Since my carrot cake had white frosting, and my yellow cake had pink frosting, I couldn’t take this approach with their respective keys. PICO-8’s color palette was simply too limited. Rather than change the key design (much), I replaced the cake varieties. The carrot cake became green velvet, and the yellow cake became lemon:

cakequest_001
This was the original design.
cakequest_004
I stared at the original for too long while writing this, so this is the current design.

Keys are useless without doors, so I modified the unused designs I made earlier and colored them to reflect the new cake varieties. I also preemptively created the other two rock sprites:

cakequest_005

*Its name is a reference to the Japanese pronunciation of “cake.” 

Meddling with the Map Again

I decided to completely scrap the test environment rather than modify it. It was structured around my wants and needs as a designer. My approach to actual levels is to attempt to structure them around the wants and needs of players as much as possible.

The above images depict the first two sections of the game. Players begin in the top-left area. Its somewhat large size helps players become familiar with Satan’s ground movement and movement speed. Similarly, the drop down to the lower section introduces gravity and demonstrates how Satan behaves in the air. Once on the ground again, players encounter their first obstacle, the chocolate rock.* Going left, players can find and pick up the trident in a small room at the end of the corridor.

Because the trident adds another control option (shooting), its room and the connecting corridor give players the space to experiment with it before they test their new ability on the rock. This, in turn, ultimately grants them another new ability and access to the next section: the titular jumping challenge. I placed the sections close together for two reasons. The first was to conserve space, and the second was because I figured players would want to start jumping as soon as they were able.

Designing the jumping challenge was an iterative process. I started at the bottom of the room and worked upward. As a whole, the challenge is intended to be a safe, risk-free environment in which players can hone their skills. The blocks on the floor serve as a general introduction to the mechanic and help demonstrate Satan’s jump height. Above these is a row of platforms that teaches players the importance of positioning and timing when jumping horizontally. The platforms start out wide to allow for experimentation without heavily penalizing mistakes. If players fall when further up, they can also try to land on these to reduce the amount of climbing they have to do.

The first set of smaller platforms tests the ability to jump vertically. As they jump across to the second set and approach the right-most platform, players encounter the chocolate door and discover that it’s impassable, like the rock from the previous section. The remaining platforms point them in the direction of the key, which makes the door disappear upon being collected and grants players access to the next section:

cakequest_2
This challenge wasn’t explicitly designed to introduce getting stuck, but it will probably happen.

*It, the trident, keys, and doors don’t appear on the map until the game runs (since code handles their placement). The tiles used for the wall, floor, and ceiling are placed manually. 

Code Catch-up

Implementing the behavior of the key and door began with their respective make functions: make_keys() and make_doors(). Keys, like the trident, have an x-coordinate, a y-coordinate, a sprite, and a hitbox. Doors, like rocks, have sprites for each quadrant. Their only difference is the addition of a locked condition, which is true by default.

FUNCTION MAKE_KEYS()
CHOC={}
CHOC.X=250
CHOC.Y=48
CHOC.SPRITE=NIL
CHOC.HITBOX={X=1,Y=0,W=6,H=8}
END

FUNCTION MAKE_DOORS()
C_DOOR={}
C_DOOR.TOP_LEFT=NIL
C_DOOR.TOP_RIGHT=NIL
C_DOOR.BOTTOM_LEFT=NIL
C_DOOR.BOTTOM_RIGHT=NIL
C_DOOR.LOCKED=TRUE
END

Keys are drawn as long as they aren’t collected. In the case of the chocolate key, this depends on the status of an attribute in make_player() called p.has_choc_key:

FUNCTION DRAW_KEYS()

IF NOT P.HAS_CHOC_KEY THEN
CHOC.SPRITE=SPR(40,CHOC.X,CHOC.Y)
END

END

If the key is collected (i.e. if the player collides with it), then p.has_choc_key is true:

FUNCTION UPDATE_PLAYER()

IF COLLIDE(P.X,P.Y,P.HITBOX,T.X,T.Y,T.HITBOX) THEN
P.HAS_TRIDENT=TRUE
END
IF COLLIDE(P.X,P.Y,P.HITBOX,CHC.X,CHC.Y,CHC.HITBOX) THEN
P.HAS_WINGS=TRUE
END
IF COLLIDE(P.X,P.Y,P.HITBOX,CHOC.X,CHOC.Y,CHOC.HITBOX) THEN
P.HAS_CHOC_KEY=TRUE
END

END

This attribute doesn’t determine whether doors are drawn. Instead, it affects the status of their locked attribute:

FUNCTION DRAW_CHOC_DOOR()
C_DOOR.TOP_LEFT=MSET(72,11,68)
C_DOOR.TOP_RIGHT=MSET(73,11,69)
C_DOOR.BOTTOM_LEFT=MSET(72,12,84)
C_DOOR.BOTTOM_RIGHT=MSET(73,12,85)
END

The first two parameters are tile coordinates, and the third is a sprite number. Like pixel coordinates, tile coordinates increase from left to right and from top to bottom. Unlike pixel coordinates, values lower than zero are exclusively outside of the map/screen boundaries.

FUNCTION UPDATE_CHOC_DOOR()

IF P.HAS_CHOC_KEY THEN
C_DOOR.LOCKED=FALSE
C_DOOR.TOP_LEFT=MSET(72,11,0)
C_DOOR.TOP_RIGHT=MSET(73,11,0)
C_DOOR.BOTTOM_LEFT=MSET(72,12,0)
C_DOOR.BOTTOM_RIGHT=MSET(73,12,0)
UPDATE_FLAGS()
END

END

Each quarter of the door is replaced with an empty sprite when its corresponding key is collected, effectively making the door invisible. It’s still treated as a solid object because of the flag assigned to its sprites (0), so the update_flags() function disables the flag using fset():

FUNCTION UPDATE_FLAGS()

IF (C_DOOR.LOCKED==FALSE) THEN
FSET(68,0,FALSE)
FSET(69,0,FALSE)
FSET(84,0,FALSE)
FSET(85,0,FALSE)
END

END

As always, the final step was adding function calls to the game loop, but going forward, I’m not going to explicitly mention it unless the location isn’t clear from the function name. The draw_choc_door() and draw_keys() functions get called in _draw(), and update_choc_door() gets called in _update().

 

The Quest to Make Cake Quest_part5.p8

The Devil Gets His Wings (Day 9)

Designing a Bolt Target and Making It Disappear

While I enjoyed testing the added functionality of the physics I implemented, I couldn’t get too attached because I needed to restrict that functionality in the name of progress. The goal was for it to be enabled only once Satan gained his wings, after all. I had already established that he would get them by eating cake. Cake, of course, is what certain objects get turned into once they are hit by a bolt from Satan’s trident. The task at hand, then, was dual-faceted. I needed to determine exactly what Satan would be shooting, and I needed to figure out how to replace that object when it was hit.

cakequest_004

Originally, I thought that Satan might shoot at specific walls or “doors” that shared color schemes with specific cake varieties. These varieties were devil’s food (chocolate) cake, carrot cake, and yellow cake. Enemies had always been outside of the game’s scope because I considered it more important to get Satan’s movement and behavior working well than to implement and manage the movement and behavior of various enemy types. Ultimately, I replaced the walls/doors with rocks.* They were more versatile in terms of map placement, and I could create them with minimal changes to the existing sprites.

I decided that chocolate cake would give Satan his wings, so I focused my efforts on the chocolate rock. First, I duplicated the solid_tile() function and created a new function called choc_tile(). The only difference between the former and the latter was the number of the sprite flag for which it checked:

FUNCTION CHOC_TILE()
LOCAL TILEX=FLR(X/8)
LOCAL TILEY=FLR(Y/8)

IF FGET(MGET(TILEX,TILEY),1) THEN
RETURN TRUE
ELSE
RETURN FALSE
END

END

Just as a reminder, this converts x and y-coordinates from pixel (screen) coordinates to tile (map) coordinates and then checks to see if the sprite at that location has been assigned the specified flag. With that done, I then wrote a make_rocks() function, since it would be more efficient in the long run than writing a function for each rock variety:

FUNCTION MAKE_ROCKS()
CR={}
CR.TOP_LEFT=NIL
CR.TOP_RIGHT=NIL
CR.BOTTOM_LEFT=NIL
CR.BOTTOM_RIGHT=NIL
CR.HIT_COUNT=0
END

The first four attributes each refer to an 8×8 sprite that comprises 1/4 of the rock, and hit_count refers to how many times the rock has been hit by a bolt, which also determines whether or not it is drawn:

FUNCTION DRAW_CHOC_ROCK()

IF (CR.HIT_COUNT==0) THEN
CR.TOP_LEFT=MSET([X],[Y],66)
CR.TOP_RIGHT=MSET([X],[Y],67)
CR.BOTTOM_LEFT=MSET([X],[Y],82)
CR.BOTTOM_RIGHT=MSET([X],[Y],83)
ELSE
UPDATE_CHOC_ROCK()
END

END

I used the built-in mset() function to assign the proper sprite to each rock quadrant. Parameters [X] and [Y] represent explicit pixel coordinates that differ in the current build from what they were at the time this code was originally written. They’re based on values of p.x and p.y I wrote down while testing, and that testing environment no longer exists (I’ll address that in a future post). The last parameter in mset() is the sprite number in the sprite sheet. The update_choc_rock() function sets the sprite number of each quadrant to zero (an empty slot) and has no additional functionality or conditions that must be met, so I’m not showing it here.

As a final step before function calls and testing, I made some minor additions to the manage_bolt_mvmt() function:

FUNCTION MANAGE_BOLT_MVMT()

FOR B IN ALL(BOLTS) DO

IF B.RIGHT THEN

IF (SOLID_TILE(B.X+16,B.Y+7)==FALSE) THEN B.X+=B.SPEED
B.Y+=B.DY
ELSE
DEL(BOLTS,B)
P.BOLT_COUNT-=1
END
IF (CHOC_TILE(B.X+16,B.Y+7)==TRUE) then
DEL(BOLTS,B)
P.BOLT_COUNT-=1
CR.HIT_COUNT+=1
END

ELSEIF B.LEFT THEN

IF (SOLID_TILE(B.X-1,B.Y+7)==FALSE) THEN
B.X-=B.SPEED
B.Y-=B.DY
ELSE
DEL(BOLTS,B)
P.BOLT_COUNT-=1
END
IF (CHOC_TILE(B.X-1,B.Y+7)==TRUE) THEN
DEL(BOLTS,B)
P.BOLT_COUNT-=1
CR.HIT_COUNT+=1
END

END

IF (B.X>P.X+30 OR B.X<P.X-30) THEN
DEL(BOLTS,B)
P.BOLT_COUNT-=1
END

END

END

The function calls I’m referring to involve make_rocks() and draw_rocks(),** which are called in _init() and _draw(), respectively. However, I’d like to briefly discuss a third call I made earlier to the built-in camera() function. This call is the first line I have in _update(), and it looks like this:

CAMERA(P.X-64,P.Y-64)

It determines the amount by which the camera is offset. This offset (zero by default) is technically applied to the position of everything on the screen because the screen moves in relation to the camera. By subtracting 64 from p.x and p.y, I ensure that Satan is in the center of the screen at all times, giving the impression that the camera is following him as he moves.

Below is the result of running the game with the code written up to this point:

cakequest_3
I didn’t explicitly mention that the rocks were also solid, and now I don’t have to.

*I’d like to say that the decision was influenced by rock cake, but it’s just a thematically appropriate coincidence. 

**The draw_rocks() function calls each of the draw functions for the individual rocks so that I can (eventually) do in one line of code what would otherwise take three.

Have Your Cake and Eat It, Too

One of the perks of programming this game is that cake has fewer ingredients than it does in the physical world and thus takes less time to make:

FUNCTION MAKE_CAKE()
CHC={}
CHC.X=[X]
CHC.Y=[Y]
CHC.SPRITE=NIL
CHC.HITBOX={X=4,Y=4,W=7,H=14}

As in draw_choc_rock(), [X] and [Y] represent explicit pixel coordinates, specifically those of the rock. I admit that I could (and should) have come up with a better name for a cake variety than chc, but it wasn’t high enough on my list of development priorities. That honor went to drawing the cake, which I did by copying the draw_trident() function and substituting a few attributes:

FUNCTION DRAW_CAKE()

IF NOT P.HAS_WINGS THEN
CHC.SPRITE=SPR(41,CHC.X,CHC.Y,2,2)
END

END

I added p.has_wings to make_player() and set it to false. It may or may not have been mentioned previously, but the last two parameters in spr() refer to a sprite’s width and height, and the above if-not statement is an alternate way of writing the below if statement:

IF (P.HAS_WINGS==FALSE)

To make the rock appear to turn into a piece of cake, I needed to call this function immediately after the rock gets updated, and that happens in draw_choc_rock():

FUNCTION DRAW_CHOC_ROCK()

IF (CR.HIT_COUNT==0) THEN
CR.TOP_LEFT=MSET([X],[Y],66)
CR.TOP_RIGHT=MSET([X],[Y],67)
CR.BOTTOM_LEFT=MSET([X],[Y],82)
CR.BOTTOM_RIGHT=MSET([X],[Y],83)
ELSE
UPDATE_CHOC_ROCK()
DRAW_CAKE()
END

END

Actually giving Satan wings required two final steps. He needed to be able to interact with the piece of cake, and his sprite needed to change when that happened.

FUNCTION UPDATE_PLAYER()

IF COLLIDE(P.X,P.Y,P.HITBOX,T.X,T.Y,T.HITBOX) THEN
P.HAS_TRIDENT=TRUE
END
IF COLLIDE(P.X,P.Y,P.HITBOX,CHC.X,CHC.Y,CHC.HITBOX) THEN
P.HAS_WINGS=TRUE
END

END

I used a separate if statement because Satan’s wings are an addition to his sprite rather than a replacement.

FUNCTION DRAW_PLAYER()

IF (P.HAS_TRIDENT==TRUE) THEN

IF (P.HAS_WINGS==TRUE) THEN
--WINGED WITH TRIDENT

IF P.IDLE THEN
P.SPRITE=SPR(37,P.X,P.Y,2,2)
ELSEIF P.LEFT THEN
P.SPRITE=SPR(7,P.X,P.Y,2,2,TRUE)

ELSEIF P.RIGHT THEN
P.SPRITE=SPR(7,P.X,P.Y,2,2)
END

The “true” parameter in spr() flips the sprite horizontally.

ELSE
--WINGLESS WITH TRIDENT

IF P.IDLE THEN
P.SPRITE=SPR(33,P.X,P.Y,2,2)
ELSEIF P.LEFT THEN
P.SPRITE=(SPR,35,P.X,P.Y,2,2,TRUE)
ELSEIF P.RIGHT THEN
P.SPRITE=SPR(35,P.X,P.Y,2,2)
END

END

ELSE
--WINGLESS WITHOUT TRIDENT

IF P.IDLE THEN
P.SPRITE=SPR(1,P.X,P.Y,2,2)
ELSEIF P.LEFT THEN
P.SPRITE=SPR(3,P.X,P.Y,2,2,TRUE)
ELSEIF P.RIGHT THEN
P.SPRITE=SPR(3,P.X,P.Y,2,2)
END

END

END

 After adding a call to make_cake() in _init(), I was done. Satan finally looked and behaved as intended:

 

cakequest_4
He can’t fly, but I think getting cake makes up for it.

 

The Quest to Make Cake Quest_part4.p8

Fun with Physics (Days 7 and 8)

Writing the Main Function

A game’s physics, like its collisions, aren’t handled by any built-in functions in PICO-8. I wasn’t looking forward to tackling the challenge of their implementation, but I knew that progress on this game would stagnate if I didn’t. One of my definite mechanics involves the ability to jump, so I opted to focus on jump-related physics. This started with more time away from the computer to think about the information I needed to know and values I potentially needed to access.

I started by thinking about gravity. Its inclusion meant that the player was able to fall. If the player was able to fall, then I would have to determine whether the player was falling and, by extension, whether the player was on the ground or moving upward (i.e. jumping). I would need a jump height and a falling speed. Perhaps acceleration would be a factor. I considered all of these and others, knowing that not everything could or would make it into the game.

I wasn’t sure how best to organize whatever actually made it in, so most of it ended up contained within the following function:

FUNCTION HANDLE_PHYSICS()
GRAVITY=2

IF (P.DY <= 0) THEN
P.FALLING=FALSE
ELSE
P.FALLING=TRUE
END

I added p.falling and p.dY to make_player() before writing this function. By default, the former is false, and the latter is set to zero. In the previous post, I mentioned that dY refers to a change in y-position. I added a statement to the end of move_player() (where handle_physics() ultimately is called) that increases p.y by the value of p.dY. PICO-8’s on-screen y-coordinates increase from top to bottom, so if p.dY is positive, then p.y increases. Conversely, p.y decreases if p.dY is negative.

IF IS_GROUNDED() THEN
P.DY=0
P.Y=FLR(P.Y/8)*8
ELSE
P.DY+=GRAVITY

IF (P.DY > 4) THEN
P.DY=4
END

The line P.Y=FLR(P.Y/8)*8 is probably the most important line in this function. I covered flr() before when discussing sprite-to-wall collision, but what happens here is that p.y is converted from a pixel coordinate to a tile coordinate and then back to a pixel coordinate. The purpose of this is to prevent the player from landing between tiles. I set p.dY to a certain value if that value is exceeded (p.dY increases by two during every frame of falling) to prevent clipping through tiles, which occurs if the falling speed is too high.

--STUCK IN CEILING
IF (((SOLID_TILE(P.X,P.Y-1)==TRUE) AND (SOLID_TILE(P.X+15,P.Y-1)==TRUE)) OR (SOLID_TILE(P.X+7,P.Y-1)==TRUE)) THEN
P.DY=0
P.SPEED=0
P.FALLING=FALSE

IF(BTN(3)) THEN
P.DY+=.5
END

--FREED FROM CEILING
ELSE
P.SPEED=2
END

END

END

In the event that the player’s jump puts Satan in contact with the ceiling, I decided that he should get stuck in it. The points of contact are both of his horns or the center of the top of his head (well, one pixel above these). Setting the speed to zero disables horizontal movement, but he can still face left and right. The value by which p.dY increases is arbitrary, chosen only so that the transition from stuck to unstuck isn’t instantaneous.

Getting Grounded and Tweaking Bolts

The is_grounded() function is the polar opposite to handle_physics() in terms of complexity. It determines whether there is a solid tile (i.e. ground) one pixel below Satan’s bottom-left or bottom-right. If there is one, it returns true. Otherwise, it returns false:

FUNCTION IS_GROUNDED()

IF ((SOLID_TILE(P.X,P.Y+16)==TRUE) OR (SOLID_TILE(P.X+15,P.Y+16)==TRUE)) THEN
RETURN TRUE
ELSE
RETURN FALSE
END

END

The trident’s ability to fire bolts is not jump-related, but it is progression-related. My plan is for Satan to encounter the trident before he gains the ability to jump, so I thought it was important to address the firing rate. I added an attribute to the player table called p.bolt_count, which is zero by default. I then used it in a function I wrote called can_fire(), which returns true if the bolt count is zero and returns false if it is not:

FUNCTION CAN_FIRE()

IF (P.BOLT_COUNT==0) THEN
RETURN TRUE
ELSE
RETURN FALSE
END

END

Jump Up, Super Star

IF (BTNP(2) OR BTNP(4)) THEN

IF IS_GROUNDED() THEN

IF (((SOLID_TILE(P.X,P.Y-1)==TRUE) OR (SOLID_TILE(P.X+15,P.Y-1)==TRUE)) OR (SOLID_TILE(P.X+7,P.Y-1)==TRUE)) THEN
P.DY=0
ELSE
P.DY-=10
END

END

END

Jumping is a form of movement, so I wrote this code in move_player(). Buttons two and four correspond to the up arrow key and the Z key, respectively. I included a choice between them in case players who wanted to fire bolts while in the air weren’t comfortable with one or the other. The actual jumping is handled by the line P.DY-=10. Checking to see if Satan’s on the ground first prevents him from reaching and getting stuck in areas he shouldn’t have access to, while the solid_tile() checks fix a bug I encountered.

If there was a solid tile (ceiling) directly above Satan when he jumped, he’d clip through it because its location was less than the jump height. If the tile he clipped through was part of a wall, then he’d get stuck 10 pixels above that tile because at least one wall tile acted as a ceiling. I could get him out because of what I wrote in handle_physics(), but he’d be unable to move once he landed on the ground. My guess was that the distance between the ceiling and the ground was too small for him to be considered unstuck from the ceiling. My solution was to set p.dY to zero under those circumstances, which effectively disables jumping.

There are some minor things I could cover here about camera control, function calls, and code organization, but I’ll save that for my next post. Until then, I’ll show you these physics in action:


On the left is my initial physics test, and on the right is my second test. I think, aside from fire rate testing, the only major difference in the second test is that p.dY‘s maximum value is six instead of four to give a more noticeable speed increase.

 

The Quest to Make Cake Quest_part3.p8

Playing with Projectiles (Day 6)

Creation and Behavior

In the list of definite mechanics, which I mentioned in the previous post, Satan is able to fire lightning from the trident while moving. To accomplish this, I wrote three functions: make_bolts()fire_bolts(), and manage_bolt_mvmt().

FUNCTION MAKE_BOLTS()
BOLTS={}
END

FUNCTION FIRE_BOLTS()
LOCAL B={
SPRITE=39,
X=P.X,
Y=P.Y,
SPEED=5,
DY=0,
LEFT=FALSE,
RIGHT=FALSE,
HITBOX={X=0,Y=1,W=8,H=6}
}

ADD(BOLTS,B)

IF P.LEFT THEN
B.LEFT=TRUE
B.RIGHT=FALSE
ELSEIF P.RIGHT THEN
B.LEFT=FALSE
B.RIGHT=TRUE
END

END

The fire_bolts() function populates the table created in make_bolts() with bolts that have the specified attributes. You may notice a difference in how the local b table is instantiated compared to how tables are instantiated in previous functions. My understanding is that unless otherwise specified, PICO-8 treats all variables as global. As a result, if I instantiated b (which is local) as an empty table and then instantiated a variable as b.name, where name could be anything, then calling fire_bolts() would result in an error because b.name would be interpreted as an attribute of a global b, which doesn’t exist. I imagine the solution to that would be to explicitly declare each of the attributes as local, but that would also negatively affect the code’s readability and be needlessly repetitive. The b.dY attribute refers to a change in y-position. It starts at zero because I don’t want there to be any change, but I might want it later (which is why I bothered to create it in the first place).

FUNCTION MANAGE_BOLT_MVMT()

FOR B IN ALL(BOLTS) DO

IF B.RIGHT THEN

IF(SOLID_TILE(B.X+16,B.Y+7)==FALSE) THEN
B.X+=B.SPEED
B.Y+=B.DY
ELSE
DEL(BOLTS,B)
END

ELSEIF B.LEFT THEN

IF(SOLID_TILE(B.X-1,,B.Y+7)==FALSE) THEN
B.X-=B.SPEED
B.Y-=B.DY
ELSE
DEL(BOLTS,B)
END

END

IF(B.X>P.X+30 OR B.X<P.X-30) THEN
DEL(BOLTS,B)
END

END

END

Just in case it wasn’t clear earlier, this function manages bolt movement. The statement at the top is Lua’s version of a “for loop.” It iterates through the bolts table, and once it finds a bolt (b), it performs an action on that bolt. In this case, bolts are assigned either a positive or negative speed based on their direction, which is based on the player’s position and is determined in fire_bolts(). A positive speed makes a bolt move to the right, and a negative speed makes a bolt move to the left. If it hits a wall, the bolt is deleted from the table, hence the call to del(), and it disappears. The statement at the end of the function checks whether a bolt has moved 30 pixels away from the player in either direction and removes it from the bolts table if it has. The specific distance was decided arbitrarily. My goal was to prevent bolts from traveling indefinitely when unobstructed.*

Drawing and Function Calls

The bolts needed to appear on-screen and actually be fired before I could test whether they behaved as I expected. To accomplish the former, I created a draw_bolts() function:

FUNCTION DRAW_BOLTS()

FOR B IN ALL(BOLTS) DO

IF B.LEFT THEN
SPR(B.SPRITE,B.X+2,B.Y+5,1,1,TRUE)
ELSEIF B.RIGHT THEN
SPR(B.SPRITE,B.X+5,B.Y+5)
END

END

END

 The bolt’s sprite is assigned based on its direction. If it is facing to the left, the sprite is flipped because the sprite faces to the right in the sprite sheet. The position of each bolt is in relation to the end of the trident, since that is the object that fires them. I specified that in a slight addition to move_player(), which I also reorganized:

FUNCTION MOVE_PLAYER()
P.IDLE=TRUE

IF(BTN(0)) THEN
P.IDLE=FALSE
P.RIGHT=FALSE
P.LEFT=TRUE

IF(SOLID_TILE(P.X-1,P.Y)==FALSE) THEN
P.STOPPED=FALSE
P.X-=P.SPEED
ELSE
P.STOPPED=TRUE
END

END

IF(BTN(1)) THEN
P.IDLE=FALSE
P.LEFT=FALSE
P.RIGHT=TRUE

IF(SOLID_TILE(P.X+16,P.Y)==FALSE) THEN
P.STOPPED=FALSE
P.X+=P.SPEED
ELSE
P.STOPPED=TRUE
END

END

IF(BTNP(5) AND P.HAS_TRIDENT==TRUE) THEN

IF NOT P.IDLE AND NOT P.STOPPED THEN
FIRE_BOLTS()
END

END

END

The most important change from the previous version of the function is the addition of the p.stopped variable, which I added to make_player() after I realized I didn’t want bolts to fire if the player is up against a wall. The other one involved replacing the if-elseif statement structure with just if statements in order to make future changes (i.e. additional functionality) easier to implement. My immediate focus, however, was on adding function calls where appropriate so I could finally start testing. The fire_bolts() function was taken care of, so I only needed to worry about make_bolts()draw_bolts(), and manage_bolt_mvmt(). I added a call to the first in _init(), a call to the second in _draw(), and a call to the third in _update().

cakequest_0
He knows an update will reduce the fire rate, so he’s enjoying himself while he can.

*This hadn’t yet been implemented at the time the GIF was created, but it was added shortly enough afterward and was so minor an addition that I thought it best to mention it here.

The Quest to Make Cake Quest_part2.p8

First Game Mechanics and Additional Sprites (Day 3)

Cake Quest, at its core, consists of four main elements: Satan, cake, a trident, and lightning. My version of the game now had the first. I took some time away from the computer to think about how they would all interact with one another and devised two lists. The first list focused on what I called definite mechanics, while the second list focused on what I called potential mechanics. Both types were rules that would govern and influence the structure of the game. The words definite and potential referred not only to the likelihood of inclusion but also to whether there was a clear plan for implementation.

Definite Mechanics

  • Satan starts without the trident.
  • The trident is a collectible that must be picked up. Once it is picked up, Satan can fire lightning projectiles from it. He cannot put it down.
  • Projectiles are unlimited and can only be fired if Satan is facing left or right.
  • Cake is also a collectible. It appears as slices and has several varieties. Each variety imbues Satan with a different ability or attribute.
  • One variety gives Satan a pair of wings. With wings, Satan cannot fly, but he gains the ability to jump.
  • Objects that can be turned into cake use the same color scheme as a given cake variety.

Potential Mechanics

  • Satan earns new abilities or attributes from a single slice of cake. Alternatively, he only earns a new ability or attribute after eating a certain number of slices.
  • Cake could give Satan health or lives. There could even be some variety or varieties that harm(s) him.
  • Satan must find and use a “cake key” to open a door leading to the level’s exit.

According to Plan

The mechanics were ordered arbitrarily, but the sprites associated with them were not. I started with the trident because its design would influence the appearance of Satan’s other sprites. Initially, the dark blue that gives it a three-dimensional appearance was not present. I added it after I realized that it would help the trident stand out more from the features of Satan’s body, and in-game, I thought it might accentuate its status as an important object. To create Satan’s other sprites, I just duplicated and slightly modified the profile and idle sprites. The “lightning” looks like a fireball or something similar because I couldn’t figure out how to make a recognizable, stereotypical bolt with the limited space I had.* The pieces of cake went through three previous iterations (see below) and therefore took the longest to make.

cakequest_000

*It needed to be big enough to be noticeable but smaller than Satan’s 16×16 sprites. It is 8 pixels long and six pixels wide. 

Drawing the Map and Collision Testing (Days 4 and 5)

Meddling with the Map

It’s difficult to draw a map if there isn’t anything to draw, so after I finished the major sprites, I created a map tile and used it to create a simple room:

cakequest_002

Using the map editor is as simple as selecting a sprite and then clicking on one of the cells to place the sprite into the cell. Each cell, like the slots in the sprite sheet, is 8×8, and the map editor, unlike the sprite editor, doesn’t have an option to adjust the zoom level. Instead, I simply had to hold the Shift key and then drag the mouse over the sprite to increase the selection size (it also works in the sprite editor, but I wasn’t aware of it when I was first drawing sprites).

Drawing the map in the editor doesn’t display the map on the screen. That needs to be done in code via the map() function. I opened another tab and placed a call to map() into its own function, appropriately titled draw_map(), and a call to that function in _draw():

FUNCTION DRAW_MAP()
MAP(0,0,0,0)
END

FUNCTION _DRAW()
CLS()
DRAW_MAP()
DRAW_PLAYER()
END

The parameters of the map() function aren’t very informative on their own. The first two refer to the x and y-coordinates of a cell’s position in the map, and the second two refer to the x and y-coordinates of a pixel’s position on the screen. In this case, these coordinates correspond to the top-left corner of the map and to the top-left corner of the screen, respectively. The result of running the code is the following:

cakequest_001

Admittedly, I wasn’t paying much attention to the cell coordinates of the map when drawing this room, so this was accidental. The room’s not perfectly square, but that’s easily fixed and not a priority for the purposes of this update.

Sprite-to-Wall Collision

PICO-8 doesn’t have any built-in functions to handle collisions, so if I wanted them, I had to write my own. To do that, I needed some information, and to get that information, I needed to write a function to display it. That function was debug(), and it looked like this:

FUNCTION DEBUG()
PRINT("PIXELX: "..P.X,0,0,7)
PRINT("PIXELY: "..P.Y,0,6,7)
PRINT("TILEX: "..FLR(P.X/8),0,12,7)
PRINT("TILEY: "..FLR(P.Y/8),0,18,7)
END

It is a modified version of a function used in Uncle Matt’s PICO-8 tutorial series. The print() function that it calls is built-in and prints a string (a sequence of characters placed within quotation marks), displaying it on the screen. The .. is used in place of the plus sign in other programming languages to add whatever comes after it to the end of the string, an action called concatenation.

From top to bottom, pixelX is the player’s x-position in pixels, pixelY is the player’s y-position in pixels, tileX is the player’s x-position in (map) tiles, and tileY is the player’s y-position in (map) tiles. Each tile is 8×8, so converting pixels to tiles requires division by eight. The flr() function (an abbreviation of floor) is a built-in math function. It returns the closest integer value equal to or below what it is given. For example, flr(1/8) returns zero and not .125.

The next two parameters in print() indicate where each string should be printed on the screen (the coordinate (0, 0) is the top-left corner), and the last parameter refers to the index of a color in the sprite editor’s color palette (seven is white). I don’t have a visual aid, but if I were to run the game with the code written at this point (debug() gets called in _draw()), pixelX would be 40, pixelY would be 80, tileX would be 5, and tileY would be 10.

In the game’s current state, if I were to move Satan toward either wall of the room I had created, he would pass through it and eventually go beyond the boundaries of the screen. My next objective was to use the information I now had, specifically tileX and tileY, to prevent him from passing through the walls.

cakequest_004

Referring back to the sprite editor, below the slider that controls the zoom level is a series of circles. Each of these is a “flag.” The leftmost flag is zero, and the rightmost flag is seven. By default, they are all inactive. Activating them is as simple as clicking on them, and when active, they change color. In the above image, flag zero is active, and its color is red. For the purposes of wall collision, I needed a function that would check to see if a map tile (more specifically, the sprite associated with that tile) had an active flag zero. If it did, that tile was considered “solid” and therefore impassible. If it didn’t, then Satan could move unimpeded:

FUNCTION SOLID_TILE(X,Y)
LOCAL TILEX=FLR(X/8)
LOCAL TILEY=FLR(Y/8)

IF FGET(MGET(TILEX,TILEY),0) THEN
RETURN TRUE
ELSE
RETURN FALSE
END

END

This function is also adapted from the aforementioned tutorial series. The local keyword establishes these tileX and tileY values as local variables, which means they are only accessible by this solid_tile() function. The fget() and mget() functions are built-in. The “f” stands for “flag,” and the “m” stands for “map.” Given a position on the map (tileX and tileY), mget() returns the number of the sprite associated with the tile in that position, and fget() returns either true or false depending on whether the specified flag is active.

To implement this function, I needed to call it in move_player() to be able to check collisions:

FUNCTION MOVE_PLAYER()

IF(BTN(0)) THEN
P.IDLE=FALSE
P.RIGHT=FALSE
P.LEFT=TRUE

IF(SOLID_TILE(P.X-1,P.Y)==FALSE) THEN
P.X-=P.SPEED
END

ELSEIF(BTN(1)) THEN
P.IDLE=FALSE
P.LEFT=FALSE
P.RIGHT=TRUE

IF(SOLID_TILE(P.X+16,P.Y)==FALSE) THEN
P.X+=P.SPEED
END

ELSE
P.IDLE=TRUE
END

END

The values of p.x and p.y are the player’s screen position, but on the sprite, they are the coordinates of the top-left corner. The adjustments P.X-1 and P.X+16 provide a buffer of one pixel on either side of the sprite that stops Satan’s movement just outside of the wall tiles:

cakequest_0

Sprite-to-Sprite Collision

I remembered from the third PICO-8 fanzine that one approach to collision detection between sprites involved hitboxes. Perhaps most familiar to fans of fighting games, hitboxes are invisible areas around a sprite or model, often in the shape of a box (in 2D) or a cube (in 3D). If the hitbox around one object intersects the hitbox around another object, those two objects are considered to have collided. In the fanzine, hitboxes are defined as tables with x, y, width, and height values, where x and y are the coordinates of the top-left corner of the hitbox:

P.HITBOX={X=4,Y=7,W=7,H=6}

cakequest_005
A visual representation of the hitbox defined above.

I added the above definition of p.hitbox to the player table in make_player(). There are several approaches to hitbox collision in PICO-8, but the most straightforward I found was from a template by Brian Vaughn (@morningtoast):

FUNCTION COLLIDE(X1,Y1,HITBOX1,X2,Y2,HITBOX2)
LOCAL LEFT=MAX(X1+HITBOX1.X,X2+HITBOX2.X)
LOCAL RIGHT=MIN(X1+HITBOX1.X+HITBOX1.W,X2+HITBOX2.X+HITBOX2.W)
LOCAL TOP=MAX(Y1+HITBOX1.Y,Y2+HITBOX2.Y)
LOCAL BOTTOM=MIN(Y1+HITBOX1.Y+HITBOX1.H,Y2+HITBOX2.Y+HITBOX2.H)

IF LEFT < RIGHT AND TOP < BOTTOM THEN
RETURN TRUE
END

RETURN FALSE
END

This function determines whether a collision has occurred based on whether the hitboxes of two objects overlap. Where specifically they overlap is unimportant. The functions max() and min() are two other built-in math functions. Given two numbers, max() returns the higher one, and min() returns the lower one. The values x1 and y1 are the x and y-positions of one object, hitbox1 is that object’s hitbox, and and h are that hitbox’s width and height. Similarly, x2 and y2 are the x and y-positions of a second object, and hitbox2 is its hitbox. The collide() function returns true if a portion of one hitbox is contained within the other. If this is false, then there is a gap between the hitboxes, and there is no collision.

Sprite Addition and Player State Modification

Before I could call this function, I needed to add another sprite into the game. Referring back to the list of definite mechanics, the obvious choice was the trident. I created two functions, make_trident() and draw_trident():

FUNCTION MAKE_TRIDENT()
T={}
T.X=80
T.Y=80
T.HITBOX={X=4,Y=0,W=9,H=16}
T.SPRITE=NIL
END

FUNCTION DRAW_TRIDENT()
T.SPRITE=SPR(5,T.X,T.Y,2,2)
END

The trident was now displayed on the screen (40 pixels to the right of Satan), but it couldn’t be interacted with because I still hadn’t called collide(). In make_player(), I added the following value to the player table:

P.HAS_TRIDENT=FALSE

Since Satan starts without the trident, he doesn’t have it, and so p.has_trident is false when the game starts. I wanted it to be true when the two sprites collided. This would necessarily change Satan’s idle and profile sprites to the sprites carrying the trident, and the trident would need to disappear. To do all of this, I needed to write another function and slightly modify both draw_trident() (to only draw the trident if Satan didn’t have it) and draw_player() (to include the additional sprites):

FUNCTION UPDATE_PLAYER()

IF COLLIDE(P.X,P.Y,P.HITBOX,T.X,T.Y,T.HITBOX) THEN
P.HAS_TRIDENT=TRUE
END

END

FUNCTION DRAW_TRIDENT()

--DRAWS IF P.HAS_TRIDENT IS FALSE
IF NOT P.HAS_TRIDENT THEN
T.SPRITE=SPR(5,T.X,T.Y,2,2)
END

END

FUNCTION DRAW_PLAYER()

IF (P.HAS_TRIDENT==TRUE) THEN

IF P.IDLE THEN
P.SPRITE=SPR(33,P.X,P.Y,2,2)
ELSEIF P.LEFT THEN
P.SPRITE=SPR(35,P.X,P.Y,2,2,TRUE)
ELSEIF P.RIGHT THEN
P.SPRITE=SPR(35,P.X,P.Y,2,2)
END

ELSE

IF P.IDLE THEN
P.SPRITE=SPR(1,P.X,P.Y,2,2)
ELSEIF P.LEFT THEN
P.SPRITE=SPR(3,P.X,P.Y,2,2,TRUE)
ELSEIF P.RIGHT THEN
P.SPRITE=SPR(3,P.X,P.Y,2,2)
END

END

END

With that done, I placed a call to make_trident() in _init(), a call to update_player() in _update(), and a call to draw_trident() in _draw(). It took more time than I expected, but I couldn’t have been happier with the result:

cakequest_1

The Quest to Make Cake Quest_part1.p8

 

Finding Potential in PICO-8

As I mentioned in the previous post, what ultimately prevented me from finishing Cake Quest was thinking too far ahead about what needed to be done. Even with the deadline gone, the project seemed just as daunting in the weeks immediately following the jam. I had sprites made and songs composed, but I had to figure out how to display those sprites on the screen and play the songs. Eventually, I would have to find or create sound effects and play them in response to certain events or player actions. I still needed to figure out what those events or player actions would be because of my unorthodox approach to this project.

It wasn’t until I first discovered PICO-8 a few weeks ago that I began to have a more concrete idea of how I could manage Cake Quest‘s development. PICO-8’s limitations reduced the potential scope of the project, and with built-in features for sprite, map, sound effect, and music creation, I wouldn’t have to divide my workflow among multiple tools. The major challenge I would face would be programming everything in Lua,* which I hadn’t used in years and had no experience using outside of Unity. Thankfully, I had a plethora of resources at my disposal, such as the collaborative PICO-8 Zine, Dylan Bennett’s Game Development with PICO-8, the official manual, and the many games others have made (loading them into PICO-8 provides access to their code). After a week of reading and experimentation, I felt confident enough to take the tentative first steps toward transitioning Cake Quest from an idea to an actual game.

*PICO-8 uses a limited API (application programming interface) in lieu of Lua’s standard libraries. What works within PICO-8 may not work in other applications of Lua, and vice versa.

Displaying and Moving the Player (Days 1 and 2)

The Game Loop, Syntax, and Organization

There are three functions in PICO-8 that can be used to handle everything that appears and happens in a game: _init(), _draw(), and _update(). _init() is called once on startup, _draw is called once every visible frame, and _update() is called once per update at 30 frames per second (by default). Both _draw() and _update() are known as the game loop, while _init(), well, initializes the game.

In a programming language such as C#, the contents of functions, loops, and logic statements are contained within curly brackets, and individual lines of code end with a semicolon. In PICO-8’s Lua, these are absent. There is nothing in particular that ends a line of code, and the functionality of curly brackets is replaced by the “end” keyword. The three functions I mentioned in the previous paragraph therefore look like this:

FUNCTION _INIT()
END

FUNCTION _UPDATE()
END

FUNCTION _DRAW()
--CLEAR THE SCREEN
CLS()
END

The font that PICO-8 uses puts everything in uppercase, so I’ve decided to do it here for consistency. I’m refraining from using that font in order to maximize readability. Code is written on up to eight tabs numbered from zero to seven. On startup, only tab zero is provided, and it is completely blank. Borrowing an organizational method used in Game Development with PICO-8, I opted to place the above three functions on the default tab and devote other tabs to specific purposes. All code pertaining to the player, for example, is on tab one.

Making the Player Sprite

The player sprites I created for the previous iteration of the project were 40×40. I decided that was an excessive amount of detail even before I took the available space limitations into account. The sprite sheet has four tabs, with space on each for up to 64 8×8 sprites. However, the latter two tabs are shared in memory with the bottom half of the map, so having a large number of sprites limits the size of the map, and vice versa. I knew that while 40×40 was excessive, 8×8 was not detailed enough, so I settled on 16×16.

cakequest_003

It’s not entirely clear from the above screenshot, but the slider directly underneath the color palette changes the pencil size (default is one pixel), and the slider underneath that changes the zoom level (default is x1). The pictured zoom level is x2, which doubles the available drawing space. I didn’t want to completely abandon the style of the original sprites, so I used them as a reference for this one.

From Sprite Editor to the Screen

I decided that displaying and moving the player required three functions. One would contain the player’s information, one would move the player, and one would draw the player. These ended up as make_player(), move_player(), and draw_player(), respectively, and I preemptively placed calls to them inside _init(), _update(), and draw():

FUNCTION _INIT()
MAKE_PLAYER()
END

FUNCTION _UPDATE()
MOVE_PLAYER()
END

FUNCTION _DRAW()
--CLEAR THE SCREEN
CLS()
DRAW_PLAYER()
END

After opening another tab and creating these three functions, I started with make_player(). This function instantiates the player as an empty table called p. A table contains a series of values within a pair of curly brackets. It started empty because I didn’t have any values in mind at the time, and the function would fill the table automatically once it was called in _init(). To make the player appear on the screen, its table needed three values: an x-coordinate, a y-coordinate, and a sprite:

FUNCTION MAKE_PLAYER()
P={}
P.X=40
P.Y=80
P.SPRITE=NIL
END

“Nil” evaluates to false and represents an empty value. It made more sense for p.sprite to change when the player is drawn, so I placed all sprite-related information into draw_player():

FUNCTION DRAW_PLAYER()
P.SPRITE=SSPR(8,0,16,16,P.X,P.Y)
END

sspr() is a built-in function that stretches a rectangle of pixels from the sprite sheet and optionally draws it inside of another rectangle. The parameters I used for this test were the x and y-coordinates of the rectangle’s top-left corner (8, 0), the length and width of the rectangle in pixels, and the x and y-coordinates representing a location on the screen. The rectangle in this case is the player sprite, so running the game after writing the above code creates the below result:

cakequest_004

While writing this post, I discovered an alternative method of drawing the player sprite using another built-in function, spr(). Its first parameter is a number that corresponds to an 8×8 sprite’s position in the sprite sheet. For example, in the screenshot at the beginning of this section, the right half of Satan’s face (from his perspective) is at position 001. I thought that meant I would have to call spr() a total of four times in order to draw the entire sprite, but I didn’t realize that it also had length and width parameters. By default, they are set to one (i.e. one 8×8 sprite). Setting length and width to two therefore makes the final sprite two sprites high and two sprites wide (i.e. 16×16, as desired). Consequently, this line of code is (mostly) equivalent to the sspr() implementation:

P.SPRITE=SPR(1,P.X,P.Y,2,2)

 Whether and how the difference between these implementations is going to affect development remains to be seen, but for the time being, I have decided to use spr().

Moving the Player

PICO-8’s input options are limited to six buttons numbered zero to five. Buttons zero through three are the directional inputs (up, down, left, right), while four and five are a circle button and an X button. Controls can be re-configured using the keyconfig command at any point, but by default, the directional inputs are mapped to the arrow keys, the circle button is mapped to the Z key, and the X button is mapped to the X key.

Before writing the move_player() function, I added a speed value, p.speed, to the player table. I then decided that all I wanted to accomplish at this stage was horizontal movement, so this value would affect the player’s x-coordinate. This meant that the first actual control I implemented into the game involved the left and right arrow keys, buttons zero and one.

FUNCTION MAKE_PLAYER()
P={}
P.X=40
P.Y=80
P.SPEED=2
P.SPRITE=NIL
END

FUNCTION MOVE_PLAYER()

IF (BTN(0)) THEN
--MOVE LEFT
P.X-=P.SPEED
ELSEIF (BTN(1)) THEN
--MOVE RIGHT
P.X+=P.SPEED
END

END

The btn() function checks whether a button is held down, which results in movement that is more fluid than btnp(), which checks whether a button is pressed. The statements P.X-=P.SPEED and P.X+=P.SPEED are shorthand for P.X=P.X-P.SPEED and P.X=P.X+P.SPEED, respectively.  Now, Satan moves in response to the player’s input:

cakequest_1

I considered that to be an acceptable starting point, but it wasn’t an acceptable stopping point.

Player States

I decided that for the time being, Satan would have three states, and each state would be represented by a different sprite. His default state would be idle and use the front-facing sprite. The other two states would be moving left and moving right. Achieving the effect I wanted only required one additional sprite and minor adjustments to the three player functions:

cakequest_005

FUNCTION MAKE_PLAYER()
P.X=40
P.Y=80
P.SPEED=2
P.SPRITE=NIL
P.LEFT=FALSE
P.RIGHT=FALSE
P.IDLE=TRUE

END

FUNCTION MOVE_PLAYER()

IF (BTN(0)) THEN
--MOVE LEFT
P.IDLE=FALSE
P.RIGHT=FALSE
P.LEFT=TRUE

P.X-=P.SPEED
ELSEIF (BTN(1)) THEN
--MOVE RIGHT
P.IDLE=FALSE
P.LEFT=FALSE
P.RIGHT=TRUE

P.X+=P.SPEED
ELSE
P.IDLE=TRUE

END

END

Satan begins the game in the idle state because no input has been given by the player. When the player presses the left arrow key, Satan moves out of the idle state and into the “left” state. Likewise, when the player presses the right arrow key, Satan moves out of the idle state and into the “right” state. If the player is not providing the given inputs, then Satan is moving neither left nor right and thus returns to the idle state.

FUNCTION DRAW_PLAYER()

IF P.IDLE THEN
P.SPRITE=SPR(1,P.X,P.Y,2,2)
ELSEIF P.LEFT THEN
P.SPRITE=SPR(3,P.X,P.Y,2,2,TRUE)
ELSEIF P.RIGHT THEN
P.SPRITE=SPR(3,P.X,P.Y,2,2)
END

END

This code assigns sprites to each of the states. The upper left corner of Satan’s profile sprite (the second sprite) is in position three in the sprite sheet, which is why the first parameter is three and not one. spr() has two additional parameters, flip_x and flip_y, which flip a sprite either horizontally or vertically. Their values are either true or false, and they are false by default. Satan’s profile sprite as drawn faces to the right, so in the “left” state, the sprite gets flipped so that it faces to the left.

The result of all of this is, of course, what’s shown in the featured image (which also happens to be the image at the end of the previous post).

Cake Quest: The Rise, Fall, and Rebirth.txt

Setting the Stage

This September, there was an itch.io game jam called A Game by its Cover, participation in which involved creating games based on Famicase designs (a Famicase is an artist-submitted Famicom cartridge design). I was intrigued by the idea and thought it would be a good opportunity to do something that wasn’t just skill acquisition. Game jams foster creativity through spontaneity and adaptation, but they generally aren’t considered ideal for experimentation because of their tight deadlines. In other words, it’s often best to stick to what one knows in order to work and progress efficiently. Because this particular game jam had more leeway compared to the 48-hour ones I was used to, however, I threw caution to the wind.

Making Decisions and Preparations

Once I decided I was going to participate, the obvious next step was to pick a Famicase to use as a basis for my game. Cake Quest immediately intrigued me. Designed by pixelhans, its premise involves a cake-obsessed Satan turning objects into cake (with a trident that fires lightning) and eating that cake in order to gain new abilities. I knew from the outset that I didn’t want to use Unity to make the game. I am more familiar with it than with anything else, but because my experience with non-tutorial projects was and is limited, I didn’t have a mental estimate for how long the project would take and therefore no reliable idea of how to most effectively manage my time and workflow.

Not using Unity also meant not using the C# programming language. The only application of it I had done outside the engine was directly in Visual Studio when I was first learning how to program, but that was almost six years ago. That left JavaScript as my only viable alternative. The only problem was I wasn’t sure how to actually make a game with it. Fortunately, the solution was simple. I went on Udemy and found two courses focused on using JavaScript to make games inside an HTML5 canvas, both taught by Gamkedo‘s Chris DeLeon. I knew that devoting the necessary time and effort to these would drastically reduce the amount of time I had to actually work on Cake Quest, but I also knew the game had little chance of being made otherwise. What complicated things further was that I didn’t make this decision during the initial week of the jam. I was too engrossed in learning how to make what I had only been taught how to find.

Keeping Busy with Bosca Ceoil (Days 1-6)

Discovery

Music has never been the starting point for any project of mine. It’s usually one of the last additions I make to an already existing game because it’s easier than planning a game around it (kudos to the people who can do and have done it). I can’t remember exactly why I decided to make an exception, but I do remember how. A few days before the jam started, I stumbled upon a blog post by Ruoyu Sun titled “Developer’s Music Making Guide.” In addition to general tips for music composition, it mentions a tool created by Terry Cavanagh (developer of games like VVVVVV and Super Hexagon) called Bosca Ceoil, which I downloaded out of curiosity. Its built-in tutorial helped me understand its interface, and with Ruoyu’s post as a guide, I started making music within minutes.

Composing Songs

Before doing anything else, I went to Autochords, another resource mentioned in the blog post. It generates a chord progression and provides three alternatives based on criteria you enter. I experimented a little and decided I wanted to use the C minor scale. Once I had a progression I liked, I then transferred it to Bosca Ceoil note by note.  There are certain notes that aren’t represented, but because Autochords lists the notes in each chord, there are multiple options for getting the desired progression. I don’t have much to say about the actual creation process other than that it involved a lot of experimentation and even more listening. I opted for the pentatonic C minor scale and made two songs. My approach to both was to repeat a chord progression 4-8 times and then alter it or one of its alternatives while adding or changing instruments, stopping when I thought the result was long enough (i.e. when I got sick of listening to it).

The first song I composed was intended to be the Cake Quest level theme, while the second was intended to be the menu theme. Neither ended up being used for reasons that will be discussed shortly, but that doesn’t mean they should remain unheard:

The Devil’s in the Details

Aside from the two songs, the above eight sprites are the only assets I created for Cake Quest. From left to right, they are Satan, Satan with his trident, Satan with trident and wings, the trident as a standalone item, a “Hell” tile based on an earlier image discussed in my second “Pixel Practice” post, a piece of devil’s food cake, a piece of carrot cake, and a piece of red velvet cake. The reason there haven’t been any parts beyond part one is because part two was going to discuss these. It never got written because Cake Quest never officially got started, let alone finished. I had nothing to show for the time I spent learning how to make HTML5 canvas games using JavaScript. I hadn’t written a line of code by the time the jam deadline arrived and passed.

I wish I could use it as an excuse, but I didn’t run out of time. The truth is that I allowed myself to think too far ahead. That is, I thought of everything I wanted and needed to do in order to make a functioning game, which then transitioned to whether and how much of that was feasible for me to do at my current skill level, and that prevented me from doing anything at all. Consequently, I wasn’t inclined to share or discuss what I had accomplished during the jam period. I should have been, but what matters is I am now.

I’ll go into more detail about why in my next post. In the meantime, the title of this post mentions a “rebirth,” doesn’t it?

cakequest_0